前言
之前我写了 GDB Dashboard 教程 ,文末提到了 pwndbg,今天详细讲述一下。
pwndbg 是一个支持 GDB1 或者 LLDB2 的插件,便于汇编级别的软件开发者、逆向工程师等3。本文主要探讨其对于 CSAPP Labs 的作用,以及相应的自定义配置。
传统的 GDB 或 LLDB 很难逆向工程,总是要手动输入大量指令,查看内存信息,例如输入 x/30gx $rsp 来查看 rsp 指向的栈。在当今时代,这并不高效。Pwndbg 是基于 Python 的模组,可以加载进 GDB 或者 LLDB,进而方便查看内存细节。

预备知识
了解 GDB,掌握 Linux 环境与命令行基础,了解 Git 使用方法。本文环境都基于 Ubuntu。
下载与安装
虽然官方文档提供了许多方法4,但是在我这总遇到一些环境问题,可能因为我使用的是 Ubuntu20.04,版本较老。后面发现克隆 Github 仓库并运行脚本更方便。
首先,克隆官方的 Github 仓库。如果较慢,可以考虑换源、设置代理等,这里不展开。
| |
如果你的系统版本较新,可以尝试直接安装:
| |
但是,我会出现下面的报错:
| |
原因在于我的系统 python 较老。所以,首先切换到更早的版本,然后再运行安装脚本:
| |
这样就能正确安装。查看用户目录下的配置文件,即 ~/.gdbinit,发现它载入了环境,其中 <path to your github repo> 取决于你运行 git clone 时候的位置:
| |
为了方便每一次调试都自动启用 pwndbg,可以取消第二行的注释:
| |
参考上一篇博客 GDB Dashboard 教程 ,这里的 ~/.config/gdb/gdbinit 应该设置了安全的路径:
| |
或者是全部目录:
| |
这样,在相应的目录下,直接运行 gdb <program>,其中 program 为一个程序,就能进入 pwndbg 页面。
| |

pwndbg+tmux 自定义配置
虽然说 Tabby 也自带分割终端的功能,但是它在分割后会自动降低非当前窗口的亮度,看起来有点奇怪。于是我改用 tmux5 了。
简单来说,Ubuntu 系统下 tmux 可以通过 apt 来安装:
| |
然后,创建新的 tmux 会话:
| |
此处 bomb 可以是任意名字。这样会自动进入当前窗口。要想退出,可以按 Control + B 键,松开,再按 D 键。
为了再次进入当前会话,输入:
| |
其中 bomb 是你刚刚取的名字。
分割窗口,按 Control + B 键,松开,再按 % 键(Shift + 5),你会发现窗口分成了左右两块。并且该分割线是可以用鼠标拖动的,至少我在 Tabby 与 VSCode 终端中都可以。
为了将调试信息都放到右边的窗口,我让 AI 帮我写了一段脚本,然后粘贴到 ~/.gdbinit 文件中:
| |
这个脚本利用 Python 定义了 setupwin 函数,输入的参数是另一个要展示的窗口,然后将各个部分都放在另一个窗口上。
用鼠标选择 tmux 中右侧窗口,输入 tty 打印信息,得到 /dev/pts/<num>,其中 num 数字因人而异。
然后,可以在系统 ~/.gdbinit 中的末尾,或者当前工作目录的 .gdbinit 中,尾部加入这一行:
| |
其中 <num> 是刚刚输出的数字。这样每次运行都会自动执行 setupwin 函数。
然后为程序打上断点并运行,就得到了这样的画面:

左侧可以输入命令,右侧有各种调试信息,包括寄存器、反汇编、栈等等。
pwndbg 调试信息讲解
初次看到 pwndbg 的调试信息可能有点蒙,感觉很多字。这正是因为其中的信息丰富。
legend
首先,最上方显示的 LEGEND,代表了不同颜色对应的内存区域,解释如下:
- STACK:栈,存储局部变量与函数调用。
- HEAP:堆,动态声明的内存。
- CODE:代码区,储存可执行指令。
- DATA:数据区,存储初始化的数据,如全局变量。
- RODATA:只读代码区。
- RWX:Readable, Writable, and eXecutable. 可以读、写、执行的代码区域。
registers
该区域存储着寄存器的信息。以上面的pwndbg+tmux分屏配置图片为例,rbx 寄存器信息如下:
| |
这当中内存是黄色高亮的,说明是栈区域。rbx 中存放的是 0x7fffffffdb78,这是一个地址,其指向的位置存放的是 0x7fffffffdf4a,这又是一个指针,指向一个字符串,其内容为 /home/ywr/dsc/learn/csapp/bomblab/bomb。
不信的话,可以利用 gdb 命令打印出来:
| |
同理,下面的 rdi 与 rsi 存储的都是 0x4023d0 ,是红色,代表是代码区域。打印其中内容:
| |
发现其指向的是一个字符串。
disarm
该区域存储着反汇编信息。注意,我这里显示的是 Intel 格式,而不是 AT&T 格式,后者是 CSAPP 的呈现方式。
为了方便,可以设置:
| |
将这一行写入 ~/.gdbinit 的文件末尾。这样就不用每次执行了。
反汇编区域最左侧是指令地址,然后是相对于当前函数的地址,接着是操作符与操作数。
在反汇编区域最右侧,会有该指令的效果,并且默认是开启模拟的,也就是会模拟之后的效果。但是我发现,这有时候会导致之前语句的结果是基于当前指令的,有些麻烦。所以我关掉了:
| |
你也会发现,对于跳转语句,pwndbg 不同与一般的 GDB,它会在到达这一行后,显示接下来跳转的行,方便你进行观察。
stack
这里是 rsp 指向的栈区域,在 CSAPP 中也会频繁用到。其中的箭头,也都说明了其值以及引用关系。
以下面的结果为例:
| |
记住栈顶拓展是从大到小的。当前 rsp 指向 0x7fffffffda00,其中内容为 0x50,也可以解释为字符 P 的 ASCII 码6。
栈的下一个元素的地址为 0x7fffffffda08,其中存放着 0x6037d0,这是输入字符串的指针,字符串为 1 2 4 7 11 16。
可以发现,pwndbg 确实相当方便,智能地解析了地址,免去了一遍遍输入指令查看。
backtrace
该区域记录了调用函数的信息,例如
| |
说明当前处在 phase_defused 函数中,该函数在 main 中调用,而 main 又在__libc_start_main 中被调用。
我的自定义配置
为了方便查看更多信息,我还增大了反汇编与栈区域的行数,以及设置了 show-compact-regs 为 on 。
我的具体配置如下:
| |
将最后几行写入 ~/.gdbinit 即可。
总结
本文讲述了如何在 Ubuntu 上安装并配置 pwndbg,希望能有所启发。
参考资料
GNU Debugger. Wikipedia. https://en.wikipedia.org/wiki/GNU_Debugger ↩︎
LLDB. https://lldb.llvm.org/ ↩︎
pwndbg. Github. https://github.com/pwndbg/pwndbg ↩︎
Setup. pwndbg documentation. https://pwndbg.re/pwndbg/latest/setup/ ↩︎
tmux Wiki. https://github.com/tmux/tmux ↩︎
ascii. Wikipedia. https://en.wikipedia.org/wiki/ASCII ↩︎
