必须同时验证mz头(0x5a4d)和pe签名(0x00004550):先读前2字节确认为0x4d 5a,再从偏移0x3c处读取e_lfanew得到pe头位置,检查该位置4字节是否等于0x00004550,且文件长度足够,缺一不可。

怎么用前64字节快速验证PE格式
只读文件头就能判断,不用加载、不调用系统API、也不依赖扩展名。核心是两个签名:DOS头的MZ(0x5A4D)和NT头的PE\0\0(0x00004550),缺一不可。
常见错误是跳过e_lfanew直接硬读偏移0x3C处的值——但e_lfanew本身才是合法入口,它指向PE头起始位置,而DOS stub长度可变,不能假设固定。
- 用
std::ifstream以std::ios::binary模式打开,读取前64字节到uint8_t buf[64] - 先检查
buf[0] == 0x4D && buf[1] == 0x5A(注意:小端序下0x5A4D在内存中是4D 5A) - 再读
buf[0x3C]开始的4字节,转成uint32_t peHeaderOffset(小端解析) - 确认文件长度 ≥
peHeaderOffset + 4,否则越界或伪造头 - 最后比对
*(uint32_t*)&buf[peHeaderOffset] == 0x00004550
为什么不能只读2字节就返回true
很多资源文件、加壳器输出、甚至某些打包工具生成的“伪PE”也保留了MZ头,但后续结构无效。单靠MZ只能说明“可能是个PE”,不是“就是有效PE”。
例如一个被截断的EXE文件,前两字节是MZ,但e_lfanew指向的位置超出文件尾,或者该位置内容不是PE<p>例如一个被截断的EXE文件,前两字节是<code>MZ,但e_lfanew指向的位置超出文件尾,或者该位置内容不是PE\0\0——这种文件Windows会拒绝加载,CreateProcess失败。
例如一个被截断的EXE文件,前两字节是MZ,但e_lfanew指向的位置超出文件尾,或者该位置内容不是PE\0\0——这种文件Windows会拒绝加载,CreateProcess失败。
CreateProcess失败。
-
e_lfanew字段在DOS头固定偏移0x3C,必须从这里读,不能跳过 - 读出的
peHeaderOffset必须在文件范围内,且该偏移处4字节必须严格等于0x00004550 - 别用
memcmp直接比字符串"PE\0\0"——首字节是\0,C字符串会提前截断
FILE*和std::ifstream哪种更稳
两者都行,但std::ifstream在跨平台项目里更少出错,尤其避免Windows下文本模式换行符转换问题;FILE*在纯C风格或需要细粒度控制fseek/fread时更直接。
关键不是用哪个接口,而是是否正确处理小端序、边界检查和IO错误。
- 用
FILE*时,fseek(f, 0x3C, SEEK_SET)后fread(&peHeaderOffset, 1, 4, f),别漏ferror(f)检查 - 用
std::ifstream时,必须设.read()并检查gcount(),不能只信good() - 无论哪种,读完立即
close()或close(),避免句柄泄漏影响批量扫描
如何区分PE和DLL/SYS等其他PE子类型
仅判断“是否为PE格式”时,不需要看Subsystem或Characteristics字段。只要MZ + PE\0\0通过,就是PE格式——包括.exe、.dll、.sys、.ocx等。
如果后续要区分用途(比如只找可直接运行的EXE),才需进一步解析IMAGE_OPTIONAL_HEADER里的AddressOfEntryPoint是否非零,或Characteristics & IMAGE_FILE_EXECUTABLE_IMAGE是否置位。
- 驱动文件(.sys)通常有
IMAGE_FILE_SYSTEM标志,但格式仍是PE - DLL不一定有入口点,但只要结构合法,仍算PE文件
- 判断“能否双击运行”和“是否PE格式”是两件事,别混在一起做布尔返回
peHeaderOffset可能指向文件末尾之后,或刚好卡在最后一个字节上导致读4字节失败。不加长度校验的代码,在遇到损坏文件或恶意构造样本时会崩溃或误判。C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











