Windows用户态下常见的DLL注入技术总结

Windows用户态下常见的DLL注入技术总结

这里总结一下Windows用户态下常见的DLL注入技术,从经典的 CreateRemoteThread 到隐蔽性极高的反射式注入等多种方法,并对比其优缺点与适用场景。

1. 基于消息钩子的注入(SetWindowsHookEx)

在安装消息钩子的时候,Windows会检查消息钩子所在的DLL是否位于目标进程中,如果没有会将DLL加载到目标进程的地址空间中,利用这一特性可以进行DLL注入。

实现步骤:

  1. 编写一个包含钩子处理函数的DLL。
  2. 在注入器程序中调用 SetWindowsHookEx 安装钩子。
  3. 当钩子被触发时,系统自动将DLL加载到目标进程。
cpp 复制代码
// 注入器代码示例
HINSTANCE hDll = LoadLibrary(L"inject.dll");
HOOKPROC hkprc = (HOOKPROC)GetProcAddress(hDll, "HookProc");
SetWindowsHookEx(WH_KEYBOARD, hkprc, hDll, dwTargetThreadId);

优点: 实现简单,系统自动完成加载。
缺点: 需要目标进程有消息循环;注入的DLL会随钩子卸载而卸载。

2. 远程线程注入(CreateRemoteThread + LoadLibrary)

我们可以在目标进程中创建一个线程,让该线程执行LoadLibrary加载我们的DLL。

实现步骤:

  1. 使用 OpenProcess 打开目标进程。
  2. 使用 VirtualAllocEx 在目标进程中分配内存,写入DLL路径。
  3. 使用 CreateRemoteThread 创建远程线程,线程函数为 LoadLibraryW
cpp 复制代码
// 核心代码示例
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwTargetPid);
LPVOID pRemoteMem = VirtualAllocEx(hProcess, NULL, 0x100, MEM_COMMIT, PAGE_READWRITE);
WriteProcessMemory(hProcess, pRemoteMem, szDllPath, wcslen(szDllPath) * 2 + 2, NULL);
LPTHREAD_START_ROUTINE pLoadLibrary = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(L"kernel32.dll"), "LoadLibraryW");
CreateRemoteThread(hProcess, NULL, 0, pLoadLibrary, pRemoteMem, 0, NULL);

实际上,还可以使用其它可以创建远程线程的函数创建远程线程,如 NtCreateThreadExRtlCreateUserThread,只要创建的远程线程执行的是LoadLibrary就可以了。

优点: 经典方法,兼容性好。
缺点: 容易被安全软件检测;需要目标进程有足够的权限。

3. APC注入(QueueUserAPC)

利用Windows的APC(异步过程调用)调用机制进行注入。

原理: 每个线程都有一个APC队列。当线程进入可告警等待状态(如 SleepExWaitForSingleObjectEx)时,会依次执行队列中的APC函数。

实现步骤:

  1. 打开目标线程,获取线程句柄。
  2. 在目标进程中分配内存并写入DLL路径。
  3. 调用 QueueUserAPCLoadLibraryW 作为APC函数加入目标线程的APC队列。
cpp 复制代码
HANDLE hThread = OpenThread(THREAD_SET_CONTEXT, FALSE, dwThreadId);
QueueUserAPC((PAPCFUNC)pLoadLibrary, hThread, (ULONG_PTR)pRemoteMem);

优点: 不需要创建新线程,隐蔽性较好。
缺点: 依赖目标线程进入可告警等待状态,触发时机不确定。

4. 注册表注入(AppInit_DLLs)

利用Windows的AppInit_DLLs机制或KnownDLLs机制进行注入。

AppInit_DLLs方式:

  1. 将DLL路径写入注册表 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows\AppInit_DLLs
  2. 设置 LoadAppInit_DLLs 为1。
  3. 系统启动时,所有加载 user32.dll 的进程都会自动加载指定的DLL。
reg 复制代码
Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows]
"AppInit_DLLs"="C:\\inject.dll"
"LoadAppInit_DLLs"=dword:00000001

优点: 无需编写注入器代码,系统自动加载。
缺点: 影响范围大(所有加载user32.dll的进程);Win8+默认禁用;容易被安全软件拦截。

注意: 在Win11中,这个注册表项默认是找不到的,查阅资料发现该功能默认关闭且隐藏,需要手动创建。这种注入方式已经不建议使用。

5. DLL劫持

利用Windows加载DLL的顺序进行DLL注入。

原理: 当程序加载DLL时,系统会按照特定顺序搜索DLL。如果攻击者将恶意DLL放在搜索路径的前面(如程序所在目录),系统会优先加载恶意DLL。

搜索顺序(无SafeDllSearchMode时):

  1. 程序所在目录
  2. 当前目录
  3. 系统目录(System32)
  4. 16位系统目录
  5. Windows目录
  6. PATH环境变量中的目录

常见劫持目标: 程序缺失的DLL、已知的DLL劫持漏洞(如 version.dlldbghelp.dll 等)。

实现方式:

  1. 分析目标程序加载了哪些DLL。
  2. 编写一个同名的代理DLL,导出与原DLL相同的函数。
  3. 在代理函数中调用原DLL的对应函数(转发或转发加载),同时执行恶意代码。
cpp 复制代码
// 代理DLL示例 - 转发LoadLibrary到原DLL
#pragma comment(linker, "/EXPORT:SomeFunction=original_dll.SomeFunction")

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
    if (ul_reason_for_call == DLL_PROCESS_ATTACH) {
        // 执行恶意代码
        MessageBox(NULL, L"Injected!", L"DLL Hijack", MB_OK);
    }
    return TRUE;
}

优点: 隐蔽性较好,不需要远程线程。
缺点: 依赖目标程序的DLL加载行为;需要找到合适的劫持目标。

6. 反射式DLL注入

不调用LoadLibrary,而是编写一段shellcode模仿该函数的行为,在目标进程中执行shellcode进行DLL注入。

原理: 手动解析DLL文件格式,在目标进程的内存中完成DLL的加载(包括重定位、导入表处理、TLS回调等),而不调用系统API。

实现步骤:

  1. 将DLL完整读入内存。
  2. 编写反射加载器(shellcode),实现:
    • 解析DLL的PE结构
    • 在目标进程分配内存
    • 修复重定位表
    • 解析并加载依赖的DLL
    • 处理导入表
    • 调用DLL入口点
  3. 将shellcode和DLL数据写入目标进程。
  4. 通过 CreateRemoteThread 等执行shellcode。

优点: 不会修改PEB中那三个与DLL列表相关的字段(InLoadOrderModuleListInMemoryOrderModuleListInInitializationOrderModuleList),隐蔽性极高。
缺点: 实现复杂,需要深入理解PE结构;兼容性需要仔细处理。

7. 导入表注入

通过修改目标PE文件的导入表(Import Table),在程序启动时强制加载指定的DLL。

原理: PE文件的导入表中记录了程序运行时需要加载的DLL及其导入函数。通过向导入表中添加新的DLL条目,可以使程序在启动时自动加载恶意DLL。

实现步骤:

  1. 解析目标PE文件的导入表结构。
  2. 在导入表末尾添加新的 IMAGE_IMPORT_DESCRIPTOR 条目。
  3. 添加对应的DLL名称字符串和导入函数名称/序号。
  4. 保存修改后的PE文件。
cpp 复制代码
// 伪代码示意
PIMAGE_IMPORT_DESCRIPTOR pImportDesc = ...; // 定位到导入表
// 在末尾添加新条目
pImportDesc[newEntry].OriginalFirstThunk = ...;
pImportDesc[newEntry].Name = RVA of "inject.dll";
pImportDesc[newEntry].FirstThunk = ...;

优点: 持久化注入,每次启动都会加载;不需要运行注入器。
缺点: 需要修改磁盘文件;容易被完整性校验检测;需要处理数字签名问题。

各技术对比总结

注入技术 是否需要远程线程 持久性 隐蔽性 实现复杂度 适用场景
SetWindowsHookEx 否(系统自动加载) 有消息循环的GUI进程
CreateRemoteThread + LoadLibrary 通用注入,兼容性最好
QueueUserAPC 否(利用APC队列) 中高 目标线程处于可告警等待状态时
注册表注入(AppInit_DLLs) 全局注入,Win7及以下系统
DLL劫持 针对特定程序,持久化
反射式DLL注入 极高 需要绕过PEB检测的场景
导入表注入 持久化注入,修改磁盘文件
相关推荐
半月夏微凉2 小时前
win11下不能预览pdf的问题解决方法
windows·pdf
XLYcmy2 小时前
Agent身份与权限系统设计方案
windows·网络安全·ai·llm·飞书·api·agent
x***r15111 小时前
jdk-11.0.16.1_windows使用步骤详解(附JDK 11环境变量配置与验证教程)
java·开发语言·windows
玖釉-14 小时前
下一个排列:从字典序到原地算法的完整推导
数据结构·c++·windows·算法
cddchina15 小时前
【Steps Recorder 和 Snipping Tool】
windows·效率工具·截图工具
我材不敲代码16 小时前
Python基础:列表详解、增删改查及常用高阶操作
开发语言·windows·python
KeanuReeves18 小时前
【常用操作】BAT常用脚本命令整理
windows
徐sir(徐慧阳)20 小时前
记一次生产库ORA-00257故障处理
windows·oracle·ora-00257
xiaoshuaishuai821 小时前
C# 服务注册与生命周期
开发语言·windows·c#