AI辅助逆向工程链路:从二进制接入到等价性校验

在没有源代码的前提下弄清一个产物怎么运转,过去依赖少数熟手的手工推断。把这件事拆成可编排的链路之后,它更像一条流水线:二进制接入、符号映射、调用追踪、等价性校验。四段串起来,回答一个具体问题------怎样把「读懂」这件事从手感变成流程。

本文按这条链路逐段展开。每段都会说明它产出什么、以什么作为判据、以及在没有达到判据时应该停下来补什么。

一、二进制接入:先把产物变成可分析对象

问题的起点是编译产物会丢失符号信息。函数名与变量名在优化后基本不可恢复,剩下的只有地址与指令序列。源码面向人编写,符号在优化阶段被清除,可复现的线索只剩地址与指令序列。

接入这一步的目标不是还原源码,而是把产物变成结构化的中间数据:段信息、导入导出表、可定位的入口点。

bin_ingest:产出可分析对象,不做任何还原假设

def bin_ingest(path, out_dir):

obj = load_binary(path) # 只读取段表与导出表

meta = {

"arch": obj.arch,

"entry": obj.entry_point,

"segments": \[s.name, s.va, s.size for s in obj.segments],

"imports": obj.imports, # 外部调用面,逆向的入手处

}

write_json(out_dir + "/ingest.json", meta)

return meta

关键点在于这一层只描述「有什么」,不猜「是什么」。把猜测留到后面,前一段的产出才可复现。如果在这一层就开始给地址起名字,后面的结果会随着一次重跑全部变化,链路的可比性就没了。

二、符号映射:给匿名地址挂上意义

接入之后的产物是一堆无语义的地址。符号映射要做的是给这些地址挂上可读的名字与类型------也就是人工逆向里耗时的大头。

这一层的价值在于可追溯:每个命名都要能指回一条证据,而不是凭感觉。

symbol_map:命名必须能指回证据

def symbol_map(meta, hints):

table = {}

for addr in meta"call_targets":

ev = hints.evidence(addr) # 字符串常量、调用上下文、系统调用

tableaddr = {

"name": hints.suggest(addr),

"type": hints.signature(addr),

"evidence": ev,

}

return table

这一步留下的证据字段,是后面校验环节能自动比对的前提。命名一旦脱离证据,整条链路就退回成手工笔记------看上去有了结构化数据,实际上仍然无法复核。

命名粒度也值得权衡。名字太细,维护成本压过收益;名字太粗,复核时等于没有。一个可用的标准是:换个人照着这份表能还原出同样的行为描述,就够了。

三、调用追踪:把散点连成关系

有了符号,下一步是把它们连起来。追踪的目标是给出调用关系,让「谁触发谁」变成可查询的数据。

call_trace:以入口为根做可达性分析

def call_trace(meta, table, root):

seen, stack, edges = set(), root, \[\]

while stack:

cur = stack.pop()

for callee in resolve_calls(meta, cur):

edges.append((table.get(cur, {}).get("name", cur),

table.get(callee, {}).get("name", callee)))

if callee not in seen:

seen.add(callee)

stack.append(callee)

return edges

到这一步,一份产物的功能轮廓才真正显形。此前需要逐段推断的工作,现在变成对关系图的检索。检索能力带来的另一个好处是可重复:同一份产物跑两遍,得到的关系图应当一致。

这里有一个容易被忽略的细节:关系图不是越全越好。从入口不可达的部分,参与判定的价值有限,却会显著抬高维护成本。先把可达部分理干净,比追求覆盖率更实际。

四、等价性校验:证明行为一致

前面三段产出的是理解结果,这一段负责证明这个理解站得住。校验不能只看输出对得上,还要覆盖输入边界。

python -m parity_check --ingest ./ingest.json --trace ./trace.json \

--cases ./cases/ --report http://your-domain.test/reports/parity.json

校验的意义在于把「我觉得一样」换成「在给定的用例集合上一样」。用例集合覆盖不到的地方,要显式写进报告,而不是默认通过。

这一点决定了整条链路的可信度。前面三段做得再细,只要校验环节放水,得到的仍然只是印象,而不是证据。

五、四个踩坑记录

  1. 把命名与推测混在一起。结果是每次重跑,命名都不一样。

  2. 跳过证据字段。短时间看不出问题,等到需要复核时无从下手。

  3. 只测主路径。边界输入没覆盖,行为差异在真实使用中才暴露。

  4. 把分析结果写进业务代码。产物一更新,链路就得重改一遍。

前两条尤其常见。它们不会让链路报错,只会让链路的结果不可信------而不可信的结果,比没有结果更麻烦。

第三条和第四条通常成对出现。因为只测了主路径,所以改动的影响面被低估;因为把结果写进了业务代码,所以每次产物更新都要连着改业务流程。这两件事一起发生的时候,返工量会成倍上升。

六、观测指标

| 指标 | 说明 |

| 可命名地址占比 | 挂上名字与类型的地址比例,偏低说明证据不足 |

| 关系图覆盖率 | 从入口可达的调用边占全部调用边的比例 |

| 用例通过率 | 等价性校验中行为一致的用例比例 |

| 重跑一致率 | 同一产物连续两次分析结果一致的比例 |

排序上,重跑一致率应该先看。这一项不达标,其余指标的比较意义有限。

把四项指标并排看,还能看出问题的位置:重跑一致率低,说明证据字段不完整;可命名地址占比低,说明接入层粒度不对;用例通过率低而关系图覆盖率高,说明理解方向出了偏差;关系图覆盖率低,则多半是入口选择有问题。

七、怎么判断这条链路是否可用

看三件事:产物是否能被结构化读取;命名是否能指回证据;行为是否有用例覆盖。三者缺一,先补那一环,不要急着扩用例规模。

顺序反了,得到的只是一个能跑出手感、却经不起复核的脚本。

相关推荐
沙漏无语1 小时前
pnpm 安装@vue-office包的幽灵依赖问题解决
前端·javascript·vue.js
霍格沃兹测试学院-小舟畅学2 小时前
Playwright MCP:AI Agent 驱动 Web 自动化测试的架构与执行机制
前端·人工智能·架构
Jmyd01232 小时前
VR全景线上展会制作流程技术解析:全景采集、点云重建与Web端渲染优化实战
前端·javascript·vr·三维数字化
北城笑笑2 小时前
Server 18 ,Nginx 双服务器 HTTP转HTTPS 代理实战( HTTP 地址换 HTTPS 域名 )
运维·服务器·前端·后端·nginx·http·https
程序员-Benothing2 小时前
Linux磁盘IO性能分析:iostat、iotop、fio实战
linux·运维·服务器
随性而行3602 小时前
企业微信API接口开发实战:如何实现消息回调接收与业务事件处理?
java·前端·python·微信·机器人·企业微信
美味蛋炒饭.2 小时前
java八股面试(3)
java·开发语言·面试
IT研究室2 小时前
最新计算机毕业设计选题推荐-基于spring boot的美食配送平台-网站-文档指导-Java-springboot
java·spring boot·课程设计
℘团子এ2 小时前
vue2中自定义指令的使用
前端·javascript·vue.js