news 2026/11/2 6:44:41

HexEdit:macOS十六进制编辑与二进制文件排障实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
HexEdit:macOS十六进制编辑与二进制文件排障实战

简介:HexEdit 是托管于 GitHub(strobejb/HexEdit)的开源十六进制编辑器源码包,面向需要直接查看、比对与改写二进制数据的程序员、逆向工程师、安全研究人员及游戏修改爱好者。它以十六进制视图呈现文件内容,支持字节级的搜索、插入、删除与修改,适用于调试可执行文件、修复损坏文件、分析恶意样本等场景,对希望了解 Win32 桌面工具实现方式的中级开发者亦有参考价值。压缩包共 220 个文件、约 528KB,以 C/C++ 源码为主体(51 个 .c、48 个 .cpp、47 个 .h),配合 8 个 vcxproj 工程与 2 个 sln 解决方案,可用 Visual Studio 直接打开并编译;另含 bmp、ico、png 位图与图标、rc 资源脚本、bat 构建批处理及 txt、md 说明文档,构成一套完整可编译的桌面编辑器工程。目前已有 206 人学习下载。读者可从中获得十六进制编辑的核心实现、文件读写与界面布局的组织方式、多工程构建脚本以及按模块划分的目录结构,便于二次开发、功能裁剪与学习借鉴。

1. 二进制文件查看的第一现场:为什么我最后留下了一个本地 GUI 工具

前段时间排查一个序列化文件读不出来的问题,文本编辑器打开是一片乱码,命令行 hexdump 一屏一屏往下翻,翻到要找的偏移时手指都酸了;好不容易定位到那个字节,改完还得退回 xxd 重新拼一遍再写回。二进制文件查看这件事,真正卡人的从来不是「能不能看到十六进制」,而是「能不能一步跳到那一行、改完还能安全落盘」。HexEdit 是一个跑在 macOS 上的开源十六进制编辑器,仓库地址就在项目标题里,能力很朴素:把任意文件按字节摊开,左边偏移、中间十六进制、右边 ASCII,支持偏移跳转、字节查找、就地编辑和保存。它适合需要检查文件头、核对结构体字段、比对资源包差异的后端和客户端工程师,也适合刚开始接触二进制、想建立「字节感」的同学。

2. 从仓库到可运行:编译 HexEdit 的完整链路与三个先决条件

拿到一个 GUI 工具仓库,最怕的是打开工程一片红、编译报错几十条,还没开始用就先放弃了。这一章把「能不能跑起来」这件事讲透:先想清楚为什么值得用本地 GUI,再走一遍从 clone 到拿到 .app 的完整命令,最后把界面分区和它对上,知道每个区域管什么。

2.1 GUI、命令行、在线工具:三种十六进制查看方式的边界

很多人第一反应是搜一个在线十六进制查看器,把文件拖进去看两眼。这个做法在「临时看一个字节」时确实快,但只要你面对的是公司内部的配置文件、带业务字段的二进制包、或者几百兆的固件镜像,上传这一步就先过不了合规。命令行工具是另一个极端,能力强、可脚本化,但对人工定位不友好:你要看第 0x1A2F 个字节,得先算行号再算列号,改一个字节还要配合 dd 小心翼翼地算 seek。

维度在线网页工具命令行 hexdump / xxd / od本地 GUI 编辑器
文件是否离开本机需要上传不出本机不出本机
大文件表现基本不可行流式读取,稳取决于实现,注意内存
就地编辑多数不支持需配合 dd 手算偏移支持,选中即改
偏移跳转支持换算麻烦输入框直接跳
可脚本化无强,适合 CI弱
典型场景看几字节魔数批量校验、流水线人工定位与修改

这三者不是替代关系,而是分工。CI 里校验产物有没有被截断,用 xxd 加 shell 判断就够;需要人眼盯着一片字节找规律、改一个字段再验证,GUI 的效率是命令行的好几倍。HexEdit 落在第三列,它的价值不在于功能多,而在于把「定位—修改—保存」这条链路缩到最短。

三个先决条件要先确认:一是本机有完整的 Xcode 或至少 Command Line Tools,否则 xcodebuild 直接不存在;二是磁盘留出几百兆给中间产物,derivedData 目录膨胀起来不小;三是明白这类老工程的工程文件命名可能和仓库名不完全一致,别拿着一个想当然的工程名去编译。

2.2 编译:从 clone 到拿到 .app 的四步

下面这套命令是我在干净环境里会照着敲的顺序。第一步取仓库,第二步确认工程文件到底叫什么,第三步无签名构建,第四步把产物捞出来。

# 1. 取仓库(地址来自项目标题) git clone https://github.com/strobejb/HexEdit.git cd HexEdit # 2. 先看工程文件叫什么,不要想当然 ls -1 | grep -i xcodeproj # 3. 列出可用的 scheme 和 target,确认名字对得上 xcodebuild -list -project HexEdit.xcodeproj # 4. 无签名构建,绕开本地没有开发者证书的报错 xcodebuild -project HexEdit.xcodeproj \ -scheme HexEdit \ -configuration Release \ CODE_SIGNING_ALLOWED=NO \ -derivedDataPath ./build # 5. 找产物 find ./build -name "*.app" -maxdepth 5

每一步的作用要说清楚。第一步不用解释,注意不要在仓库目录里再套一层同名目录,后面所有相对路径都以仓库根为准。第二步的ls | grep是为了防止工程文件名和仓库名有出入,老项目改过名是常事,直接写死HexEdit.xcodeproj有可能报「project does not exist」。第三步的-list是后悔药,它会打印出这个工程里所有 scheme 和 target,如果第四步报「scheme not found」,就从这个列表里挑一个替换掉-scheme后面的值,或者干脆改用-target指定 target 名。

第四步的几个参数逐个说明:-project指向工程文件;-scheme指向构建方案,决定用哪套配置和依赖;-configuration Release出的是优化过的发布版,日常自用也可以换成 Debug,体积大一点但符号全;CODE_SIGNING_ALLOWED=NO是关键,它让构建跳过签名环节,本地没有配置开发者证书时这是最常见的失败点;-derivedDataPath ./build把中间产物和最终 .app 收拢到仓库目录下,方便第五步用 find 一把捞出来,否则产物会散落在系统默认的 DerivedData 目录里,找起来很烦。

构建失败时优先看三类报错:签名相关(补上CODE_SIGNING_ALLOWED=NO)、SDK 版本相关(老工程可能指定了已下线的 SDK,需要在工程设置里改成当前版本)、依赖缺失(少数工程用 CocoaPods 或 SPM,先看根目录有没有对应的清单文件再决定要不要先装依赖)。这三类覆盖了绝大多数翻车场景。

2.3 首次打开:界面四个区域各自管什么

把 .app 拖进 Applications 或者直接双击运行,第一次打开一个二进制文件时,界面上其实只有四个区域在干活,认清了就不会乱点。

区域位置作用常用操作
偏移列最左侧显示每行第一个字节的文件内偏移读数值、算距离
十六进制区中间每字节两位十六进制,等宽显示选中、编辑、查找命中高亮
ASCII 区右侧把 0x20–0x7E 映射成可打印字符快速扫字符串、找锚点
状态栏底部当前光标偏移、选中长度、文件总长核对「我改的是第几个字节」

偏移列的数值默认通常是十六进制的,这一点要先确认,否则你把一个十进制的 1024 当成十六进制去找,会定位到 0x1024 也就是 4132 的位置,差出三千多个字节。状态栏是最容易被忽略但最该常看的地方:选中一段字节后,它会告诉你起点偏移和长度,这两个数字直接决定你后面填长度字段时填多少。

实际操作顺序建议是:先跳到偏移 0 看前 16 个字节,确认文件类型;再用 ASCII 区扫一遍有没有可读的字段名或路径,这些字符串往往是最好的锚点;最后根据锚点反推结构,跳到目标偏移做修改。这个顺序比「一上来就全局搜索」靠谱得多,因为二进制文件里重复的字节序列太常见,搜到一个 4 字节的值可能命中几十处。

3. 十六进制视图的正确读法:偏移、字节序与文件头识别

工具装好了,接下来是「看得懂」。十六进制视图看起来只是一堆两位数,实际上它同时承载了三层信息:位置(这是第几个字节)、数值(这个字节是多少)、解释(这两个字节合起来是什么)。这一章把这三层拆开讲,每一层都给出可复现的验证方法。

3.1 偏移量:十进制、十六进制与「第几个字节」的错位

偏移的第一原则是「从 0 开始数」。文件里第一个字节的偏移是 0,不是 1,这听起来是废话,但在实际排障里出错率极高,因为报错信息、日志、协议文档三处的计数习惯经常不一致。有的日志说「第 1024 字节出错」,指的是 1-based 的第 1024 个,对应偏移 1023;有的协议文档写 offset 1024,指的是 0-based 的偏移 1024。差一个字节,在解析长度字段时就是天壤之别。

第二个原则是「进制要显式」。HexEdit 这类工具的偏移列通常以十六进制呈现,跳转输入框一般也接受0x前缀。我的习惯是:凡是手输的偏移,一律带0x前缀,绝不裸写数字。裸写1A2F有可能被当成十六进制,也有可能被当成十进制解析失败,不同工具行为不一样,带上前缀就没有歧义。

第三个原则是「行首偏移不等于选中偏移」。十六进制区一行通常显示 16 个字节,行首显示的是这一行第一个字节的偏移。你用鼠标在这一行中间点一下,光标实际落在的行内第 N 个字节,真实偏移是「行首偏移 + N」。状态栏会告诉你准确值,但很多人不看状态栏,直接拿行首偏移去算,结果整体偏移 16 的整数倍,这种错误在解析定长结构体时特别隐蔽。

举个具体例子:一个 PNG 文件,你想找到 IDAT 数据块的起始位置。PNG 的结构是 8 字节签名加若干数据块,每个数据块由「4 字节长度 + 4 字节类型 + 数据 + 4 字节 CRC」组成。第一块通常是 IHDR,长度固定 13,所以第一块总长 4+4+13+4 = 25 字节,加上 8 字节签名,第二块的起始偏移就是 33,也就是 0x21。跳到 0x21,看接下来 4 个字节的类型字段是不是49 44 41 54(IDAT 的 ASCII),就能验证你算得对不对。这种「算出来再验证」的闭环,比凭感觉滑动滚动条可靠得多。

3.2 字节序:同一串01 00 00 00的两种读法

字节序是二进制查看里最容易让人怀疑人生的地方。同一段字节,按小端解释是 1,按大端解释是 16777216,两者差了一千六百万倍,而界面上显示的东西一模一样。判断方法只有一个:知道这个字段是谁写的、按什么规矩写的。

import struct raw = bytes.fromhex('01 00 00 00') # 小端:低位字节在前,x86 / ARM 常见 print('little:', struct.unpack('<I', raw)[0]) # 1 # 大端:高位字节在前,网络字节序、部分文件格式 print('big :', struct.unpack('>I', raw)[0]) # 16777216

这段代码的价值在于给你一个可以随时对照的「翻译器」。当你在 HexEdit 里看到一串 4 字节,拿不准该读成什么,把这 4 个字节抄进bytes.fromhex(),两种端序各跑一次,哪个数值落在合理区间(比如版本号是 1 到 5、长度字段不超过文件总长)就按哪个解释。这个排除法笨但有效,尤其是面对没有文档的私有格式时。

常见场景的端序分布可以记一下:网络协议里的多字节整数按大端,这是历史约定;x86 和大多数 ARM 平台的内存布局是小端;PNG 的长度字段按大端存;ZIP 的大部分字段按小端存;Java 的 DataOutputStream 写出的是大端。真正动手时不要靠记忆,靠「两种都试一遍,看哪个合理」这条硬规则。

还有一类坑是「字段宽度猜错」。你看到01 00 00 00,按 4 字节读是 1,按 2 字节读是 1,按 8 字节读还是 1,这时候三种解释都合理,需要靠上下文判断:看这个字段后面跟着什么、看整个结构体的总长度是否对得上、看同类文件里这个位置的字节数是否稳定。宽度猜错的后果是后面所有字段的偏移全部错位,越读越乱。

3.3 文件头识别:前 16 个字节能干多少事

扩展名是可以骗人的,文件头不会。绝大多数格式都在文件起始位置放了一段固定字节,业内叫魔数(magic number),它的作用就是让解析器在不看扩展名的情况下判断类型。跳到偏移 0,看前 8 到 16 个字节,对照下面这张表,基本能锁定真实格式。

格式起始字节(十六进制)ASCII 观感补充判断
PNG89 50 4E 47 0D 0A 1A 0A.PNG....第 8 字节起是 IHDR 长度
JPEGFF D8 FF不可读结尾应有FF D9
GIF47 49 46 38GIF8后接版本号 7a 或 9a
ZIP50 4B 03 04PK..空包可能是50 4B 05 06
GZIP1F 8B不可读第 3 字节通常是 08
PDF25 50 44 46%PDF后接版本如2D 31 2E 37
ELF7F 45 4C 46.ELF第 5 字节标识 32/64 位
Mach-O 64CF FA ED FE不可读小端存放,读出来是FEEDFACF

用这张表的方法很机械:打开文件先跳 0,把前 8 个字节念一遍。如果 ASCII 区能读出PK、%PDF、GIF8这类可打印字符,基本一眼定案;如果全是不可打印字节,就对照十六进制列。有一个细节要注意:Mach-O 的魔数在文件里是按小端存的,你眼睛看到的是CF FA ED FE,但按大端读出来的数值是0xFEEDFACF,很多文档写的是后者。看到这种「字节顺序和文档写的反着」的情况不要慌,先确认文档描述的是字节序列还是数值。

识别出真实格式之后,接下来的动作就有方向了:是 ZIP 就去找中央目录结束标记50 4B 05 06,是 PNG 就按数据块链往下走,是私有格式就回到 3.1 和 3.2 的方法,从文件头开始一个字段一个字段地推。整个流程里,HexEdit 承担的是「让我能稳定跳到任意偏移并看清那 16 个字节」这件事,剩下的推理还是得靠人。

4. 编辑与落盘:写回前必须确认的四件事

查看是只读的,改坏了大不了重开;编辑不是。二进制编辑最危险的地方在于,工具不会拦你——它不知道你改的那个字节是长度字段还是校验和,你点保存它就老老实实写回去。这一章讲清楚四种编辑模式和落盘前的检查项,把「改完打不开」的概率压到最低。

4.1 覆盖与插入:一个字节能让整个文件错位

十六进制编辑器一般有两种编辑模式。覆盖模式(overwrite)是改一个字节就替换一个字节,文件总长度不变;插入模式(insert)是在光标位置塞进新字节,后面的所有内容整体后移。前者安全,后者危险,区别在于「偏移是否守恒」。

对于任何带索引、带偏移表、带长度字段的格式,插入模式几乎是必炸的。ZIP 的中央目录里记录着每个条目的本地头偏移,你往文件中间插一个字节,后面所有偏移全部失效,解压工具直接报错。SQLite 的页是定长的,插入一个字节会让所有页边界错位。自定义的二进制容器里如果有「记录起始偏移数组」,插入操作等于把这个数组全部作废。血泪经验是:除非你明确知道这个文件没有任何内部偏移引用,否则永远用覆盖模式,需要变长就用「重写整个文件」的方式,而不是「在中间插一段」。

覆盖模式也不是绝对安全。把 4 字节的长度字段从10 00 00 00改成20 00 00 00,文件总长度没变,但解析器会认为后面有 32 字节数据,读到文件尾就崩。这种情况下正确的做法是:先想清楚长度变了之后,数据区要不要跟着补、后面的偏移要不要跟着调,把这些算明白再动手,而不是改完保存再看运气。

4.2 保存前算三笔账:长度、校验和、对齐

我改二进制文件前会强制自己算三笔账,任何一笔对不上就不点保存。

第一笔是长度账。文件总长有没有变?变的的话,哪些字段记录了这个长度?常见的有:文件头里的 total_size、每个数据块前的前缀长度、尾部索引里的条目数。改完内容要回头把这些字段逐个回填。

第二笔是校验和账。大量格式在尾部或者块尾放了 CRC32、MD5、Adler32 之类的校验值,你改了内容不重算校验,解析器第一件事就是校验失败。用 Python 重算 CRC32 是最快的验证方式。

import zlib with open('sample.bin', 'rb') as f: data = f.read() # 整体 CRC32,注意有些格式只对数据区算,不含头部 crc_all = zlib.crc32(data) & 0xFFFFFFFF print('crc32(all) =', hex(crc_all)) # 只对偏移 16 之后的数据区算,具体边界看格式定义 crc_body = zlib.crc32(data[16:]) & 0xFFFFFFFF print('crc32(body) =', hex(crc_body))

这段代码的关键在& 0xFFFFFFFF:Python 的 zlib.crc32 在部分版本上返回有符号数,不掩码会得到负数,和文件里存的 4 字节无符号值对不上。另外data[16:]这个切片边界必须按格式定义来,很多格式的校验范围不含头部,你拿整体 CRC 去比对永远对不上,会误以为改错了。

第三笔是对齐账。有些格式要求数据区按 4 字节或 16 字节对齐,你在中间改了长度导致后面不再对齐,解析器可能直接拒绝。这种问题在 HexEdit 里看不出来,得靠格式文档或者「改完之后用原程序打开验证」来发现。

提示:改之前先复制一份原文件,命名成xxx.bin.bak,并且用md5或shasum记下原始摘要。改坏了能一键回退,比任何撤销功能都可靠。

4.3 大文件:加载、渲染与「先只读确认」策略

几百兆甚至上 G 的文件,GUI 编辑器打开时会明显卡顿,原因通常有两个:一是把整个文件读进内存,二是给每个字节都建了显示对象。不同实现策略差别很大,有的做了分页加载,有的没有。实际使用时,我的策略是「先用命令行确认,再进 GUI 精确定位」。

# 看文件真实大小,排除稀疏文件造成的错觉 ls -lh target.bin # 让系统帮你判断类型,比自己猜快 file target.bin # 只看前 64 字节,确认魔数和头部结构 xxd -l 64 target.bin # 看尾部 64 字节,很多格式的索引和校验在末尾 xxd -s -64 target.bin

这四条命令覆盖了「大小、类型、头、尾」四个信息点,全部在终端里完成,不占 GUI 资源。确认了目标偏移在哪个区间之后,再打开 GUI 跳过去,只在这一小段里操作。这样即使文件很大,实际渲染压力也有限。

如果打开后界面长时间无响应,先判断是「正在加载」还是「已经卡死」:看活动监视器里进程的 CPU 和内存占用,加载中通常内存持续上涨,卡死则是占用不动、界面不响应。前者耐心等,后者强制退出换策略——用dd把目标区间切出来单独看,改完再拼回去。

# 把偏移 0x1000 开始的 512 字节切出来单独分析 dd if=target.bin of=chunk.bin bs=1 skip=4096 count=512

bs=1表示按字节寻址,skip=4096对应偏移 0x1000,count=512是要取的字节数。这个命令在文件很大时特别有用,切出来的小块可以随便折腾,不影响原文件。

4.4 查找:字节序列比字符串可靠

在二进制文件里找东西,用字符串搜索经常失灵,因为你要找的内容可能被拆开、可能夹着长度前缀、可能根本不是可打印字符。可靠的做法是搜字节序列。

比如要找 JSON 里的某个键,直接搜"user_id"这几个字符的 ASCII 没问题,因为 JSON 是文本。但如果要找的是 Protobuf 编码后的字段,字段名根本不在文件里,只有字段号和 wire type 组成的 tag 字节,这时候就得搜0x12(field 2, length-delimited)这类特征字节,再结合后面的长度字节判断是不是目标。

查找结果的多命中问题要有心理准备。一个 4 字节的整数在几百 K 的文件里出现十几次很正常,判断哪一处是目标,靠的是「上下文一致性」:看命中位置前面的几个字节是否符合预期结构、看命中位置后面的数据长度是否合理、看这个偏移是否落在某个已知数据块的范围内。HexEdit 的查找结果会带你跳转,但判断还是人来做。

搜完之后建议把命中的偏移记下来,写在便签或者注释里。二进制排障最痛苦的不是找不到,而是找到了、改完了、过两天再来看忘了当时是怎么定位的。

5. 避坑清单:四类翻车现场与可复现的排查路径

前面几章讲的是「怎么做对」,这一章讲「哪里会错」。下面四条都是我实际踩过或者见别人踩过的,每条按「现象—原因—解决」写,照着对照能省不少时间。

5.1 打开阶段的两个错觉

坑一:打开后一片空白或者全是 00,以为文件坏了。

现象很迷惑:文件管理器里明明显示有几十 K,用 HexEdit 打开却是一片00,滚动条也拉不动多少。原因通常有三种。第一种是稀疏文件,文件逻辑长度很大但实际占用的磁盘块很少,中间没写过的区域读出来就是 0,这在虚拟机磁盘镜像和某些数据库文件里很常见。第二种是打开的根本不是你以为的那个文件,比如通过符号链接打开,链接指向的目标是个空文件或者被截断的文件。第三种是权限问题导致只读到了部分内容,但工具没报错。

排查顺序是先用ls -lh看真实大小,再用ls -l看是不是符号链接,然后用file确认类型,最后用xxd -l 64直接在终端里读前 64 字节做交叉验证。如果终端读出来有内容、GUI 读出来是空的,那就是工具的加载问题,换文件或者重启工具。如果终端读出来也是空的,问题在文件本身,不在工具。

坑二:明明是 UTF-8 文本,ASCII 栏却是一堆点。

现象是十六进制区能看到E4 BD A0这样的字节,ASCII 栏对应位置显示三个点。原因很简单:ASCII 栏只把 0x20 到 0x7E 映射成可打印字符,一个中文字符在 UTF-8 里占 3 个字节,每个字节都落在 0x80 以上,全部显示成不可打印的占位符。这不是文件坏了,是显示规则决定的。

解决方法是回到十六进制区看字节本身。UTF-8 的中文有明确规律:三字节编码的首字节在E4–E9之间,后两个字节都是80–BF。看到E4 BD A0,对照码表就知道是「你」字。要在 GUI 里读文本内容,更省事的做法是把这段字节复制出来,用 Python 解码验证:

raw = bytes.fromhex('E4 BD A0 E5 A5 BD') print(raw.decode('utf-8')) # 你好

如果解码报UnicodeDecodeError,说明这段字节不是合法的 UTF-8,可能是 GBK 或者干脆是二进制数据碰巧长得像文本。这时候不要硬解,回到字节层面继续分析。

5.2 保存阶段的两个陷阱

坑三:改完保存,原程序读不了,报「格式错误」或「校验失败」。

这是最高频的翻车场景。现象是文件大小没变、字节也确实改了,但程序就是打不开。原因基本锁定在两类:一是改了内容没更新校验和,二是改了长度字段没同步数据区。校验和的问题前面讲过,用 zlib 重算再回填即可。长度字段的问题更隐蔽,因为很多格式的长度字段不止一处——文件头有一个总长度,每个数据块前有一个块长度,尾部索引里还有一个条目长度,改了一处漏了另外两处,照样报错。

解决方法是改之前先把所有「疑似长度字段」的位置列出来:搜文件总长的十六进制值,看它在文件里出现了几次;搜数据块数量的值,看它出现在哪。把这些偏移记下来,改完内容后逐个核对回填。这一步很枯燥,但比反复试错快。

坑四:改了字节,重新打开发现没变化。

现象是明明改了、也点了保存,重新打开还是老内容。原因有三种:一是保存到了副本,很多编辑器在「另存为」之后标题栏的路径变了,但用户没注意,继续在原路径找;二是文件被其他进程占用,写入被系统拒绝但工具没弹错误;三是工具本身有缓存,显示的是内存里的旧副本。

排查方法是保存后立刻在终端里算摘要对比:

# 保存前后各跑一次,对比 md5 md5 target.bin # 确认修改时间确实更新了 stat -f "%Sm %N" target.bin

如果 md5 没变,说明压根没写进去,回去检查保存路径和文件权限。如果 md5 变了但程序读出来还是老内容,那是程序自己的缓存,重启目标程序再试。养成「保存后立刻验一次摘要」的习惯,能挡掉大半「改了没生效」的玄学问题。

6. 进阶用法:把十六进制查看接进日常排障流程

单次使用 HexEdit 是「打开—看—改—关」,用熟了之后应该把它变成一个可复现的流程,这样下次遇到同类问题不用重新摸索。我的做法分三步:先造样本,再定位,最后验证。

造样本这一步很多人跳过,直接拿线上文件开刀,结果改坏了没法回退。更稳的方式是用 Python 手工拼一个结构已知的小文件,把魔数、版本号、长度字段、校验和都放进去,然后用 HexEdit 打开它,验证自己能不能准确定位到每个字段。下面这个脚本生成一个 26 字节的样本,结构是「4 字节魔数 + 2 字节版本 + 4 字节条数 + 16 字节负载」。

import struct header = b'BINX' # 4 字节魔数,偏移 0 version = struct.pack('<H', 2) # 小端 uint16,偏移 4 count = struct.pack('<I', 3) # 小端 uint32,偏移 6 payload = bytes(range(0x10, 0x20)) # 16 字节负载,偏移 10 data = header + version + count + payload assert len(data) == 26 with open('sample.bin', 'wb') as f: f.write(data) # 反读一遍,确认偏移和端序都对得上 with open('sample.bin', 'rb') as f: raw = f.read() print('magic :', raw[0:4]) # b'BINX' print('version:', struct.unpack('<H', raw[4:6])[0]) # 2 print('count :', struct.unpack('<I', raw[6:10])[0]) # 3 print('payload:', raw[10:].hex(' ')) # 10 11 ... 1f

关键点在偏移计算:魔数占 0–3,版本号从 4 开始占 2 字节,条数从 6 开始占 4 字节,负载从 10 开始。这三个起始偏移必须在脚本里和 HexEdit 里看到的完全一致,只要有一处对不上,说明你对结构的理解有偏差,回到第 3 章重新推。struct.pack的<表示小端,H是 2 字节无符号,I是 4 字节无符号,这三个符号记熟了,绝大多数定长字段都能描述。

定位阶段的目标是「用最少的跳转次数找到目标字段」。我的习惯是先跳 0 确认魔数,再跳状态栏显示的偏移核对版本号,最后跳到负载区看内容是否符合预期。三次跳转之内能闭环,说明结构理解正确;跳了五六次还在猜,说明前面的推断有问题,应该退回去重新看文件头。

验证阶段是很多人省掉但最该保留的一步。改完之后不要只用原程序打开看「有没有报错」,还要做一次字节级对比:

# 改之前备份 cp sample.bin sample.bin.bak # 改完之后对比差异,确认只动了预期的那几个字节 cmp -l sample.bin.bak sample.bin

cmp -l会逐字节列出差异位置和两边的值,输出格式是「字节序号(1-based) 旧值 新值」,值都是八进制。如果差异数量和你预期的一致、位置也对,说明改动是干净的;如果冒出来一堆你没动过的位置,说明某次操作误触了别的地方,赶紧用备份还原。

这套「造样本—定位—验证」的流程,我用它排查过序列化格式兼容问题、资源包被截断问题、以及某次配置文件里一个字节被写成全角字符导致的解析失败。每次都是先在小样本上跑通,再去动真实文件,返工率明显下降。

从那以后我每次动二进制文件之前,都会强制自己走一遍:复制备份、算一遍摘要、把要改的字段偏移写在便签上、改完用 cmp 对比差异。这几步加起来不到两分钟,但省下的返工时间是以小时计的。仓库地址就在项目标题里,clone 下来按第 2 章的命令编译一遍,再拿第 6 章的样本文件练手,基本一个下午就能把这套流程跑顺。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/2 6:44:28

Java PDF流水线工具:iText7+SM2签章+中文字体+动态水印

简介&#xff1a;本资源是一套基于iText 5.x的PDF文档处理Java工程实践源码包&#xff0c;面向Java开发工程师及PDF自动化处理需求者&#xff0c;解决PDF创建、电子签章、斜角水印添加与文本内容替换等高频业务场景问题。压缩包共32个文件&#xff0c;含7个核心Java源文件&…

作者头像 李华
网站建设 2026/11/2 6:44:11

ZED双目相机+YOLO目标检测的米制距离测量实现

简介&#xff1a;一套面向计算机视觉与机器人感知学习者的ZED相机结合YOLO双目测距工程实践包&#xff0c;重点解决立体相机目标检测与目标距离估算的联调问题。压缩包共42个文件、约78.97MB&#xff0c;以20个Python脚本为核心&#xff0c;配合5个XML工程配置、2个cfg与2个wei…

作者头像 李华
网站建设 2026/11/2 6:43:48

免安装记事本便携化实战:配置随目录、编码换行避坑

简介&#xff1a;这份压缩包提供 Notepad 7.5.1 免安装版本&#xff0c;面向需要在受限权限环境、移动存储设备或临时办公场景中快速编辑文本与代码的程序员、运维人员和普通用户&#xff0c;解压即可运行&#xff0c;省去安装流程。包内共 155 个文件&#xff0c;以 xml 语言与…

作者头像 李华
网站建设 2026/11/2 6:42:19

EGM96重力场模型:球谐系数解析与高程异常计算实战

简介&#xff1a;这份资源面向地球物理、测绘与导航方向的学习者和开发者&#xff0c;围绕EGM96重力场模型&#xff0c;演示如何在VS2012的C#环境中计算高程异常与重力异常。核心难点在于标准向前列递推勒让德函数的实现&#xff0c;以及结合球谐系数生成重力场分量&#xff0c…

作者头像 李华
网站建设 2026/11/2 6:40:35

我们给AI做了一个经验储存器,防止它一换窗口就失忆

模型参数设置M2 于 2025-10-27 开源。参数为 230B 总参&#xff0c;10B 激活&#xff0c;激活参数占比约 4.3%。 语言模型部分共 62 层&#xff0c;全部是 MoE 层&#xff0c;没有 dense 层。模型宽度 3072&#xff1b;每层 256 个本地专家&#xff0c;每个专家的中间维度是 15…

作者头像 李华
网站建设 2026/11/2 6:40:18

降AIGC后查重率反弹?一个工具同时降重,实测知网检测降到0%

我的学校要求我们写文章&#xff0c;文章的查重率要≤30%&#xff0c;AIGC率要&#xff1c;40%。所以我在写完文章后就立马把文章放进检测平台里检测&#xff0c;检测结果还没出来的时候我就可忐忑我希望我的文章AIGC疑似度和重复率不要太高。结果出来后我自己都气笑了&#xf…

作者头像 李华