GDB+Pwndbg增强调试体验
当调试缺乏调试符号的"剥离二进制"文件时,GDB会丧失大量功能:函数和变量名变成无意义的地址;设置断点需要从外部查找函数地址;查看结构化数据时需要手动解析内存转储。
今年夏天在Trail of Bits,我扩展了Pwndbg(由我的导师Dominik Czarnota维护的GDB插件),新增两项功能使剥离二进制调试体验接近IDE调试器水平。Pwndbg现在可以集成Binary Ninja增强调试智能,并支持Go结构体转储功能。
Binary Ninja集成
通过在Binary Ninja中安装XML-RPC服务器并从Pwndbg查询,实现了两者的集成。这使得Pwndbg能够访问Binary Ninja的分析数据库,用于同步符号、函数签名、栈变量偏移等信息,恢复大部分调试体验。
反编译功能直接从Binary Ninja获取语法标记而非文本序列化,支持完全语法高亮显示,可配置使用Binary Ninja的3种中间语言级别。反编译代码直接显示在Pwndbg上下文中,当前行高亮显示,与汇编视图一致。
还实现了在Binary Ninja中显示程序计数器(PC)寄存器箭头标记的功能,以及从Binary Ninja内部设置断点的功能,减少了两者间的切换操作。
最复杂的集成组件是栈变量名同步。Pwndbg中任何出现栈地址的地方(如寄存器视图、栈视图或函数参数预览),集成功能都会检查是否为Binary Ninja中的命名栈变量,并显示正确标签。该功能还会检查父栈帧,确保调用者的变量也能正确标记。
实现该功能的主要困难在于Binary Ninja仅提供相对于栈帧基址的偏移量,因此需要推导帧基址来计算绝对地址。虽然大多数架构(如x86)有帧指针寄存器指向帧基址,但编译器可以自由使用这些寄存器。幸运的是,Binary Ninja具有常量值传播功能,可以判断寄存器是否是帧基址的可预测偏移量。
Go语言调试
调试非C语言(有时甚至是C语言)编译的可执行文件时,复杂的内存布局使得值转储变得困难。例如转储Go切片需要一个命令转储指针和长度,另一个命令检查切片内容;而转储map对小map可能需要十余条命令,大map甚至需要数百条。
为此我创建了go-dump
命令。参考Go编译器源码,我实现了所有Go内置类型的转储功能,包括整数、字符串、复数、指针、切片、数组和map。内置类型使用Go原生语法表示,无需学习新语法。
go-dump
命令还能解析和转储任意嵌套类型,使得任何类型都只需一个命令即可完成转储。
解析Go运行时类型
虽然Go专用转储比手动内存转储更方便,但仍存在可用性问题。需要知道待转储值的完整类型,这在处理具有多字段或嵌套结构的结构体时尤其困难。此外,某些信息(如结构体字段名和用户定义类型名)在编译时没有影响,因此无法获知。
Go编译器为程序中使用的每个类型生成运行时类型对象(供reflect包使用),包含任意嵌套结构体的布局、类型名、大小、对齐等信息。这些类型对象也可以与对应类型的值匹配——接口值存储类型对象指针和数据指针,堆分配值在分配函数(通常是runtime.newobject)中传递其类型对象。
我编写了一个能够递归提取这些信息的解析器,通过go-type
命令显示给定地址的运行时类型信息。对于结构体,这些信息包括每个字段的类型、名称和偏移量。
这可以通过两种方式用于转储值。第一种更简单的方式仅适用于接口值,因为类型指针与数据指针一起存储,便于自动检索。这些值可以使用Go的any类型(空接口)或接口类型(非空接口)进行转储。转储时命令会自动检索和解析类型,无需输入任何类型信息即可完成转储。
第二种方式适用于所有值,但需要查找并指定值的类型指针。在许多情况下,只需查找传递给值分配函数的指针即可,但对于全局变量或分配位置难以查找的变量,可能需要猜测类型指针。不过这种方式通常仍比手动推导类型布局更容易,并且能够转储最复杂的类型。我在Go编译器(最大最复杂的开源Go代码库之一)的剥离构建版本上测试了几个大型结构体类型,都能毫无问题地完成转储。
回顾与展望
今年夏天,我增强了Pwndbg使其能与Binary Ninja集成访问丰富的调试信息,并添加了go-dump
命令用于转储Go值。所有这些功能都已在Pwndbg开发分支和最新版本(2024.08.29)中提供。
未来还有更多可以改进调试体验的工作。我的Binary Ninja集成采用模块化设计,便于未来添加更多反编译器支持。我认为完全支持Ghidra(当前集成仅同步反编译)将非常棒,因为Ghidra是免费开源的反编译器,使该功能对所有人开放。
在Go调试方面,可以改进对goroutine的显示和操作支持,这目前是Delve调试器(专用于Go调试)相对于GDB/Pwndbg的主要优势之一。例如Delve能够列出每个goroutine及其创建指令,并有命令在goroutine间切换。
致谢
在Trail of Bits工作的这个夏天是段绝妙的经历,我要感谢他们给我机会参与Pwndbg开发。特别感谢我的主管Dominik Czarnota,他对代码审查非常负责,为我的工作提供了反馈和想法;也感谢Pwndbg社区,他们在开发过程中解答了我所有问题。