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

gcc与mingw32-gcc加载DLL到内存时的PE签名偏移问题问询

内存加载DLL时PE签名偏移1字节的问题(MinGW vs GCC)

问题背景

我编写了一个内存加载DLL并调用其导出函数的POC程序client.exe:

  • 用gcc.exe client.c -o client.exe编译后运行完全正常;
  • 用x86_64-w64-mingw32-gcc.exe client.c -o client.exe编译后,内存中加载的DLL的PE签名(50 45 00 00)偏移了1字节,调试发现pImageNtHeader->Signature指向的是以45开头的字节,而非正确的50开头。

问题集中在malloc和fread的内存加载环节,后续的库加载、函数地址获取逻辑无异常。

可能的原因与解决方案

1. 文件打开模式未指定二进制模式

Windows平台下,fopen默认是文本模式,会自动将\n转换为\r\n,导致读取的文件内容字节数偏移。MinGW和GCC在默认模式的处理上存在差异,必须强制用二进制模式打开DLL文件:

FILE* fp = fopen("target.dll", "rb"); // 末尾的b表示二进制模式

2. PE结构指针计算错误

检查IMAGE_NT_HEADERS的指针计算逻辑,必须基于DOS头的e_lfanew字段做字节级偏移,不能依赖结构体指针的自动步长:

// 正确的指针转换方式
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)buffer;
PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((BYTE*)pDosHeader + pDosHeader->e_lfanew);

如果直接用(PIMAGE_NT_HEADERS)(pDosHeader + pDosHeader->e_lfanew),会因为结构体指针的步长(IMAGE_DOS_HEADER大小为64字节)导致偏移计算错误。

3. 结构体打包(Packing)不匹配

PE文件的结构是按1字节对齐存储的,而MinGW和GCC的默认结构体对齐规则可能不同。必须手动指定结构体的打包规则,避免因对齐填充导致的内存偏移:

#pragma pack(push, 1)
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;
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

// 同样给IMAGE_NT_HEADERS、IMAGE_FILE_HEADER等结构加上打包指令
#pragma pack(pop)

4. 架构匹配问题

你使用的是x86_64-w64-mingw32-gcc编译64位程序,如果目标DLL是32位,或者代码中混用了32/64位PE结构定义,会导致指针偏移:

  • 64位程序必须使用IMAGE_NT_HEADERS64,32位程序使用IMAGE_NT_HEADERS32;
  • 不要硬编码PE结构的偏移值(比如手动写死0x3C虽然在32/64位中都对应e_lfanew,但结构体成员的偏移可能因架构变化)。

5. 文件读取完整性检查

确认fread读取的字节数与DLL实际大小一致:

// 先获取文件大小
fseek(fp, 0, SEEK_END);
size_t fileSize = ftell(fp);
fseek(fp, 0, SEEK_SET);

// 分配内存并读取
BYTE* buffer = (BYTE*)malloc(fileSize);
size_t readBytes = fread(buffer, 1, fileSize, fp);
if (readBytes != fileSize) {
    // 处理读取失败逻辑
    free(buffer);
    fclose(fp);
    return -1;
}

文本模式下ftell返回的是字符数而非字节数,这也是必须用二进制模式打开的原因之一。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 19:27:22