不能直接用loadlibrary加载反射式注入dll,因其违背反射式注入“不调用系统api、自主完成重定位/iat修复”的核心原则;pe解析需动态判断magic值、校验datadirectory长度、以实际加载地址为准;重定位须按image_base_relocation块遍历,iat修复需手动解析导出表且区分架构。

为什么不能直接用 LoadLibrary 加载反射式注入的 DLL
因为反射式注入要求目标进程里不调用 Windows 系统 API(如 LoadLibrary、VirtualAlloc 等),所有逻辑必须由注入体自己完成。一旦调用了 LoadLibrary,不仅会暴露行为(EDR 检测导入表或 API 调用链),还会绕过你对重定位、IAT 修复、节区映射等关键步骤的控制 —— 这就不是“反射式”了,只是普通远程线程加载。
PE 头解析要跳过 DOS 头和 NT 头里的陷阱字段
很多反射式注入代码直接硬编码偏移读取 OptionalHeader.ImageBase 或 DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC],但实际 PE 文件可能:① OptionalHeader.Magic 是 0x20b(PE32+)而非 0x10b(PE32),结构体大小不同;② NumberOfRvaAndSizes 小于 16,访问 DataDirectory[5] 会越界;③ ImageBase 在 ASLR 启用时毫无意义,必须以实际加载地址为准。
实操建议:
- 用
dosHeader->e_lfanew定位 NT 头,再根据NTHeaders->OptionalHeader.Magic选择IMAGE_OPTIONAL_HEADER32或IMAGE_OPTIONAL_HEADER64 -
DataDirectory数组必须先检查NumberOfRvaAndSizes > IMAGE_DIRECTORY_ENTRY_BASERELOC,否则跳过重定位 - 不要信任文件中的
ImageBase,运行时基址是分配内存后的真实地址,记为actual_base
手动重定位的核心是遍历每个块(Block)并修正高/低位字节
重定位表(.reloc)不是线性列表,而是由多个 IMAGE_BASE_RELOCATION 块组成,每个块开头是 VirtualAddress 和 SizeOfBlock,后面紧跟着若干 16 位重定位项 —— 高 4 位是类型(通常是 IMAGE_REL_BASED_HIGHLOW 或 IMAGE_REL_BASED_DIR64),低 12 位是相对于 VirtualAddress 的偏移。
常见错误:
- 把整个
SizeOfBlock当作重定位项个数(实际是字节数,需除以 2) - 没按
VirtualAddress对齐计算目标地址,导致写入位置错位(例如写到 .text 外的内存) - 64 位下用了
IMAGE_REL_BASED_HIGHLOW(32 位类型),应优先检查是否为IMAGE_REL_BASED_DIR64(0xA)
示例关键逻辑(伪代码):
auto reloc = (IMAGE_BASE_RELOCATION*)((BYTE*)mapped + relocDir->VirtualAddress);
while (reloc->VirtualAddress != 0) {
WORD* entries = (WORD*)(reloc + 1);
int count = (reloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);
for (int i = 0; i > 12;
WORD offset = entries[i] & 0x0fff;
if (type == IMAGE_REL_BASED_DIR64) {
DWORD64* ptr = (DWORD64*)((BYTE*)actual_base + reloc->VirtualAddress + offset);
*ptr += (DWORD64)actual_base - (DWORD64)header->OptionalHeader.ImageBase;
}
}
reloc = (IMAGE_BASE_RELOCATION*)((BYTE*)reloc + reloc->SizeOfBlock);
}
IAT 修复前必须先解析导出表并缓存函数地址
反射式注入中 IAT 不是靠系统填充的,得自己查 kernel32.dll 等模块的导出表,把 GetProcAddress 返回的地址填进 IAT 表项。但注意:GetModuleHandleA("kernel32.dll") 是允许的(它不触发新模块加载),而 LoadLibrary 绝对不行。
容易被忽略的点:
- 32/64 位下 IAT 表项大小不同(4 字节 vs 8 字节),需匹配目标 PE 架构
- 有些 DLL 的 IAT 指向的是导入名称表(INT)而非导入地址表(IAT)本身,得先通过
IMAGE_IMPORT_DESCRIPTOR定位真正的 IAT 起始地址 - 若目标 DLL 有延迟导入(Delay Import),对应目录项在
DataDirectory[IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT],不处理会导致调用时崩溃
实际部署时,多数反射式注入体只硬编码处理 kernel32 和 ntdll 的 IAT,其余模块(如 user32)靠运行时按需解析 —— 这能减小初始体积,也降低静态扫描风险。
重定位和 IAT 修复不是一次到位的线性过程,尤其当 DLL 依赖深度较大时,顺序错一点就会让某个函数指针指向无效地址。最稳妥的做法是:先完成自身重定位,再用修复好的 GetProcAddress 去解析依赖模块,最后逐层填 IAT —— 中间任何一步失败都得清理已分配内存并退出,不然目标进程大概率卡死或崩溃。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











