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

PE解析器fopen/fgetc文件指针随机跳转问题排查

PE解析器文件指针异常跳转问题排查

我正在编写一个基础PE(可移植可执行文件)解析器,使用fopen、fread、fgetc和ftell等文件操作函数时遇到异常:在部分正常可解析的PE文件中,文件指针会毫无缘由地出现跳转(例如从0x118跳至0x308),跳转后读取的字节与文件原始字节不符,导致解析失败。已排除文件损坏、换行符解析错误等问题,测试文件可被PE-Bear正常加载。

最小复现代码

#include <stdio.h>
#include <stdlib.h>
#include <urlmon.h>
#include <assert.h>
#include <winnt.h>
#define ___IMAGE_NUMBEROF_DIRECTORY_ENTRIES    16


typedef struct __IMAGE_DOS_HEADER {
    WORD   e_magic;
    WORD   e_cblp;
    WORD   e_cp;
    WORD   e_crlc;
    WORD   e_cparhdr;
    WORD   e_minalloc;
    WORD   e_maxalloc;
    WORD   e_ss;
    WORD   e_sp;
    WORD   e_csum;
    WORD   e_ip;
    WORD   e_cs;
    WORD   e_lfarlc;
    WORD   e_ovno;
    WORD   e_res[4];
    WORD   e_oemid;
    WORD   e_oeminfo;
    WORD   e_res2[10];
    LONG   e_lfanew; //offset in the file of the NT header structure
} ___IMAGE_DOS_HEADER, * ___PIMAGE_DOS_HEADER;

typedef struct __IMAGE_DATA_DIRECTORY {
    DWORD   VirtualAddress;
    DWORD   Size;
} ___IMAGE_DATA_DIRECTORY, * ___PIMAGE_DATA_DIRECTORY;

typedef struct __IMAGE_OPTIONAL_HEADER64 {
WORD        Magic;
BYTE        MajorLinkerVersion;
BYTE        MinorLinkerVersion;
DWORD       SizeOfCode;
DWORD       SizeOfInitializedData;
DWORD       SizeOfUninitializedData;
DWORD       AddressOfEntryPoint;
DWORD       BaseOfCode;
ULONGLONG   ImageBase;
DWORD       SectionAlignment;
DWORD       FileAlignment;
WORD        MajorOperatingSystemVersion;
WORD        MinorOperatingSystemVersion;
WORD        MajorImageVersion;
WORD        MinorImageVersion;
WORD        MajorSubsystemVersion;
WORD        MinorSubsystemVersion;
DWORD       Win32VersionValue;
DWORD       SizeOfImage;
DWORD       SizeOfHeaders;
DWORD       CheckSum;
WORD        Subsystem;
WORD        DllCharacteristics;
ULONGLONG   SizeOfStackReserve;
ULONGLONG   SizeOfStackCommit;
ULONGLONG   SizeOfHeapReserve;
ULONGLONG   SizeOfHeapCommit;
DWORD       LoaderFlags;
DWORD       NumberOfRvaAndSizes;
___IMAGE_DATA_DIRECTORY DataDirectory[___IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} ___IMAGE_OPTIONAL_HEADER64, * ___PIMAGE_OPTIONAL_HEADER64;


___IMAGE_DOS_HEADER *parse_dos_header(FILE *fp){
    ___IMAGE_DOS_HEADER *dos_hdr = malloc(sizeof(___IMAGE_DOS_HEADER));
    fread(dos_hdr,sizeof(___IMAGE_DOS_HEADER),1,fp);
//    printf("offset optional header is %lu\n",dos_hdr->e_lfanew);
    return dos_hdr;
}

___IMAGE_OPTIONAL_HEADER64* parse_optional_header(FILE *fp, ___IMAGE_DOS_HEADER* dos_hdr){

    ___IMAGE_OPTIONAL_HEADER64 *opt_hdr = malloc(sizeof(___IMAGE_OPTIONAL_HEADER64));
    long offset = dos_hdr->e_lfanew + sizeof(long) + sizeof(IMAGE_FILE_HEADER);
    fseek(fp,offset,SEEK_SET);
    fread(opt_hdr,sizeof(___IMAGE_OPTIONAL_HEADER64),1,fp);
    fseek(fp,offset,SEEK_SET);
    for(int i=0;i<512;i++){
        //debug loop to test char by char
        char byte = fgetc(fp);
        if (byte == EOF) break;
        printf("ftell position %lx\n",ftell(fp));
        printf("%02x\n",byte);
    }
    assert(opt_hdr->Magic == 0x20b);
    long end = ftell(fp);
    fseek(fp,0,SEEK_END);
    long end_file = ftell(fp);
    assert(end_file>end);
    printf("Passed the check\n");
    rewind(fp);
    return opt_hdr;
}

int main(int argc, char* argv[]){

    if(argc!=2){
        printf("Specify the PE you want to parse through argv[1]\n usage : ./binary <path_to_pe>\n");
        exit(-1);
    }
    FILE *fp;
    fopen_s(&fp,argv[1],"rb");
    fseek(fp, 0L, SEEK_END);
    long long sz = ftell(fp);
    rewind(fp);
    if(sz<sizeof(___IMAGE_DOS_HEADER)){
        printf("Not enough byte to parse the DOS header, exiting\n");
        exit(-1);
    }
    ___IMAGE_DOS_HEADER *dos_hdr = parse_dos_header(fp);
    ___IMAGE_OPTIONAL_HEADER64* opt_hdr = parse_optional_header(fp,dos_hdr);
}

异常输出片段

ftell position 109
0b
ftell position 10a
02
ftell position 10b
0e
ftell position 10c
1d
ftell position 10d
00
ftell position 10e
ffffff88
ftell position 10f
01
ftell position 110
00
ftell position 111
00
ftell position 112
ffffffec
ftell position 113
00
ftell position 114
00
ftell position 115
00
ftell position 116
00
ftell position 117
00
ftell position 118
00
ftell position 308   <<< return value of ftell jumping from 118 to 308
ffffffc0
ftell position 309
00
...

核心问题分析

  1. 结构体对齐不匹配
    PE文件中的所有结构都是按1字节紧凑排列的,没有填充字节。但C编译器默认会按结构体中最大成员的大小(如ULONGLONG对应8字节)进行对齐,导致自定义结构体的sizeof结果远大于实际PE结构的大小。这会让fread读取过多字节,或计算偏移时出错,直接导致文件指针位置异常。

  2. 自定义结构体冗余且错误
    WinNT.h中已经提供了标准的IMAGE_DOS_HEADER、IMAGE_OPTIONAL_HEADER64等PE结构定义,重复自定义不仅冗余,还容易出现成员类型、顺序错误,进一步加剧偏移计算的问题。

  3. 偏移计算错误
    NT头的签名(Signature)是DWORD类型(4字节),代码中误用sizeof(long)计算偏移:在64位系统中long是8字节,会导致偏移多算4字节,后续的文件操作都基于错误的起始位置。

  4. fgetc返回值处理错误
    fgetc返回int类型(范围-1到255),用signed char接收会将0x80以上的字节解析为负数,与EOF(-1)混淆,导致循环提前终止或错误判断,间接引发指针位置异常。

修复方案

  1. 强制结构体1字节对齐
    在自定义结构体定义前后添加对齐指令,确保结构体大小与PE文件中的结构一致:
#pragma pack(push, 1)
// 结构体定义
#pragma pack(pop)
  1. 使用系统标准PE结构体
    直接使用WinNT.h中定义的标准结构,避免自定义错误:
  • 替换___IMAGE_DOS_HEADER为IMAGE_DOS_HEADER
  • 替换___IMAGE_OPTIONAL_HEADER64为IMAGE_OPTIONAL_HEADER64
  • 替换___IMAGE_DATA_DIRECTORY为IMAGE_DATA_DIRECTORY
  1. 修正偏移计算
    将偏移计算中的sizeof(long)改为sizeof(DWORD):
long offset = dos_hdr->e_lfanew + sizeof(DWORD) + sizeof(IMAGE_FILE_HEADER);
  1. 正确处理fgetc返回值
    用int类型接收fgetc的返回值,避免与EOF混淆:
int byte = fgetc(fp);
if (byte == EOF) break;
printf("ftell position %lx\n", ftell(fp));
printf("%02x\n", (unsigned char)byte);
  1. 检查文件操作返回值
    添加fread、fseek等函数的返回值检查,确保操作成功,避免后续基于错误状态的操作:
if (fread(dos_hdr, sizeof(IMAGE_DOS_HEADER), 1, fp) != 1) {
    // 处理读取错误
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 12:17:03