首页
/ Ghidra BSim 实战教程:评估函数匹配质量并在反编译 Diff 视图中应用信息

Ghidra BSim 实战教程:评估函数匹配质量并在反编译 Diff 视图中应用信息

2026-09-05 19:17:54作者:翟江哲Frasier

本文基于 Ghidra 官方 BSim 教程的“Evaluating Matches”一节(BSimTutorial_Evaluating_Matches.md),系统讲解 BSim 查询结果的人工评估与“信息应用”工作流:如何解读反编译并排 diff 视图中的各种高亮颜色与同步滚动行为,如何通过 Apply From Other Function 菜单把匹配函数的名称、签名、完整数据类型安全地迁移到查询方函数,以及如何利用 Compare Matching Callees 对调用者与被调用者成对比较。读完本篇,读者将能够独立完成“BSim 匹配 → diff 验证 → 应用信息 → 递归下钻被调用者”这一逆向工程闭环,并理解其中每一步在源码中的对应实现。

1. 前置条件:教程系列走到这一步时手头有什么

在展开本篇之前,先明确 BSim 教程系列(见 BSim 教程总目录)在前几节已经搭建好的三要素,本篇的所有操作都建立在这三者之上:

  1. 一个剥离符号的可执行文件postgres,无原始函数名);
  2. 一个 Ghidra 项目,其中包含若干用于构建该可执行文件的带调试信息的目标文件[^1];
  3. 一个 BSim 数据库(本教程中名为 example),内含上述目标文件的 BSim 向量签名。

[^1]: 原文档脚注:使用 BSim 并不强制要求目标文件带有调试信息(前一节的练习中已演示过无调试信息的情形),但带调试信息会方便很多。需要注意:应用调试信息可能会改变 BSim 签名,这会对“带调试信息函数”与“不带调试信息函数”之间的匹配质量产生负面影响。

本篇的目标就是用 BSim 辅助逆向 postgres 这个剥离版本,同时顺带演示反编译 diff 视图(decompiler diff view)的各项能力。这些视图能力的实现位于 CodeCompare 特性模块:DecompilerCodeComparisonView.java 负责并排双反编译窗口的组织,BSim 侧则通过 BSimSearchResultsProvider.java 的结果表提供 Compare Functions 等动作入口。

2. 练习一:发起全量查询并定位高置信度匹配

2.1 操作步骤

  1. 将剥离版 postgres 导入并分析到教程项目中(沿用默认自动分析选项);
  2. 在 Listing 窗口按 Ctrl-A 选中全部函数——这是查询整个程序所有函数的最省事方式(无选择时只查当前地址所在函数,有选择时查所有入口点落在选择范围内的函数);
  3. 对 BSim 数据库 example 发起 BSim 查询(BSim -> Search Functions...,或点击 Code Browser 工具栏的 BSim 图标);
    • 注意: 后续几节练习都会复用本次查询结果。如果不关闭 BSim Search Results 窗口,之后无需重复查询;
  4. 在结果表中按置信度(confidence)排序,找到匹配函数为 grouping_planner 的那一行——postgres 中对应的函数应该是默认名(FUN_xxxxx);
  5. 对该匹配执行 Compare Functions 动作,在并排反编译视图中检查;注意匹配方函数因带有调试信息,其数据类型信息明显更完整
  6. 思考题:为什么两个函数中 double 形参的位置(相对其他参数)不一致?

2.2 为什么 double 参数的位置会不同(原文答案)

浮点值与整数/指针值使用两组互不相干的寄存器传递。两种排序都没有错,因为它们各自都与该函数的指令一致。带调试信息的版本中,调试信息记录了函数特定的签名(含参数顺序),Ghidra 会将其应用上去;而不带调试信息的版本中,反编译器是靠启发式推断函数签名的。

这段答案触及了一个 BSim 用户常见的困惑:即使两个函数逻辑完全一致,反编译呈现的参数布局也可能不同——一个来自调试信息的“真值”,另一个来自反编译启发式的“猜测”。在 diff 视图中看到参数顺序差异时,不必怀疑匹配本身的有效性。关于相似性(similarity)与置信度(confidence)两个评分指标的定义与阈值取舍,可参见 Basic BSim Queries 一节:置信度衡量匹配的“含金量”,共享稀有特征对置信度贡献更大,因此按置信度排序能快速把真正有信息量的匹配排到前面。

3. 反编译 Diff 视图:聚焦令牌与四种高亮颜色

当两个函数存在相当数量的差异时,decompiler diff 面板会“五彩缤纷”。而且在两侧窗口随意点击时,令牌(token)会不断获得或失去各种颜色的高亮。原文档对此给出了一套术语与颜色约定,值得完整保留。

Decomp Diff Window

术语:聚焦令牌(focused token)——在任一反编译面板中点击某个令牌,该令牌即成为聚焦令牌

颜色 含义 触发条件
青色(Cyan) 高亮两个函数之间的差异 打开比较窗口后自动标注,无需点击
粉色(Pink) 高亮聚焦令牌及其匹配令牌 聚焦令牌在另一侧存在匹配时
淡紫色(Lavender) 高亮无匹配的聚焦令牌 聚焦令牌在另一侧找不到对应令牌时
橙色(Orange) 高亮不可参与匹配的聚焦令牌 该令牌天生不会被指派匹配令牌

补充规则:某些令牌(例如空白令牌、用于变量声明的令牌)永远不会被指派匹配令牌,因此聚焦到它们时呈现橙色属于正常现象,不代表匹配失败。

3.1 源码佐证:高亮颜色是可配置的选项而非硬编码

从源码结构看,这些颜色并非写死的常量,而是 DecompilerCodeComparisonOptions.java 中注册的主题颜色绑定(theme color bindings),每个都有默认值和用户可覆盖的键名:

  • "Focused Token Match Highlight"(粉色,聚焦令牌的匹配);
  • "Focused Token Unmatched Highlight"(淡紫色,聚焦令牌无匹配);
  • 聚焦令牌不可匹配的高亮(橙色);
  • "color.bg.codecompare.highlight.diff"(青色,差异高亮)。

选项类在构造时通过 options.getColor(KEY, DEFAULT_..._COLOR) 读取配置,也就是说用户在 Ghidra 的颜色选项中可以调整这些默认高亮色。具体的着色逻辑由 DiffClangHighlightController.java 执行:它实现 CTokenListener,在令牌状态(focused / matched / unmatched / ineligible)变化时驱动 C 显示层重新着色。

4. 练习二:锁定与解锁同步滚动

4.1 默认行为:同步滚动与行对齐

diff 窗口默认开启同步滚动:滚动一侧窗口时,另一侧窗口随之滚动。其工作原理是“用左侧函数的一行去匹配右侧函数的一行”,两个函数就以这些行作为对齐基准相互对齐;初始对齐使用两个函数的签名行

在任一侧函数中点击时,"对齐行"会随之变化:

  • 若聚焦令牌有匹配:滚动会围绕包含这对匹配令牌的行重新居中;
  • 若聚焦令牌无匹配:函数会以最接近聚焦令牌、且有匹配的令牌为基准重新对齐。

工具栏上的锁形(lock)与开锁形(unlock)图标可以切换同步滚动的开/关,练习就是分别体验这两种模式。

4.2 源码佐证:滚动协调器

该行为的实现位于 DualDecompilerScrollCoordinator.java,它监听两侧 C 显示组件的滚动与令牌聚焦事件,在两侧之间传播滚动量并维护对齐关系;DecompilerCodeComparisonView.java 中持有左右两个 CDisplay,并在令牌选中变化时调用各自的 DiffClangHighlightController 完成第 3 节所述的着色联动。

5. 练习三:把匹配信息“应用”到被查询函数

5.1 三个 Apply 动作的差异

如果对某个匹配满意,你通常想把匹配函数(右侧)的信息应用到被查询函数(左侧)上,例如函数名或函数签名。但“能安全应用多少信息”有微妙之处,因此在左侧面板右键打开的 Apply From Other Function 菜单下提供了三个层次递进的动作:

  1. Function Name:把右侧函数的名称与命名空间应用到左侧函数。风险最低,只迁移命名;
  2. Function Signature:应用名称、命名空间以及**“骨架”(skeleton)数据类型**。注意:结构体与联合体的具体定义不会被迁移,取而代之的是创建空的占位结构体,从而避免把未验证的结构定义带入程序;
  3. Function Signature and Data Types:应用名称与签名,并携带完整的数据类型定义。这可能会向程序导入大量数据类型(考虑结构体引用其他结构体的连锁效应)。

警告(原文强调):应用“签名 + 完整数据类型”前,必须绝对确信两侧数据类型定义完全一致。哪怕只是数据类型定义的微小改动,即使 BSim 相似度达到 1.0,也可能把错误的数据类型带进程序。此外,对跨架构匹配应用完整数据类型同样是有问题的。

5.2 练习操作

由于本教程中 grouping_planner 的匹配来源明确(同一代码库、带调试信息的目标文件),可以直接应用 Function Signature and Data Types 到左侧函数,左侧函数的签名信息会立即刷新。

5.3 源码佐证:Apply 动作的两种实现路径

BSim 结果表与 diff 视图中的 Apply 动作背后是成对的任务类。从源码结构看:

在 decompiler diff 视图中,同名动作则来自 CodeCompare 模块的一组以“Matched Tokens”命名的动作类,例如 ApplyCalleeSignatureWithDatatypesFromMatchedTokensAction.java(对匹配的被调用者应用含数据类型的签名);这些动作统一挂在 AbstractMatchedTokensAction.java 定义的菜单父项(MENU_PARENT = "Apply From Other Function")之下,与教程描述的右键菜单完全对应。

另外,BSim Search Results 窗口的 Function Matches 表(函数级结果表)的行上也有同名的批量 Apply 动作,可以对多行结果一次性应用;该表的 Status 列会记录哪些行的匹配已经被应用过。

6. 练习四:成对比较被调用者(Compare Matching Callees)

6.1 令牌匹配算法的边界

令牌匹配算法把程序 A 中的一处函数调用与程序 B 中的一处调用配对时,依据的是 CALL 指令的流入与流出数据流;它完全不处理被调用者(callee)的函数体。这意味着:调用点对上了,不代表两边调用的是“同一个函数”,需要显式下钻验证。

6.2 操作步骤

  1. 在反编译 diff 窗口的左侧面板点击一下,按 Ctrl-F 进入查找(对应源码中的 DecompilerDiffViewFindAction.java);
  2. 输入 FUN_ 作为搜索词,筛选出满足条件的匹配调用令牌:左侧窗口的被调用者是默认名(FUN_ 开头),而右侧窗口的被调用者是非默认名且不是外部函数;
  3. 对其中一个匹配的调用令牌右键执行 Compare Matching Callees 动作——这会为新打开一个比较窗口,比较两侧的被调用函数。该动作由 CompareFuncsFromMatchedTokensAction.java 实现(菜单文本即 "Compare Matching Callees"),继承自 AbstractMatchedCalleeTokensAction.java,后者负责从一对匹配令牌中提取左右两侧的 FunctionCallSite;
  4. 在被调用者的比较窗口中,把右侧函数的签名和完整数据类型应用到左侧函数;
  5. 回到调用者的 diff 视图,验证更新已反映出来(左侧调用点的名称/类型随之变化)——这正是 BSim 工作流的精髓:从调用点顺藤摸瓜,逐层命名、定型被调用者。

7. 练习五:对同一函数评估多个候选匹配

反编译 diff 窗口每个面板顶部都有一个下拉菜单,用于控制该面板显示哪个函数。当你需要评估“同一个查询函数 vs 多个候选匹配”时,这个下拉菜单非常有用。

操作步骤:

  1. 在 BSim Search Results 窗口中,右键点击某列表头,选择 Add/Remove Columns,启用 Matches 列(每行的匹配明细会以子行展开);
  2. postgres 中找到两个各恰好有两个匹配的函数
  3. 在 matches 表中选中对应的四行,执行 Compare Functions 动作;
  4. 在打开的比较窗口中,实验各面板顶部的下拉菜单,切换左右两侧分别显示哪个候选函数,横向对比多个候选匹配的质量。

8. 小结与延伸阅读

本篇教程节完成了 BSim 工作流中最关键的“人机协同”环节:BSim 负责召回候选匹配,反编译 diff 视图负责让差异可见(青色差异、粉色/淡紫/橙色聚焦状态、可切换的同步滚动),Apply 动作负责把已验证的信息安全地沉淀到被分析程序(名称 → 骨架签名 → 完整数据类型,风险逐级递增),Compare Matching Callees 则支持沿调用链递归下钻

接下来教程将讨论 Executable Results(可执行文件级结果表):如何从函数级匹配上升到可执行文件级匹配,见 From Matching Functions to Matching Executables。与之相邻的章节还有 Overview QueriesBSim FiltersScripting and Visualization

实现代码索引(供深入阅读):

登录后查看全文
热门项目推荐
相关项目推荐