You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PE文件ParseHeaders函数无法读取NT/可选头的问题求助

PE文件解析问题排查与修复

核心报错原因分析

从输出看,两个关键错误:Unknown optional header size和Failed to read NT headers,分别来自ParseSections和ParseIAT函数,核心问题是PE结构的偏移计算错误、NT头读取逻辑错误。


问题1:ParseSections函数的可选头大小判断与节区偏移计算错误

错误点

  1. 直接用sizeof(IMAGE_OPTIONAL_HEADER32/64)和FileHeader.SizeOfOptionalHeader比较,不符合PE规范:文件中SizeOfOptionalHeader是实际可选头大小,和结构体大小可能因编译器对齐有差异,且应该通过可选头的Magic值判断位数,而非大小比较。
  2. 节区头的偏移计算完全错误:正确的节区头起始位置是NT头的起始地址(e_lfanew) + NT签名(4字节) + 文件头大小(20字节) + 可选头大小,而非sizeof(IMAGE_DOS_HEADER) + sizeof(IMAGE_NT_HEADERS) + sizeOfHeaders。

修复后的ParseSections函数

PIMAGE_SECTION_HEADER ParseSections(FILE* pefile, IN IMAGE_NT_HEADERS* NtHeaders) {
    // 通过Magic值判断是32位还是64位可选头
    size_t sizeOfHeaders = 0;
    if (NtHeaders->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) {
        sizeOfHeaders = NtHeaders->OptionalHeader.OptionalHeader32.SizeOfHeaders;
    }
    else if (NtHeaders->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) {
        sizeOfHeaders = NtHeaders->OptionalHeader.OptionalHeader64.SizeOfHeaders;
    }
    else {
        printf("Unknown optional header magic value\n");
        return NULL;
    }

    // 计算节区头的正确偏移:NT头起始位置 + NT头总大小
    DWORD ntHeaderTotalSize = sizeof(DWORD) // NT签名
                            + sizeof(IMAGE_FILE_HEADER)
                            + NtHeaders->FileHeader.SizeOfOptionalHeader;
    DWORD sectionHeaderOffset = NtHeaders->FileHeader.e_lfanew + ntHeaderTotalSize;

    if (fseek(pefile, sectionHeaderOffset, SEEK_SET) != 0) {
        printf("Failed to seek to section headers\n");
        return NULL;
    }

    // 分配节区头内存
    PIMAGE_SECTION_HEADER SectionHeaders = (PIMAGE_SECTION_HEADER)malloc(
        NtHeaders->FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER)
    );
    if (SectionHeaders == NULL) {
        printf("Allocation failed (section headers)\n");
        return NULL;
    }

    // 读取节区头
    size_t readCount = fread(SectionHeaders, sizeof(IMAGE_SECTION_HEADER), 
                            NtHeaders->FileHeader.NumberOfSections, pefile);
    if (readCount != NtHeaders->FileHeader.NumberOfSections) {
        printf("Failed to read section headers\n");
        free(SectionHeaders);
        return NULL;
    }

    return SectionHeaders;
}

问题2:ParseIAT函数中NT头读取逻辑错误

错误点

ParseIAT中直接从文件开头读取sizeof(IMAGE_NT_HEADERS),但NT头实际位于DOS头的e_lfanew偏移处,且IMAGE_NT_HEADERS结构体大小对32/64位PE不同,直接读取会导致数据错误。此外,主函数已经解析过NT头,无需重复读取,可直接传入已有的NtHeaders和SectionHeaders,避免重复解析。

修复方案

  1. 修改ParseIAT函数参数,直接传入已解析的IMAGE_NT_HEADERS和IMAGE_SECTION_HEADER,避免重复读取文件。
  2. 移除错误的NT头读取逻辑。
  3. 修正x64 PE的IAT条目类型为64位,避免数据截断。

修复后的ParseIAT函数定义

// 修改函数参数,直接传入已解析的头信息
void ParseIAT(FILE* pefile, IMAGE_NT_HEADERS* NtHeaders, PIMAGE_SECTION_HEADER SectionHeaders) {
    if (!pefile || !NtHeaders || !SectionHeaders) {
        printf("Invalid input parameters\n");
        return;
    }

    // 确认是x64 PE
    if (NtHeaders->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR64_MAGIC) {
        printf("Error: Not x64 PE\n");
        return;
    }

    IMAGE_OPTIONAL_HEADER64 OptionalHeader64 = NtHeaders->OptionalHeader.OptionalHeader64;
    IMAGE_DATA_DIRECTORY ImportDirectory = OptionalHeader64.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
    uint32_t ImportDirectoryRVA = ImportDirectory.VirtualAddress;
    uint32_t ImportDirectorySize = ImportDirectory.Size;

    if (ImportDirectoryRVA == 0) {
        printf("No import directory\n");
        return;
    }

    printf("    [>] Import Directory Address: 0x%08X - Size: 0x%08X\n\n", ImportDirectoryRVA, ImportDirectorySize);

    // 转换导入目录RVA到文件偏移
    uint32_t ImportDirectoryOffset = rva_to_offset(ImportDirectoryRVA, SectionHeaders, NtHeaders->FileHeader.NumberOfSections);
    if (ImportDirectoryOffset == 0xFFFFFFFF) {
        printf("Invalid import directory offset\n");
        return;
    }

    if (fseek(pefile, ImportDirectoryOffset, SEEK_SET) != 0) {
        printf("Failed to seek to import directory\n");
        return;
    }

    IMAGE_IMPORT_DESCRIPTOR ImportDescriptor;
    while (fread(&ImportDescriptor, sizeof(ImportDescriptor), 1, pefile) == 1) {
        if (ImportDescriptor.Name == 0) {
            break;
        }

        // 读取模块名
        uint32_t moduleNameRVA = ImportDescriptor.Name;
        uint32_t moduleNameOffset = rva_to_offset(moduleNameRVA, SectionHeaders, NtHeaders->FileHeader.NumberOfSections);
        if (moduleNameOffset == 0xFFFFFFFF) {
            printf("Invalid module name offset\n");
            return;
        }

        char moduleName[256];
        fseek(pefile, moduleNameOffset, SEEK_SET);
        if (fread(moduleName, 1, sizeof(moduleName) - 1, pefile) <= 0) {
            printf("Failed to read module name\n");
            return;
        }
        moduleName[sizeof(moduleName) - 1] = '\0';

        printf("[+] Module: %s\n", moduleName);

        // 读取IAT
        uint32_t iatRVA = ImportDescriptor.FirstThunk;
        uint32_t iatOffset = rva_to_offset(iatRVA, SectionHeaders, NtHeaders->FileHeader.NumberOfSections);
        if (iatOffset == 0xFFFFFFFF) {
            printf("Invalid IAT offset\n");
            return;
        }

        if (fseek(pefile, iatOffset, SEEK_SET) != 0) {
            printf("Failed to seek IAT offset\n");
            return;
        }

        printf("[+] Import Address Table Offset: 0x%08X\n", iatOffset);
        // x64的IAT条目是64位,修正类型为uint64_t
        uint64_t iatEntry;
        while (fread(&iatEntry, sizeof(iatEntry), 1, pefile) == 1) {
            if (iatEntry == 0) {
                break;
            }

            printf("    [>] Function Address: 0x%016llX ", iatEntry);

            if (iatEntry & 0x8000000000000000) {
                uint16_t ordinal = (uint16_t)(iatEntry & 0xFFFF);
                printf("        [>] Function imported by ordinal: %u\n", ordinal);
            }
            else {
                uint32_t functionNameRVA = (uint32_t)iatEntry;
                uint32_t functionNameOffset = rva_to_offset(functionNameRVA, SectionHeaders, NtHeaders->FileHeader.NumberOfSections);
                if (functionNameOffset == 0xFFFFFFFF) {
                    printf("Invalid function name offset\n");
                    return;
                }

                fseek(pefile, functionNameOffset, SEEK_SET);
                IMAGE_IMPORT_BY_NAME functionName;
                if (fread(&functionName, sizeof(functionName), 1, pefile) != 1) {
                    printf("Failed to read function name\n");
                    return;
                }

                printf("        [>] Name: %s\n", functionName.Name);
            }
        }
    }
}

主函数调用修改

// 主函数中调用ParseIAT时,传入已解析的NtHeaders和SectionHeaders
if (architecture == IMAGE_NT_OPTIONAL_HDR64_MAGIC) {
    ParseEAT(pefile, NtHeaders, SectionHeaders); // 同理修改ParseEAT的参数逻辑
    ParseIAT(pefile, NtHeaders, SectionHeaders);
    // 释放内存,避免泄漏
    free(SectionHeaders);
    free(NtHeaders);
}

问题3:rva_to_offset函数的节区范围判断优化

错误点

用sectionEndRVA = sectionStartRVA + sections[i].Misc.VirtualSize判断RVA是否在节区范围内,部分情况下(如节区的VirtualSize大于SizeOfRawData),可能导致RVA在内存中存在但文件中无对应数据,需补充判断文件偏移的有效性。

修复后的rva_to_offset函数

uint32_t rva_to_offset(uint32_t rva, IMAGE_SECTION_HEADER* sections, int numberOfSections) {
    for (int i = 0; i < numberOfSections; i++) {
        uint32_t sectionStartRVA = sections[i].VirtualAddress;
        // 节区的RVA范围是[sectionStartRVA, sectionStartRVA + VirtualSize)
        uint32_t sectionEndRVA = sectionStartRVA + sections[i].Misc.VirtualSize;
        if (rva >= sectionStartRVA && rva < sectionEndRVA) {
            uint32_t offsetInSection = rva - sectionStartRVA;
            // 检查偏移是否在文件中的节区数据范围内
            if (offsetInSection <= sections[i].SizeOfRawData) {
                uint32_t offset = sections[i].PointerToRawData + offsetInSection;
                printf("RVA 0x%08X maps to offset 0x%08X in section %d\n", rva, offset, i);
                return offset;
            } else {
                printf("RVA 0x%08X is in section %d but beyond file data\n", rva, i);
                return 0xFFFFFFFF;
            }
        }
    }
    printf("RVA 0x%08X not found in any section\n", rva);
    return 0xFFFFFFFF;
}

额外注意事项

  1. 内存泄漏:主函数中NtHeaders和SectionHeaders是malloc分配的,使用完后必须调用free释放。
  2. 文件指针管理:每次读取操作后注意文件指针位置,必要时用fseek重置,避免后续读取错位。
  3. PE结构兼容性:处理PE文件时需严格遵循PE规范,避免依赖结构体的默认大小,优先使用文件中存储的字段值。

内容的提问来源于stack exchange,提问作者kot123

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 04:59:53