
二进制文件这东西第一次打交道的人多半是被逼的。要么是下载下来的资源打不开要么是程序读出来的数据对不上要么是排查一个通信问题时发现抓到的东西根本不是给人看的。我最早也是这个路子——同事发来一个几百 KB 的文件说你看下里面是什么我双击打开满屏方块和问号那一刻是真懵。后来慢慢才明白看二进制文件不是靠某个神奇工具而是一套先画像、再定位、后解析的手艺活先判断它是什么格式再用十六进制把关键位置摊开最后按格式规范把字节翻译成人能读懂的意思。这套方法不管你是做运维、写后端、做嵌入式还是单纯想搞清楚某个文件为什么坏了都用得上。下面我按自己平时干活的顺序把这件事从头到尾拆一遍工具、参数、坑点都摆出来能直接抄的我就写清楚怎么抄。1. 先搞懂本质二进制文件和文本文件到底差在哪1.1 所有文件都是字节区别只在怎么解释先把一个容易绕晕的概念说清楚磁盘上根本不存在文本文件和二进制文件两种物理形态它们都是同一串字节。区别在于我们约定用哪套规则去解释这串字节。文本文件的约定是每个字节都能在某个字符集里找到对应字符比如ABC存成十六进制的41 42 43回车换行存成0D 0A而二进制文件的约定是这些字节代表数字、结构、机器指令或压缩后的数据比如一个 ELF 可执行文件开头永远是7F 45 4C 46对应 ASCII 里的.ELF。理解这一层很多困惑就散了。所谓打开是乱码本质上不是文件坏了而是你用错了翻译字典。UTF-8 编码的中文一个字占三个字节首字节通常在E4到E9之间GBK 编码的中文一个字占两个字节两个字节都落在81到FE的区间。同一段中文用错编码去看出来就是一串怪字。这跟拿英文字典去查日文一个道理——不是文字本身有问题是字典不匹配。再往下还有一层即便你用对了编码二进制数据里也天然带着大量不可打印字节。00到1F这一段在 ASCII 里是控制字符0A是换行、0D是回车、07是响铃、1B是转义序列的开头。文本编辑器读到这些字节会直接执行它们换行、响铃、甚至改终端颜色而不是显示成文字。所以看二进制文件第一步永远是把字节变成可显示的十六进制而不是想办法打开它。1.2 哪些实际场景逼着你必须打开它我把工作中真正需要翻二进制的场景归了归类基本逃不出下面这几类文件损坏排查。一个图片打不开、一个压缩包解不开最常见的原因是头部若干字节被破坏了。这时候只要用十六进制看一眼开头是不是89 50 4E 47PNG或50 4B 03 04ZIP三秒钟就能定性比反复重下文件高效得多。私有格式的逆向理解。行业设备导出的数据文件、串口通信的报文、老系统留下的自定义格式往往没有任何文档。这时候只能靠对比多个样本找哪些位置的字节是固定的、哪些在变一层层把结构猜出来。升级包与固件校验。拿到一个升级文件得先确认它是不是完整的、有没有被截断、里面是不是还套着别的结构。这类文件通常只看头部不够要全程扫描找特征签名。编码与换行问题。跨平台传文件Windows 的0D 0A和 Unix 的0A混在一起脚本里多出来的\r就是这么来的。直接在十六进制里看一眼比猜半天靠谱。数据里到底藏了什么。一个几 MB 的文件肉眼只能看到一堆结构但strings一跑里面的地址、路径、版本号、错误文案全冒出来了这份信息量往往比结构本身还有用。学习文件格式本身。想真正搞懂 PNG 怎么存像素、ELF 怎么组织段、SQLite 怎么排页光看文章记不住自己拿一个真实文件对照着十六进制看一遍印象完全不一样。这几类场景看着杂但底层动作是一致的定位 → 提取 → 解释。工具换了一茬又一茬这个动作没变过。1.3 三种查看思路各自适合什么活新手最常犯的错是一上手就找最强大的工具。实际上查看二进制文件有三条完全不同的路子用错了方向会非常浪费时间。思路代表工具输出形态适合场景门槛十六进制裸看xxd、hexdump、od、010 Editor地址 字节 可打印字符找特征、比差异、定位偏移低结构化解析readelf、objdump、格式模板、自写脚本字段名 值 含义已知格式、要读字段值中反汇编反编译objdump -d、反编译工具汇编 / 伪代码分析可执行逻辑高我的习惯是永远从第一种开始。原因很实际十六进制视图不假设任何结构它不会替你做判断也不会因为格式猜错而误导你。而结构化工具虽然输出好看但它是拿一套格式规范去套你的文件如果文件被改过、被截断过或者格式版本对不上工具可能给你一堆看着合理其实是错的结果。先用裸看确认头部对不对、长度对不对、有没有明显的重复模式再去调用结构化工具顺序反了容易走弯路。2. 命令行三板斧xxd、hexdump、od 怎么选怎么用2.1 xxd日常首选参数最好记xxd是我用得最多的一个理由很简单——它的默认输出格式最符合直觉参数命名也最规整。xxd demo.bin输出大概是这个样子00000000: 504b 0304 1400 0000 0800 9c8a 1b5c 8f3d PK...........\. 00000010: 2a4b 0000 0000 0000 0000 2100 1c00 7465 *K........!...te 00000020: 7374 2f68 656c 6c6f 2e74 7874 5554 0903 st/hello.txtUT..三列的含义分别是行首是文件偏移十六进制中间是这一行的字节默认每行 16 个两位一组右侧同一位置对应的可打印 ASCII 字符不可打印的用点号占位。为什么默认是每行 16 字节因为这个长度最好算16 字节等于 8 个 16 位字、4 个 32 位字行首地址的最后一个十六进制位正好等于行内的字节偏移想要第 5 个字节直接看行首地址加 5不用动笔算。常用参数我基本就记这几个参数作用典型用法-l N只输出前 N 字节xxd -l 64 demo.bin-s OFF从偏移 OFF 开始支持0x前缀和负数xxd -s 0x100 -l 32 demo.bin-c N每行显示 N 字节xxd -c 8 demo.bin-g N每 N 字节分一组xxd -g 1 demo.bin-b显示二进制而不是十六进制xxd -b -l 16 demo.bin-u十六进制字母大写xxd -u -l 32 demo.bin-i输出成 C 语言数组xxd -i demo.bin res.h-r反向把 hexdump 文本还原成二进制xxd -r dump.txt out.bin-i和-r这两个值得单独说一下。-i是把一个二进制文件直接转成 C 源码里的数组定义做嵌入式或者把一个图标、一段字库嵌进程序时特别省事不用再手写转换脚本。-r则是反向操作先把文件xxd成文本用编辑器改几个字节再xxd -r还原回去。改单个字节的时候这招比任何图形工具都快。注意用-r还原时原始的偏移列必须保持严格递增且不能被破坏。手动删行、改地址、混入注释都会让还原出来的文件错位。稳妥做法是只改中间那一段十六进制数字别动别的地方。2.2 hexdump格式串是它的灵魂hexdump比xxd更老也更强但强的地方需要花点心思。hexdump -C demo.bin-C是 canonical 模式输出跟xxd很像偏移、16 字节十六进制、右侧 ASCII。日常用这个就够了。但hexdump真正的价值在于它可以用-e自定义格式串把字节按你想要的形态打印出来比如只打 32 位小端整数hexdump -e %08_ax 4/4 %08x \n demo.bin这行的意思是每行先打一个八位十六进制地址然后按 4 字节一组、每组用八位十六进制十六进制打印共 4 组最后换行。写这种格式串有学习成本但一旦写顺了分析特定结构效率极高。另外两个参数必须知道-n N限制读多少字节-s OFF指定起始偏移。还有-v它的作用是禁止折叠重复行。hexdump默认遇到连续相同的行会打一个*然后跳过看全零区域时很方便但如果你想确认一大段零到底有多长就必须加-v。2.3 od嵌入式环境里的救命稻草odoctal dump是老牌工具名字里带 octal 是因为默认按八进制输出。虽然现在八进制少用了但它有个不可替代的优势几乎所有精简系统里都自带它。我调试板子的时候BusyBox 里往往只有od没有xxd也没有hexdump -C这时候就靠它了。od -A x -t x1z demo.bin-A x表示地址用十六进制显示还可以选o八进制、d十进制、n不显示地址。-t x1z表示类型是1 字节一组的十六进制z后缀是额外在行尾加上可打印字符。这两个参数组合起来输出效果基本等价于hexdump -C。od的类型参数值得多记几个x1一字节十六进制、x2两字节、x4四字节、c可打印字符、d2有符号十进制两字节、u4无符号十进制四字节。分析一个纯数值数组时直接用od -A d -t d2比先看十六进制再手算快得多。2.4 大文件别硬看先切再定位几百 MB 甚至几个 GB 的文件直接把xxd丢进去会刷屏到天荒地老而且完全没意义——人眼一次能处理的信息就那么几十行。这时候要建立只看关心的地方的习惯。看头部head -c 512 demo.bin | xxd看尾部tail -c 512 demo.bin | xxd跳到指定偏移看一段两种写法xxd -s 1048576 -l 256 demo.bin dd ifdemo.bin bs1 skip1048576 count256 2/dev/null | xxd这两种写法里我优先用xxd -s因为它直接在文件里做 seek速度快dd加bs1是一个字节一个字节读大偏移时慢得让人抓狂。真要用dd把bs设成 512 或 4096配合skip的块数来算会快很多。还有一个我特别常用的招——先搜特征串拿到偏移再跳过去看grep -abo PK\x03\x04 demo.bin-a是让grep不再把二进制文件当不匹配处理-b输出字节偏移-o只输出匹配的那一部分。这条命令跑完你会得到所有 ZIP 头的偏移位置。有了偏移再用xxd -s定点看效率比一页页翻高出一个量级。找嵌套结构、判断一个文件里塞了几段内容基本都靠这一招开场。3. 先画像再深挖file、strings、binwalk 的使用姿势3.1 file三秒钟给文件定性拿到一个陌生文件我做的第一件事永远是filefile demo.bin file -k demo.bin file -i demo.bin-k是不轻易放弃文件前面有一堆无关字节时它会把所有可能的候选类型都列出来而不是只报第一个匹配。-i输出 MIME 类型写脚本做自动分类时用得上。file的工作原理是查一张魔数数据库通常位于系统的 magic 目录下读文件头部若干字节去匹配签名。这决定了它的两个局限第一它主要看头部如果前面被塞了自定义的头它可能认错第二文件被截断时它会犹豫可能输出 data 或者给出一个带问号的结论。所以file的结论要当参考不要当判决书。它说是什么你就按什么格式去验证一遍头部验证通过了才继续往下走。3.2 strings把文件里人话的部分捞出来strings是我认为性价比第二高的工具第一是xxd。它做的唯一一件事就是把文件里所有连续的可打印字符序列抓出来。strings -n 6 demo.bin strings -t x -n 6 demo.bin | head -50 strings -e l demo.bin | head -30-n 6表示至少 6 个连续可打印字符才算一个字符串默认是 4。这个值调高一点能显著减少噪音——二进制文件里偶然凑出三四个可打印字节的概率很高但连续六个就不太可能是巧合了。-t x让每条字符串前面带上它所在的十六进制偏移这一步非常关键有了偏移你就能用xxd -s 偏移跳回去看这条字符串周围的字节结构从猜变成看。-e l和-e b分别按 16 位小端、16 位大端去解析字符串。这个参数经常被忽略但用在资源文件、老系统数据上非常有效——里的文本有时是宽字符存储的普通strings一条都抓不到加上-e l立刻就出来了。实际操作中一个文件strings跑完通常能看到这些高价值信息内部文件路径、编译时的绝对路径、版本号字符串、错误提示文案、URL、数据库表名。这些东西不仅能帮你判断文件的用途还能反过来告诉你它的内部组织方式——看到一串assets/xxx.png基本就能确定这是个打包资源了。3.3 binwalk找嵌套结构的一把好手binwalk的定位很明确不是只看头部而是全程扫描在整份数据里找所有已知的文件签名。binwalk demo.bin binwalk -e demo.bin第一行只列出发现的内容和偏移第二行会尝试把嵌套的部分提取出来。用于固件镜像、打包资源、多段拼接的数据文件时特别好用——一份文件里可能塞了压缩包、文件系统、内核镜像好几层binwalk能一层层给你列出来。注意-e会往当前目录写文件跑之前先切到一个空的临时目录避免把工作目录搞乱。提取出来的东西五花八门逐个用file确认类型再决定要不要继续拆。3.4 常用文件签名速查表这张表建议直接保存看头部的时候对着看格式魔数十六进制ASCII 形态出现位置PNG89 50 4E 47 0D 0A 1A 0A.PNG....文件开头JPEGFF D8 FF不可打印文件开头GIF47 49 46 38GIF8文件开头ZIP / JAR / DOCX50 4B 03 04PK..文件开头或各条目处GZIP1F 8B不可打印文件开头7z37 7A BC AF 27 1C7z....文件开头RAR52 61 72 21 1A 07 00Rar!...文件开头PDF25 50 44 46 2D%PDF-文件开头ELF7F 45 4C 46.ELF文件开头PE / EXE / DLL4D 5AMZ文件开头BMP42 4DBM文件开头RIFF / WAV / AVI52 49 46 46RIFF文件开头FLAC66 4C 61 43fLaC文件开头SQLite53 51 4C 69 74 65 20 66 6F 72 6D 61 74 20 33 00SQLite format 3文件开头TAR75 73 74 61 72ustar偏移 257记不住不要紧关键是要养成看一眼头 16 字节的条件反射。xxd -l 16 文件这一个动作能解决相当大比例的问题。4. 图形化编辑器与结构化解析什么时候该换工具4.1 图形化十六进制编辑器的真实价值命令行看字节快但有两件事它做起来别扭需要一屏看几百字节并快速扫视以及需要改一个字节马上看结果。这两件事正是图形编辑器的强项。我用得比较多的是 010 Editor 和 HxD 这一类它们的能力可以分成三档第一档是大文件承载能力。好的编辑器用内存映射方式打开文件几个 GB 的文件也能瞬间打开并直接跳转不会像命令行那样必须先想好偏移。第二档是直接编辑与校验。改完字节可以直接算 CRC、算校验和改固件里某个参数时不用来回切工具。第三档也是最有价值的一档是模板解析。比如 010 Editor 自带大量格式模板PNG、ZIP、ELF 都有加载模板后左边是十六进制右边直接变成一棵字段树你点某个字段它自动高亮对应的字节区间。这一下就把结构和字节对应起来了比自己一条条查规范快太多。注意只要涉及修改务必先备份原文件。另外一些编辑器默认开启覆写模式一不小心多敲一个字符就把后面所有字节顶掉一位整个文件就废了。编辑前确认当前是覆写还是插入模式。4.2 可执行文件交给专门的结构化工具对 ELF、PE 这类可执行文件纯看十六进制性价比很低因为头部字段太多手算偏移容易出错。这时候直接上专用工具readelf -h a.out readelf -S a.out readelf -l a.out objdump -d a.out | head -40 objdump -s -j .rodata a.out nm -C a.out | head -20readelf -h输出 ELF 头里面e_ident那 16 个字节就是你在xxd -l 64里看到的第一行e_entry是入口地址e_machine告诉你目标架构。readelf -S列所有节和它们的文件偏移、大小这份偏移表在你想从文件里把某个节抠出来时是必需的。objdump -d反汇编代码段objdump -s -j .rodata把只读数据段以十六进制打印出来——这条命令在找硬编码字符串、常量表时特别有用。nm列出符号表加了-C会把 C 的符号名还原成可读形式。关键结论是这类工具不是替代了十六进制视角而是建立在其之上。工具输出的每个偏移、每个长度你都可以回到xxd里验证一遍。反过来当工具报出格式不识别的时候也只有十六进制视角能告诉你到底哪里不对。4.3 自定义格式自己动手写解析脚本真实工作中最常遇到的其实是没有任何现成工具的私有格式。这时候只能自己来。我一般按这套流程走第一步取样对比。拿同一类型的三到五个文件都用xxd -l 256打出来逐行对比。哪些字节在所有样本里都一样那就是固定字段魔数、版本号哪些在变那是数据字段。变化的位置范围往往就是这个字段的偏移和长度。第二步记录假设。把猜出来的字段写下来偏移多少、几个字节、什么含义、什么字节序。这一步别省每次分析完都记下次遇到同族格式能省一大半时间。第三步写脚本验证。Python 的struct模块是干这个的最顺手工具import struct HEADER_FMT 4sHHI HEADER_SIZE struct.calcsize(HEADER_FMT) def parse_header(buf): magic, version, count, flags struct.unpack_from(HEADER_FMT, buf, 0) return { magic: magic, version: version, count: count, flags: flags, header_size: HEADER_SIZE, } def parse_records(buf, off): records [] while off len(buf): if off 4 len(buf): break (length,) struct.unpack_from(I, buf, off) off 4 if off length len(buf): break name buf[off:off length].decode(utf-8, replace) off length records.append(name) return records with open(demo.bin, rb) as f: data f.read() print(parse_header(data)) print(parse_records(data, HEADER_SIZE))这段代码里有几个点必须讲清楚因为它们是自写解析脚本最容易翻车的地方。字节序符号。struct的格式串开头那一个字符决定了字节序是小端是大端是本机字节序!是网络序也是大端。这个字符不只是声明字节序它还同时关闭了对齐填充。如果不写struct会按 C 语言的内存对齐规则自动插入填充字节算出来的偏移就跟真实文件对不上。二进制文件格式几乎都是紧凑排列、没有填充的所以格式串一定要显式带上或。unpack_from而不是unpack。unpack要求给你的字节串长度和格式串完全一致稍微多一点就报错。解析文件时你手上通常是整份数据要用unpack_from(buf, offset)指定从哪里开始读这样才不会因为长度不匹配就崩掉。变长字段要手动推算。上面parse_records就是个典型先读 4 字节长度再把接下来length个字节当字符串然后指针前移。这类长度前缀 内容的结构在私有格式里到处都是写熟了就是套模板。每次前移都要做边界检查off length len(buf)这种情况说明文件被截断了直接停掉比抛异常更好用。5. 常见问题与排查技巧实录5.1 打开全是乱码从哪一步开始查乱码是最常见的求助问题但它其实分好几种处理方式完全不同。第一种编码不匹配。内容本来是一段正常中文只是用错了编码解释。判断方法很直接用xxd -l 64看高位字节的分布。如果中文对应的字节是以E4到E9开头、每个字三字节那是 UTF-8如果是两个字节一组、都在81到FE之间那是 GBK。确认后转换iconv -f GBK -t UTF-8 in.txt out.txt iconv -f UTF-8 -t UTF-8 -c in.txt out.txt第二条命令里的-c是遇到无法转换的字符就跳过处理混了脏数据的文件时很实用不会因为一个坏字节整份转换失败。第二种内容本来就不是文本。文件是压缩数据、加密数据或者结构化的二进制你用文本编辑器打开当然是一片怪符号。这种情况不是乱码是看错工具了直接换xxd。第三种终端显示问题。有人用cat直接把二进制打到终端上结果字符集被打乱、颜色变了、甚至终端卡住不响应。这是因为文件里含有终端控制序列。恢复的办法reset如果reset不管用试stty sane再回车。这是我早期踩得最多的坑之一后来养成习惯任何不确定类型的文件先file再决定用什么看绝不cat。5.2 端序搞反数值能差出天文数字这是二进制解析里最经典也最容易中招的问题。同一串字节34 12 00 00解释方式结果十六进制结果十进制小端 32 位0x000012344660大端 32 位0x34120000873201664差了将近二十万倍。而且这个错误特别隐蔽因为两个数看起来都挺像个正常的数字不会立刻报错等到程序跑出莫名其妙的结果时已经排查了很远。判断规律其实有个偷懒的办法看高位零在哪一头。数值比较小的时候小端存储会把零字节放在后面34 12 00 00大端存储会把零字节放在前面00 00 12 34。所以看到一串数字后面拖着一堆00大概率是小端前面顶着一堆00大概率是大端。至于哪种格式用什么端序我按经验整理了一下PC 和主流服务器架构x86、ARM 常见配置普遍是小端网络协议按惯例是大端PNG、JPEG 这类图像格式内部字段是大端BMP 是小端ELF 头的端序由文件自己声明e_ident里有一位专门标记。真正稳妥的做法不是背而是同时按两种端序解一遍看哪个结果落在合理范围内。版本号一般是 1 到 10 这个量级长度字段不会超过文件本身大小用这个做交叉验证基本不会错。5.3 偏移量算错的四种典型情况偏移算错是分析卡壳的头号原因我把遇到过的整理成四类十六进制和十进制混用。xxd和hexdump -C显示的行首地址都是十六进制的但od -A d是十进制、od -A o是八进制。工具之间来回切的时候很容易把0x100256当成 100。用-s参数时习惯性写成0x前缀就不会出错。文件偏移和内存地址混淆。ELF 文件里同一个内容有两个地址文件里的偏移和加载到内存后的虚拟地址。这两个值通常不相等。用readelf -S看节的时候要注意看的是Offset列还是Address列。基准点不统一。有的格式文档说偏移 8 处是版本号这个 8 可能是相对文件开头也可能是相对某个子结构开头。遇到这种情况先用xxd从两个基准各数一遍看哪个位置的值落在合理范围。换行符吃掉了字节。把二进制数据转成文本传输、或者在某些编辑器里改过之后0D 0A会变成0A整个文件后面的偏移全部前移一位。判断方法是对比文件长度如果比预期少了若干个字节且少的数量正好等于行数基本就是这个问题。5.4 常见问题速查表现象最可能的原因第一步动作双击打开一堆方块不是文本格式file 文件确认类型中文显示成怪字编码判断错误xxd -l 64看高位字节分布数值大得离谱端序搞反两种端序各解一遍比对file输出data头部不标准或被截断xxd -l 64人工看头部找不到某个字符串是宽字符或分段存储strings -e l或调低-n终端花屏不响应打印了控制字符reset或stty sane解出来的数据对不上偏移基准搞错从两个基准各数一遍嵌套结构提取失败中间有额外填充字节binwalk看全部偏移再手切文件长度比预期短换行被转换过对比行数与缺失字节数5.5 几条我认为最值得记住的经验第一别急着用高级工具。每次遇到新文件先file、再xxd -l 64、再strings -t x这三步加起来不超过十秒但能解决大部分问题。上来就开反编译器的往往绕更远。第二所有结论都要交叉验证。工具说是什么格式你就去头部看一眼签名对不对脚本解出来的长度你就跟文件总大小对一下解出来的版本号你看它是不是一个合理的小整数。二进制分析里看着对和真的对差别很大。第三善用偏移和对比。手上有多个同类型文件时xxd -l 256并排一放固定的字段和变化的字段一目了然。这比读任何文档都快。我现在分析新格式第一件事就是找三五个样本做对比而不是先去搜规范。第四把发现记下来。字段偏移、字节序、长度规律这些猜出来的东西当时记得清清楚楚隔两周就忘得干净。养成随手写个笔记或者直接在脚本注释里记下来的习惯长期看省的时间远超记录的成本。第五对需要修改的文件永远留一份原样。看和改是两回事。读的时候大胆一点没关系一旦要动字节先复制一份出来操作改完用校验和或者重新解析一遍确认没破坏结构。这个习惯是我丢掉过一次几小时的成果之后才养成的。我个人的体会是看二进制文件这件事工具是次要的真正拉开差距的是顺序感——先做什么、后做什么、什么时候该停下来验证。工具会更新换代这套顺序十年没变过。还有一个容易被忽略的小技巧把常用的几个命令做成别名或者小脚本比如一个看头 64 字节 跑 strings 列文件信息的一键命令用得越多越顺手慢慢你会发现那些原本让人头大的文件现在看一眼开头就知道该怎么下手了。