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