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);

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

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

3. APC注入(QueueUserAPC)

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

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

实现步骤:

  1. 打开目标线程,获取线程句柄。
  2. 在目标进程中分配内存并写入DLL路径。
  3. 调用 QueueUserAPC 将 LoadLibraryW 作为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.dll、dbghelp.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列表相关的字段(InLoadOrderModuleList、InMemoryOrderModuleList、InInitializationOrderModuleList),隐蔽性极高。
缺点: 实现复杂,需要深入理解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检测的场景
导入表注入 否 高 中 高 持久化注入,修改磁盘文件
相关推荐
段一凡-华北理工大学11 分钟前
大模型应用开发 100 天:Python + LLM 从入门到精通 day26~Prompt 调试与优化——A/B 测试与效果评估
windows·python·大模型·prompt·智能体·提示词工程·高炉智能化
fox_charon1 小时前
Windows 下 CLI 参数的引号陷阱:为什么 --resume 'uuid' 会失败
windows·shell·cmd·cli·引号
染指11103 小时前
128.Agent-LangChain核心组件-中间件-调用模型的时候
人工智能·windows·中间件·langchain·agent
Frag0ut4 小时前
Chrome与Chromium内核浏览器在Windows 11上的新特性全景解析
前端·chrome·windows·web安全·chromium·gemini ai·playready drm
数安旭说5 小时前
企业终端安全实战:Windows软件管控、正版检测、变更审计与私有软件库落地
windows·企业安全·软件需求·终端安全·软件管理·正版检测·终端审计
小羊没烦恼!6 小时前
在Scrum中实施敏捷建模
java·开发语言·windows·算法·c#
玖石书6 小时前
python-uv-windows环境安装方案
windows·python·uv
程序员老陆6 小时前
WIN32_LEAN_AND_MEAN:Windows 头文件里的“精简编译开关”
c++·windows
玖石书7 小时前
LVGL的windows开发环境安装指南
windows·数字人·lvgl·仿真
qetfw7 小时前
Windows Server AD CS:根 CA、Web 证书模板与域内自动注册
前端·windows·windows-server