Skip to content

PE 头解析与节区定位 ​

场景 ​

需要手动解析 PE 文件的 DOS/NT 头、节区表、导入导出表,获取模块的完整内存布局。

输入信号 ​

  • 有模块基址或 PE 文件的原始字节
  • 需要确定代码段位置、数据段范围
  • 需要提取导入表 API 地址

PE 结构速查 ​

PE 文件布局:
┌─────────────────────┐ ← DOS_HEADER (e_magic = "MZ")
│  IMAGE_DOS_HEADER    │
│  e_lfanew → NT offset│
├─────────────────────┤ ← NT_HEADERS (Signature = "PE\0\0")
│  IMAGE_NT_HEADERS    │
│  FileHeader          │ Machine=0x8664(x64), NumberOfSections
│  OptionalHeader      │ ImageBase, SizeOfImage, EntryPoint
├─────────────────────┤ ← SECTION_HEADER[0..N-1]
│  IMAGE_SECTION_HEADER│ .text / .rdata / .data / .reloc ...
│  VirtualAddress      │ RVA (运行时相对基址偏移)
│  PointerToRawData    │ 文件偏移
│  VirtualSize         │ 运行时大小
├─────────────────────┤
│  节区数据            │
├─────────────────────┤
│  Import Directory    │ → IMAGE_IMPORT_DESCRIPTOR[]
├─────────────────────┤
│  Export Directory    │ → IMAGE_EXPORT_DIRECTORY
└─────────────────────┘

手动解析实现 ​

cpp
bool ParsePE(uintptr_t moduleBase) {
    // 1. DOS Header
    auto dos = (IMAGE_DOS_HEADER*)moduleBase;
    if (dos->e_magic != IMAGE_DOS_SIGNATURE)  // 0x5A4D = "MZ"
        return false;

    // 2. NT Headers
    auto nt = (IMAGE_NT_HEADERS*)(moduleBase + dos->e_lfanew);
    if (nt->Signature != IMAGE_NT_SIGNATURE)  // 0x00004550 = "PE\0\0"
        return false;

    printf("Machine: 0x%04X\n", nt->FileHeader.Machine);
    printf("Sections: %d\n", nt->FileHeader.NumberOfSections);
    printf("ImageBase: 0x%llX\n", nt->OptionalHeader.ImageBase);
    printf("EntryPoint: 0x%X\n", nt->OptionalHeader.AddressOfEntryPoint);
    printf("SizeOfImage: 0x%X\n", nt->OptionalHeader.SizeOfImage);

    // 3. 节区遍历
    auto section = IMAGE_FIRST_SECTION(nt);
    for (int i = 0; i < nt->FileHeader.NumberOfSections; i++) {
        char name[9] = {0};
        memcpy(name, section[i].Name, 8);

        printf("  %s: VA=0x%X Size=0x%X RawOff=0x%X Flags=0x%X\n",
            name,
            section[i].VirtualAddress,
            section[i].Misc.VirtualSize,
            section[i].PointerToRawData,
            section[i].Characteristics);

        // 判断节区属性
        if (section[i].Characteristics & IMAGE_SCN_MEM_EXECUTE)
            printf("    EXECUTE\n");
        if (section[i].Characteristics & IMAGE_SCN_MEM_READ)
            printf("    READ\n");
        if (section[i].Characteristics & IMAGE_SCN_MEM_WRITE)
            printf("    WRITE\n");
    }
    return true;
}

按名称定位节区 ​

cpp
uintptr_t GetSectionVA(uintptr_t moduleBase, const char* name) {
    auto dos = (IMAGE_DOS_HEADER*)moduleBase;
    auto nt = (IMAGE_NT_HEADERS*)(moduleBase + dos->e_lfanew);
    auto section = IMAGE_FIRST_SECTION(nt);

    for (int i = 0; i < nt->FileHeader.NumberOfSections; i++) {
        if (memcmp(section[i].Name, name, strlen(name)) == 0) {
            return moduleBase + section[i].VirtualAddress;
        }
    }
    return 0;
}

// 使用
uintptr_t textSection = GetSectionVA(base, ".text");
uintptr_t dataSection = GetSectionVA(base, ".data");
uintptr_t rdataSection = GetSectionVA(base, ".rdata");

导入表遍历 ​

cpp
void DumpImports(uintptr_t moduleBase) {
    auto dos = (IMAGE_DOS_HEADER*)moduleBase;
    auto nt = (IMAGE_NT_HEADERS*)(moduleBase + dos->e_lfanew);

    // 导入表 RVA
    DWORD importRva = nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
    if (!importRva) return;

    auto import = (IMAGE_IMPORT_DESCRIPTOR*)(moduleBase + importRva);

    while (import->Name) {
        const char* dllName = (const char*)(moduleBase + import->Name);
        printf("%s:\n", dllName);

        // IAT (Import Address Table) 和 INT (Import Name Table)
        auto iat = (uintptr_t*)(moduleBase + import->FirstThunk);
        auto int_ = (IMAGE_THUNK_DATA*)(moduleBase +
            (import->OriginalFirstThunk ? import->OriginalFirstThunk : import->FirstThunk));

        int idx = 0;
        while (int_->u1.AddressOfData) {
            if (int_->u1.Ordinal & IMAGE_ORDINAL_FLAG) {
                printf("  Ordinal: %llu\n", int_->u1.Ordinal & 0xFFFF);
            } else {
                auto importByName = (IMAGE_IMPORT_BY_NAME*)(moduleBase + int_->u1.AddressOfData);
                printf("  %s = 0x%llX\n", importByName->Name, iat[idx]);
            }
            int_++;
            idx++;
        }
        import++;
    }
}

导出表解析 ​

cpp
FARPROC GetExportByName(uintptr_t moduleBase, const char* funcName) {
    auto dos = (IMAGE_DOS_HEADER*)moduleBase;
    auto nt = (IMAGE_NT_HEADERS*)(moduleBase + dos->e_lfanew);

    DWORD exportRva = nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
    if (!exportRva) return NULL;

    auto exports = (IMAGE_EXPORT_DIRECTORY*)(moduleBase + exportRva);

    auto names = (DWORD*)(moduleBase + exports->AddressOfNames);
    auto ordinals = (WORD*)(moduleBase + exports->AddressOfNameOrdinals);
    auto functions = (DWORD*)(moduleBase + exports->AddressOfFunctions);

    for (DWORD i = 0; i < exports->NumberOfNames; i++) {
        const char* name = (const char*)(moduleBase + names[i]);
        if (strcmp(name, funcName) == 0) {
            return (FARPROC)(moduleBase + functions[ordinals[i]]);
        }
    }
    return NULL;
}

攻击链 ​

获取模块基址 → DOS header 验证 "MZ" → e_lfanew 定位 NT header
→ FileHeader.NumberOfSections → IMAGE_FIRST_SECTION 遍历节区
→ 按名称/属性定位目标节区 → 在节区内扫描/读写

MCP 工具映射 ​

AI Agent 可调用以下 MCP 工具自动完成或加速上述攻击链步骤:

攻击链步骤MCP 工具说明
PE 初筛,输出 header/sections/importstriage_pe自动输出 PE header/sections/imports
节区详细信息rizin_sections节区详细信息
PE 基础信息rizin_bin_infoPE 基础信息(entry point, image base 等)
导入表枚举rizin_imports导入表枚举
file offset / RVA / VA 互转pe_address_to_offsetfile offset / RVA / VA 互转

证据与验证闭环 ​

  • 记录样本 SHA256、架构、映像基址、RVA/VA/文件偏移换算及工具版本。
  • 静态结论绑定函数、Xref、导入、字符串和反编译片段;动态结论绑定断点、寄存器、栈、内存与调用时序。
  • 记录样本哈希、dump/patch 路径、原始字节、新字节和行为差异。
  • 将 x64dbg/Frida/Procmon/Ghidra 输出保存到 exports/windows/,按同一输入重跑,确认断点命中、内存变化和输出差异。

GPL-3.0 · 仅供授权环境下的学习与防御性研究使用