GDB调试命令与技巧
Step & stepi
step和stepi(即s和si)就是单步步入,进入函数内部,比如说在某一行发生了函数调用,step/stepi就会进入函数体内部,把函数体执行一遍,再返回执行下一条指令。同理,step是在源码层面的操作指令,stepi是在机器指令层面的。
step 汇编调试系统函数的汇编指令时,会一个 step 就跳几条汇编指令,因为 step 是基于源码层面的。
x 查询指令
[collapse title=”展开查看详情” status=”false”]
x
指令用于查看内存地址的值,x
命令语法:
1 | x/<n/f/u> <target addr> |
- n :输出个数
- f :显示格式。 在 pwn 题中通常都是使用 16 进制查看。
- x :十六进制(常用)
- d :十进制格式
- u :十六进制格式显示无符号整型
- o :八进制格式量
- t :二进制格式
- c :字符格式
- f :浮点数格式
- u :查看字节单元数。在 pwn 题中,根据题目是 32 位还是 64 位灵活切换 w 和 g
- b :单字节(8 位,1 个字节)
- h :双字节(16 位,2 个字节)
- w :字(32位,4 个字节)(常用)
- g :大字(64 位,8 个字节)(常用)
可能常用形式:
x /20xg addr
查 64 位程序内存信息
x /20xw addr
查 32 位程序内存信息
[/collapse]
查看调用中的堆栈
where
:显示调用堆栈frame
:显示调用堆栈顶部up
:向调用堆栈底部移动down
:向调用堆栈顶部移动
GDB 调试 PIE 程序
[collapse title=”展开查看详情” status=”false”]
方法一:
安装 pwndbg 插件,然后这样下断点:0x相对基址偏移就是 IDA 显示的三位数
1 | b *$rebase(0x相对基址偏移) |
方法二:
在 /proc 目录中,每个进程都会在此目录下新建一个以进程 id 为名的文件夹,其中存储着进程的动态链接和地址的信息。
在每个进程的 map_file 文件夹中,存储着各个地址段的动态链接文件(地址)。
查找当前进程 pid 为 6158 :
1 | ps -aux|grep 程序名 |
知道 pid 之后有两种方式获取 elf 机制
方式一:
进入目录 /proc/{pid}/map_files 查询动态链接文件(地址):
1 | ls |
第一个 0x557d7b317000 为 elf 基地址。
真实地址为:0x557d7b317000 + 偏移(ida显示的三位地址)
方式二:
使用 /usr/bin 目录下 pmap 程序。
pmap + pid命令可以将该进程的地址信息和相应地址段的权限打印出。
1 | /usr/bin/pmap 6158 |
方法三:
IDA 远程调试
与 Pwntools 联动
上面方法都是在命令行启动的 GDB 情况下,如果编写成 exp 脚本调试方法看[这里](# Pwntools 调试 PIE 程序)
[/collapse]
Pwntools 调试 PIE 程序
[collapse title=”展开查看详情” status=”false”]
传入明确地址
也就是在 gdb.attach(p,"b *真实地址")
这样传参。这个真实地址寻址原理在 [GDB 调试 PIE 程序](# GDB 调试 PIE 程序) 提及,在 exp 中就用 os 库执行命令获取并传参。
1 | # 脚本摘选自网络,未找到原作者 |
base = int(os.popen(“pmap {}| awk ‘{{print $1}}’”.format(io.pid)).readlines()[1], 16)
获取 elf 基地址,根据传入偏移量,给gdb.attach()
传入指令:b *当前断点真实地址
。如果 pie 参数为假,传入指令:b *$rebase(偏移量)
,偏移量是 ida 显示三位地址。
传入偏移量
和传入明确地址 DEBUG 函数 pie 为假情况差不多。但有帖子和我也试了一下,开启 pie 传入 gdb 命令 b *$rebase(偏移量)
还是能的。
在你想打断点的前面调用 gdb.attach(p,"b *$rebase(偏移量)")
,一般能停下来,但是会停在断点的前面,然后自己手动 step 单步过去咯。
1 | from pwn import * |
[/collapse]
单步跟还是会跳过一些指令
[collapse title=”展开查看详情” status=”false”]
比如说 call xxx@plt
调用 plt 时,step 不会跟进 plt 函数中,改用 si 。
[/collapse]
脚本中 GDB 放置位置
[collapse title=”展开查看详情” status=”false”]
调用 gdb 有一定时延
首先明确的在脚本中调用 gdb 并不是准确停在调用这一行,而是会执行到脚本的 下一(或几)行,举个例子:
1 | p.sendline('string1') |
log 中会看到 string2 已经输入了,gdb 才真正进入。
1 |
|
假如想将上面这个程序,停在刚输入完而未进入 printf ,基于上面提及的特点,在最后一次输入前调用 gdb 。
1 | from pwn import * |
总结:就是在最后一条输入命令前打断点。因为有时延,所以相邻命令也被执行。这种方法应该是用于大部分栈题目。堆题目因为菜单停顿,就直接调用咯。
[/collapse]
GDB 没有调试信息
[collapse title=”展开查看详情” status=”false”]
程序已完成
顾名思义,程序已经退出,gdb 当然找不到进程信息。这种情况的几种可能:栈题目或者线性的程序(执行完一系列操作就退出)退出了;gdb 调用后面没有其他操作。
程序退出情况
提前调用 gdb 咯。且不要再脚本末尾调用 gdb ,就是避免[gdb 后无其他操作](# gdb 后无其他操作)。
gdb 后无其他操作
可能在堆调试比较常见,就在 gdb.attach() 后面加一行无关操作,比如说调用题目输出函数等,或者直接 sleep(1) ,等 gdb 获取到进程信息就行。
[/collapse]
gdb 查询各内存段权限
在有些题目 ida 分析的内存段权限好像有错误,可以用 gdb 验证。(eg:get_started_3dsctf_2016)
使用命令:
1 | maintenance info sections |
例子:
1 | maintenance info sections |
如果需要在其他窗口调试,需要获取 PID ,具体请看这里:GDB检查内存权限