gdb调试入门指北
GDB安装及其插件控制
下载 GDB
,这个项目将 GDB
的插件放到了一个文件夹下,方便读取文件路径
$ git clone https://gitee.com/hongsofwing/GDB-Plugins.git
初始化,然后安装
$ cd pwndbg
$ git init
$ ./setup.sh
安装好之后就可以使用 GDB
了,并且可以切换插件
想用 peda:
$ echo "source ~/GDB-Plugins/peda/peda.py" > ~/.gdbinit
想用 peda-heap:
$ echo "source ~/GDB-Plugins/peda-heap/peda.py" > ~/.gdbinit
想用 gef:
$ echo "source ~/GDB-Plugins/gef/gef.py" > ~/.gdbinit
想用 pwndbg:
$ echo "source ~/GDB-Plugins/pwndbg/gdbinit.py" > ~/.gdbinit
调试命令
对二进制文件的操作
启动 gdb
调试程序
$ gdb ./pwn
下断点
pwndbg> b function_name
pwndbg> b *(&function_name+offset)
pwndbg> b *0x...
pwndbg> b *$rebase(0x...) # 当文件开了PIE时可用(其实做题的时候可以在 把地址随机
pwndbg> b 15 # 在第15行下断点,要有源码才行
pwndbg> b +0x10 # 在程序当前停住的位置+0x10处下断点
基础指令
pwndbg> q # 退出
pwndbg> r # 运行程序直到遇到断点
pwndbg> c # 继续执行程序直到遇到断点
pwndbg> n # 单步步过
pwndbg> s # 单步步入,与n相比,不同之处就是在有函数调用时会跟踪进去
pwndbg> fin # 跳出当前函数,执行到函数返回处
pwndbg> context # 重新打印页面信息
对寄存器的操作
直接查看所有寄存器的值
pwndbg> i registers
查看具体某个寄存器
pwndbg> i r esp
修改寄存器的值,这里举了一个修改栈顶指针的例子,其实这也是一个调试的小技巧,因为在 gdb 查看栈空间时,一般是不会显示栈顶指针上面的内存的数据的,假如想看的话可以上抬栈顶指针后再看栈
pwndbg> set $esp = 0x1
pwndbg> set $esp -= 0x10
设置地址随机化开关
在 gdb 中是可以设置地址随机化开关的,这其实对我们本地调试还是蛮有帮助的
pwndbg> set disable-randomization on # 开
pwndbg> set disable-randomization off # 关
pwndbg> show disable-randomization # 查
也可以通过设置 randomize_va_space
来停用该特性,0代表关闭 ASLR,默认是2
$ sudo echo 0 > /proc/sys/kernel/randomize_va_space
或者
$ sudo sysctl -w kernel.randomize_va_space=0
查看内存
x 命令使用规则如下:
格式:x/<n/u/f> <addr>
以 addr 为起始地址,返回 n 个单元的值,每个单元对应 u 个字节,输出格式是 f
如:x/3uh 0x54320表示:以地址 0x54320 作为起始地址,返回3个单元的值,每个单元有两个字节,输出格式为无符号十六进制。
也就是说返回了 2*3=6 个字节的数据。每两个字节作为一个单元输出,共输出3个单元,以十六进制输出。
第一个参数 n 需传入正整数,表示需要显示的内存单元的个数,即从当前地址向后显示 n 个内存单元的内容,一个内存单元的大小由第三个参数 u 来定义
第二个参数 u (unit)就是指以多少个字节作为一个内存单元,默认为4(b = 1 byte, h = 2 bytes, w = 4 bytes, g = 8 bytes
第三个参数 f 需一个地址,表示将 addr 指向的内存内容以什么格式输出(此处需特别注意输出整型数据的格式
s --> 字符串
x --> 按十六进制格式显示变量
d --> 按十进制格式显示变量
u --> 按十六进制格式显示无符号整型
o --> 按八进制格式显示变量
t --> 按二进制格式显示变量
a --> 按十六进制格式显示变量
c --> 按字符格式显示变量
f --> 按浮点数格式显示变量
第四个参数
<addr>
表示指向一片内存地址
例,x/32gx 0x....
表示以64位为一个单位来查看内存,g表示8字节
x/32wx 0x....
表示以32位为一个单位来查看内存,w表示4字节
当程序开了 PIE
pwndbg> x/8gx $rebase(0x....)
搜索内存中的指定数据,例如,在 0x400100 - 0x400200
这个地址范围查找 0x6161
peda> searchmem 0x61 0x400100 0x400200
查看指定指针附近的内存数据
pwndbg> display /20i $pc
直接查看变量、函数、结构体等的地址的(适用于可执行文件保留了符号的
pwndbg> p &__malloc_hook
pwndbg> p &main
pwndbg> p &main_arena # 查看 main_arena 结构体的起始地址
修改内存
假如要将 0xaaa
处的数据修改成 0xbbb
pwndbg> set {unsigned int}0xaaa = 0xbbb
vmmap
获取调试进程中的虚拟映射地址范围,及直接查看 libc 基址(在此例中,libc_base = 0x7ffff7a0d000
pwndbg> vmmap
pwndbg> vmmap libc
'''
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x7ffff7a0d000 0x7ffff7bcd000 r-xp 1c0000 0 /lib/x86_64-linux-gnu/libc-2.23.so
0x7ffff7bcd000 0x7ffff7dcd000 ---p 200000 1c0000 /lib/x86_64-linux-gnu/libc-2.23.so
0x7ffff7dcd000 0x7ffff7dd1000 r--p 4000 1c0000 /lib/x86_64-linux-gnu/libc-2.23.so
0x7ffff7dd1000 0x7ffff7dd3000 rw-p 2000 1c4000 /lib/x86_64-linux-gnu/libc-2.23.so
'''
查看文件信息
显示来自 /proc/pid 的各种信息
pwndbg> procinfo
从调试的 elf 文件获取头信息
pwndbg> elfheader
在exp中连gdb
方法一
往 exp
中在像调试的地方加入 raw_input()
,作用是能使 exp
运行到此处后暂停往下运行,如何开一个终端去运行这个 exp
另开一个终端窗口,执行 gdb attach xxxxxx
,通过 pid 号去连接这个进程(这个进程号在 exp 跑起来的时候是可见的
[+] Starting local process './pwn': pid 4874
在 gdb
里下断点(要断在输入点后的代码,在前面的话很难断下来),断下来之后输入 c
,然后再在运行 exp 的终端窗口按 enter
结束阻塞
方法二
这个是比较常用的方法
先开一个终端运行 python 代码,让程序跑起来
from pwn import*
context.log_level = 'debug'
p = process('./heap')
def debug(content):
gdb.attach(p, content)
pause()
debug("b *0x..")
p.interactive()
然后会弹出使用 gdb
连上了程序的新终端,此时程序是暂停运行了的,先在 gdb
中输入 c
让程序继续运行,然后就可以继续往程序运行的终端输入数据了。当你想看看自己写入的东西,或者说想看看当前的堆结构时,可以在 gdb 窗口输入 Ctrl + c
让程序暂停,此时 gdb 窗口会显示当前的内存状况和各个寄存器的值。
看堆空间
看 fastbins / unsortedbin / smallbins / largebins
pwndbg> fast
pwndbg> unsort
pwndbg> small
pwndbg> large
pwngdb 下还有
pwndbg> bins # 查看所有bins,有时候可能检测不出来堆块(也可能是我这的问题
pwndbg> vis_heap_chunks #可以直接看堆空间信息,挺好用的
pwndbg> parseheap
pwndbg> heap
pwndbg> heapinfo # 好用
gdb报错总结
报错一
关于在 exp 中使用 gdb.attach()
时一直卡在 waiting for debugger
这是 pwntools 的 bug,github 的 issue 中有解决方案:https://github.com/Gallopsled/pwntools/issues/1984
方法是更新 pwntools
版本至已经修复的版本
pip install -U pwntools==4.8.0b0
报错二
序
做 pwn 题时,经常会遇到题目使用的 libc
版本与自己的虚拟机版本不同的情况,虽然可以去装各个大版本的 wsl
来运行调试程序,但是未免还是会遇到一些小的版本需要亲手去 patch
查看本机 libc
版本:
ldd --version
使用 patchelf
修改链接后,会发现在使用 gdb
调试程序时,堆栈相关指令用不了了,记录一些遇到这种问题的解决方法
bins: This command only works with libc debug symbols.
They can probably be installed via the package manager of your choice.
See also: https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html
E.g. on Ubuntu/Debian you might need to do the following steps (for 64-bit and 32-bit binaries):
sudo apt-get install libc6-dbg
sudo dpkg --add-architecture i386
sudo apt-get install libc-dbg:i386
问题分析
libc.so
文件为包含代码的、加载进目标进程地址空间的文件,一般发布时不带调试信息,直接使用这种 .so
文件加载,在 pwndbg
中使用 bin
, heap
等命令将会提示无符号信息。在 gdb 里输入命令:
show debug-file-directory
可以发现单独调试信息文件的目录在 /usr/lib/debug
,libc
的 .dbg
文件与 .so
文件在存储时是独立的两个文件,其中 .dbg
文件中仅包含调试符号信息,不包含代码
解决方法
我的做法直接将上述的 debug 文件替换成对应 libc
的 .debug
文件,当然在做这一步前应该要确保之后能恢复到原来的环境,或许可以先给虚拟机打个快照
建议先将原本的 debug 文件拷贝一份到别的地方,方便在调试完之后恢复,这样就能省去打快照、恢复快照的功夫!!!
改 debug 文件的命令如下:
sudo rm -rf /usr/lib/debug
sudo cp -r ~/glibc-all-in-one/libs/2.31xxx/.debug/ /usr/lib/debug
恢复(这里我是将原本的 debug 文件放到了桌面):
sudo rm -rf /usr/lib/debug
sudo cp -r /home/wei/Desktop/debug/ /usr/lib/debug