1. 先说清楚:“AI 拆软件”拆的到底是什么
这些年我一直在做二进制安全方向的活儿,有个感受越来越深:闭源软件真正的护城河,根本不是什么高深的加密算法,而是“源代码不可读”这件事本身。代码经过编译变成机器码,再打包分发到用户手里,开发商吃准了大多数人看不懂、懒得看、也没耐心一点点还原,于是二十年里,“看不懂”就是最结实的一道墙。
但这堵墙现在确实开始漏风了。原因不是某种神秘的反编译神器横空出世,而是大模型这类工具把“读汇编猜意图”的成本拉到了极低。以前一个新手逆向一个函数,可能要花一晚上查指令手册、翻系统调用表、看各种调用约定;现在把反编译后的伪代码丢给代码模型,它几秒钟就能给出一个“很像人话”的逻辑解释,甚至连变量命名都帮你拟好了。
不过我必须先泼一盆冷水:AI 不是真的“学会了拆软件”。它更像是一个读过海量代码的助手,善于把碎成一地的语义碎片重新拼成有结构的描述。它能帮你省掉大量机械劳动,但最终能不能稳、准、狠地把逻辑拿捏住,取决于你怎么喂它、怎么验证它。这篇文章我不打算讲什么玄乎的“AI 替代逆向工程师”论调,而是用我实际做过的一个闭源固件协议还原案例,拆解一套可以照抄的工作流:反编译输出怎么清洗、提示词怎么设计、模型给的结果怎么验证,以及那些它一而再再而三犯傻的地方。
1.1 编译过程到底丢掉了哪些信息
先建立一个共同认知。一段 C 语言代码从源码变成可在处理器上运行的机器码,编译器干的事不仅仅是“翻译”,而是大量“丢弃”。
变量名没了,函数名大多变成地址,循环变成跳转指令,数组下标变成指针偏移,结构体变成一堆偏移量计算。最关键的是,类型信息几乎全部丢失——编译器眼里只有整数、浮点、指针、字节数组,你写的time_t、uint32_t、char*,到机器码层面全是一堆寄存器里的数字。
这意味着逆向工程本质上是一个“逆翻译”过程。你看到一段汇编:
mov eax, [ebp-0xC] shl eax, 2 add eax, [ebp-0x8] mov ecx, [eax]你要推断出“这大概是在访问某个结构体数组的第 N 个元素”。没有符号、没有类型、没有注释,全靠经验去猜。这活儿十年前是纯人工,五年前开始有各种脚本辅助,而现在的大模型直接把这个“猜”的过程给半自动化了,因为它见过足够多的高级语言源码和汇编/伪代码之间的对应关系。
1.2 传统逆向像做翻译,AI 逆向像做考古
传统逆向工程的核心能力,我称之为“做翻译”。一个函数放进反编译工具,工具吐出一段类似高级语言的伪代码,然后人肉阅读、推断变量含义、识别加密算法特征、给函数重命名。翻译得好不好,取决于人的经验积累。我刚入行时,师傅给我的训练是先不看任何符号信息,纯靠汇编还原函数意图,练习量上来了才算入门。
而 AI 辅助逆向更像“做考古”。反编译工具已经帮你把陶片(机器码)拼成了陶罐的大致形状(伪代码),大模型负责推断这个陶罐原本是干嘛的——是装水的、装粮食的,还是祭祀用的。它不需要事无巨细地翻译每一条汇编指令,而是结合上下文语义、字符串引用、函数调用关系、常量特征,给出一个整体判断。
正是这个“整体判断”的能力,让 AI 在处理我接下来要讲的协议还原任务时,比纯脚本方案实用得多。脚本只能做规则匹配,而协议分析中有大量“只可意会”的模式,比如某个魔数出现在固定偏移位置,某个循环按特定步长遍历缓冲区,这些都是高度上下文相关的事情。
2. 大模型凭什么“看懂”闭源代码:语义迁移与文本化输入
可能有人会问:大模型再强,它看到的也只是反编译工具吐出来的文本,凭什么能比人更快看出来这段代码是在做 AES 解密还是在做 JSON 解析?这里面的关键,我总结为三点:语义迁移、模式记忆、上下文拼接。
2.1 伪代码和高级语言之间存在“统计关联”
大模型训练时吃过海量开源仓库的代码,C、C++、Java、Python、Go,各种语言都有。而反编译工具(尤其是做得比较成熟的那些)吐出来的伪代码,虽然长得有点像 C,但其实是“C 的方言”——保留了 C 的控制流骨架,同时夹杂大量工具特定的记号,比如地址标注、偏移量算式、无符号整数强转。
模型在训练时虽然没有专门“背过”某款反编译工具的语法,但它对 C 的控制流结构、常见算法模式(比如循环遍历、校验和、位运算、查表)非常熟悉。所以你给它一段带明显工具风格的伪代码,它依然能把里面“像 C 的部分”提取出来,再结合自身对常见代码模式的记忆,推断出这段代码在干嘛。
这个能力在函数级别特别明显。拿一个典型的 CRC32 实现来说,反编译后你会看到大量table[i]索引、value ^= data循环、poly常量。人眼看到这些需要几秒钟联想,而大模型几乎是一瞬间就能说出“这像 CRC 校验”,并且给你指出具体的多项式常数和字节序处理。这就是统计关联的威力——它见过太多长得像 CRC32 的实现。
2.2 让模型“看懂”的关键是文本化与上下文拼接
知道了原理,接下来就得解决实操问题:怎么把二进制分析流程改造成“大模型友好的”?
我的经验是四个字:文本化、分段。反编译工具本身有图形视图、反汇编视图、伪代码视图,但这些视图是给人看的,没法直接喂给大模型。你需要把伪代码视图里的内容,连同函数名、地址、调用关系、字符串引用一起,以纯文本形式提取出来。这一步可以用反编译工具的脚本接口(比如 IDAPython 或者某开源反编译框架的插件系统),批量导出当前函数的代码体、引用它的函数列表、它调用的函数列表,以及函数里出现的常量。
然后是上下文拼接。大模型理解一个函数,光看函数体是不够的,还需要知道这个函数从哪里被调用、调用了谁、用了哪些全局变量。所以我的做法是把“函数本身 + 调用它的父函数 + 它调用的子函数 + 字符串引用”打包成一个文本上下文,像拼图一样喂给模型。比如我在分析某个网络协议处理函数时,如果上下文里有一堆“login”“password”“session”之类的字符串,模型给出的解读命中率会直线上升。
另外一个容易被忽略的输入项是反编译工具给出的注释和变量名。有些商业反编译器自己已经做了一轮变量重命名和类型推断,这些信息也是文本的一部分。喂给大模型时把注释保留下来,不丢信息,模型的推断会更稳。
3. 一次完整的 AI 辅助逆向复盘:还原某闭源设备的私有协议
理论说多了容易飘,我来走一遍实际操作。为了不涉及敏感内容,这里用的是我做过的一个模拟项目:一个老旧物联网设备的闭源固件,芯片是某嵌入式 M 系处理器,固件里封装了一套设备与管理平台之间的私有 TCP 协议,我需要找出协议里登录报文的格式和加密逻辑,以便做一个兼容性的网关适配。
注意,这类工作属于典型的安全研究/兼容性分析场景,目的是为了互通互联和漏洞排查,不是拿来破解别人的商业授权或者盗版软件。
3.1 样本信息收集:先把“外围线索”捞干净
拿到固件后的第一步,我没有急着上大模型,而是先做人工信息收集。把固件用 binwalk 解包,看文件系统结构、找版本号、找字符串、找硬编码的 IP 或端口。这一步花的时间往往比后面所有 AI 分析的时间都长,但它决定了后续分析的“方向感”。
这个样本里,我找到了一条很有用的字符串:/dev/shm/.session,说明设备会往共享内存里写会话信息。又找到一串 16 字节的魔数0xBE 0xEF 0xCA 0xFE ...,很像某种自定义协议头。这些线索全部记录下来,作为后面验证模型结论的锚点——如果 AI 给出的协议判断和这些锚点对不上,那就得警惕。
3.2 反编译输出清洗:先让机器看得懂
固件解包后,我从里面挑出了处理网络数据包的入口函数——它大概率是某个线程的回调函数,地址范围、字符串引用、交叉引用都比较密集。用反编译工具加载后,定位到一个可疑函数,地址大概在 0x10872 附近,函数长度约 300 行伪代码。
接下来是清洗输出。反编译工具默认吐出来的伪代码其实很脏:大量*(undefined4 *)(param_1 + offset)这种类型,local_38、local_2c这种毫无信息量的变量名,还有一堆工具无法消解的间接调用。我直接用脚本做了三件事:
- 去掉所有非 ASCII 乱码和注释噪音,保留地址标注;
- 把所有
local_XX这类变量名按出现顺序重命名为v1, v2, v3...,避免模型在无意义变量名上过度分心; - 把函数中用到的字符串引用和常量,以注释形式追加到对应代码行后面。
清洗完的伪代码,从 300 行缩到了约 220 行,虽然还是丑,但至少喂给模型时不会被各种噪音带偏。
3.3 分段投喂与提问策略:像带新人一样带模型
我不会把 220 行一次性全丢给大模型,因为上下文长了以后,模型后半段的注意力会明显下降。我习惯的做法是:先整体给一段“摘要请求”,让模型用 5 句话概括这个函数大概在做什么;然后分段细问,一次只给它 30 到 50 行代码,问某个具体逻辑段的意图。
比如第一次投喂,我把函数头 40 行和字符串引用列表放进去,问的是:“这个函数初始化阶段看起来在做什么?那些魔数常量和字符串可能存在什么关联?”模型给出的判断是“看起来在初始化一个会话结构体,16 字节魔数可能是协议头签名,后面跟版本号和保留字段”,方向基本对。
第二次投喂,我给了中间一段包含循环和异或操作的代码,问的是“这段运算像哪种常见加密或校验算法?”模型的回答是“异或循环 + 查表特征,像是自定义校验,也可能是一层 XOR 混淆,不像是标准的 CRC 和 AES”。这个说法提示我需要重点验证。
这里有一个很关键的口诀:永远不要让模型替你做最终决定,而是让它给出候选假设 + 依据。我会明确在提示词里写:“请提供至少两种可能解释,并说明哪种更合理,为什么。”这样既能利用模型的模式识别优势,又能逼着它把推理依据摊开,方便我做下一步验证。
3.4 交叉验证:动态调试确认还原结论
AI 给了假设,接下来必须验证。这一步我用模拟器和调试器配合,在关键函数入口打点,跑了一遍设备启动流程。具体验证点有三个:
第一,协议头魔数。在模拟器里对网卡输入口打点,收到第一条 TCP 数据时检查前 4 字节,确实就是0xBE 0xEF 0xCA 0xFE。模型判断的“协议头签名”可靠。
第二,登录报文的加密段。我在函数处理流程里找到模型指认的那段异或循环,打印出输入输出对比,发现算法果真是先按固定 8 字节密钥做 XOR,再做一次字节乱序。这两个操作都不是标准算法,但模型给它起的名字“自定义混淆层”是对的。
第三,会话 ID 生成。模型认为这里可能用了系统时间戳做种子生成随机数,我查了调用关系,发现它确实调用了芯片平台的定时器接口。佐证到位。
整个协议还原下来,模型帮我节省了两天左右的死磕时间,尤其是它快速锁定了“魔数是协议头而非加密特征”这一点,避免了我在错误方向上绕弯路。但代价是,我必须花额外的精力去验证它的每一个结论。人机协作在这个过程中是密不可分的。
4. 实测里 AI 逆向的四个“蠢”处,以及我的绕坑方案
真实环境的逆向不是 Demo,模型也不是万灵丹。我实测下来,它有四个特别容易翻车的点,每个我都踩过坑。
4.1 一本正经地“幻觉参数”
大模型最坑的地方,就是它会为自己编造的事实提供义正辞严的解释。我曾经问过一个函数里某处常量0x1FF7的含义,模型毫不犹豫地说这是“某个查找表的长度”。后来我验证发现那个常量其实是一个差的种子值,跟查找表毫无关系。
应对方案是:涉及常量、偏移、魔数时,一律要求模型把依据定位到具体代码行,并且开启“二次审查”提示。我会要求它“检查是否可能因为位宽、端序、符号扩展导致误判”,这会让模型自发地重新审视自己的结论,显著减少低级幻觉。说到底,模型的输出是个“提议”,不是“事实”,这个心态必须时刻保持。
4.2 长函数与上下文撕裂
有的函数动辄上千行伪代码,包含多个嵌套循环和大量分支。分段投喂时,如果模型在前一段得出的结论有偏差,后一段的分析会被带偏,整个推理链条就像多米诺骨牌一样倒塌。
我的解法是维护一个“结论缓存表”——就像记笔记一样,把模型每个阶段的结论、依据、验证状态记下来。后续提问时,把这堆笔记作为“历史上下文”重新粘贴给模型。比如我这样写:“你已经确认功能 A 是协议初始化,下面代码疑似在调用功能 A 后的校验,请基于此继续分析。”这种带状态提示的方式,比每次无脑丢代码稳定得多。
4.3 机器码里的“不可见契约”
二进制的逻辑不只在代码里,还在编译器的优化、链接器的布局、操作系统的 ABI 里。模型看的是反编译出来的伪代码,但它看不到一个关键信息:这段代码原本的开发者用了什么编译选项、什么优化等级、什么数据结构设计思路。
举个例子,有一次我在分析一段带有大量switch跳转表的代码。模型判断它是“命令解析器”,依据是“case 分支太多且每个分支调用的函数都不同”。这判断没错,但它无法告诉你原本的设计里,某个分支的“优先级”其实比另一个高,因为在伪代码里顺序不代表优先级。这类“不可见契约”,只能靠动态调试和运行行为来补全。AI 能指出方向,但最后的真相还是要靠设备跑起来说话。
4.4 架构差异带来的“翻译腔”
大模型对 x86/x64 的熟悉程度,远高于对 ARM、MIPS、RISC-V 等嵌入式架构。如果你把一段 ARM Thumb 指令集的反编译结果直接丢给模型,它可能把一个条件执行指令看成普通分支,把立即数池当成数据表。
这个问题没有特别好的自动化解法,只能靠你在喂给模型前做好预处理:把反汇编模式切换到更适合的工具显示,把伪代码里的内存访问统一成规范的*(uint32_t *)(addr)格式,甚至手动补上“这段代码是 ARM 指令,注意条件执行”的前置提示。指令集架构的知识,目前还是人工必须兜底的部分。
5. 顺手聊两句合规:能拆不等于能发
最后必须说点扫兴但重要的事。AI 辅助逆向让技术门槛降低了,但我见过太多人因此忽视合规边界,最后把自己的分析成果变成麻烦。
5.1 合法研究边界
逆向工程的合法性,在不同地区、不同用途下差别很大。我自己写这份分析时的原则是:只做自己拥有合法拷贝的固件,目标限于安全研究、兼容性适配、漏洞发现与上报;不做破解授权、不绕过付费License、不做不义用途。
如果你是做安全研究的,建议一开始就把研究对象限定在你拥有设备、固件或软件的合法授权范围内。有些厂商的 EULA 里明确写着禁止逆向,技术上再容易,也不代表法律上没问题。这堵墙虽然开始漏风,但你自己心里得有一堵更结实的合规墙。
5.2 数据脱敏与供应链风险
另外有个细节特别容易被忽略:大模型分析代码时,你上传的二进制片段、反编译伪代码、字符串表,如果走的是公共服务,那这些数据就离开了你的控制。
我在分析上述设备固件时,完全用的是本地部署的代码模型,或者对上传内容做严格脱敏——所有厂商内部网址、硬编码密钥、真实设备序列号,在投喂前全部换成占位符。尤其是那些会识别出开发者隐私的字符串(比如个人邮箱、内部 IP、注释里的人员姓名),更是必须处理干净。别图省事直接把 raw 二进制丢给在线服务,出了事真的追悔莫及。
这已经不是“快不快”的问题,而是“能不能碰”的问题。
最后说点实在的体会。AI 辅助逆向这半年用下来,最让我惊喜的其实不是它能解出某个刁钻算法,而是帮我把枯燥的“体力活”分解掉了:变量重命名、函数意图归纳、模式匹配这些以前必须亲力亲为的事,现在可以放心交给模型,我只需要在关键路径上把好验证关。但我也越来越清楚,模型是个“永远跟你说好的实习生”——它输出的是可能性,不是真相,你有责任成为那个说“不,这里不对”的人。未来能走多远,取决于你喂给它的上下文有多干净,以及你对二进制本身的敬畏还剩多少。