通过在加载所需DLL的目标进程中创建一个线程来注入DLL,其思想是在目标进程中创建一个线程,该线程使用要注入的DLL路径调用LoadLibrary函数。
下面的注入器示例演示了这种技术。首先,我们需要检查命令行参数:
int main(int argc, const char* argv[]) {
if (argc < 3) {
printf("Usage: injector <pid> <dllpath>\n");
return 0;
}
注入器需要目标的进程ID和要注入的DLL。接下来,我们打开目标进程的句柄:
HANDLE hProcess = ::OpenProcess(
PROCESS_VM_WRITE | PROCESS_VM_OPERATION | PROCESS_CREATE_THREAD,
FALSE, atoi(argv[1]));
if (!hProcess)
return Error("Failed to open process");
我们需要相当多的访问掩码位来为这种注入技术获得足够的权限。这意味着某些进程将无法访问。这个注入方法的诀窍是,从二进制的角度来看,LoadLibrary函数和线程的函数本质上是相同的:
HMODULE WINAPI LoadLibrary(PCTSTR);
DWORD WINAPI ThreadFunction(PVOID);
这两个原型都接受一个指针,这就是诀窍所在:我们可以创建一个运行函数 LoadLibrary 的线程!这样做很好,因为 LoadLibrary 的代码已经在目标进程中了(因为它是 kernel32.dll 的一部分,必须加载到每个属于 Windows 子系统的进程中)。
下一项任务是准备要加载的 DLL 路径。路径字符串本身必须放在目标进程中,因为 LoadLibrary 将在目标进程中执行。为此,我们可以使用 VirtualAllocEx 函数:
void* buffer = ::VirtualAllocEx(hProcess, nullptr, 1 << 12,
MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
if (!buffer)
return Error("Failed to allocate buffer in target process");
使用VirtualAllocEx需要PROCESS_VM_OPERATION访问掩码,我们在OpenProcess时请求。我们分配了一个4 KB的缓冲区,这是多余的,但即使我们指定的缓冲区小于4 KB,它也会四舍五入到4 KB。注意,返回的指针在调用者的进程中没有任何意义——它是在目标进程中分配的地址。
接下来,我们需要用WriteProcessMemory将DLL路径复制到分配的缓冲区:
if (!::WriteProcessMemory(hProcess, buffer,
argv[2], ::strlen(argv[2]) + 1, nullptr))
return Error("Failed to write to target process");
使用WriteProcessMemory要求进程句柄具有PROCESS_VM_WRITE访问掩码,它确实具有。一切就绪,是时候创建远程线程了:
DWORD tid;
HANDLE hThread = ::CreateRemoteThread(hProcess, nullptr, 0,
(LPTHREAD_START_ROUTINE)::GetProcAddress(::GetModuleHandle(L"kernel32"), "LoadLibraryA"),
buffer, 0, &tid);
if (!hThread)
return Error("Failed to create remote thread");
CreateRemoteThread接受目标进程句柄(必须具有PROCESS_CREATE_THREAD访问掩码)、NULL安全描述符、默认堆栈大小和线程的启动例程。这就是我们利用LoadLibrary和线程函数的二进制等价的地方。GetProcAddress调用用于动态定位LoadLibraryA的地址,利用它与当前进程中的地址相同这一事实。这是该技术的关键——不需要将代码复制到目标进程中。线程的参数是buffer——我们复制DLL路径的目标进程中的地址。
就是这样。需要注意的重要一点是,注入的DLL必须与目标进程具有相同的位数。
剩下的就是做一些清理工作:
printf("Thread %u created successfully!\n", tid);
if (WAIT_OBJECT_0 == ::WaitForSingleObject(hThread, 5000))
printf("Thread exited.\n");
else
printf("Thread still hanging around...\n");
// be nice
::VirtualFreeEx(hProcess, buffer, 0, MEM_RELEASE);
::CloseHandle(hThread);
::CloseHandle(hProcess);
等待线程终止并不是强制性的,但是我们需要在调用VirtualFreeEx之前给它一些时间来移除用VirtualAllocEx完成的分配。这是礼貌的,但不是绝对必要的。我们不妨将提交的4 KB保留在目标进程中。
下面是一个命令行测试示例:
C:\>Injector.exe 44532 C:\Temp\Injected.dll
必须指定DLL的完整路径,因为加载规则是从目标进程的角度出发,而不是从调用方的角度出发。注入DLL的DllMain显示了一个简单的消息框:
BOOL WINAPI DllMain(HMODULE hModule, DWORD reason, PVOID) {
switch (reason) {
case DLL_PROCESS_ATTACH:
wchar_t text[128];
::StringCchPrintf(text, _countof(text),
L"Injected into process %u",
::GetCurrentProcessId());
::MessageBox(nullptr, text, L"Injected.Dll", MB_OK);
break;
}
return TRUE;
}
完整代码如下:
// Injector.cpp : This file contains the 'main' function. Program execution begins and ends there.
//
#include <Windows.h>
#include <stdio.h>
int Error(const char* msg) {
printf("%s (%u)\n", msg, ::GetLastError());
return 1;
}
int main(int argc, const char* argv[]) {
if (argc < 3) {
printf("Usage: injector <pid> <dllpath>\n");
return 0;
}
HANDLE hProcess = ::OpenProcess(PROCESS_VM_WRITE | PROCESS_VM_OPERATION | PROCESS_CREATE_THREAD,
FALSE, atoi(argv[1]));
if (!hProcess)
return Error("Failed to open process");
void* buffer = ::VirtualAllocEx(hProcess, nullptr, 1 << 12,
MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
if (!buffer)
return Error("Failed to allocate buffer in target process");
if (!::WriteProcessMemory(hProcess, buffer, argv[2], ::strlen(argv[2]) + 1, nullptr))
return Error("Failed to write to target process");
DWORD tid;
HANDLE hThread = ::CreateRemoteThread(hProcess, nullptr, 0,
(LPTHREAD_START_ROUTINE)::GetProcAddress(::GetModuleHandle(L"kernel32"), "LoadLibraryA"),
buffer, 0, &tid);
if (!hThread)
return Error("Failed to create remote thread");
printf("Thread %u created successfully!\n", tid);
if (WAIT_OBJECT_0 == ::WaitForSingleObject(hThread, 5000))
printf("Thread exited.\n");
else
printf("Thread still hanging around...\n");
// be nice
::VirtualFreeEx(hProcess, buffer, 0, MEM_RELEASE);
::CloseHandle(hThread);
::CloseHandle(hProcess);
return 0;
}