Dragon Dance 是一个 Ghidra 插件,用于可视化和操作二进制代码覆盖率数据。覆盖率数据可以从多个覆盖率源导入。目前该插件支持 Dynamorio 和 Intel Pin 二进制插桩工具。Dynamorio 有自己的覆盖率收集模块,名为 "drcov"。Intel Pin 没有提供内置的覆盖率收集器模块。为了处理这种缺乏模块的情况,我不得不为 Intel Pin 编写自己的覆盖率收集模块。所以我为 Intel Pin 编写了一个名为 ddph(Dragon Dance Pin Helper)的覆盖率收集模块。你可以使用它。你可以通过这个 链接 查看 ddph 的源代码。如果你懒得自己编译,可以使用我提供的适用于 Windows、macOS 和 Linux 的预编译二进制文件。

Dragon Dance 可以在同一会话中导入并使用多个覆盖率数据。(它还支持多会话,但目前 GUI 无法使用该功能)。您可以快速切换这些数据,或者对它们进行交集、差集、对称差集或求和运算。
Dragon Dance 允许您查看已执行指令的强度。这样您可以了解哪些指令执行得有多频繁。您还可以在函数图窗口中查看覆盖率可视化。

Dragon Dance 还支持自己的脚本系统。

它为您提供了一种灵活的方式来操作覆盖率数据。您可以对它们执行加载、删除、显示、交集、差集、对称差集、求和等操作。以下部分将包含脚本系统和 API。按 Alt + Enter 键执行脚本。
内置函数是内部覆盖率操作的实现,为脚本系统提供接口。内置函数可以返回覆盖率对象变量,也可以不返回。内置函数可能具有别名。
它们接受 Built-in Arg 作为参数。参数可以是可变长度的。
Built-in Arg 是一个用于保存不同类型值的引用。Built-in Args 按从左到右的顺序传递。Built-in Arg 可以保存以下值类型:
变量仅负责保存覆盖率对象。它们可以由内置函数加载。它们可以作为参数(Built-in Arg)传递给内置函数。
有两种类型的覆盖率对象:
物理覆盖率对象 和 逻辑覆盖率对象
物理覆盖率对象指向直接从覆盖率文件加载的覆盖率对象。它们在 GUI 上的覆盖率表中可见。因此,您可以通过 GUI 操作与它们交互。
逻辑覆盖率对象指向经过内置函数处理并返回结果的覆盖率对象。它们在 GUI 上不可见,但可以存在于变量中,直到被销毁。
覆盖率对象由 Variable 对象自动维护,适用于这两种类型的覆盖率对象。例如:``` cov1 = load("firstcoverage.out") cov2 = load("secondcov.out")
cov1 = diff(cov1,cov2)
在此示例中,`cov1` 和 `cov2` 是变量,且两个变量都具有物理覆盖率对象。`diff` 内置函数接收这两个变量,并将返回值赋给 `cov1` 变量。该覆盖操作会将结果覆盖率对象存入变量,但不会删除原覆盖率对象,因为它是物理覆盖率对象,其覆盖率数据将保留在会话以及 GUI 表格中。
让我们按如下方式思考前面的示例;```
cov1 = load("first.out")
cov2 = load("second.out")
cov3 = load("third.out")
rvar = sum(cov1,cov2,cov3)
rvar = diff(rvar, cov2)
在这个例子中,三个物理覆盖率变量进入求和操作,求和操作返回逻辑结果覆盖率对象。然后,diff操作接受一个逻辑变量和一个物理变量,并覆盖名为rvar的变量。
在这种情况下,结果将被设置到rvar中,其之前的覆盖率值立即被销毁。因为这是一个逻辑对象,应该被删除以防止对象泄漏。如果你想要销毁一个包含物理覆盖率对象的变量,你必须调用内置的discard函数。所有内置函数将在下面详细介绍。
你可以使用嵌套的内置函数调用来编写复杂的脚本,例如可以这样写:
cres = diff(intersect(a, load("another.log"), c, d), sum(e,f) )
你不必逐行编写逻辑。
以下API文档及其行为在最终版本发布前可能会发生变化。
clear()
| 属性 | 描述 |
|---|---|
| 返回值 | 无 |
| 最小参数数量 | 0 |
| 最大参数数量 | 0 |
| 描述 | 此内置函数清除当前可视化的覆盖率,并将活动覆盖率设置为空。 |
| 别名 | 无 |
cwd( String : workingDirectory )
| 属性 | 描述 |
|---|---|
| 返回值 | 无 |
| 最小参数数量 | 1 |
diff( Variable : var1, var2, ..... varN )
| 属性 | 描述 |
|---|---|
| 返回值 | Variable |
| 最小参数数量 | 2 |
| 最大参数数量 | 不限 |
| 描述 | 对给定可变长度的变量应用差集操作。并返回结果覆盖率变量。 |
| 别名 | 无 |
discard( Variable : var1, var2, ..... varN )
| 属性 | 描述 |
|---|---|
| 返回值 | 无 |
| 最小参数数量 | 1 |
| 最大参数数量 | 不限 |
| 描述 | 销毁变量,无论物理还是逻辑。它将首先销毁覆盖率对象,然后从变量列表中取消注册变量名称。此调用后,所有给定的变量变为未定义。 |
| 别名 |
distinct( Variable : var1, var2, ..... varN )
| 属性 | 描述 |
|---|---|
| 返回值 | Variable |
| 最小参数数量 | 2 |
| 最大参数数量 | 不限 |
| 描述 | 对给定可变长度的变量应用异或(xor)操作。并返回结果覆盖率变量。 |
| 别名 | xor |
goto( Integer : offset )
| 属性 | 描述 |
|---|---|
| 返回值 | 无 |
| 最小参数数量 | 1 |
| 最大参数数量 | 1 |
| 描述 | 通过给定偏移定位当前地址选择。实际地址值通过将偏移值加到镜像基址计算得出。 |
| 别名 | 无 |
import( String : filePathOrCoverageName )
| 属性 | 描述 |
|---|---|
| 返回值 | Variable |
| 最小参数数量 | 1 |
| 最大参数数量 | 1 |
intersect( Variable : var1, var2, ..... varN )
| 属性 | 描述 |
|---|---|
| 返回值 | Variable |
| 最小参数数量 | 2 |
| 最大参数数量 | 不限 |
| 描述 | 对给定可变长度的变量应用交集操作。并返回结果覆盖率变量。 |
| 别名 | and |
show( Variable : var )
| 属性 | 描述 |
|---|---|
| 返回值 | 无 |
| 最小参数数量 | 1 |
| 最大参数数量 | 1 |
| 描述 | 可视化给定的覆盖率变量。如果存在一个活动可视化的覆盖率对象且是逻辑的,该函数立即销毁之前的覆盖率对象并显示给定的对象。 |
| 别名 |
sum( Variable : var1, var2, ..... varN )
| 属性 | 描述 |
|---|---|
| 返回值 | Variable |
| 最小参数数量 | 1 |
| 最大参数数量 | 不限 |
| 描述 | 对给定可变长度的变量应用求和操作。并返回结果覆盖率变量。 |
| 别名 | or, union |
修复
Dragon Dance 可以在导入覆盖率数据文件时尝试修复 ghidra 中可能的误分析情况。在某些二进制文件上,由于编译器产生意外代码,ghidra 无法反编译函数的指令。Dragon Dance 检查已加载的镜像和覆盖率数据的完整性。如果它们彼此有效,并且地址属于可执行段但缺少指令反编译,则插件会询问是否修复。然后它尝试通过反编译原始段来修复。

在插件的未来版本中,可能包含更多的修复或针对镜像的变通方法。
安装非常简单。
启动 Ghidra。
点击“文件”菜单,然后选择“安装扩展...”
点击窗口右上角的绿色加号图标
选择插件 zip 包,然后点击确定。
从列表中选择 dragondance
点击确定并重启 ghidra
在插件安装后,首次将二进制文件加载到 Ghidra 时,Ghidra 应该会询问你是否要配置新安装的插件。
如果点击“是”按钮,DragonDance 插件应该立即出现。
如果点击“否”按钮,你需要手动激活。
要手动激活,
点击“文件”菜单,然后从反汇编窗口(CodeBrowser)中选择“配置”
点击配置工具窗口右上角的小插头图标。
从插件列表中找到 DragonDance 项,启用其复选框,然后点击确定。
Dragon Dance 窗口应该出现。
激活后,你应该能够在“窗口”菜单中看到 Dragon Dance 项。
如前所述,Dragon Dance 可以从 Dynamorio 和 Intel Pin 导入覆盖率数据。(目前来说)。实际上,这些是通用的二进制插桩工具。你需要使用适当的模块与它们一起收集覆盖率数据。Dynamorio 有自己的覆盖率模块,称为 drcov。你可以使用该内置模块收集覆盖率。
使用 Dynamorio
你可以使用以下命令从 Dynamorio 收集覆盖率数据:
drrun -t drcov -logdir [覆盖率输出目录路径] -- [要检查的可执行文件路径] [可执行文件参数]
输出将以 drcov.[可执行文件名].[ID].proc.log 格式放置到给定目录中。
使用 Intel Pin
如前所述,Intel Pin 不提供任何内置的覆盖率收集模块。你必须使用自定义的 pin 模块。幸运的是,我制作了自己的模块来从 Pin 收集覆盖率。这为我们带来了一些优势。我可以在需要时扩展它,或添加额外功能、选项。
虽然后续版本可能也能工作,但目前仅支持 Intel PIN 3.7。这些版本未立即在 Intel PIN 页面上提供,此处提供直接下载链接:
你可以访问 ddph 的源代码此处。我将分享适用于 Windows、macOS 和 Linux 的二进制文件。你也可以使用其构建 shell 脚本构建自己的二进制文件。
要使用 Intel Pin 收集覆盖率数据,请使用以下命令:
pin -t ddph.[so,dylib,dll] [ddph 选项] -- [要检查的可执行文件路径] [可执行文件参数]
ddph 有一些收集选项。
-o:使用此选项可以指定覆盖率输出文件名。(默认值: ddph.out)
-l:可以指定操作日志文件名。如果将此选项传递为"no",ddph 将不执行日志记录操作。(默认值: ddph.log)
-p:捕获详细级别。此选项可以是 reduced 或 high。high 级别逐个捕获所有指令并构建预处理执行块。这将提供更密集的覆盖率输出,但比 reduced 慢。reduced 级别使用 pin 的 trace 块,因此比 high 级别快得多。但这不会在覆盖率输出上造成巨大差异。如果你不想做特定的事情,请考虑使用 reduced 级别。(默认值: reduced)
对于 macOS 用户: 从 macOS 10.11(OS X El Capitan)开始,操作系统附带了一个名为 System Integrity Protection SIP 的安全层。这会阻止用户模式进程尝试对另一个进程进行注入或修改,即使你在 root 权限下运行。
要克服这种阻止,你必须禁用它。请按照以下步骤操作。
重启 macOS
在启动过程中,按住 Command + R 键并保持按下。
系统最终会进入恢复模式
从“实用工具”部分打开终端
输入 csrutil status 并按回车。你应该看到 SIP 已启用。
输入 csrutil disable 并按回车。
再次输入 csrutil status 以确认是否已禁用。然后重启操作系统,让其正常启动。现在你可以使用二进制插桩工具了。



首先,下载 ghidra(最新版本,目前为 9.1.2)和 dragondance``` $ wget https://ghidra-sre.org/ghidra_9.1.2_PUBLIC_20200212.zip $ wget https://github.com/0ffffffffh/dragondance/archive/master.zip $ unzip ghidra_9.1.2_PUBLIC_20200212.zip $ unzip master.zip
接下来安装 gradle 和 jdk```
$ sudo apt install openjdk-11-jdk
$ wget https://services.gradle.org/distributions/gradle-5.2.1-bin.zip
$ sudo unzip -d /opt/gradle gradle-5.2.1-bin.zip
创建新的配置文件``` $ sudo vi /etc/profile.d/gradle.sh
并将以下内容添加到每个后续登录的PATH中,以添加gradle。```
export GRADLE_HOME=/opt/gradle/gradle-5.2.1
export PATH=${GRADLE_HOME}/bin:${PATH}
立即执行,无需注销``` $ source /etc/profile.d/gradle.sh
我们现在可以继续构建dragondance```
$ cd dragondance-master/
$ gradle -PGHIDRA_INSTALL_DIR=/home/ubuntu/ghidra_9.1.2_PUBLIC
> Task :buildExtension
Created ghidra_9.1.2_PUBLIC_20200506_dragondance-master.zip in /home/ubuntu/dragondance-master/dist
BUILD SUCCESSFUL in 29s
5 actionable tasks: 5 executed
ubuntu@ubuntu:~/dragondance-master$
你可能需要调整路径到你下载Ghidra的位置。现在你可以在 dragondance-master/dist 中找到构建好的扩展。
命令提示符风格逐行执行脚本。
上下文变更感知
基于函数(例程)的覆盖可视化
执行流感知
脚本中更多内置功能
自有覆盖数据库格式,以便更快保存和加载,并在会话中保留最新更改。
伪代码着色。(Ghidra 未为此提供 API。因此我必须研究 Ghidra 的源代码,找出实现方法或变通方案。)
UI 增强
Oğuz Kartal (@0ffffffffh)
| 最大参数数量 |
| 1 |
| 描述 | 使用给定路径设置当前工作目录。在cwd之后的所有没有绝对路径的导入调用,覆盖率文件将在当前工作目录中搜索。 |
| 别名 | 无 |
| del |
| 描述 | 从物理覆盖率文件导入覆盖率数据。接受相对或绝对路径。或者之前物理加载的覆盖率名称。如果给定路径是绝对路径,import直接从该路径加载。否则,它会在当前工作目录下查找加载。在这两种情况下,import都会检查该覆盖率数据是否已经加载(通过路径)。如果已加载,返回缓存的覆盖率变量。或者如果给定值是一个物理覆盖率的名称,它从其会话中查找覆盖率映射,如果存在则返回覆盖率对象。 |
| 别名 | get, load |
| 无 |