Manticore GUI 轻松上手 - 符号执行引擎的交互式插件开发

本文详细介绍了如何为符号执行引擎Manticore开发跨反汇编器的GUI插件,包括基于gRPC的服务器架构设计、Ghidra插件实现过程,以及通过协议缓冲区实现前后端解耦的开发模式。

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 仓库的 server 目录下。)

快速分步指南

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

  1. 首先修改 MUI 服务器二进制文件。
    • 如果新的 RPC 或对现有消息的修改是必需的,编辑协议缓冲区(ManticoreServer.proto 文件)。
    • 通过运行 just generate 命令为服务器脚本生成服务接口代码。
    • 将功能和请求验证代码添加到服务器脚本(manticore_server.py),通过它可以直接与 Manticore API 交互。
    • 在适用的情况下,为新功能编写测试。
    • 通过运行 just build 命令构建 shiv 服务器可执行文件。
  2. 然后,对于每个前端插件:
    • 使用/复制新的服务器二进制文件;
    • 以插件使用的编程语言生成服务接口代码;以及
    • 进行任何相关的前端更改(并在适用的情况下,作为 MUI 的公共资源的一部分共享标准化数据)。

结论

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

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

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

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