每个进程都有自己的虚拟内存地址空间, Windows 提供了一些API函数来让我们可以从进程外部修改进程的内存,启动线程等。这样我们就可以通过一些非常规的手段让进程执行一些我们自己的代码了。其基本的工作原理是 利用 CreateRemoteThread 在目标进程中建立一个线程,然后调用进程中已经存在的 LoadLibrary() 函数来加载我们指定的 DLL。

CreateRemoteThread()

这个函数与 CreateThread 很像,只是多了一个参数而已。

HANDLE CreateRemoteThread(
HANDLE hProcess,
PSECURITY_ATTRIBUTES psa,
DWORD dwStackSize,
PTHREAD_START_ROUTINE pfnStartAddr,
PVOID pvParam,
DWORD fdwCreate,
PDWORD pdwThreadId);”

hProcess 表明了我们想要在其中建立线程的进程句柄,想要获得一个进程的句柄,我们可以用 OpenProcess() 函数来获得。

LoadLibrary()

这个方法会加载指定路径的 DLL 文件。

HINSTANCE LoadLibrary(PCTSTR pszLibFile);

其实这个函数根据我们的字符集会调用不同的函数:

HINSTANCE WINAPI LoadLibraryA(LPCSTR  pszLibFileName);
HINSTANCE WINAPI LoadLibraryW(LPCWSTR pszLibFileName);
#ifdef UNICODE
#define LoadLibrary LoadLibraryW
#else
#define LoadLibrary LoadLibraryA
#endif // !UNICODE”

同时其原型与线程的启动函数非常的相似:

DWORD WINAPI ThreadFunc(PVOID pvParam);

看起来我们只需要这样就可以启动一个线程来加载我们自己的DLL了:

HANDLE hThread = CreateRemoteThread(hProcessRemote, NULL, 0,
LoadLibraryA, "C:\\MyLib.dll", 0, NULL);

HANDLE hThread = CreateRemoteThread(hProcessRemote, NULL, 0,
LoadLibraryW, "C:\\MyLib.dll", 0, NULL);

这样我们建立的新线程就会立马的调用 LoadLibrary{A|W}() 函数了。

问题

但实际上我们是不能如同上面这样做,因为存在两个问题。

第一个问题是,对于我们想调用的 LoadLibrary{A|W}() 函数,编译后是不可以直接用此名字调用的,这样会出现莫名其妙的问题,我们只能通过查找到这个函数名称所在的内存地址进行调用。

// Get the real address of LoadLibraryA in Kernel32.dll.
PTHREAD_START_ROUTINE pfnThreadRtn = (PTHREAD_START_ROUTINE)
GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryA");

HANDLE hThread = CreateRemoteThread(hProcessRemote, NULL, 0,
pfnThreadRtn, "C:\\MyLib.dll", 0, NULL);

第二个问题是,对于我们需要传递给 LoadLibrary{A|W}() 函数的参数,我们想要加载的 DLL 路径 是位于我们调用进程的地址空间内。这也会出现问题,所以我们还需要将 DLL 路径名先写到目标进程内才行。 这里我们就要用到几个函数了

// 请求向目标进程分配内存
PVOID VirtualAllocEx(
HANDLE hProcess,
PVOID pvAddress,
SIZE_T dwSize,
DWORD flAllocationType,
DWORD flProtect);

// 请求目标进程释放内存
BOOL VirtualFreeEx(
HANDLE hProcess,
PVOID pvAddress,
SIZE_T dwSize,
DWORD dwFreeType);

// 读目标进程内存
BOOL ReadProcessMemory(
HANDLE hProcess,
PVOID pvAddressRemote,
PVOID pvBufferLocal,
DWORD dwSize,
PDWORD pdwNumBytesRead);

// 写目标进程内存
BOOL WriteProcessMemory(
HANDLE hProcess,
PVOID pvAddressRemote,
PVOID pvBufferLocal,
DWORD dwSize,
PDWORD pdwNumBytesWritten);

总结注入过程

  1. VirtualAllocEx() 分配一块内存,我们会用来存储我们的 DLL 路径。
  2. WriteProcessMemory() 将我们的 DLL 路径写上 第 1 步申请的内存中。
  3. GetProcAddress() 函数来获取我们的 LoadLibrary{A|W}() 函数地址。
  4. CreateRemoteThread() 来创建远程线程,执行我们找到的 LoadLibrary{A|W}() 函数地址,同时传递 第 1 步中申请的内存作为参数(现在其已经存储了我们的 DLL 路径了)。
  5. VirtualFreeEx() 用来释放我们申请存储 DLL 路径的内存。
  6. 调用 GetProcAddress() 来获取 FreeLibrary() 函数的地址。
  7. 调用 CreateRemoteThread() 建立线程执行 FreeLibrary() 来释放我们 DLL 的 HINSTANCE。

这几个步骤中我们需要获得的进程句柄可以用 OpenProcess() 来获得:

hProcess = OpenProcess(
PROCESS_CREATE_THREAD | // For CreateRemoteThread
PROCESS_VM_OPERATION | // For VirtualAllocEx/VirtualFreeEx
PROCESS_VM_WRITE, // For WriteProcessMemory
FALSE, dwProcessId);

具体的例子可以查看 Windows Via C/C++ 的第 22.4.1 节。