初探dll注入
字数 1382 2025-08-09 13:33:38
DLL注入技术详解
1. 全局钩子注入
原理
Windows系统中大部分应用基于消息机制,通过钩子机制可以截获和监视系统消息。全局钩子通过DLL文件实现钩子函数,当对应事件发生时,系统会将DLL加载到发生事件的进程地址空间中。
核心API
SetWindowsHookEx: 安装钩子到挂钩链CallNextHookEx: 将当前钩子传递给下一个钩子UnhookWindowsHookEx: 卸载钩子
实现步骤
-
创建DLL项目,导出以下函数:
extern "C" _declspec(dllexport) int SetHook(); extern "C" _declspec(dllexport) LRESULT GetMsgProc(int code, WPARAM wParam, LPARAM lParam); extern "C" _declspec(dllexport) BOOL UnsetHook(); -
实现钩子回调函数:
LRESULT GetMsgProc(int code, WPARAM wParam, LPARAM lParam) { return ::CallNextHookEx(g_hHook, code, wParam, lParam); } -
设置全局钩子:
BOOL SetHook() { g_Hook = ::SetWindowsHookEx(WH_GETMESSAGE, (HOOKPROC)GetMsgProc, g_hDllMoudle, 0); return g_Hook != NULL; } -
使用共享内存实现进程通信:
#pragma data_seg("mydata") HHOOK g_hHook = NULL; #pragma data_seg() #pragma comment(linker, "/SECTION:mydata,RWS") -
创建注入程序加载DLL并调用SetHook函数
2. 远程线程注入
原理
通过CreateRemoteThread在一个进程中创建线程,将DLL加载到目标进程地址空间。
核心API
CreateRemoteThread: 在远程进程中创建线程VirtualAllocEx: 在远程进程中申请内存WriteProcessMemory: 向远程进程写入数据GetProcAddress: 获取LoadLibrary地址
实现步骤
-
获取目标进程PID:
DWORD _GetProcessPID(LPCTSTR lpProcessName) { // 使用CreateToolhelp32Snapshot拍摄进程快照 // 遍历进程列表匹配进程名 } -
打开目标进程:
HANDLE hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, _Pid); -
在目标进程中分配内存:
LPVOID pAllocMemory = ::VirtualAllocEx(hProcess, NULL, _Size, MEM_COMMIT, PAGE_READWRITE); -
写入DLL路径:
BOOL Write = ::WriteProcessMemory(hProcess, pAllocMemory, DllName, _Size, NULL); -
获取LoadLibrary地址:
FARPROC pThread = ::GetProcAddress(::GetModuleHandle(L"kernel32.dll"), "LoadLibraryW"); -
创建远程线程:
HANDLE hThread = ::CreateRemoteThread(hProcess, NULL, 0, addr, pAllocMemory, 0, NULL);
3. 突破Session 0的远程线程注入
Session 0隔离机制
从Windows Vista开始,服务和用户应用程序被隔离到不同会话中,只有服务运行在Session 0中。
核心API
ZwCreateThreadEx: 比CreateRemoteThread更底层的线程创建函数OpenProcessToken: 打开进程访问令牌LookupPrivilegeValue: 查看系统特权AdjustTokenPrivileges: 调整令牌权限
实现步骤
-
提权函数实现:
BOOL EnableDebugPrivilege() { // 获取当前进程令牌 // 查找SE_DEBUG_NAME特权 // 调整令牌权限 } -
32位和64位下ZwCreateThreadEx定义:
#ifdef _WIN64 typedef DWORD(WINAPI* typedef_ZwCreateThreadEx)( PHANDLE ThreadHandle, ACCESS_MASK DesiredAccess, LPVOID ObjectAttributes, HANDLE ProcessHandle, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, ULONG CreateThreadFlags, SIZE_T ZeroBits, SIZE_T StackSize, SIZE_T MaximumStackSize, LPVOID pUnkown); #else // 32位定义... #endif -
注入流程:
// 1. 提权 // 2. 打开目标进程 // 3. 在目标进程分配内存 // 4. 写入DLL路径 // 5. 获取LoadLibrary地址 // 6. 获取ZwCreateThreadEx地址 // 7. 调用ZwCreateThreadEx创建远程线程
4. APC注入
原理
利用异步过程调用(APC)机制,将APC函数插入到线程APC队列中,当线程处于可警告状态时执行。
核心API
QueueUserAPC: 向线程APC队列添加APC函数SleepEx/WaitForSingleObjectEx: 使线程进入可警告状态
实现条件
- 多线程环境
- 目标程序会调用同步对象(SleepEx等)
实现步骤
-
获取目标进程所有线程:
BOOL GetProcessThreadList(DWORD th32ProcessID, DWORD** ppThreadIdList, LPDWORD pThreadIdListLength) { // 使用CreateToolhelp32Snapshot拍摄线程快照 // 遍历线程列表 } -
APC注入主函数:
BOOL APCInject(HANDLE hProcess, CHAR* wzDllFullPath, LPDWORD pThreadIdList, DWORD dwThreadIdListLength) { // 1. 在目标进程分配内存 // 2. 写入DLL路径 // 3. 获取LoadLibrary地址 // 4. 遍历线程插入APC } -
遍历线程插入APC:
for(int i = dwThreadIdListLength-1; i >= 0; i--) { HANDLE hThread = ::OpenThread(THREAD_ALL_ACCESS, FALSE, pThreadIdList[i]); ::QueueUserAPC((PAPCFUNC)loadLibraryAddress, hThread, (ULONG_PTR)lpAddr); }
技术对比
| 注入方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 全局钩子 | 实现简单 | 需要DLL支持 | GUI程序 |
| 远程线程 | 通用性强 | 受Session 0限制 | 普通进程注入 |
| ZwCreateThreadEx | 可突破Session 0 | 实现复杂 | 系统服务注入 |
| APC注入 | 隐蔽性高 | 需要多线程环境 | 多线程程序注入 |
注意事项
- 32位和64位程序注入时需要注意位数匹配
- 注入系统进程需要提权(SE_DEBUG_NAME)
- 注入DLL中避免使用MessageBox等UI操作
- 注入后需要清理内存和句柄
- 考虑目标进程的UAC和完整性级别