pwndbg调试技巧
本文最后更新于 2022年5月24日 中午
官方文档在这里 ,主要总结常用的和容易遗忘的调试命令
网上的帮助文档: GDB break(b):设置断点 (biancheng.net)
常用技巧
在 GDB 里面用 magic 可查看后下断点在 hook 函数,之后继续运行即可断在 hook 函数之前,
1
2
3pwndbg> magic
pwndbg> b *__malloc_hook
pwndbg> c断点断在 IDA 中的地址再加随机偏移
1
pwndbg> b *$rebase(0x400123)
通过泄露双向列表 bins 中的 main_arena+0x88 的 fd 指针来得到 libc 基址,并查看:
1
2
3
4pwndbg> heap
pwndbg> p &main_arena
pwndbg> libc
pwndbg> p 0x1 - 0x2通过 realloc_hook 调整栈帧使 one_gadget 生效
点打在 hook 函数,一步步走到 exec_comm ,然后查看堆栈,查看 realloc 调整偏移
1
2
3
4
5
6pwndbg> b *__malloc_hook
pwndbg> c
...
pwndbg> x/20gx $rsp + 0x30
pwndbg> x/20i __libc_realloc
常用指令:
查看帮助信息: help
查看一些信息
1 |
|
查看调用栈 backtrace
GDB’s follow-fork-mode
parameter can be used to set whether to trace parent or child after fork() calls
执行指令
1 |
|
断点指令
涉及 IO, 断点打在 send 之前!这样在 IO 停止暂停后,GDB 就可以一步一步走程序 pause()就是 python 程序的断点
常用,给 0x123456 地址处的指令下断点 b *0x123456
给函数 fun_name 下断点,目标文件要保留符号才行 b fun_name
, 如b main
$rebase 在调试开PIE的程序的时候可以直接加上程序的随机地址 b *$rebase(0x123456)
给 file_name 的 15 行下断点,要有源码才行 b file_name:15
在程序当前停住的位置下 0x10 的位置下断点 b +0x10
pwndbg 不工作
条件断点,rdi 值为 5 的时候才断 break fun if $rdi==5
删除、禁用断点:
来查看断点编号 info break(简写: i b)
删除 5 号断点,直接 delete 不接数字删除所有 delete 5
禁用 5 号断点 disable 5
启用 5 号断点 enable 5
清除下面的所有断点clear
内存断点指令 watch:
0x123456 地址的数据改变的时候会断 watch 0x123456
变量 a 改变的时候会断 watch a
查看 watch 断点信息 info watchpoints
捕获断点 catch:
syscall 系统调用的时候断住 catch syscall
syscall 系统调用的时候断住,只断一次 tcatch syscall
info break
1 |
|
打印查看
巨牛逼的命令:查看泄露地址相关信息: xi 0x123
查看 libc 基地址:libc
栈的 offset:distance rbp rsp
垃圾数据生成:pi 0x110 > cyclic xxx+8
查看可读可写段:先start
一下 后vmmap
存储在堆中名为 arena 的空间的,直接用dq &main_arena 20
查看
查看bss
等数据段时,用dd 0x08048000
dq 0x08048000
查看
查看代码:u __malloc_hook 12
查看想要修改的位置附近是否有可能存在可以伪造的 chunk 内存地址:find_fake_fast &__malloc_hook
find_fake_fast &__free_hook
查看内存指令 x:
相当于自带了* or []
,自带地址解析
查看格式化的内存:x/20gx 0x7fabc
查看反汇编 1:x/20i $rip
查看反汇编 2:disassemble 符号
查看 hook:x &__malloc_hook
查看地址解释:x/20a 0x7fabc
打印指令 p(print):
打印 fun_name 的地址,需要保留符号 p fun_name
计算 0x10-0x08 的结果 p 0x10-0x08
地址相减: distance 0x1 0x2
查看变量 a 的地址 p &a
查看 0x123456 地址的值,注意和 x 指令的区别,x 指令查看地址的值不用星号 p *(0x123456)
打印 rdi 寄存器的值 p, 注意和 x 的区别,这只是显示 rdi 的值,而不是 rdi 指向的值 p $rdi
打印 rdi 指向的值,注意和 x 的区别,p 会打印出数字 p *($rdi)
修改和查找指令
修改数据指令 set
1 |
|
查找数据:
从当前位置向后查包含 rdi 的指令,返回若干 search rdi
寻找输入的字符串:search k3ppf0r3
帮助: search -h
堆操作指令(pwndbg 插件独有)
特别注意:
heap
命令显示的Addr
为 chunk 的首地址,含有 chunk 头部;Size
为内存中实际的内存值,往往最后面为 1,表示 prev_in_use
1 |
|
1 |
|
调试备忘:
gdb查看技巧:最左边的地址编号代表的是所在那一格子(由dd dq决定)中最右边,故小端序整体从右往左读(明确数据按照地址从小往大填入),对于两位16进制需一体从左往右顺序读 在gdb排版的最右边它将顺序调整好后输出ASCII码
gdb.attach()
1 gdb 如果断不住,就这样断:
1 |
|
2 左边是交互,右边是程序在内存中的运行情况,可以查看堆栈、寄存器情况等
3 继续运行, 在右边按c
或【ni
+ finish
】 ,在左边进行 IO 交互;
中断查看堆栈情况, 在右边按CTRL + c
查看 rop 链等运行时的信息
1 |
|
脚本预设
1 |
|
这样调试有一个缺点,那就是 gdb 在 attach 到程序之后,你要调试的断点可能已经早就过去了,来不及下断点,这就会导致 gdbscript 执行失败。 若第一种失败,那么可以这样做:
1 |
|
在 GDB 过程中的理解
堆栈形状
栈(高地址向低地址生长):
1 |
|
堆:
1 |
|
数据流
二进制程序以 read 函数接收,当 socket 用 send 发过去的字节流(明确以字节为单位,debug 显示的是人类的阅读顺序),以发送时的字节排列顺序填入虚拟内存中(从小到大填);
二进制程序以 write 函数打印,将内存中数据 (以从小到大的顺序) 忠实的打印出来
x 命令的打印阅读顺序:
<- __\ 0 <- __\ 4/8
用db addr
指令可验证
寄存中显示字符串存在 bug,它将字符串当成指针反过来解析了
去掉符号表的混淆调试
- 对应着 ida 的汇编代码看,ida 中地址与 gdb 中的地址后三位一样
terminal 优化:
~/.gdbinit 内容如下:
1 |
|
微信支付
支付宝支付
“如果你觉得这个博客对你有帮助,欢迎打赏!”