关于PE文件的内存加载分享
字数 1543 2025-08-05 08:19:19

PE文件内存加载技术详解

一、内存加载概述

内存加载是一种将PE格式文件(DLL或EXE)直接从内存加载执行的技术,无需通过传统API如LoadLibrary操作。这种技术在攻防对抗中具有重要意义:

  1. 优势

    • 文件不存在于磁盘,规避静态检测
    • 避免文件加载触发的内核回调
    • 加载的程序不会在PEB和进程中呈现
    • 规避敏感调用链检测(如cmd→灰进程)
  2. 主要方法

    • 针对PE文件:编写PELoader模拟LoadLibrary操作
    • 针对.NET程序集:利用C#反射特性(Assembly.Load)

二、PELoader实现原理

1. 核心流程

  1. 获取必要API地址

    • 通过PEB获取LoadLibraryGetProcAddressVirtualAlloc等关键API地址
  2. 内存映射PE文件

    • 根据SizeOfImage(PE可选头字段)申请内存空间
    • 将DLL头部和各节区复制到分配的内存
    • 设置对应内存权限(需注意对齐问题,SectionAlignmentFileAlignment可能不同)
  3. 修复重定位表

    • 计算实际加载地址与偏好基址(ImageBase)的偏移量
    • 根据重定位表将所有需要修复的地址加上该偏移量
  4. 修复IAT表

    • 遍历所有导入的DLL
    • 对每个导入函数,根据序号或名称补丁函数地址
  5. 跳转执行

    • 跳转到PE文件的OEP(Original Entry Point)执行

2. 实现细节

以ReflectiveDLLInjection为例:

// 伪代码示例
void ReflectiveLoader() {
    // 1. 计算DLL基址
    PVOID pDllBase = GetCurrentImageBase();
    
    // 2. 获取必要API
    PLOADLIBRARYA pLoadLibraryA = GetProcAddressFromPEB("LoadLibraryA");
    PGETPROCADDRESS pGetProcAddress = GetProcAddressFromPEB("GetProcAddress");
    
    // 3. 映射PE到内存
    PVOID pMappedDll = VirtualAlloc(NULL, pNtHeaders->OptionalHeader.SizeOfImage, 
                                   MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    
    // 4. 复制节区
    CopyHeadersAndSections(pDllBase, pMappedDll);
    
    // 5. 修复重定位
    ProcessRelocations(pMappedDll);
    
    // 6. 修复IAT
    ResolveImports(pMappedDll, pLoadLibraryA, pGetProcAddress);
    
    // 7. 调用DllMain
    CallDllMain(pMappedDll, DLL_PROCESS_ATTACH);
}

三、内存加载的检测规避技术

1. RWX内存问题

传统实现会分配RWX(可读可写可执行)内存,这非常可疑。改进方案:

  • 分配RX(可读可执行)和RW(可读可写)分离的内存区域
  • 仅在需要修改时设为RW,执行时设为RX

2. Module Stomping技术

原理

  1. 将合法Windows DLL注入目标进程
  2. 用shellcode覆盖DLL的入口点
  3. 创建新线程执行shellcode

优势

  • 可执行内存对应合法DLL文件,规避映像文件检测
  • 内存区域属性看起来合法

实现示例

// 1. 注入合法DLL(如amsi.dll)
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, targetPid);
PVOID pRemoteBuf = VirtualAllocEx(hProcess, NULL, sizeof(dllPath), MEM_COMMIT, PAGE_READWRITE);
WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)dllPath, sizeof(dllPath), NULL);
CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)LoadLibraryW, pRemoteBuf, 0, NULL);

// 2. 查找注入DLL的基址
HMODULE hModules[256];
DWORD cbNeeded;
EnumProcessModules(hProcess, hModules, sizeof(hModules), &cbNeeded);
for (HMODULE hMod : hModules) {
    if (IsTargetModule(hProcess, hMod)) {
        targetModule = hMod;
        break;
    }
}

// 3. 获取入口点并覆盖
PIMAGE_DOS_HEADER pDosHeader = ReadProcessMemory(hProcess, targetModule, sizeof(IMAGE_DOS_HEADER));
PIMAGE_NT_HEADERS pNtHeaders = ReadProcessMemory(hProcess, targetModule + pDosHeader->e_lfanew, sizeof(IMAGE_NT_HEADERS));
PVOID pEntryPoint = targetModule + pNtHeaders->OptionalHeader.AddressOfEntryPoint;
WriteProcessMemory(hProcess, pEntryPoint, shellcode, sizeof(shellcode), NULL);

// 4. 执行
CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pEntryPoint, NULL, 0, NULL);

四、Loader类型比较

1. 内置式Loader(Cobalt Strike风格)

特点

  • 修改DOS头使其包含可直接调用ReflectiveLoader的shellcode
  • ReflectiveLoader函数内置在DLL中
  • 通过硬编码偏移计算ReflectiveLoader位置

示例

4D 5A                 ; "MZ" DOS头
41 52 55 48 89 E5 48  ; 开始PE头
81 EC 20 00 00 00     ; sub rsp, 0x20
48 8D 1D EA FF FF FF  ; lea rbx, [rip - 0x16]
48 89 DF              ; mov rdi, rbx
48 81 C3 44 64 01 00  ; add rbx, 0x16444
FF D3                 ; call rbx

2. 前置式Loader

特点

  • ReflectiveLoader放在DLL前面而非内部
  • 可反射加载任意PE文件,无需源码
  • 不需要导出函数,规避相关检测

优势

  • 更通用,不限于特定DLL
  • 降低特征检测风险

五、实践注意事项

  1. 内存权限管理

    • 避免长时间保持RWX权限
    • 使用VirtualProtect动态调整权限
  2. 异常处理

    • 确保加载失败时能安全退出
    • 处理内存分配失败等情况
  3. 兼容性考虑

    • 处理32/64位差异
    • 考虑不同Windows版本特性
  4. 规避技巧

    • 使用合法模块镂空(如amsi.dll)
    • 延迟执行技术
    • 间接系统调用

六、参考实现

  1. 开源项目

  2. 关键数据结构

typedef struct _IMAGE_RELOCATION_ENTRY {
    WORD Offset : 12;
    WORD Type : 4;
} IMAGE_RELOCATION_ENTRY;

typedef struct _IMAGE_IMPORT_DESCRIPTOR {
    union {
        DWORD Characteristics;
        DWORD OriginalFirstThunk;
    };
    DWORD TimeDateStamp;
    DWORD ForwarderChain;
    DWORD Name;
    DWORD FirstThunk;
} IMAGE_IMPORT_DESCRIPTOR;

七、防御检测建议

  1. 检测点

    • 非常规内存权限(RWX)
    • 未关联映像文件的执行内存
    • 模块镂空行为
    • PEB中缺失的模块
  2. 防御措施

    • 监控关键API调用链
    • 检测内存中的PE签名
    • 行为分析而不仅是静态检测

通过深入理解PE内存加载技术原理,安全人员可以更好地开发防御措施,而渗透测试人员则能更有效地规避检测。

PE文件内存加载技术详解 一、内存加载概述 内存加载是一种将PE格式文件(DLL或EXE)直接从内存加载执行的技术,无需通过传统API如 LoadLibrary 操作。这种技术在攻防对抗中具有重要意义: 优势 : 文件不存在于磁盘,规避静态检测 避免文件加载触发的内核回调 加载的程序不会在PEB和进程中呈现 规避敏感调用链检测(如cmd→灰进程) 主要方法 : 针对PE文件:编写PELoader模拟 LoadLibrary 操作 针对.NET程序集:利用C#反射特性( Assembly.Load ) 二、PELoader实现原理 1. 核心流程 获取必要API地址 : 通过PEB获取 LoadLibrary 、 GetProcAddress 、 VirtualAlloc 等关键API地址 内存映射PE文件 : 根据 SizeOfImage (PE可选头字段)申请内存空间 将DLL头部和各节区复制到分配的内存 设置对应内存权限(需注意对齐问题, SectionAlignment 与 FileAlignment 可能不同) 修复重定位表 : 计算实际加载地址与偏好基址( ImageBase )的偏移量 根据重定位表将所有需要修复的地址加上该偏移量 修复IAT表 : 遍历所有导入的DLL 对每个导入函数,根据序号或名称补丁函数地址 跳转执行 : 跳转到PE文件的OEP(Original Entry Point)执行 2. 实现细节 以ReflectiveDLLInjection为例: 三、内存加载的检测规避技术 1. RWX内存问题 传统实现会分配RWX(可读可写可执行)内存,这非常可疑。改进方案: 分配RX(可读可执行)和RW(可读可写)分离的内存区域 仅在需要修改时设为RW,执行时设为RX 2. Module Stomping技术 原理 : 将合法Windows DLL注入目标进程 用shellcode覆盖DLL的入口点 创建新线程执行shellcode 优势 : 可执行内存对应合法DLL文件,规避映像文件检测 内存区域属性看起来合法 实现示例 : 四、Loader类型比较 1. 内置式Loader(Cobalt Strike风格) 特点 : 修改DOS头使其包含可直接调用 ReflectiveLoader 的shellcode ReflectiveLoader 函数内置在DLL中 通过硬编码偏移计算 ReflectiveLoader 位置 示例 : 2. 前置式Loader 特点 : ReflectiveLoader 放在DLL前面而非内部 可反射加载任意PE文件,无需源码 不需要导出函数,规避相关检测 优势 : 更通用,不限于特定DLL 降低特征检测风险 五、实践注意事项 内存权限管理 : 避免长时间保持RWX权限 使用 VirtualProtect 动态调整权限 异常处理 : 确保加载失败时能安全退出 处理内存分配失败等情况 兼容性考虑 : 处理32/64位差异 考虑不同Windows版本特性 规避技巧 : 使用合法模块镂空(如 amsi.dll ) 延迟执行技术 间接系统调用 六、参考实现 开源项目 : ReflectiveDLLInjection Donut (支持Module Overloading) 关键数据结构 : 七、防御检测建议 检测点 : 非常规内存权限(RWX) 未关联映像文件的执行内存 模块镂空行为 PEB中缺失的模块 防御措施 : 监控关键API调用链 检测内存中的PE签名 行为分析而不仅是静态检测 通过深入理解PE内存加载技术原理,安全人员可以更好地开发防御措施,而渗透测试人员则能更有效地规避检测。