可执行二进制文件需同时满足权限位、扩展名(windows)和文件头魔数三重验证:linux/macos检查os.filemode&0111≠0及elf/mach-o魔数,windows检查.exe等扩展名及mz头。

可执行二进制 ≠ 任意二进制文件
判断一个文件是否为「可执行二进制」,不能等同于判断它是不是二进制(即含 \x00)。Linux/Unix 下的可执行性由文件权限位(os.FileMode 的 0111)和 ELF/PE/Mach-O 等格式头共同决定;Windows 下则更依赖扩展名(如 .exe、.bat)与文件头。Go 标准库不提供跨平台“是否可运行”的原子判断,需分层验证。
先检查文件权限和扩展名(轻量快速)
对大多数场景,只需做两件事就能筛掉 90% 的非可执行文件:
-
os.Stat()获取os.FileInfo,用fi.Mode()&0111 != 0判断是否有任一执行位(user/group/other)被设置 - 对 Windows 路径,检查扩展名是否在
[]string{".exe", ".bat", ".cmd", ".com"}中(注意大小写不敏感) - Linux/macOS 下扩展名无关紧要,但常见误判点是:脚本文件(如
#!/bin/sh)虽无\x00且权限可执行,但它本质是文本——IsBinaryFile会返回false,但它是可执行的
再验证文件头是否为合法可执行格式
仅靠权限或扩展名不够可靠(比如一个普通 .txt 文件被 chmod +x 后也能“执行”,只是失败)。真正确认可执行性,需读取前若干字节比对魔数:
- ELF 文件:前 4 字节为
[]byte{0x7f, 'E', 'L', 'F'} - PE 文件(Windows):偏移 0x3c 处为 DWORD 指向 PE header,接着 2 字节应为
0x5a4d(即"MZ");实际可用io.LimitReader(f, 100)读取后直接检查前 2 字节是否等于[]byte{'M', 'Z'} - Mach-O(macOS):前 4 字节为
0xcefaedfe(32 位)或0xcffaedfe(64 位),需按小端解析 - 不要依赖
http.DetectContentType—— 它不识别 ELF/PE,只会返回application/octet-stream,无法区分可执行与普通二进制
go build 生成的可执行文件有特殊性
Go 编译出的二进制默认是静态链接的 ELF/PE,但有两个关键例外:
- 若用了
-buildmode=pie,Linux 下仍是 ELF,但readelf -h binary | grep Type显示DYN而非EXEC;这种文件依然可执行,只是加载地址随机化 - CGO_ENABLED=0 时生成的二进制不含 libc 依赖,
ldd binary输出 “not a dynamic executable”;但只要魔数匹配、权限到位,它就是可执行的 - 交叉编译产物(如
GOOS=linux GOARCH=arm64 go build)仍遵循对应平台格式,魔数检测逻辑不变
真正容易被忽略的是:文件存在、有执行权限、魔数正确,三者缺一不可。少验证任何一层,都可能把脚本当二进制,或把数据文件当可执行体。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











