Manticore GUI插件开发:简化符号执行工具集成

本文详细介绍如何为符号执行引擎Manticore开发跨平台GUI插件,包括Ghidra插件实现、gRPC服务器架构设计、协议缓冲区通信及状态管理功能开发,显著降低符号执行技术的使用门槛。

Manticore GUI 开发指南

Trail of Bits 维护着 Manticore——一个能够分析智能合约和原生二进制文件的符号执行引擎。尽管符号执行是一种能够增强漏洞发现过程的强大技术,但它需要一定的基础领域知识,因此存在学习曲线。考虑到用户与此类引擎交互的多种方式,以及需要在反汇编器和终端或脚本编辑器之间频繁切换上下文,将符号执行集成到工作流中对初学者来说可能令人望而生畏。

Trail of Bits 寻求简化这一过程的方法之一是为 Manticore 开发图形用户界面(GUI),并将其嵌入到流行的交互式反汇编器中。去年夏天,前实习生 Alan Chang 开发了第一个此类接口——Binary Ninja 的 Manticore 用户界面(MUI)插件。我们发现,将 Manticore 直接与交互式反汇编器配对,为漏洞研究人员提供了更便捷的方式来实际使用(并受益于)符号执行。因此,在我于 Trail of Bits 的冬季和夏季实习期间,我的目标是通过为 Ghidra 开发 MUI 插件并构建基础设施来帮助扩展 MUI 生态系统,使这些插件更易于使用、维护和开发。

基于 gRPC 服务器的 MUI 插件架构

Ghidra 插件

我们认为鼓励更多人使用 MUI 插件的最直接方法是简单地为更多种类的反汇编器开发 MUI 插件!因此,我在冬季实习期间开发了 Ghidra 版本的 MUI 插件;我选择 Ghidra 主要是因为它是流行的,并且与商业工具 Binary Ninja 不同,它是免费和开源的。此外,Trail of Bits 的一些内部项目已经在使用 Ghidra,因此我有充足的机会探索 Ghidra 插件开发。最后,通过开发 Ghidra 插件(一个用 Java 而不是 Python 编写的插件),我们可以开发一个不依赖于单一编程语言的解决方案,获得可以指导未来插件开发的见解。

这个初始的 Ghidra 插件尽可能地模仿了现有的 Binary Ninja 插件。虽然熟悉 Java Swing 和 Ghidra 的小部件花了一些时间,但一旦开始,简单地模仿现有的视觉组件和用户界面是一项相当简单的任务。

然而,由于 Ghidra 插件是用 Java 编写的,它不能依赖 Manticore Python 包或直接调用 Manticore 的 Python API。我们解决这一挑战的方法是使用一个名为 shiv 的工具,将 Manticore 库及其所有依赖项无缝捆绑到一个 Python zipapp 中。这样,我们可以创建一个“包含电池”的 Manticore 二进制文件,然后将 Binary Ninja 插件与 Manticore API 的交互转换为适当的命令行参数。然后,我们将这个二进制文件放在 Ghidra 的 os 目录中特定于平台的相关子目录中,这有助于跨平台支持。

在冬季实习结束时,我能够为 Ghidra 插件添加额外功能,例如除了具有专用输入字段的参数外,还能够指定任意的 Manticore 参数,并在同一 Ghidra 会话中支持多个 Manticore 实例。然而,这揭示了一个额外的问题。

功能对等和跨反汇编器开发

很快变得明显的是,如果我们希望扩展 MUI 项目以支持更多反汇编器,我们的插件开发方法将不可持续。对于每个新的 MUI 功能,我们首先必须确定如何实现该功能,考虑插件与 Manticore 交互的方式(例如,通过直接调用 Manticore API 或通过 Manticore 的命令行界面选项)。此外,跨插件共享的某些前端信息(例如,固定的描述字符串或合理的默认选项)必须在每个实现中重复和标准化。

为了解决这个问题,我在夏季开发了一个用于 MUI 的集中式远程过程调用(RPC)服务器二进制文件。该服务器处理与功能齐全的 Manticore Python API 的所有交互,并通过协议缓冲区中定义的单个 RPC 处理 MUI 功能。我们选择使用 gRPC 作为我们的 RPC 框架,因为它的性能、广泛采用以及对跨多种编程语言的代码生成的强大支持。因此,未来的 MUI 插件可以轻松包含并依赖于它们自己的 gRPC 生成的代码。

服务器是用 Python 编写的,使其能够访问 Manticore Python API 的全部功能,但被捆绑到一个可以在任何语言中调用的 shiv 二进制文件中。这促进了一种新的客户端-服务器架构,允许开发者实现任何后端 Manticore 功能和测试只需一次。前端反汇编器插件的开发者只需几行简单的代码就可以向服务器发出 RPC 请求,这意味着他们在单个插件上的工作几乎可以完全专注于前端/UI 更改。

为了减轻处理固定字符串和其他在插件之间相同的前端信息的“琐事”,我们可以将这些数据存储在 JSON 文件中,这些文件与 MUI 插件发布一起打包,并在启动时加载。通过这种方式,我们可以标准化数据,例如用于启动 Manticore 执行的运行对话框的字段、字段描述符和默认值。

演示:为 MUI 开发功能

让我们看看为 MUI 开发功能的过程。假设我们想要在 Manticore 实例运行时启用手动状态管理。具体来说,我们希望能够做到以下两点:

  1. 暂停一个状态并在稍后恢复它,如果我们在未来实现像执行跟踪这样的功能,这将非常有用。
  2. 自行决定终止一个状态。这样,如果一个状态绕过了避免钩子或陷入无限循环,我们将能够放弃它。

首先,我们将在协议缓冲区文件中定义一个新的 RPC 及其消息格式。服务器将接收状态的数字 ID、我们正在处理的 Manticore 实例,以及一个 StateAction 枚举,指示是恢复、暂停还是终止状态。

我们还需要更新一个现有的消息——ManticoreStateList。MUI 插件有状态列表,显示所有状态和每个状态的状态;这些列表通过 GetStateList RPC 更新。因为用户将“暂停”状态视为与预先存在的状态状态不同是有益的,我们将在 RPC 的响应消息中添加一个新的 paused_states 字段,其中将包含用户暂停的状态列表。

这样,我们可以继续生成 Python 服务接口代码和 mypy 类型存根!在我夏季实习期间,我使用命令运行器 just 来为开发者抽象这项工作,因此我们可以运行 just generate 命令来…只需生成所需的代码!

现在我们可以继续实现后端功能。这段代码包含在一个单独的 Servicer 类中,其中每个方法代表一个单独的 RPC。

我们将从验证 RPC 请求数据开始。虽然 gRPC 生成的代码可以检查提供给它的字段是否类型正确,但它不能强制使用任何字段或验证字段格式良好。因此,我们将编写自己的检查来断言请求的有效性;如果请求无效,我们还将设置一个错误代码和错误详细信息返回给请求者。

最后,我们可以通过直接访问 Manticore 的 Python API 来实现状态暂停和状态终止功能。这种直接访问提供了比通过命令行选项与 Manticore 交互更多的控制,例如,使我们能够创建一个虚拟的繁忙状态或放弃特定状态。如果一切顺利,前端插件将收到一个空的响应,该响应将填充默认的 OK 状态代码。

我们方法的一个额外好处是,我们可以为我们的 RPC 编写测试,而无需处理生成服务器和连接到它。相反,我们可以简单地直接调用 Servicer 的方法,并传入一个新的 Context 对象,我们可以在以后检查错误代码。

完成后,我们可以使用我们值得信赖的命令运行器来“just build”shiv 二进制文件,这将给我们一个新鲜的服务器二进制文件,我们可以在我们的插件中使用。

首先,我们需要生成 Java 服务接口代码,该代码将基于更新的协议缓冲区。gRPC 维护一个 Java 库 grpc-java,它将为我们处理这个问题。

然后,我们必须编写实际执行 RPC 的函数。在我们的插件中,我们将所有这样的“连接器”函数封装在一个文件中。在 Ghidra 插件中,编写连接器函数仅涉及三个步骤。首先,我们创建一个 StreamObserver 对象来异步处理 RPC 响应。在这种情况下,我们只需要实现处理错误情况的行为,因为成功的 ControlState RPC 的“结果”将通过 GetStateList RPC 对用户可用。然后我们构建 ControlStateRequest 对象,根据需要填充字段。最后,我们通过 gRPC 生成的 ManticoreServerStub 暴露的方法实际执行 RPC,该存根方便地为我们处理所有与服务器的通信。

唯一剩下的事情是进行适当的 UI 更改!对于这个功能,我们可以简单地在状态列表中的状态上右键单击打开上下文菜单,然后用调用 controlState 方法的操作填充状态!

随着 Manticore 功能由 gRPC 服务器处理,我们必须在 Ghidra 和 Binary Ninja 插件的“前端”实现的 UI 更改相当简单。

演示中讨论的“假设”状态管理功能实际上已经实现,并且现在是 MUI 的一部分!如果你有兴趣查看所有更改和提交,请查看服务器和 Ghidra 插件的拉取请求。(这些拉取请求是在代码位置重构之前进行的。代码现在位于主 Manticore 存储库的服务器目录下。)

快速分步指南

总结一下,向 MUI 添加新功能的步骤如下:

  1. 从修改 MUI 服务器二进制文件开始。
  2. 如果需要新的 RPC 或修改现有消息,请编辑协议缓冲区(ManticoreServer.proto 文件)。
  3. 通过运行 just generate 命令生成服务器脚本的服务接口代码。
  4. 将功能和请求验证代码添加到服务器脚本(manticore_server.py),通过它可以直接与 Manticore API 交互。
  5. 在适用的情况下,为新功能编写测试。
  6. 通过运行 just build 命令构建 shiv 服务器可执行文件。

然后,对于每个前端插件:

  1. 使用/复制新的服务器二进制文件;
  2. 生成插件所用编程语言的服务接口代码;以及
  3. 进行任何相关的前端更改(并在适用的情况下,作为 MUI 公共资源的一部分共享标准化数据)。

结论

我在 Trail of Bits 的实习非常充实,我为 MUI 项目的进展感到自豪。在我前几次尝试在夺旗挑战中应用符号执行时,我曾与看似黑盒的性质作斗争,我相信 MUI 项目将使符号执行对初学者更易访问。此外,与成熟的交互式反汇编器的集成将使符号执行成为漏洞发现过程中更自然的一部分。

我也对 MUI 开发体验的进展感到满意。插件开发并不是最顺畅的体验,部分原因是面向用户的插件安装过程不是为快速原型设计或增量更改而设计的。因此,在冬季实习期间从头开始构建 Ghidra 插件是一次令人心惊胆战的经历。除了熟悉 Java 和掌握一个新的插件开发框架(两者都有自己的学习曲线)之外,我花了很多时间思考添加某个功能是否甚至可能!借助新的 MUI 服务器架构,我现在能够更高效地利用那段时间,只思考新功能如何有助于漏洞发现过程。

除了使开发过程更省时之外,新的 MUI 服务器架构还提供了以开发为中心的功能,使其更加顺畅。这些包括 just 脚本、Gradle 方法和单元测试。在以前的项目中,我将此类功能的实现视为琐事;即使在我的实习期间,我也是在导师 Eric Kilmer 的一些督促和指导后才开始将它们添加到新服务器中。然而,那项工作在我的开发速度、代码质量以及调试时的挫败感方面产生了天壤之别!

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计