将DLL挂接到远程进程之中(远程注入)
要实现线程的远程注入必须使用Windows提供的CreateRemoteThread函数来创建一个远程线程
该函数的原型如下:
HANDLE CreateRemoteThread(
HANDLE hProcess,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);
参数说明:
hProcess:目标进程的句柄
lpThreadAttributes:指向线程的安全描述结构体的指针,一般设置为NULL,表示使用默认的安全级别
dwStackSize:线程堆栈大小,一般设置为0,表示使用默认的大小,一般为1M
lpStartAddress:线程函数的地址
lpParameter:线程参数
dwCreationFlags:线程的创建方式
CREATE_SUSPENDED 线程以挂起方式创建
lpThreadId:输出参数,记录创建的远程线程的ID
CreateRemoteThread函数介绍完毕,其他详细信息参考MSDN中关于该函数的详细说明!
既然知道了使用这个函数来创建一个远程线程,接下来我们就来定义线程函数体,和普通的线程函数的
定义相同,远程线程的线程函数必须定义程类的静态成员函数或者全局函数
例如:
DWORD __stdcall threadProc(LPVOID lParam)
{
//我们在这里先将该线程函数定义为空函数
return 0;
}
在这里我们先将线程函数体定义为空,因为要作为远程注入的线程,线程函数体的编写方式和普通线程函数
稍有不同。
然后将线程代码拷贝到目标进程地址空间中(该地址必须是页面属性为PAGE_EXECUTE_READWRITE的页面)或者
其他宿主进程能执行地方(如:共享内存映射区)。在这里我们选择宿主进程。在拷贝线程体的时候我们需要
使用VirtualAllocEx函数在宿主进程中申请一块存储区域,然后再通过WriteProcessMemory函数将线程代码写
入宿主进程中。
要取得宿主进程的ID可以有很多种方法可以使用Psapi.h中的函数,也可以使用Toolhelp函数,在这里提供一种
使用Toolhelp实现的函数,函数如下
//根据进程名称得到进程的ID,如果有多个实例在同时运行的话,只返回第一个枚举到的进程ID
DWORD processNameToId(LPCTSTR lpszProcessName)
{
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
PROCESSENTRY32 pe;
pe.dwSize = sizeof(PROCESSENTRY32);
if (!Process32First(hSnapshot, &pe)) {
MessageBox(NULL,
"The frist entry of the process list has not been copyied to the buffer",
"Notice", MB_ICONINFORMATION | MB_OK);
return 0;
}
while (Process32Next(hSnapshot, &pe)) {
if (!strcmp(lpszProcessName, pe.szExeFile)) {
return pe.th32ProcessID;
}
}
return 0;
}
以上步骤完成之后就可以使用CreateRemoteThread创建远程线程了!示例代码如下
#include <windows.h>
#include <TlHelp32.h>
#include <iostream>
//要插入宿主进程中的线程函数
DWORD __stdcall threadProc(LPVOID lParam)
{
return 0;
}
int main(int argc, char* argv[])
{
const DWORD dwThreadSize = 4096;
DWORD dwWriteBytes;
std::cout << "Please input the name of target process" << std::endl;
char szExeName[MAX_PATH] = { 0 };
//等待输入宿主进程名称
std::cin >> szExeName;
//得到指定名称进程的进程ID,如果有多个进程实例,则得到第一个进程ID
DWORD dwProcessId = processNameToId(szExeName);
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId)
void* pRemoteThread = VirtualAllocEx(hTargetProcess, 0,
dwThreadSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
//把线程体写入宿主进程中
if (!WriteProcessMemory(hTargetProcess,
pRemoteThread, &threadProc, dwThreadSize, 0)) {
MessageBox(NULL, "Write data to target process failed !",
"Notice", MB_ICONINFORMATION | MB_OK);
return 0;
}
//在宿主进程中创建线程
HANDLE hRemoteThread = CreateRemoteThread(
hTargetProcess, NULL, 0, (DWORD (__stdcall *)(void *))pRemoteThread,
NULL, 0, &dwWriteBytes);
if (!hRemoteThread) {
MessageBox(NULL, "Create remote thread failed !", "Notice", MB_ICONSTOP);
return -1;
}
return 0;
}
当上面的代码运行的时候会在宿主进程中创建一条由程序员定义的线程,只不过现在这个线程函数体为空
什么都不做。
下面我们来编写具体的线程函数体的内容,在这里我们只是简单的显示一个消息对话框MessageBox
修改之后的线程函数体如下:
DWORD __stdcall threadProc(LPVOID lParam)
{
MessageBox(NULL, "hello", "hello", MB_OK);
return 0;
}
线程体修改完毕之后我们运行程序,将线程注入到宿主进程之中。不过此时会产生一个非法访问的错误。原
因就是线程体中的MessageBox(NULL, "hello", "hello", MB_OK);函数的第二和第三个参数所指向的字符串
是存在于当前进程的地址空间中,宿主进程中的线程访问该字符串"hello"就会出现访问内存非法的错误。
解决的方法就是将该字符串的内容也拷贝到宿主进程的地址空间中,而且连同MessageBox函数在User32.dll
中的地址也拷贝到宿主进程之中。
要将字符串和MessageBox函数的入口地址拷贝到宿主进程中我们首先定义下面这个RemoteParam结构体,用来
存放MessageBox函数的入口地址和MessageBox显示的字符串的内容,该结构的定义如下:
//线程参数
typedef struct _RemoteParam {
char szMsg[12]; //MessageBox函数显示的字符串
DWORD dwMessageBox;//MessageBox函数的入口地址
} RemoteParam, * PRemoteParam;
RemoteParam remoteData;
ZeroMemory(&remoteData, sizeof(RemoteParam));
HINSTANCE hUser32 = LoadLibrary("User32.dll");
remoteData.dwMessageBox = (DWORD)GetProcAddress(hUser32, "MessageBoxA");
strcat(remoteData.szMsg, "Hello/0");
//在宿主进程中分配存储空间
RemoteParam* pRemoteParam = (RemoteParam*)VirtualAllocEx(
hTargetProcess , 0, sizeof(RemoteParam), MEM_COMMIT, PAGE_READWRITE);
if (!pRemoteParam) {
MessageBox(NULL, "Alloc memory failed !",
"Notice", MB_ICONINFORMATION | MB_OK);
return 0;
}
//将字符串和MessageBox函数的入口地址写入宿主进程
if (!WriteProcessMemory(hTargetProcess ,
pRemoteParam, &remoteData, sizeof(remoteData), 0)) {
MessageBox(NULL, "Write data to target process failed !",
"Notice", MB_ICONINFORMATION | MB_OK);
return 0;
}
//创建远程线程
HANDLE hRemoteThread = CreateRemoteThread(
hTargetProcess, NULL, 0, (DWORD (__stdcall *)(void *))pRemoteThread,
pRemoteParam, 0, &dwWriteBytes);
另外还需要注意的一点是,在打开进程的时候有些系统进程是无法用OpenProcess函数
打开的,这个时候就需要提升进程的访问权限,进而来达到访问系统进程的目的,在这里
我提供了一个提升进程访问权限的函数enableDebugPriv(),该函数的定义如下:
//提升进程访问权限
bool enableDebugPriv()
{
HANDLE hToken;
LUID sedebugnameValue;
TOKEN_PRIVILEGES tkp;
if (!OpenProcessToken(GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) {
return false;
}
if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &sedebugnameValue)) {
CloseHandle(hToken);
return false;
}
tkp.PrivilegeCount = 1;
tkp.Privileges[0].Luid = sedebugnameValue;
tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if (!AdjustTokenPrivileges(hToken, FALSE, &tkp, sizeof(tkp), NULL, NULL)) {
CloseHandle(hToken);
return false;
}
return true;
}
至此创建远程线程的工作全部结束,下面就给出完整的代码:
#pragma once
#include "stdafx.h"
#include <windows.h>
#include <TlHelp32.h>
#include <iostream>
//线程参数结构体定义
typedef struct _RemoteParam {
char szMsg[12]; //MessageBox函数中显示的字符提示
DWORD dwMessageBox;//MessageBox函数的入口地址
} RemoteParam, * PRemoteParam;
//定义MessageBox类型的函数指针
typedef int (__stdcall * PFN_MESSAGEBOX)(HWND, LPCTSTR, LPCTSTR, DWORD);
//线程函数定义
DWORD __stdcall threadProc(LPVOID lParam)
{
RemoteParam* pRP = (RemoteParam*)lParam;
PFN_MESSAGEBOX pfnMessageBox;
pfnMessageBox = (PFN_MESSAGEBOX)pRP->dwMessageBox;
pfnMessageBox(NULL, pRP->szMsg, pRP->szMsg, 0);
return 0;
}
//提升进程访问权限
bool enableDebugPriv()
{
HANDLE hToken;
LUID sedebugnameValue;
TOKEN_PRIVILEGES tkp;
if (!OpenProcessToken(GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) {
return false;
}
if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &sedebugnameValue)) {
CloseHandle(hToken);
return false;
}
tkp.PrivilegeCount = 1;
tkp.Privileges[0].Luid = sedebugnameValue;
tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if (!AdjustTokenPrivileges(hToken, FALSE, &tkp, sizeof(tkp), NULL, NULL)) {
CloseHandle(hToken);
return false;
}
return true;
}
//根据进程名称得到进程ID,如果有多个运行实例的话,返回第一个枚举到的进程的ID
DWORD processNameToId(LPCTSTR lpszProcessName)
{
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
PROCESSENTRY32 pe;
pe.dwSize = sizeof(PROCESSENTRY32);
if (!Process32First(hSnapshot, &pe)) {
MessageBox(NULL,
"The frist entry of the process list has not been copyied to the buffer",
"Notice", MB_ICONINFORMATION | MB_OK);
return 0;
}
while (Process32Next(hSnapshot, &pe)) {
if (!strcmp(lpszProcessName, pe.szExeFile)) {
return pe.th32ProcessID;
}
}
return 0;
}
int main(int argc, char* argv[])
{
//定义线程体的大小
const DWORD dwThreadSize = 4096;
DWORD dwWriteBytes;
//提升进程访问权限
enableDebugPriv();
//等待输入进程名称,注意大小写匹配
std::cout << "Please input the name of target process !" << std::endl;
char szExeName[MAX_PATH] = { 0 };
std::cin >> szExeName;
DWORD dwProcessId = processNameToId(szExeName);
if (dwProcessId == 0) {
MessageBox(NULL, "The target process have not been found !",
"Notice", MB_ICONINFORMATION | MB_OK);
return -1;
}
//根据进程ID得到进程句柄
HANDLE hTargetProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
if (!hTargetProcess) {
MessageBox(NULL, "Open target process failed !",
"Notice", MB_ICONINFORMATION | MB_OK);
return 0;
}
//在宿主进程中为线程体开辟一块存储区域
//在这里需要注意MEM_COMMIT | MEM_RESERVE内存非配类型以及PAGE_EXECUTE_READWRITE内存保护类型
//其具体含义请参考MSDN中关于VirtualAllocEx函数的说明。
void* pRemoteThread = VirtualAllocEx(hTargetProcess, 0,
dwThreadSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (!pRemoteThread) {
MessageBox(NULL, "Alloc memory in target process failed !",
"notice", MB_ICONINFORMATION | MB_OK);
return 0;
}
//将线程体拷贝到宿主进程中
if (!WriteProcessMemory(hTargetProcess,
pRemoteThread, &threadProc, dwThreadSize, 0)) {
MessageBox(NULL, "Write data to target process failed !",
"Notice", MB_ICONINFORMATION | MB_OK);
return 0;
}
//定义线程参数结构体变量
RemoteParam remoteData;
ZeroMemory(&remoteData, sizeof(RemoteParam));
//填充结构体变量中的成员
HINSTANCE hUser32 = LoadLibrary("User32.dll");
remoteData.dwMessageBox = (DWORD)GetProcAddress(hUser32, "MessageBoxA");
strcat(remoteData.szMsg, "Hello/0");
//为线程参数在宿主进程中开辟存储区域
RemoteParam* pRemoteParam = (RemoteParam*)VirtualAllocEx(
hTargetProcess , 0, sizeof(RemoteParam), MEM_COMMIT, PAGE_READWRITE);
if (!pRemoteParam) {
MessageBox(NULL, "Alloc memory failed !",
"Notice", MB_ICONINFORMATION | MB_OK);
return 0;
}
//将线程参数拷贝到宿主进程地址空间中
if (!WriteProcessMemory(hTargetProcess ,
pRemoteParam, &remoteData, sizeof(remoteData), 0)) {
MessageBox(NULL, "Write data to target process failed !",
"Notice", MB_ICONINFORMATION | MB_OK);
return 0;
}
//在宿主进程中创建线程
HANDLE hRemoteThread = CreateRemoteThread(
hTargetProcess, NULL, 0, (DWORD (__stdcall *)(void *))pRemoteThread,
pRemoteParam, 0, &dwWriteBytes);
if (!hRemoteThread) {
MessageBox(NULL, "Create remote thread failed !", "Notice", MB_ICONINFORMATION | MB_OK);
return 0;
}
CloseHandle(hRemoteThread);
return 0;
}
上面是远程线程注入的一段简单的代码,以及一些简要的说明,放在这里和大家共享!
希望各位不要见笑! :)
在上一篇文章《线程的远程注入》中介绍了如何让其他的进程中执行自己的代码的一种方法
及自己定义一个线程,在线程体中编写执行代码,然后使用VirtualAllocEx函数为线程体以
及线程中用到的字符常量和调用的MessageBox入口函数地址,在目标进程中开辟存储区,然
后再通过WriteProcessMemory函数,将这些数据写入目标进程的地址空间中。最后通过
CreateRemoteThread函数,让自己的线程运行在目标进程中。
上面的方法需要为线程和常量在目标进程中开辟足够的存储空间,而且这个空间的大小很难
确定,稍不注意就会发生访问违规的错误。
下面我来介绍另外一种在其他进程中执行自己的代码的方法,让目标进程加载我们自己编写的
DLL模块。
首先我们来创建一个DLL模块(稍后我们将要把这个DLL加载到目标进程中,让目标进程来运行
DLL中的代码)。
我们创建的这个DLL模块非常简单,只是得到加载该DLL的进程的ID,然后通过MessageBox函数
显示出来,当然也可以写一些复杂的代码。不过我们这里只是介绍一下方法,有个显示结果足
以:)。下面就是DLL模块的代码:
//Test.dll源代码
//
#include <windows.h>
BOOL APIENTRY DllMain(HANDLE hMoudle, DWORD dwReason, LPVOID lpReserved)
{
char* pszProcessId = (char*)malloc(10*sizeof(char));
switch(dwReason) {
case DLL_PROCESS_ATTACH:
_itoa(GetCurrentProcessId(), pszProcessId, 10);
MessageBox(NULL, pszProcessId, "Notice", MB_ICONINFORMATION | MB_OK);
case DLL_PROCESS_DETACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
break;
}
return TRUE;
}
下一步我们就可以专注于如何把这个DLL挂接到目标进程中。
首先我们通过OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId)来打开我们试图加载DLL的进程(注意
进程的打开权限一定要设置为PROCESS_ALL_ACCESS,因为我们要在目标进程中创建线程)。
然后我们可以使用CreateRemoteThread来创建LoadLibraryA线程来启动我们的这个DLL。
LoadLibraryA存在于系统的kernel32.dll中用来加载DLL模块,该函数只有一个参数就是DLL文件的名称(该
名称包括路径)。由于加载DLL的操作是在其他进程中进行,所以我们必须把DLL的文件名称拷贝到目标进程
的地址空间中。
我们必须计算出该文件名所占的内存空间
int nLength = (strlen(pszFileName)+1)*sizeof(char);
接下来使用VirtualAllocEx函数为DLL文件名在目标进程中分配地址空间,在使用WriteProcessMemory将
DLL的文件名拷贝到刚刚分配的地址中。
下一步就是取得LoadLibraryA函数的入口地址
PTHREAD_START_ROUTINE pfnLoadLibraryA =
(PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle("Kernel32.dll"), "LoadLibraryA");
因为kernel32.dll模块是系统的核心模块,所以该模块中的函数地址在所有进程中全都相同。
我们在本进程中得到的LoadLibraryA函数的入口地址同样适用于其他进程。
所有条件已经具备,最后我们使用CreateRemoteThread函数,将LoadLibraryA函数的入口地址以及DLL的文件
名作为参数,即可让目标进程加载我们自己的DLL,至于你要让你的DLL中运行什么代码,就自己看着办吧!
让远程进程挂接自己的DLL的思路就是这样,下面给出完整代码:
#include <windows.h>
#include <psapi.h>
#include <string>
#include <iostream>
#pragma comment(lib, "Psapi.lib")
//提升进程访问权限
void enableDebugPriv()
{
HANDLE hToken;
LUID sedebugnameValue;
TOKEN_PRIVILEGES tkp;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
return;
if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &sedebugnameValue)) {
CloseHandle(hToken);
return;
}
tkp.PrivilegeCount = 1;
tkp.Privileges[0].Luid = sedebugnameValue;
tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if (!AdjustTokenPrivileges(hToken, FALSE, &tkp, sizeof(tkp), NULL, NULL))
CloseHandle(hToken);
}
//根据进程名称取得进程ID,如果有多个运行实例则返回第一个枚举出来的进程ID
DWORD getSpecifiedProcessId(const char* pszProcessName)
{
DWORD processId[1024], cbNeeded, dwProcessesCount;
HANDLE hProcess;
HMODULE hMod;
char szProcessName[MAX_PATH] = "UnknownProcess";
DWORD dwArrayInBytes = sizeof(processId)*sizeof(DWORD);
if (!EnumProcesses(processId, dwArrayInBytes, &cbNeeded))
return 0;
//计算数组中的元素个数
dwProcessesCount = cbNeeded / sizeof(DWORD);
enableDebugPriv();
for (UINT i = 0; i < dwProcessesCount; i++) {
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processId[i]);
if (!hProcess) {
continue;
} else {
if (EnumProcessModules(hProcess, &hMod, sizeof(hMod), &cbNeeded)) {
GetModuleBaseName(hProcess, hMod, szProcessName, sizeof(szProcessName));
if (!_stricmp(szProcessName, pszProcessName)) {
CloseHandle(hProcess);
return processId[i];
}
}
}
}
CloseHandle(hProcess);
return 0;
}
int main(int argc, char* argv[])
{
std::cout << "please input the name of target process !" << std::endl;
//等待输入进程名称
std::string strProcessName;
std::cin >> strProcessName;
//在这里为了简单起见,使用了绝对路径
char szDllPath[MAX_PATH] = "D://test.dll";
char szFileName[MAX_PATH] = "D://test.dll";
//提升进程访问权限
enableDebugPriv();
if (strProcessName.empty()) {
MessageBox(NULL, "The target process name is invalid !", "Notice", MB_ICONSTOP);
return -1;
}
//根据进程名称得到进程ID
DWORD dwTargetProcessId = getSpecifiedProcessId(strProcessName.c_str());
HANDLE hTargetProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwTargetProcessId);
if (!hTargetProcess) {
MessageBox(NULL, "Open target process failed !", "Notice", MB_ICONSTOP);
return -1;
}
//计算DLL文件名称所占的存储空间
int memorySize = (strlen(szDllPath) + 1) * sizeof(char);
//在目标进程中开辟存储空间,用来存放DLL的文件名称
char* pszFileNameRemote = (char*)VirtualAllocEx(hTargetProcess,
0, memorySize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (!pszFileNameRemote) {
MessageBox(NULL, "Alloc dll name string in target process failed !", "Notice", MB_ICONSTOP);
return -1;
}
//将DLL的文件名写入目标进程地址空间
if (!WriteProcessMemory(hTargetProcess, pszFileNameRemote,
(LPVOID)szFileName, memorySize, NULL)) {
MessageBox(NULL, "Write dll name string to target process failed !",
"Notice", MB_ICONSTOP);
return -1;
}
PTHREAD_START_ROUTINE pfnStartAddr =
(PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA");
HANDLE hRemoteThread = CreateRemoteThread(hTargetProcess,
NULL, 0, pfnStartAddr, pszFileNameRemote, 0, NULL);
WaitForSingleObject(hRemoteThread, INFINITE);
VirtualFreeEx(hTargetProcess, 0, memorySize, NULL);
if (hRemoteThread)
CloseHandle(hTargetProcess);
return 0;
}
本文参考了西祠高手shotgun的《揭开木马的神秘面纱》,代码为自己所写,放在这里供大家参考。
大家可以参考上面的步骤自己实现一遍!
将DLL挂接到远程进程之中(远程注入)的更多相关文章
- 使用VC++通过远程进程注入来实现HOOK指定进程的某个API
前阵子读到一篇关于<HOOK API入门之Hook自己程序的MessageBoxW>的博客,博客地址:http://blog.csdn.net/friendan/article/detai ...
- C# 远程服务器 安装、卸载 Windows 服务,读取远程注册表,关闭杀掉远程进程
这里安装windows服务我们用sc命令,这里需要远程服务器IP,服务名称.显示名称.描述以及执行文件,安装后需要验证服务是否安装成功,验证方法可以直接调用ServiceController来查询服务 ...
- EasyHook远程进程注入并hook api的实现
EasyHook远程进程注入并hook api的实现 http://blog.csdn.net/v6543210/article/details/44276155
- Centos7-Docker1.12开启守护进程(远程调用)
本文讲述了Docker1.12.6在Linux下开启守护进程(远程调用),理论上来说其他版本也是一样的改法,博主参考很多都是巨坑,综合自己实战分享给大家,如有错误请留言; - 修改配置 1.修改 do ...
- 附加进程 到远程服务器中Docker容器内 调试
很多时候,我们在本地开发过程中程序运行很正常,但是发布到线上之后由于环境的原因,可能会有一些异常.通常我们会通过日志来分析问题,除了日志还有一种常用的调试手段就是:附加进程. VS中的附加进程非常强大 ...
- Wow64(32位进程)注入DLL到64位进程
转载自: https://blog.poxiao.me/p/wow64-process-inject-dll-into-x64-process/ 向其他进程注入DLL通常的做法是通过调用CreateR ...
- MFC:AfxLoadLibrary-将指定的 DLL 映射到调用进程的地址空间
Visual Studio 2012 - Visual C++ LoadLibrary 和 AfxLoadLibrary 进程调用 LoadLibrary (或 AfxLoadLibrary) 以显式 ...
- 设置Windows 远程协助与远程桌面
家庭局域网组建完成后,即可通过远程协助解决各种问题,或联机玩游戏等. 使用Windows 7\8\10 远程协助与远程桌面 Windows 8系统中自带了远程协助功能,家庭用户只需要做简单的设置,就可 ...
- Windows提供了两种将DLL映像到进程地址空间的方法(隐式和显式)
调用DLL,首先需要将DLL文件映像到用户进程的地址空间中,然后才能进行函数调用,这个函数和进程内部一般函数的调用方法相同.Windows提供了两种将DLL映像到进程地址空间的方法: 1. 隐式的加载 ...
随机推荐
- uWSGI+Nginx+Flask在Linux下的部署
搞了一天多,终于搞通了uWSGI的部署原理,下面总结一下遇到的一些坑,希望给读者能够少走弯路. 简单来说,uWSGI是一个web服务器,Nginx进行反向代理的其实跟这些服务器可以说没有 ...
- java版云笔记(一)
云笔记项目 这个项目的sql文件,需求文档,需要的html文件,jar包都可以去下载,下载地址为:http://download.csdn.net/download/liveor_die/998584 ...
- Butter Knife:一个安卓视图注入框架
Butter Knife:一个安卓视图注入框架 2014年5月8日 星期四 14:52 官网: http://jakewharton.github.io/butterknife/ GitHub地址: ...
- 交通运输线(LCA)
题目大意: 战后有很多城市被严重破坏,我们需要重建城市.然而,有些建设材料只能在某些地方产生.因此,我们必须通过城市交通,来运送这些材料的城市.由于大部分道路已经在战争期间完全遭到破坏,可能有两个城市 ...
- 对于JAVA多线程卖票小程序的理解
昨天把多线程重新看了一遍,发现自己还是有许多需要理解的地方,现在写一篇总结. 一:利用继承Thread类会出现的问题: public class SellTicketsThread extends T ...
- JavaScript 兼容性总结
请实现鼠标点击任意标签,alert该标签的名称(注意兼容性) function elementName(evt){ evt = evt|| window.event; var selected = ...
- [你必须知道的.NET]第十九回:对象创建始末(下)
本文将介绍以下内容: 对象的创建过程 内存分配分析 内存布局研究 接上回[第十八回:对象创建始末(上)],继续对对象创建话题的讨论>>> 2.2 托管堆的内存分配机制 引用类型的实例 ...
- [实战]MVC5+EF6+MySql企业网盘实战(9)——编辑文件名
写在前面 上篇文章实现了文件的下载,本篇文章将实现编辑文件名的功能. 系列文章 [EF]vs15+ef6+mysql code first方式 [实战]MVC5+EF6+MySql企业网盘实战(1) ...
- jquery通配符说明
按姓名匹配 1,name前缀为aa的所有div的jquery对象 Js代码 收藏代码$("div[name^='aa']"); 2,name后缀为aa的所有div的jquery对象 ...
- 【.NET】学习SQLite(1)
前沿 SQLite,是一款轻型的数据库,是遵守ACID的关系型数据库管理系统,它的设计目标是嵌入式的,而且目前已经在很多嵌入式产品中使用了它,它占用资源非常的低,在嵌入式设备中,可能只需要几百K的内存 ...