Reflective DLL Injection的核心约束是绕过系统Loader,全程手动完成PE加载:解析PE头、修正重定位、填充导入表、处理TLS、调用DllMain,且DLL二进制始终驻留内存、不落地、不注册模块。

什么是 Reflective DLL Injection 的核心约束
它不是常规的 LoadLibrary 调用,而是把 DLL 文件内容完整读入内存后,由注入者手动完成重定位、导入表解析、TLS 初始化、入口点调用等全部 PE 加载逻辑。关键在于:整个过程不写入磁盘、不依赖系统 loader,DLL 的二进制数据始终只存在于目标进程的内存中。
- 必须能解析 PE 头(
DOS_HEADER、NT_HEADERS、IMAGE_OPTIONAL_HEADER) - 必须遍历并修正所有重定位项(
IMAGE_BASE_RELOCATION) - 必须逐个解析
IMAGE_IMPORT_DESCRIPTOR,动态获取每个导入函数地址(如kernel32.dll!VirtualAlloc) - 必须处理 TLS 回调(如果存在
IMAGE_TLS_DIRECTORY) - 入口点(
DllMain)必须用CreateRemoteThread或QueueUserAPC触发,且传入的hModule是你手动分配的基址,不是系统给的
为什么不能直接用 LoadLibrary + VirtualAlloc + WriteProcessMemory
因为那只是“把文件丢进去”,没做任何加载工作。目标进程看到的只是一段原始字节,没有重定位修正、没有 IAT 填充、没有 TLS 初始化——一旦执行,大概率在第一条调用外部 API 的指令就触发 ACCESS_VIOLATION 或跳转到错误地址。
-
LoadLibrary会走系统 loader,生成合法模块句柄并注册到 PEB 的Ldr链表,而 reflective 注入绕过这一切 - 如果你只
WriteProcessMemory写入原始 DLL 数据,然后跳过去执行,GetProcAddress会找不到这个模块,EnumProcessModules也看不到它 - 更隐蔽的副作用:某些 EDR(如 Windows Defender、CrowdStrike)会监控
LoadLibrary系列 API 调用,但对裸地址跳转+手动解析的反射注入检测较弱——前提是你的重定位和导入处理完全正确
最关键的三个易错点:重定位、导入解析、堆栈对齐
- 重定位表处理漏掉某个区块(比如 .rdata 或 .pdata),会导致某处硬编码地址仍指向原始编译基址(如
0x10000000),而非你 VirtualAlloc 分配的真实地址(如 0x7ff8a1230000)
- 导入解析时未检查
IMAGE_THUNK_DATA 的高 32 位是否为 0(即是否为序号导入),直接当函数名处理,结果调用 GetProcAddress(hMod, "123") 失败
- 在 x64 下,
CreateRemoteThread 启动反射 DLL 入口前,没确保目标线程栈指针满足 16 字节对齐(Windows x64 ABI 要求),导致 DllMain 内部调用某些 API(如 InitializeCriticalSection)崩溃
0x10000000),而非你 VirtualAlloc 分配的真实地址(如 0x7ff8a1230000)IMAGE_THUNK_DATA 的高 32 位是否为 0(即是否为序号导入),直接当函数名处理,结果调用 GetProcAddress(hMod, "123") 失败CreateRemoteThread 启动反射 DLL 入口前,没确保目标线程栈指针满足 16 字节对齐(Windows x64 ABI 要求),导致 DllMain 内部调用某些 API(如 InitializeCriticalSection)崩溃示例片段(伪代码逻辑):
// 检查重定位块是否覆盖全部需修正区域
PIMAGE_BASE_RELOCATION rel = relocDir;
while (rel->VirtualAddress != 0) {
WORD* entry = (WORD*)((BYTE*)rel + sizeof(IMAGE_BASE_RELOCATION));
int count = (rel->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);
for (int i = 0; i < count; ++i) {
WORD type = entry[i] >> 12;
DWORD offset = entry[i] & 0x0fff;
if (type == IMAGE_REL_BASED_DIR64) { // x64 only
ULONGLONG* addr = (ULONGLONG*)((BYTE*)remoteBase + rel->VirtualAddress + offset);
*addr += delta; // delta = remoteBase - originalImageBase
}
}
rel = (PIMAGE_BASE_RELOCATION)((BYTE*)rel + rel->SizeOfBlock);
}
实际部署时最常被忽略的细节
- 反射 DLL 自身必须用
/BASE:0x10000000 /DYNAMICBASE:NO /FIXED 编译,否则重定位表可能为空或含无效项
- 目标进程如果是 WoW64(32 位进程跑在 64 位系统),你注入的 DLL 架构必须严格匹配,且重定位类型要用
IMAGE_REL_BASED_HIGHLOW 而非 DIR64
- 不要假设远程进程一定有
ntdll.dll 导出 RtlCopyMemory;更稳妥的是用 kernel32.dll!CopyMemory,或自己实现极简 memcpy 循环
-
DllMain 返回 TRUE 并不表示成功——它只表示函数没抛异常;真正失败往往发生在后续首次调用 VirtualAlloc 或 CreateThread 时,此时错误已无法回传
/BASE:0x10000000 /DYNAMICBASE:NO /FIXED 编译,否则重定位表可能为空或含无效项IMAGE_REL_BASED_HIGHLOW 而非 DIR64
ntdll.dll 导出 RtlCopyMemory;更稳妥的是用 kernel32.dll!CopyMemory,或自己实现极简 memcpy 循环DllMain 返回 TRUE 并不表示成功——它只表示函数没抛异常;真正失败往往发生在后续首次调用 VirtualAlloc 或 CreateThread 时,此时错误已无法回传反射注入不是“写完就能跑”的技术,它把 Windows loader 的内部逻辑摊开在你面前。哪怕一个重定位项算错,或一个导入函数地址填反了,结果都是静默崩溃或行为错乱。调试它最有效的方式,是在目标进程中下断点到你写的反射加载器入口,单步跟重定位和 IAT 填充过程,而不是靠日志或猜测。
立即学习“C++免费学习笔记(深入)”;


















