Windows平台下的DLL注入是系统安全研究和底层开发中的常见技术,涉及在目标进程中加载自定义DLL以执行特定代码。本文基于实际注入实践,介绍三种经典注入方法:全局钩子注入、远程线程注入,以及突破Session 0隔离的远线程注入。每种方法均给出Win32 API原理、关键步骤和完整代码示例。
一、全局钩子注入
Windows消息机制中,钩子(Hook)可以截获系统或线程的消息。全局钩子注入利用SetWindowsHookEx将DLL中的钩子函数安装到钩子链中,当系统产生消息时自动加载DLL。
1.1 生成DLL
新建DLL项目,在预编译头中声明导出函数:- // pch.h
- extern "C" _declspec(dllexport) int SetHook();
- extern "C" _declspec(dllexport) LRESULT GetMsgProc(int code, WPARAM wParam, LPARAM lParam);
- extern "C" _declspec(dllexport) BOOL UnsetHook();
复制代码
在pch.cpp中实现:- #include "pch.h"
- #include <Windows.h>
- #include <stdio.h>
- extern HMODULE g_hDllModule;
- #pragma data_seg("mydata")
- HHOOK g_hHook = NULL;
- #pragma data_seg()
- #pragma comment(linker, "/SECTION:mydata,RWS")
- LRESULT GetMsgProc(int code, WPARAM wParam, LPARAM lParam) {
- return ::CallNextHookEx(g_hHook, code, wParam, lParam);
- }
- BOOL SetHook() {
- g_hHook = SetWindowsHookEx(WH_GETMESSAGE, (HOOKPROC)GetMsgProc, g_hDllModule, 0);
- if (g_hHook == NULL) return FALSE;
- return TRUE;
- }
- BOOL UnsetHook() {
- if (g_hHook) ::UnhookWindowsHookEx(g_hHook);
- return TRUE;
- }
复制代码
DLL入口文件dllmain.cpp保存模块句柄:- #include "pch.h"
- HMODULE g_hDllModule = NULL;
- BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
- switch (ul_reason_for_call) {
- case DLL_PROCESS_ATTACH:
- g_hDllModule = hModule;
- break;
- case DLL_THREAD_ATTACH:
- case DLL_THREAD_DETACH:
- case DLL_PROCESS_DETACH:
- break;
- }
- return TRUE;
- }
复制代码
以Release X86配置生成得到DLL文件。
1.2 测试注入
创建控制台项目,加载DLL并调用SetHook:- #include <Windows.h>
- #include <stdio.h>
- int main() {
- typedef BOOL(*typedef_SetGlobalHook)();
- typedef BOOL(*typedef_UnsetGlobalHook)();
- HMODULE hDll = NULL;
- typedef_SetGlobalHook SetGlobalHook = NULL;
- typedef_UnsetGlobalHook UnsetGlobalHook = NULL;
- BOOL bRet = FALSE;
- do {
- hDll = ::LoadLibraryW(TEXT("C:\\path\\to\\DLLInjection.dll"));
- if (hDll == NULL) { printf("LoadLibrary Error[%d]\n", ::GetLastError()); break; }
- SetGlobalHook = (typedef_SetGlobalHook)::GetProcAddress(hDll, "SetHook");
- if (SetGlobalHook == NULL) { printf("GetProcAddress Error[%d]\n", ::GetLastError()); break; }
- bRet = SetGlobalHook();
- if (bRet) printf("SetGlobalHook OK.\n");
- else printf("SetGlobalHook Error.\n");
- system("pause");
- UnsetGlobalHook = (typedef_UnsetGlobalHook)::GetProcAddress(hDll, "UnsetHook");
- if (UnsetGlobalHook == NULL) { printf("GetProcAddress Error[%d]\n", ::GetLastError()); break; }
- UnsetGlobalHook();
- printf("UnsetGlobalHook OK.\n");
- } while (FALSE);
- system("pause");
- return 0;
- }
复制代码
全局钩子注入的优点是无需目标进程配合,缺点是会影响系统性能,且容易被安全软件检测。
二、远程线程注入
远程线程注入的核心思路是在目标进程中创建线程,线程入口为LoadLibrary,参数为DLL路径,从而将DLL加载到目标进程空间。
2.1 生成DLL
仅需在DllMain中弹出消息框验证:- #include "pch.h"
- BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
- switch (ul_reason_for_call) {
- case DLL_PROCESS_ATTACH:
- MessageBox(NULL, L"success!", L"RemoteThreadInjection", MB_OK);
- break;
- case DLL_THREAD_ATTACH:
- MessageBox(NULL, L"success!", L"RemoteThreadInjection", MB_OK);
- break;
- }
- return TRUE;
- }
复制代码
2.2 实现注入函数
通过OpenProcess打开目标进程,VirtualAllocEx分配内存,WriteProcessMemory写入DLL路径,然后获取kernel32.dll中LoadLibraryW的地址,最后用CreateRemoteThread创建线程:- #include <Windows.h>
- #include <tchar.h>
- #include <stdio.h>
- DWORD _RemoteThreadInjection(DWORD _Pid, LPCWSTR DllName) {
- HANDLE hprocess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, _Pid);
- if (hprocess == NULL) { printf("OpenProcess Error\n"); return FALSE; }
- SIZE_T _Size = (_tcslen(DllName) + 1) * sizeof(TCHAR);
- LPVOID pDllAddr = ::VirtualAllocEx(hprocess, NULL, _Size, MEM_COMMIT, PAGE_READWRITE);
- if (pDllAddr == NULL) { printf("Memory Allocation Error\n"); return FALSE; }
- BOOL isWriteSuccess = ::WriteProcessMemory(hprocess, pDllAddr, DllName, _Size, NULL);
- if (!isWriteSuccess) { printf("Write Process Memory Error\n"); return FALSE; }
- FARPROC pThread = ::GetProcAddress(::GetModuleHandle(L"kernel32.dll"), "LoadLibraryW");
- LPTHREAD_START_ROUTINE addr = (LPTHREAD_START_ROUTINE)pThread;
- HANDLE hThread = ::CreateRemoteThread(hprocess, NULL, 0, addr, pDllAddr, 0, NULL);
- if (hThread == NULL) { printf("CreateRemoteThread Error\n"); return FALSE; }
- WaitForSingleObject(hThread, -1);
- ::VirtualFreeEx(hprocess, pDllAddr, _Size, MEM_DECOMMIT);
- ::CloseHandle(hprocess);
- return TRUE;
- }
- int main() {
- DWORD _PID = 1220; // 替换为目标进程PID
- _RemoteThreadInjection(_PID, L"C:\\path\\to\\DLLInjection.dll");
- return 0;
- }
复制代码
注意: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 实现代码- #include <Windows.h>
- #include <stdio.h>
- // 定义ZwCreateThreadEx函数指针
- 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 pUnknown);
- BOOL EnableDebugPrivilege() {
- HANDLE hToken;
- if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken)) {
- TOKEN_PRIVILEGES tp;
- tp.PrivilegeCount = 1;
- LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid);
- tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
- AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);
- BOOL flag = (GetLastError() == ERROR_SUCCESS);
- CloseHandle(hToken);
- return flag;
- }
- return FALSE;
- }
- BOOL BypassSession0Injection(DWORD _PID, const char* DLLPath) {
- HANDLE hProcess = NULL;
- SIZE_T dwSize = 0;
- LPVOID pDllAddr = NULL;
- FARPROC LoadLibraryA_Addr = NULL;
- HANDLE hRemoteThread = NULL;
- DWORD dwStatus = 0;
- // 提权
- if (!EnableDebugPrivilege()) {
- printf("SeDebugPrivilege enable failed.\n");
- return FALSE;
- }
- hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, _PID);
- if (hProcess == NULL) { printf("OpenProcess Error\n"); return FALSE; }
- dwSize = ::strlen(DLLPath) + 1;
- pDllAddr = ::VirtualAllocEx(hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE);
- if (pDllAddr == NULL) { printf("VirtualAllocEx Error\n"); return FALSE; }
- if (!::WriteProcessMemory(hProcess, pDllAddr, DLLPath, dwSize, NULL)) {
- printf("WriteProcessMemory Error\n");
- return FALSE;
- }
- HMODULE hNtdDll = ::LoadLibrary(L"ntdll.dll");
- if (hNtdDll == NULL) { printf("Load ntdll.dll Error\n"); return FALSE; }
- LoadLibraryA_Addr = ::GetProcAddress(::GetModuleHandle(L"kernel32.dll"), "LoadLibraryA");
- if (LoadLibraryA_Addr == NULL) { printf("GetProcAddress Error\n"); return FALSE; }
- typedef_ZwCreateThreadEx ZwCreateThreadEx = (typedef_ZwCreateThreadEx)::GetProcAddress(hNtdDll, "ZwCreateThreadEx");
- if (ZwCreateThreadEx == NULL) { printf("ZwCreateThreadEx not found\n"); return FALSE; }
- dwStatus = ZwCreateThreadEx(&hRemoteThread, PROCESS_ALL_ACCESS,
- NULL, hProcess, (LPTHREAD_START_ROUTINE)LoadLibraryA_Addr, pDllAddr,
- 0, 0, 0, 0, NULL);
- if (hRemoteThread == NULL) { printf("ZwCreateThreadEx failed\n"); return FALSE; }
- ::CloseHandle(hProcess);
- ::FreeLibrary(hNtdDll);
- return TRUE;
- }
- int main() {
- // 以系统服务PID为例,需管理员权限
- BypassSession0Injection(3788, "C:\\path\\to\\artifact_x86.dll");
- return 0;
- }
复制代码
关键点:ZwCreateThreadEx的第7个参数CreateThreadFlags置0可绕过Session检查;必须先提升权限。
四、总结
三种注入方法各有利弊。全局钩子注入简单但影响系统范围;远程线程注入灵活但无法穿透Session 0;突破Session 0的注入利用底层API,适合对系统服务进行DLL加载。实际应用中需根据目标进程性质和权限环境选择合适技术,并注意DLL的32/64位匹配。 |