REA 对话式逆向最容易踩的坑,不是模型答不上来,而是问题问得太笼统:一句「帮我分析这个程序」通常换来一段上下文自洽、却没有任何锚点的描述,读完仍然不知道该信哪一句,也不知道下一句该问什么。可行的缓解方式是把提问顺序固定下来——先问架构、位宽、入口这类能对着文件头逐字段核对的硬事实,再问 strings 与导入表能支撑的功能线索,最后才问函数边界和调用关系,并且每一轮结束都把回答拿回反汇编里核对一次。下面给的是这套顺序的提问模板、每轮的验证动作,以及笼统提问和可核对提问的对照。
把提问顺序固定成四轮:架构/位宽/入口 → strings 与导入表 → 函数边界与调用关系 → 反汇编回环核对。每一轮只解决一类事实,每条结论都要求给出文件头字段、指令地址或原始字节作为依据,给不出的条目明确标为「无依据」并保留,不要删。这样 REA 的回答即使读起来通顺,也能被逐条钉在可验证的指令或配置上。代价是轮次变多、前期推进偏慢,适合需要留下可复核记录的逆向场景。
开场先固定架构、位宽、入口三个可验证事实
这一轮的目的不是理解程序,而是让后面所有回答都建立在同一组前提上。位宽判断错了,反汇编的整体解释会跟着错位;入口地址搞错了,后面追问的函数边界会全部落在错误的起点上。
样本:sample.bin
请只回答下面三项,逐项给依据,不要展开功能分析,不要画调用图:
1. 架构与位宽(例如 ARM64 / x86-64 / ARM32 / MIPS32)
2. 文件格式与装载入口(ELF / PE / Mach-O;入口地址或入口符号)
3. 入口处前 4 条指令的反汇编文本,以及这几条指令体现的编译器或系统特征
每条结论后面追加一行:依据:字段名=值,或 地址 + 原始字节
无法判断的写「无依据」,不要猜测补全。
「依据」这一行是关键。建议在提问里就把可接受的依据形式限定成三类:文件头字段名与取值、指令地址加原始字节、导入或导出符号名。比如 e_machine、Class、Entry point 这类字段可以直接看到;入口序言可以写成「0x1000 处 4 字节 xx xx xx xx」这种形式。凡是说不出这三类依据的结论,一律要求 REA 写「无依据」,并保留在回答里不要删——这些条目恰好是后面几轮需要重点确认的地方。
你自己这边同步做一次核对,不需要复杂工具:readelf -h 或 file 看文件头,objdump -f 看格式与入口,再把入口地址附近的几十个字节反汇编一遍,和 REA 给的序言对上。核对结果只记「一致 / 不一致 / 无法判断」三种即可。
第二轮只问字符串与导入表能支撑的功能线索
功能猜测必须限制在有锚点的范围内。锚点就是字符串和导入符号:它们能说明程序大致涉及哪些能力,但不能说明这些能力怎么组织,更不能直接推出调用关系,所以这一轮只问「可能的功能模块」这一层,不要顺带问函数结构。
贴入时建议分批,一次 20 到 30 条,按地址升序排好,避免一次上百条把对应关系冲散。字符串部分带地址贴:
下面是 strings 从 .rodata 取到的部分命中,格式为 地址 内容:
0x40210 license key invalid
0x40228 config load failed
...
请逐条回答三点:
a) 最可能归属的功能类别(只允许从:配置解析 / 网络通信 / 授权校验 / 日志 / 加解密 / 界面文案 中选,选不出就写「无法归类」)
b) 依据是什么(字符串措辞、邻近字符串、是否出现在导入符号附近)
c) 是否已被代码引用(无法确定就写「未见引用点」)
不要输出完整功能推断链,不要给调用图。
导入符号同样分批,按库分组贴会更清楚,例如 libc、网络库、加解密库各贴一组,让 REA 回答「这组符号说明本程序具备哪类能力、依据是哪几个符号名、有没有哪个符号其实用不到」。被标为「无法归类」和「未见引用点」的条目单独收好,它们是下一轮定位函数时的优先候选。
这一轮的验证动作是交叉引用:对每条被判定有功能的字符串,确认它的地址是否真的被某条指令加载过。只出现在数据段、没有任何引用点的字符串,通常只是遗留文案或调试信息,不宜作为功能结论的主依据。
第三轮再问函数边界与调用关系
函数边界没定下来之前,不要要求 REA 画调用图。边界不清时画出来的图,边往往是靠函数名和字符串「补」出来的,看起来完整,实际上经不起核对。这一轮的措辞要限定到起始地址、结束特征、调用者三件事:
已知入口在 0x1000 附近。请先只回答函数边界,不要展开功能:
1. 该函数的起始地址
2. 结束特征与结束位置(ret / 尾跳转 / 异常抛出;给出结束指令的地址)
3. 直接调用者(列出调用点地址和该处调用指令的原始字节)
每条结论必须给出:指令地址 + 原始字节 + 反汇编文本,三者至少给两项。
结束地址不确定时,写「边界不确定,候选区间 A-B」,不要用估计值填满。
调用关系放在边界确认之后问,并且限制范围:只画已经确认边界的函数之间的边;指向导入符号的调用边标注为「外部」;边界未确认的函数用虚线或「未确认」标注,不要递归展开。要求 REA 对每条边给出被调用目标的地址和来源,地址落在可执行段之外时,明确标注为可疑。
笼统提问与可核对提问的差别在这里最明显。问「它主要做什么、逻辑是什么」,通常会得到一段无法逐条验证的叙述;改问「0x1040 这条调用指令的目标是哪个函数、该函数起始地址凭什么判定」,回答就必须落到地址和字节上。
把每轮回答落到反汇编里做一次回环核对
回环核对要解决的问题是:回答与上下文自洽,不等于与真实指令一致。检查项建议固定成四条,每条都记录结果:
- 地址是否落在合法指令边界:把 REA 给的地址拿去反汇编,确认它是一条指令的起点而不是中间字节,变长指令集尤其要确认。
- 调用目标是否存在:调用指令的目标是否落在已识别函数的起点或可执行段内;落在数据段或未映射区域的,标为可疑。
- 字符串引用是否真被加载:引用点是否确实用加载类指令把该字符串地址取到寄存器,而不是只在符号表或调试信息里出现过。
- 依据能否复现:REA 给的字段名、字节序列,能否在你自己的工具输出里找到同样结果。
核对时可以用几类固定命令,把结论对应到具体输出上:
objdump -d `--start-address`=0x1000 `--stop-address`=0x1060 sample.bin
readelf -h sample.elf
strings -t x sample.bin | sort
objdump -T sample.bin
核对记录建议用一张简单表逐条走,字段固定为:结论 / REA 给的依据 / 你执行的核对动作 / 核对结果 / 是否需要修正。格式示例如下,其中的地址与字节仅为占位,实际请换成自己样本的输出:
结论:0x1040 处 call 0x1234 进入解析函数
依据:REA 给出 0x1040 e8 ef 01 00 00
核对:objdump -d 取 0x1040-0x1046,确认 0x1040 为指令起点,0x1234 为函数起点
结果:一致 / 不一致,改为:……
出现不一致时不要直接改结论了事,把修正写回同一张表,并回到对应轮次重新提问:边界类的不一致通常从第三轮重问,功能类的不一致回到第二轮补引用点。整轮下来,如果每条结论都能找到文件头字段、指令字节或工具输出作为对应,这份逆向记录才具备被他人复核的条件;反之,宁可保留「无依据」「边界不确定」这样的标记,也不要用一段通顺的描述把它们盖过去。