REA 给出的字符串和导入表线索读起来自洽,但这只说明它在当前上下文里没有被自身矛盾推翻,并不等于每条线索都已落到具体指令上。人工反汇编看到的关键跳转同样可能漏掉间接寻址或跳转表,凭一屏反汇编就否定 REA 的结论也不稳妥。处理这类分歧,先把两条线拉到同一个地址上:REA 输出当待验证线索,人工反汇编当证据源,用一张对照表收口,冲突时以能复现的证据为准。
判断方向是:把 REA 的字符串、导入表结论和人工反汇编结论写进同一张表,每条结论都绑定一个可复现的地址或符号;第一轮先用交叉引用筛掉引用不上的线索;关键跳转以人工看到的跳转目标和条件来源为准。适用场景是自动化分析结果与人工核对冲突时该信哪边;风险边界是 REA 的描述不能直接当结论,间接跳转未回溯清楚前应标为待确认,而不是二选一强行合并。
给 REA 的线索和人工核对结果建同一张表
表格的作用不是记录观点,而是让 REA 的每条描述和人工核对的每条发现都指向同一个地址。字段建议固定为五列:线索类型、来源、关联地址、证据、状态。其中“关联地址”用虚拟地址,字符串填字符串所在地址,导入函数填 thunk 或 IAT 项地址,跳转填目标指令地址;“证据”一栏只写能复现的东西,例如引用该地址的指令、调用点、跳转表基址,不写“看起来像”“应该是”。状态先约定几种取值:一致、冲突、仅 REA、仅人工、待确认。
| 线索类型 | 来源 | 关联地址 | 证据 | 状态 |
|---|---|---|---|---|
| 字符串引用 | REA 回答 | 0x140012340 | REA 称该字符串在初始化路径被引用,未给出指令地址 | 仅 REA |
| 字符串引用 | 人工反汇编 | 0x140003a21 | lea rcx, [rip+...] 指向 0x140012340 | 一致 |
| 导入函数调用 | REA 回答 | 0x1400200a8 | REA 称调用了 CreateFileW,未给 thunk 地址 | 待确认 |
| 关键跳转 | 人工反汇编 | 0x4012a8 | cmp eax, 3;ja 0x4012d0 为默认分支 | 冲突 |
表里的地址都只是占位示例,替换成你自己样本里的真实地址即可。填表时注意一件事:REA 没给地址的结论不能直接写成“一致”,只能先记进“仅 REA”,等人工找到落点再改状态。人工这一侧同理,如果只在反汇编里看到一段可疑代码,没确认它被哪条路径执行到,也先标“待确认”。这样表的最后一行永远是“还需要查哪个地址”,而不是“谁说得对”。
用字符串与导入表做第一轮交叉比对
第一轮的目标是筛掉引用不上的线索,做法是每个地址都查一次交叉引用,查不到就先降级为“仅存在于数据段”。字符串先确认自身虚拟地址,再找引用它的指令。在 IDA 里可以在 Strings 窗口选中该字符串按 Ctrl+X,看是否有代码引用;在 r2 里可以先列出字符串再查引用:
rabin2 -z ./sample.bin # 列出字符串及其虚拟地址
r2 -A ./sample.bin
[0x00401000]> axt @ 0x140012340 # 查该地址被哪些指令引用
[0x00401000]> axt @ sym.imp.CreateFileW
如果某个字符串只有数据段地址、没有任何 xref,通常说明它是资源、日志模板、未走到的分支或干脆是残留数据,不能作为“程序会加载并使用它”的证据。导入函数同理:先拿到导入表项或 thunk 地址,再查调用点。只有 IAT 项而没有 xref,一般说明该导入存在于表里但当前分析路径没有直接调用;它仍可能被间接调用,需要继续查 call qword ptr [rip+...]、jmp [rax] 这类形式,确认后再把状态从“仅 REA”改成“一致”或“冲突”。
这一轮不要急着下结论,只做两件事:把能对上地址的线索标“一致”,把查不到任何引用的线索标“待确认”并写明“未找到 xref”。后续都基于这两类状态推进。
关键跳转的人工核对动作
关键跳转要和 REA 的描述对齐,先看在反汇编里这条跳转的目标到底落在哪。检查项建议按顺序过一遍:跳转地址是绝对目标还是寄存器、内存间接目标;条件跳转的条件来自哪条 cmp 或 test,条件是否被中间的指令重写;两个分支是否在同一个块合流,合流点的栈平衡是否一致;如果 REA 说“条件不满足就跳到 X”,人工看到的条件取反后是否也跳到 X。
间接跳转是最容易两边对不上的地方,通常需要先做数据流回溯:看跳转前一到几条指令里索引寄存器从哪来。如果索引来自栈上的返回值或表项选择,就继续追到表初始化或 memcpy 的位置;如果看到 movsxd rax, [rdi+rax*4] 加 add rax, rdi 再加 jmp rax 这种组合,表里存的往往是相对偏移,光看 jmp rax 一行会误判目标。跳转表可以这样确认范围和默认分支:
[0x00401000]> af @ 0x401230
[0x00401000]> pdf
[0x00401000]> pxq @ 0x402000 # dump 跳转表,确认项数与指针
[0x00401000]> pd 12 @ 0x4012a0 # 看越界检查与默认分支
确认越界检查时,留意 cmp eax, N 加 ja default 这类模式,N 往往等于表项数减一。表项数、默认分支、索引来源这三项都对上,这条跳转才能写“一致”;只对上其中一两项,写“待确认”,并注明缺哪一项。
冲突结论的处理与记录
涉及控制流的冲突,处理规则可以定得简单些:以人工反汇编里能复现的指令和字节为优先,REA 的描述保留在“证据”列并标注未验证,不要把模型描述直接抄成结论。状态字段用固定标记,避免后来自己看不懂:REA 未验证的写 [REA 未验证],人工看过的写 [人工确认 @地址],证据不完整的写 [待确认:缺跳转表项/缺默认分支]。等三行记录能拼出一条完整路径,再回头把“冲突”改成“一致”,或把 REA 的推测标注为“已排除”。
需要重新向 REA 提问时,把材料补齐再问,通常包括:目标地址所在节与其对应的函数入口、该地址前后几条反汇编、相关的 xref 列表、导入 thunk 地址、字符串虚拟地址与所在节、文件偏移和 RVA 的映射关系。如果你做过重命名或 patch,也要说明,否则 REA 会基于旧符号给出另一条对不上的线索。提问尽量拆成单点,一次只问一个地址或一条跳转,例如“0x4012a8 处的 ja 默认分支实际落在哪”,比笼统问“这段控制流对不对”更容易拿到可核对的结果。