第41章 DLL注入技术(一):远程线程注入、SetWindowsHookEx注入
DLL注入,说白了就是让一个进程强行加载你写的DLL。这招在逆向工程里太常用了——你想hook某个API、监控程序行为、或者给目标进程打补丁,都得先把代码塞进去。
我刚开始搞逆向那会儿,觉得注入这事儿挺神秘的。后来做多了才发现,原理其实不复杂。Windows提供了多种注入方式,这一章我们先聊两个最经典的:远程线程注入和SetWindowsHookEx注入。
41.1 远程线程注入
远程线程注入,是我个人用得最多的一种方式。它的核心思路就一句话:在目标进程里创建一个线程,让这个线程去加载你的DLL。
具体怎么做?Windows提供了几个关键API:
OpenProcess— 打开目标进程,拿到句柄VirtualAllocEx— 在目标进程里分配内存WriteProcessMemory— 把DLL路径写进去CreateRemoteThread— 在目标进程里创建线程LoadLibraryA— 让线程执行这个函数,加载DLL
嗯,这里要注意:CreateRemoteThread的线程函数地址,我们传的是LoadLibraryA的地址。为什么能这么干?因为kernel32.dll在每个进程里的加载地址基本一样(除非开启了ASLR但没开重定位)。
核心原理:远程线程注入的本质,就是利用CreateRemoteThread让目标进程执行LoadLibrary,从而加载你的DLL。
代码实现
我写个简单的例子,你一看就明白:
// 远程线程注入示例
BOOL InjectDLL(DWORD dwProcessId, const char* szDllPath) {
HANDLE hProcess = NULL;
HANDLE hThread = NULL;
LPVOID pRemoteBuf = NULL;
HMODULE hKernel32 = NULL;
LPTHREAD_START_ROUTINE pLoadLibrary = NULL;
// 1. 打开目标进程
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
if (!hProcess) return FALSE;
// 2. 在目标进程分配内存
int nPathLen = strlen(szDllPath) + 1;
pRemoteBuf = VirtualAllocEx(hProcess, NULL, nPathLen,
MEM_COMMIT, PAGE_READWRITE);
if (!pRemoteBuf) {
CloseHandle(hProcess);
return FALSE;
}
// 3. 写入DLL路径
WriteProcessMemory(hProcess, pRemoteBuf,
(LPVOID)szDllPath, nPathLen, NULL);
// 4. 获取LoadLibraryA地址
hKernel32 = GetModuleHandleA("kernel32.dll");
pLoadLibrary = (LPTHREAD_START_ROUTINE)
GetProcAddress(hKernel32, "LoadLibraryA");
// 5. 创建远程线程
hThread = CreateRemoteThread(hProcess, NULL, 0,
pLoadLibrary, pRemoteBuf, 0, NULL);
if (!hThread) {
VirtualFreeEx(hProcess, pRemoteBuf, 0, MEM_RELEASE);
CloseHandle(hProcess);
return FALSE;
}
// 6. 等待线程结束,清理
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
CloseHandle(hProcess);
return TRUE;
}
这段代码我用了很多年,基本没出过问题。但有一点要提醒你:目标进程的权限。如果你要注入系统进程或者高权限进程,你的程序也得有相应权限。我曾经在注入svchost.exe时翻过车,就是因为忘了提权。
小技巧:如果目标进程是32位的,你的注入器也得是32位的。同理,64位对64位。跨位数注入?嗯,那得用其他方法,后面章节会讲。
远程线程注入的优缺点
| 优点 | 缺点 |
|---|---|
| 实现简单,代码量少 | 容易被安全软件检测(CreateRemoteThread是敏感API) |
| 兼容性好,Windows全版本支持 | 需要目标进程有PROCESS_CREATE_THREAD权限 |
| 注入成功率高 | 会留下明显的线程痕迹 |
41.2 SetWindowsHookEx注入
另一种经典注入方式,是利用Windows的钩子机制。你想想看,Windows的消息处理系统本身就支持钩子——你可以安装一个全局钩子,拦截键盘、鼠标等消息。而钩子函数必须放在DLL里,系统会自动把DLL注入到所有相关进程。
说白了,SetWindowsHookEx注入就是利用系统机制,让Windows帮你完成注入。你只需要提供一个DLL,里面包含钩子处理函数,然后调用SetWindowsHookEx注册钩子就行了。
实现步骤
- 编写一个DLL,里面实现钩子处理函数(比如
KeyboardProc) - 在DLL的
DllMain里写你的注入逻辑(比如启动一个线程) - 调用
SetWindowsHookEx注册钩子,指定DLL句柄 - 系统会把你的DLL注入到所有满足条件的进程中
这里有个关键点:钩子类型决定了注入范围。比如WH_KEYBOARD_LL是低级键盘钩子,不需要注入到目标进程,而是在你的进程里处理。但WH_KEYBOARD(非LL版本)就需要注入到目标进程。
注意:全局钩子会影响系统性能。每个消息都要经过你的DLL处理,如果处理慢了,整个系统都会卡。我曾经在生产环境里见过有人用全局钩子做监控,结果用户反馈电脑变慢,最后查出来是钩子DLL里有个死循环。
代码示例
DLL端的代码大概长这样:
// HookDLL.cpp
#include <windows.h>
HHOOK g_hHook = NULL;
// 钩子处理函数
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) {
// 这里可以写你的注入逻辑
// 比如启动一个线程做其他事情
return CallNextHookEx(g_hHook, nCode, wParam, lParam);
}
// 安装钩子
extern "C" __declspec(dllexport)
BOOL InstallHook() {
HMODULE hMod = GetModuleHandleA("HookDLL.dll");
g_hHook = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc,
hMod, 0);
return (g_hHook != NULL);
}
// 卸载钩子
extern "C" __declspec(dllexport)
BOOL UninstallHook() {
if (g_hHook) {
UnhookWindowsHookEx(g_hHook);
g_hHook = NULL;
return TRUE;
}
return FALSE;
}
// DllMain
BOOL APIENTRY DllMain(HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved) {
if (ul_reason_for_call == DLL_PROCESS_ATTACH) {
// DLL被注入到目标进程时触发
// 在这里写你的注入后逻辑
MessageBox(NULL, "DLL Injected!", "Success", MB_OK);
}
return TRUE;
}
调用端就简单了:
// 加载DLL,调用InstallHook
HMODULE hDll = LoadLibraryA("HookDLL.dll");
if (hDll) {
typedef BOOL (*InstallHookFunc)();
InstallHookFunc InstallHook = (InstallHookFunc)
GetProcAddress(hDll, "InstallHook");
if (InstallHook) InstallHook();
}
SetWindowsHookEx注入的优缺点
| 优点 | 缺点 |
|---|---|
| 系统自动注入,不需要手动创建远程线程 | 只能注入有消息队列的GUI进程 |
| 相对隐蔽,不容易被简单检测 | 全局钩子影响系统性能 |
| 可以同时注入多个进程 | 钩子DLL必须导出钩子处理函数 |
41.3 两种注入方式的对比
我个人的经验是:远程线程注入适合精确打击——你知道目标进程的PID,只想注入这一个进程。SetWindowsHookEx注入适合广撒网——你想监控所有键盘输入,或者注入所有记事本进程。
但说实话,现在很多安全软件对这两种方式都有检测。远程线程的CreateRemoteThread是重点监控对象,而全局钩子也会被一些HIPS(主机入侵防御系统)拦截。
避坑指南:我曾经在写一个游戏辅助工具时,用了远程线程注入。结果游戏的反作弊系统直接检测到了CreateRemoteThread调用,把我进程干掉了。后来我改用APC注入才绕过去。所以,没有万能的注入方式,要根据场景选。
41.4 知识体系图
下面这张图帮你理清两种注入方式的脉络:
41.5 写在最后
这两种注入方式,是逆向工程里最基础也最实用的。远程线程注入就像一把手术刀,精准但容易被发现。SetWindowsHookEx注入更像一张网,覆盖面广但有限制。
我个人建议你先把远程线程注入练熟,因为它能帮你理解Windows进程和线程的核心机制。等你把基础打牢了,再去研究更高级的注入方式——比如APC注入、线程劫持、或者利用Windows漏洞的注入。
嗯,这一章就到这里。代码你可以直接拿去用,但记得在合法范围内使用。搞逆向的,底线还是要有的。
公众号:蓝海资料掘金营,微信deep3321