通过检查文件头魔数可快速判断可执行性:Windows PE需验证前2字节为"MZ"(0x5A4D)且e_lfanew指向的PE签名等于"PE\0\0"(0x00004550);Linux/macOS ELF需前4字节为"\x7fELF",再解析e_type确认为ET_EXEC或ET_DYN。

怎么用头信息判断 Windows 可执行文件(PE)
直接读取文件前 64 字节,验证 MZ 和 PE\0\0 两个签名即可快速筛掉绝大多数非 PE 文件。这不是万能检测(加壳或混淆后可能失效),但零调用系统 API、不触发权限检查,适合批量扫描或沙箱预检。
关键步骤:
- 以二进制只读打开文件,
fseek(f, 0x3C, SEEK_SET)跳到 DOS 头中e_lfanew字段位置(固定偏移 60) - 读取 4 字节小端整数作为 PE 头起始偏移
peHeaderOffset;若文件长度 ≤peHeaderOffset + 4,直接返回 false(损坏或伪造) -
fseek(f, peHeaderOffset, SEEK_SET),再读 4 字节,比对是否等于0x00004550(即 "PE\0\0") - 注意:不能跳过
e_lfanew直接硬读 0x80 —— DOS stub 长度可变,e_lfanew才是唯一合法入口
怎么用头信息判断 Linux/macOS 可执行文件(ELF)
ELF 的魔数 \x7fELF 是最硬的格式标识,比扩展名或权限位可靠得多。但它只说明“是 ELF”,不等于“能执行”——目标文件(.o)、core dump、共享库都带这个头。
要真正判断是否为可执行程序,必须继续解析 ELF header 中的 e_type 字段:
立即学习“C++免费学习笔记(深入)”;
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
- 先确认前 4 字节是
\x7fELF(buf[0] == 0x7f && buf[1] == 'E' && buf[2] == 'L' && buf[3] == 'F') - 再读
e_ident[EI_CLASS]判断是ELFCLASS32还是ELFCLASS64,决定后续结构体大小和字段偏移 - 读
e_ident[EI_DATA]校验字节序(ELFDATA2LSB或ELFDATA2MSB),否则e_type解析会错乱 - 最终检查
e_type是否为ET_EXEC(传统可执行)或ET_DYN(PIE/共享库,可直接运行)
为什么不能只靠 access(path, X_OK) 判断“是否可执行程序”
access(path, X_OK) 返回 0,只代表当前进程**有权限执行该路径下的文件**,和文件内容完全无关。一个 chmod +x hello.txt 的纯文本文件,access 也会返回成功。
它适用于“我能不能跑这个文件”,但不回答“这文件本身是不是可执行格式”。两者语义不同,混用会导致逻辑错误:
-
access受挂载选项影响(如noexec分区上所有文件都失败) - 在 setuid 程序中,
access用的是 real UID,而execve用的是 effective UID,行为可能不一致 - 它无法区分 ELF 的
ET_REL(不可执行)和ET_EXEC(可执行)
跨平台统一识别时最容易忽略的细节
Windows、Linux、macOS 各自依赖不同的魔数,但共性是:它们都在文件开头极小范围内(≤ 64 字节),且内核加载器强制校验。绕过这些,就不可能被系统执行。
实际编码中容易踩的坑:
- 没做文件长度边界检查:
e_lfanew偏移超出文件大小时仍尝试读取,导致未定义行为 - 把
\x7fELF当字符串用strcmp比较 —— 首字节0x7f不是可打印字符,strcmp会在第一个\0截断 - 在 macOS 上误用 Linux 的
<elf.h>—— 该头文件非 POSIX 标准,macOS 默认不提供,应自行定义结构体 - 忽略 Mach-O:macOS 下还要检查
\xce\xfa\xed\xfe(32 位)或\xcf\xfa\xed\xfe(64 位),否则漏判原生可执行文件
真正的可执行性判断,永远始于魔数,止于格式语义,中间不掺杂权限、路径、扩展名这些不可信信号。


















