服务器上改一个字节,这活儿多半是走投无路才接的。下载中断的压缩包、开机报格式错的固件、从存储卡里捞出来却打不开的照片——文件就在那儿,只差几个字节不对。hexedit就是为这种场景准备的:一个用 ncurses 写的终端十六进制编辑器,装完也就几 KB,全程键盘操作,不改变文件长度,只干一件事——把某个偏移上的字节换成你要的值。没有图形界面、没有 X11 转发、没有鼠标,一个 SSH 会话就能干活。
它小众到什么程度?很多人常年用xxd看二进制,但真到要"动手改"的时候,第一反应还是去装 HxD 或者 010 Editor。问题是这些工具在纯终端环境里根本跑不起来。而 hexedit 的定位非常清楚:它是一个定长的、原地覆盖式的二进制编辑器。这个定位既是它的全部优点,也是它的全部局限。把这一点吃透,你就知道什么时候该用它、什么时候该换别的工具。
下面我按"它是什么 → 屏幕怎么看 → 键怎么按 → 活怎么干 → 坑在哪 → 什么时候别用"的顺序,把 hexedit 拆开讲一遍。里面夹了不少我在实际操作里踩出来的经验,尤其是终端流控和校验和那两个坑,属于文档里不会写、但一定会遇上的。
1. hexedit 的定位:它只干"原地改字节"这一件事
1.1 和 xxd、od、hexdump 的根本分工
先把工具族谱理清楚。xxd、od、hexdump这三个是查看器,它们把二进制渲染成文本,输出到标准输出,然后你可以grep、可以管道给下一个程序。它们做的事情是"读",不碰原文件。
hexedit是编辑器,它接管整个终端,用 ncurses 画出一个全屏界面,光标停在哪个字节上,你就能改哪个字节,改完按保存直接写回原文件。它不产生中间文本,不需要你来回倒腾格式。
提示:区分方法很简单——如果这个工具的输出能被
>重定向到文件里,那它基本是查看器;如果它占满整个屏幕、按 q 才退得出来,那它是编辑器。
这个分工决定了两者的配合方式:先用查看器定位,再用编辑器下手。比如xxd -l 64 firmware.bin看开头长什么样,grep -aboP找出魔数的十进制偏移,然后hexedit -o 那个偏移 firmware.bin直接跳过去改。全流程不需要离开终端。
1.2 定长覆盖这个设计,为什么反而是优点
hexedit 最容易被吐槽的一点是:它不能插入字节,也不能删除字节。文件本来是 1024 字节,你改完还是 1024 字节,一个不多一个不少。
新手会觉得这是缺陷,用过一阵子之后你会发现这是特性。原因在于偏移量的语义:一旦允许插删,被插入点之后的所有字节偏移全部平移。而在二进制世界里,偏移是被硬引用着的——ELF 里的段表记录着偏移、ZIP 的中央目录记录着每个条目的偏移、跳转表里存的是地址、校验区间有始有末。你往中间塞一个字节,后面这些引用全部错位,文件直接报废,而且报错信息往往离真正的原因十万八千里。
所以定长覆盖给了一个非常强的保证:改前和改后,同一偏移指向的还是同一个逻辑位置。这对打补丁、修文件头、改单个标志位来说,是最省心的模型。真要插删,那是另一个工具族的事,后面第 5 节会给出落地方案。
1.3 安装:主流发行版一行命令的事
大部分发行版都有现成包,包名就叫hexedit,不要和hexcurse、bvi、dhex搞混,那几个是不同作者写的同类工具。
- Debian / Ubuntu:
sudo apt install hexedit - Fedora / RHEL 系:
sudo dnf install hexedit(老版本用yum,某些发行版要走 EPEL 源) - Arch:
sudo pacman -S hexedit - openSUSE:
sudo zypper install hexedit - macOS:先试
brew install hexedit,如果仓库里没有,就下载源码包./configure && make && sudo make install,编译前确认装了 ncurses 的开发头文件
源码编译这条路值得提前知道,因为有些精简版的服务器镜像里没有对应包,尤其是内网环境。源码包很小,依赖也只有 ncurses,几分钟就能过。
1.4 命令行参数:把"打开就定位"变成肌肉记忆
hexedit 的参数不多,但用得对了能省掉每次打开后手动跳转的麻烦:
hexedit [选项] 文件| 参数 | 用途 | 典型场景 |
|---|---|---|
-o, --offset <偏移> | 打开后光标立刻定位到指定偏移 | 已知问题字节在 0x3E 处 |
-s, --search <字符串> | 打开后立刻执行一次搜索 | 想直接跳到某段魔数 |
--color | 开启彩色高亮 | 长时间盯着屏幕时更舒服 |
--help/--version | 查看帮助和版本 | 确认按键是否和本文一致 |
-l, --length <长度> | 只处理文件前 N 个字节 | 大文件只想看头部时 |
偏移量支持0x前缀的十六进制,比如-o 0x1000。这一点很重要,因为文档、反汇编器、strings输出基本都是十六进制偏移,直接照抄过去就行,不需要心算转换。
举例,一个下载中断、文件头被覆盖的压缩包:
hexedit --color -s "PK" -o 0x0 broken.zip第一条命令就同时做了三件事:开彩色、搜PK、把光标放到文件开头。如果某个偏移是你每周都要看的,直接写成 alias:
alias fw='hexedit --color -o 0x1000 /srv/images/firmware.bin'1.5 运行环境的最低要求
hexedit 依赖 ncurses 和正确的TERM环境变量。如果TERM是空的或者设成了dumb,界面会画得乱七八糟甚至直接报错。SSH 进去之后先echo $TERM确认一下,正常应该是xterm-256color之类。
另外它需要终端至少有一定宽度才能把"偏移 + 十六进制 + ASCII"三栏排开。终端太窄的时候,能看到的内容会变少,甚至布局错乱。用 tmux 或 screen 分屏的时候尤其要注意这一点——我自己就试过在一个窄分栏里开 hexedit,结果每行只能显示 8 个字节,看着特别别扭。
2. 屏幕上的三栏分别代表什么
2.1 偏移量、十六进制区、ASCII 预览
hexedit 的界面横着切三块。
最左边一列是偏移量,用十六进制显示,宽度会随文件大小自动调整——小文件是 8 位,大文件会加宽。这里有个新手最容易犯的错:偏移是从 0 开始数的,第一行的偏移是00000000,不是00000001。也就是说,"第 100 个字节"的偏移是 99(0x63)。这个差一位的问题,在对着协议文档和文件格式规范改数据的时候,会让你改错位置还找不到原因。
中间是十六进制区,每个字节两个十六进制字符,中间用空格或分组隔开。十六进制区是真正决定文件内容的地方,你在这里看到的就是磁盘上的实际数值。
右边是ASCII 渲染区,把每个字节按字符解释一遍。可打印字符原样显示,不可打印的(控制字符、高位字节)统一显示成点号。这一栏的价值在于快速扫字符串——找http://、找文件路径、找版本号,眼睛扫一遍就出来了,比在十六进制里认 ASCII 快十倍。
底部通常有一行状态信息,显示当前文件名、文件大小、当前光标偏移。编辑过程中文件会有"已修改"的标记,提醒你还没落盘。
2.2 Tab 键的真正作用:决定你敲进去的是字符还是字节值
这是 hexedit 里最需要理解的一个机制,也是新手最容易被绕晕的地方。
光标可以停在十六进制区,也可以停在 ASCII 区。你敲进去的东西怎么被解释,完全取决于光标在哪个区:
- 光标在十六进制区,你敲
4再敲1,写入的字节是0x41 - 光标在 ASCII 区,你敲
A,写入的字节同样是0x41
两条路走到同一个结果,但输入方式完全不同。Tab键就是在两个区之间切换输入目标。
由此可以推出一个实用结论:想改成某个具体数值,用十六进制区;想改成某个可读字符,用 ASCII 区。反过来会出问题——在十六进制区敲字母A,hexedit 会提示非法字符,因为它只接受 0-9 和 A-F;在 ASCII 区敲4和1,写入的是两个字节0x34和0x31,而不是一个字节0x41。这个差别在批量填充的时候会让你多写一倍的数据。
提示:改文件头这类工作,我一般在十六进制区做,因为要对照的规范文档给的就是十六进制值。改字符串常量这类工作,切到 ASCII 区直接打字更快。
2.3 只读还是可写:F2 存不下去的两种原因
按 F2 保存没反应,通常是两个原因之一。
第一种是权限问题。hexedit 打开文件时会检查写权限,没有写权限就以只读模式打开,此时编辑动作可能被允许但保存一定失败。先ls -l看一眼 owner 和权限位,需要提权就用sudo hexedit。但要特别提醒:提权改系统文件是很危险的动作,/etc下很多东西是有校验或者会被包管理器覆盖的,改之前一定想清楚是不是真的应该这么干。
第二种是文件本身的状态变了。如果你在 hexedit 打开某个文件的同时,另一个进程把这个文件删了、重命名了或者整体重写了,保存就可能失败或者写到已经不存在的 inode 上。这种问题在服务器上很常见,比如你改的是某个服务正在写的日志或者数据文件。稳妥做法是先把服务停下来,或者改一份副本。
3. 按键操作的全链路梳理
3.1 定位:Ctrl-G 与偏移量的进制换算
最常用的定位方式是Ctrl-G,会弹出一个输入框,你填偏移量,回车就跳过去。输入支持0x前缀的十六进制,也支持纯十进制。
这里的关键是偏移量从哪来。常见的三个来源:
# 来源一:十六进制偏移,strings 命令输出 strings -t x firmware.bin | grep -i "version" # 来源二:十进制字节偏移,grep 的 -b 参数 grep -aboP "MAGIC" firmware.bin # 来源三:反汇编器或者文档里直接给的地址strings -t x给的是十六进制偏移,可以直接丢进Ctrl-G;grep -ab给的是十进制偏移,也能直接填,因为 hexedit 认十进制。但要注意 GNU grep 的-b算的是从文件开头算起的字节偏移,如果前面加了-o只输出匹配部分,偏移就正好是匹配串的起始位置,非常好用。
还有一个细节:很多格式规范里的偏移是相对某个段或者某个结构体起始位置的,换算成文件绝对偏移需要加上段基址。这一步别偷懒,算错了改的位置就全错。
3.2 搜索与替换:Ctrl-S 与 Ctrl-R
Ctrl-S是向前搜索,会弹出输入框。输入框里同样可以用 Tab 切换十六进制模式和 ASCII 模式,这一点很重要,因为有些字节根本没法用 ASCII 输入——比如0x00(NUL)、0x0A(换行)、0x89(PNG 的第一个字节,超出 ASCII 范围)。这种情况下必须切到十六进制模式,输入FF D8 FF这样的字节序列。
Ctrl-S找到第一个匹配后会停下来,继续按可以往下找。如果搜到文件末尾还没有,通常会提示没找到。
Ctrl-R是替换,会依次问你"找什么"和"换成什么"。这里有个必须强调的点:替换是覆盖面很广的操作。如果这个字节序列在文件里出现了几千次,而你只想改其中一处,一次替换就可能把文件改得面目全非。我的做法是:永远不要用替换做单点修改——单点修改就用光标定位加手动输入,慢一点但是安全。替换只用在"全文件范围同意图批量修改"的场景,而且改之前必须备份。
注意:搜索和替换的输入内容都要区分十六进制与 ASCII 模式。搜
50 4B和搜字符串PK在很多情况下结果一样,但在大小端或者含控制字符的序列上就完全不同了。养成"按字节搜就用十六进制"的习惯,最不容易出错。
3.3 改写、备份与落盘
编辑动作本身很简单:把光标移到目标字节,敲入新值,光标自动前进。如果你要连续改一段,就一路敲下去。改过的字节通常会高亮显示,一眼能看出哪些位置动过。
落盘有三个出口:F2只保存不退出,Ctrl-X保存并退出,Ctrl-C直接退出。Ctrl-C是最危险的那个——因为你在 shell 里已经形成肌肉记忆,很多人下意识就按了Ctrl-C想"取消当前操作",结果直接退出了编辑器。所以我自己的用法是:编辑期间完全不碰 Ctrl-C,改完统一用 Ctrl-X 保存退出。
保存之前,务必要做一次备份。这不是客套话:
cp firmware.bin firmware.bin.bak一个字节的改动就可能让设备变砖,而这个备份只花半秒。我在第一次给设备打补丁的时候没备份,把校验区的一个字节改错,结果设备连恢复模式都进不去,最后只能拆机接调试口重刷。从那以后一条铁律:hexedit 之前先 cp,改完先 diff。
3.4 撤销能力的实际边界
hexedit 有撤销功能,Ctrl-Z可以回退上一步操作。但千万别把它当成安全网。撤销的层数是有限的,而且一旦保存落盘,撤销就只能撤销"保存之后"的改动,已经写进文件的内容不会因为按了 Ctrl-Z 就自动还原。
所以正确的心理预期是:撤销只用来修手滑,不用来兜底。真正的兜底是备份文件加上cmp差异对比。改完之后跑一遍:
cmp -l firmware.bin firmware.bin.bak | head -20cmp -l会逐字节比出差异,输出三个字段:字节位置(十进制,从 1 开始)、第一个文件里的字节值(八进制)、第二个文件里的字节值(八进制)。注意这里输出的是八进制,别当成十六进制读,否则你会以为改错了地方。只改了一处的话,这里应该只输出一行。
4. 实战里最常见的几类活儿
4.1 修文件头:把"打不开"的文件救回来
这是 hexedit 最经典的用途。场景通常是:从损坏的存储介质、下载工具、或者某个不靠谱的传输链路里捞出来的文件,双击打不开,系统说"文件格式不支持"。
第一步永远是确认现场,用file和xxd两条命令:
file broken.jpg xxd -l 32 broken.jpg如果file报的是data,说明它连文件类型都认不出来,基本可以确定文件头的魔数(magic number)坏了。这时候对照标准魔数表,把正确的字节写回去。
我把工作中最常遇到的一组魔数列在这里,不用每次都去查:
| 文件类型 | 起始字节(十六进制) | ASCII 表现 |
|---|---|---|
| PNG | 89 50 4E 47 0D 0A 1A 0A | .PNG.... |
| JPEG | FF D8 FF | ÿØÿ |
| GIF | 47 49 46 38 | GIF8 |
| ZIP | 50 4B 03 04 | PK.. |
| RAR | 52 61 72 21 | Rar! |
| 7z | 37 7A BC AF 27 1C | 7z..' |
| gzip | 1F 8B | .‹ |
25 50 44 46 | %PDF | |
| ELF 可执行 | 7F 45 4C 46 | .ELF |
| Windows PE | 4D 5A | MZ |
| Mach-O | FE ED FA CE或CF FA ED FE | 视字节序 |
| SQLite 数据库 | 53 51 4C 69 74 65 20 66 6F 72 6D 61 74 20 33 00 | SQLite format 3. |
| Java class | CA FE BA BE | 不可打印 |
| WebAssembly | 00 61 73 6D | .asm |
| BMP | 42 4D | BM |
操作流程是这样的:hexedit broken.jpg,Ctrl-G跳 0,在十六进制区依次输入FF D8 FF,F2保存,退出,再跑一次file broken.jpg验证。
这里必须说清楚 hexedit 的能力边界:它能做的只是"把已经被覆盖的头部字节改回正确值"。如果原始文件头是被剪切走了(比如文件整体前移了 16 个字节,FF D8 FF现在躺在偏移 0x10 上),那 hexedit 帮不了你——因为你没法把后面的数据整体往前搬。这种"数据整体位移"的修复,得靠dd把从 0x10 开始的所有内容复制到新文件的开头,属于另一个套路。判断方法是:把偏移 0x00 到 0x40 的内容用xxd全看一遍,如果正确的魔数出现在后面而不是被覆盖成别的值,那就是位移不是覆盖。
4.2 二进制补丁:改固件、改可执行文件里的一个字节
这一类活儿的难度不在于操作,而在于改完之后文件还能不能用。
先说最简单的:改可执行文件里的一段提示字符串。比如某个程序里硬编码了/etc/app/config,你想让它读/etc/app/confia——这种做法本身很粗糙,但作为例子很直观。步骤是:strings -t x app | grep "/etc/app"拿到偏移,Ctrl-G跳过去,Tab 切到 ASCII 区,把最后一个字符改掉。
这里有个硬约束必须记住:不能改超出原字符串的长度。原始字符串后面紧跟着一个0x00作为结束符,如果你把字符串写得更长,就会覆盖掉后面那个0x00,然后程序读这个字符串的时候会一路读下去,读到后面别的数据,行为完全不可预测。所以字符串修改的规则是:等长替换,或者短了用0x00补齐。
再说改指令。这是逆向里很经典的手法,x86 架构下有些指令长度相同,可以一字节互换。举个实际例子,某段校验逻辑是:
00001234: 75 1A jne +0x1A75是条件跳转 JNE 的机器码。如果你想让它无条件跳转,把75改成EB就行,两条指令都是两字节,后面的相对偏移1A不用动:
00001234: EB 1A jmp +0x1A这种改动之所以安全,是因为指令总长度没变,后续所有指令的地址全部保持不变。如果换成一条长度不同的指令,后面所有代码的地址都会平移,除非你同步改掉所有引用,否则程序直接崩。这就是为什么"定长编辑"在逆向补丁里是刚需。
改固件比改可执行文件麻烦的地方在于校验和。很多设备的固件镜像末尾或者头部带一段 CRC32、MD5 或者厂商自定义的校验值。你改了内容但没重算校验,设备加载固件时校验失败,轻则拒绝启动新固件,重则进恢复模式。所以动手之前必须查清楚:这个格式有没有校验字段,校验算法是什么,校验范围覆盖哪些字节。如果覆盖了你改的那个字节,就必须改完之后重新计算并写回;如果算法不明或者算不出来,那就别改。
4.3 逆向、取证与 CTF 场景里的定位套路
这类场景的重点是"快速找到该改哪儿",hexedit 的作用是最后一步的落地执行。
找魔数用grep:
grep -aboP "\x89PNG" disk_image.bin-a把文件当文本处理,-b输出字节偏移,-o只输出匹配内容,-P启用 PCRE 才认得了\x89这种转义。输出形如1234567:PNG,前面的数字就是十进制偏移,直接丢给Ctrl-G。
找字符串用strings:
strings -t x -n 6 disk_image.bin | grep -i "flag\|key\|password"-t x让偏移以十六进制显示,-n 6表示只输出长度不小于 6 的字符串,可以过滤掉大量噪声。
做文件拼接(file carving)的时候,hexedit 用来确认边界。比如你怀疑一段数据是嵌入的 PNG,跳到疑似起始位置看是不是89 50 4E 47,再往后找到49 45 4E 44 AE 42 60 82(PNG 的 IEND 块)确认结束位置,然后把这两点之间的数据切出来。
取证场景里还有一种用法:把疑似被改过的字节和原始版本对比。这时候cmp -l和xxd配合使用效率最高,先用cmp定位差异位置,再用 hexedit 跳到那个位置看上下文,判断这处改动是有意义的还是噪声。
4.4 只有终端可用时的应急修复
这个场景特别真实:内网的跳板机、生产环境的容器、没有图形栈的嵌入式设备。你连复制文件到本地都不方便,更别说装 GUI 工具。这时候 hexedit 是唯一的选择。
配合 tmux 或者 screen 使用会更稳:
tmux new -s fix hexedit /data/corrupted.db一旦网络抖动断开,重连之后tmux attach -t fix就能接着干,编辑状态还在。用普通 SSH 会话直接编辑,断线时未保存的修改全丢,运气不好的话还可能留下一个半写状态的文件。
5. 那些文档里不写、但一定会踩的坑
5.1 Ctrl-S 把终端卡死:软件流控在捣鬼
这是 hexedit 使用中最高频的"灵异事件"。你按Ctrl-S想搜索,结果整个终端像死了一样,什么键都没反应。
原因不在 hexedit,在终端本身。终端默认开启了 IXON 软件流控,Ctrl-S在这个协议里是 XOFF,含义是"暂停输出",Ctrl-Q是 XON,"恢复输出"。所以 hexedit 想用Ctrl-S当搜索快捷键,终端却把它拦截成了暂停信号。
两个层面的解法:
第一个是临时救急,按Ctrl-Q就能恢复输出。
第二个是根治,在~/.bashrc里加上一行:
stty -ixon这行关闭软件流控,之后Ctrl-S就正常传给前台程序了。我建议所有经常在终端里干活的人都加上这一行,因为不只是 hexedit,less、vim、screen都有类似问题。
提示:如果你在
stty -ixon之前已经卡住了,Ctrl-Q恢复之后不要急着继续操作,先确认刚才的编辑有没有生效,因为卡死期间按键可能已经被吞掉了。
5.2 保存了但没生效:缓存、挂载与权限的三重陷阱
"我明明改了,怎么还是老样子",这个问题有至少三种成因。
第一种是文件被别的进程缓存着。Linux 的页缓存不会因为你改了磁盘上的文件就自动失效,如果某个进程已经把这块数据读进内存并一直持有,你改完之后它读到的还是旧值。典型场景是数据库文件、正在运行的服务配置文件、被 loop 设备挂载的镜像文件。解决办法是让持有方重新读——最保险的是停服务、umount、再改。
第二种是挂在 loop 设备上的镜像。你直接对镜像文件改字节,改动会写进文件,但如果这个镜像正被作为块设备挂载着,挂载点的视图不会变,而且下一次 umount 的时候,内核可能把内存里的脏页回写,覆盖掉你的修改。所以改镜像文件之前,先umount。
第三种是权限。这个前面提过,但有一种隐蔽情况:文件所有者是你,但文件所在的文件系统被以只读方式挂载了。这时候ls -l看不出问题,写操作却会失败。用mount | grep 那个分区确认挂载选项里没有ro。
改完之后一定要重新读一遍验证。file、xxd -l 32、sha256sum,随便哪个都行,反正别相信"保存成功了"这个提示。
5.3 校验和与长度字段:改一个字节牵一发动全身
这是二进制编辑最容易忽略的系统性问题。很多文件格式除了内容本身,还维护着一堆派生数据:长度字段、偏移表、CRC、哈希、数值校验位。
举个具体的例子,改一个 GIF 文件里的注释字符串,看上去无害,但 GIF 的每一个数据块前面都有一个长度字节。你把字符串改长了,块的长度字段就错了,解码器读到这里就崩。改短了同理,会残留垃圾字节。
再比如 ZIP。每个本地文件头里有 CRC32、压缩后大小、未压缩大小三个字段。你改了压缩数据里的任何一个字节,CRC32 必然对不上,解压工具会报"CRC 校验失败"。这时候要么重算 CRC 写回去,要么老实地解压、修改、重新打包。
所以 hexedit 的操作规范里必须加一条:动手之前,先搞清楚这个文件格式有没有派生字段,以及派生字段覆盖的范围。这个判断成本不高,查一次格式规范就有答案,但漏掉它的代价可能是改完之后文件变成砖头。
5.4 大文件与内存占用的实际表现
hexedit 的设计是围绕内存缓冲来做文件访问的。小文件(几百 KB 到几十 MB)完全无感,打开即用。但到 GB 级别,问题就出来了:打开会明显变慢,内存占用直线上升,翻页的时候偶尔会卡顿,极端情况下直接打不开。
如果你的目标是在一个 4 GB 的磁盘镜像里改几个字节,更合适的组合是xxd + dd:
# 先看目标位置 xxd -s 0x1000 -l 64 disk.img # 直接写一个字节进去 printf '\xFF' | dd of=disk.img bs=1 seek=4096 conv=notrunc status=noneseek是偏移(字节为单位),bs=1表示一次处理一个字节,conv=notrunc是关键——没有这个参数,dd会把文件截断到写入位置,你的镜像后半部分就没了。status=none只是让输出干净点。
这套写法还有个额外好处:可脚本化。如果你要处理一批文件,写成循环比手动开编辑器快得多。
for f in *.bin; do printf '\x01' | dd of="$f" bs=1 seek=$((0x20)) conv=notrunc status=none done5.5 版本差异与按键漂移
hexedit 有多个版本的源码分支,不同发行版打包的版本号不一样,个别按键可能有出入。最权威的按键清单永远是F1打开的内置帮助页,它显示的就是你手上这个版本的实际情况。
养成习惯:新环境里第一次用,先按F1扫一眼,花十秒钟确认Ctrl-S、Ctrl-R、Ctrl-G是不是我描述的那几个功能,以及有没有版本特有的快捷键。这比照着某篇几年前的教程硬试要靠谱得多。
另外,终端类型也会影响显示效果。TERM=xterm和TERM=xterm-256color下颜色表现不同,--color参数在某些终端里可能显示不出来。宽字符(中文、日文)在 ASCII 区一律显示成点号,这是正常的,因为那些字节确实不是单字节字符。
6. 什么情况下该换个工具
6.1 需要插入或删除字节
这是 hexedit 硬性做不了的事。要在偏移 0x100 处插入 4 个字节,用 shell 就能搞定:
head -c 256 original.bin > new.bin printf '\x00\x00\x00\x00' >> new.bin tail -c +257 original.bin >> new.binhead -c 256取前 256 字节(偏移 0 到 255),tail -c +257从第 257 个字节开始取(也就是偏移 256)。中间插入 4 个零字节。逻辑很直白。
用 Python 写更清楚,也不容易在 1-based / 0-based 上出错:
data = open('original.bin', 'rb').read() open('new.bin', 'wb').write(data[:0x100] + b'\x00' * 4 + data[0x100:])切片的含义一目了然,推荐处理复杂插删时用这个。
6.2 需要按结构理解二进制
hexedit 给你的是裸字节,它不知道什么是 ELF 段表、什么是 PNG 块、什么是 ZIP 中央目录。要在这些结构里做定位,你得自己查规范、自己算偏移。
如果你的工作大量涉及"理解二进制结构",那应该上带模板(pattern)功能的工具。这类工具可以加载一份结构描述文件,把裸字节直接渲染成带字段名和字段类型的树,找字段、改字段都是点一下的事,比手工算偏移快一个数量级。hexedit 在这种场景下只适合当"最后一公里"的执行工具——定位靠结构解析工具,改字节靠 hexedit。
6.3 需要可重复、可审计的批量修改
一次性手改一个字节,hexedit 是最舒服的。但当你要改一万个文件,或者这个修改需要写进部署脚本、需要在 CI 里跑、需要留审计记录,那交互式编辑器就完全不合适了。
这种场景必须是脚本。前面给的dd写法就是最小可用的方案。如果逻辑更复杂(比如先读某个字段判断再决定改不改),用 Python 处理:
import sys path = sys.argv[1] with open(path, 'r+b') as f: f.seek(0x20) magic = f.read(4) if magic == b'ABCD': f.seek(0x24) f.write(b'\x01') print(f'{path}: patched') else: print(f'{path}: skipped')注意'r+b'模式——读写二进制,不截断文件。这个模式是原地覆盖的正确打开方式。
6.4 同类工具横向对照
把常用工具放在一起比一下,选型的时候心里有数:
| 工具 | 形态 | 最擅长 | 明显短板 |
|---|---|---|---|
| hexedit | 终端 TUI | 轻量、纯键盘、定长改字节 | 不能插删、无结构解析 |
| bvi | 终端 TUI | vi 键位、支持插删 | 上手门槛在 vi 本身 |
| dhex | 终端 TUI | 自带差异对比视图 | 界面风格偏老 |
| hexyl | 命令行 | 彩色输出、现代感强 | 只读,不能编辑 |
| xxd | 命令行 | 双向转换、可脚本 | 不交互,改字节要配 dd |
| 结构解析型工具 | GUI / TUI | 模板化理解二进制 | 体积大,纯终端环境受限 |
| 反汇编器 | 命令行 | 反汇编与改字节一体 | 学习曲线陡 |
选择逻辑很清晰:定长改字节,终端环境,要快 —— 用 hexedit;要在文件中间插删 —— 换bvi或者脚本;要理解结构 —— 上模板工具;要批量自动化 —— 写脚本。
我在日常里最常用的组合是:xxd看,grep/strings定位,hexedit改,cmp验证。这四步串起来,从发现问题到验证完成,通常在五分钟以内。
最后分享一个我自己的小习惯:只要是动过二进制的活儿,我都会在处理目录里留一个xxx.bin.bak和一份changes.txt,记下"改了哪个偏移、从什么改到什么、为什么改"。这个习惯救过我两次——一次是三个月后设备又出问题,翻记录发现是当初那处补丁和新固件冲突;另一次是同事接手我的工作,直接照着记录就能复现修改过程,完全不用猜。改二进制这种不可逆的操作,把过程记下来,比记住结论更重要。