GDB调试工具

0x00 介绍

gdb是用来debug c/c++程序的工具,用来检测程序的逻辑错误 
要想使用必须在gcc编译的时候加上-g选项 
加了-g之后会生成调试表 

GDB的三种调试方式如下:

  1. 运行并调试一个新进程:
    • 运行GDB, 并通过命令行或 file命令指定目标程序;
    • 输入run命令, GDB将进行以下操作:
      • 通过 fork() 系统调用创建一个子进程;
      • 在新创建的子进程中执行操作ptrace(PTRACE_TRACEME, 0, 0, 0);
      • 在子进程中通过execv()系统调用加载用户指定的可执行文件。
  2. attach 并调试一个已经运行的进程:
    • 用户确定需要进行调试的进程PID
    • 运行GDB, 输入 attach <pid>, 或者gdb -p <pid>
    • GDB会对其执行操作ptrace(PTRACE_ATTACH, 0, 0, 0);
  3. 远程调试目标机上新创建的进程:
    • gdb 运行在调试机上,gdbserver 运行在被调试机上,两者之间的通信数据格式为GDB串行协议(Remote Serial Protocol)定义;
    • RSP协议数据基本格式为 $..........#xx;
    • gdbserver的启动方式相当于第一种GDB调试方式.

note:
GDB attach到一个进程时,可能会出现权限问题

1
2
3
gef➤  attach 1
Attaching to process 1
ptrace: Operation not permitted.

这是由于内核参数 ptrace_scope=1 被设置了,因此普通用户没有权限对其他进程进行 attach 操作,而 root 权限启动GDB不会受权限所限制,但更好的解决方案是关闭它:

1
2
3
4
╰──➤ cat /proc/sys/kernel/yama/ptrace_scope
1
╰──➤ sudo echo 0 > /proc/sys/kernel/yama/ptrace_scope # 临时
╰──➤ sudo echo "kernel.yama.ptrace_scope = 0" > /etc/sysctl.d/10-ptrace.conf # 永久

0x01 gdb简单使用

e.g. 调试hello程序的步骤

  1. 编译生成ELF文件 并加上-g 选项

    1
    gcc hello.c -o hello -g
  2. 使用gdb进行调试

    1
    2
    3
    gdb hello
    ## 或者使用tui界面的gdb
    gdb -tui hello

note: 若编译时忘记加-g 选项而且已经在gdb中打开了一个程序,可以重新编译之后在用file 命令加载可执行程序

gdb中常用命令如下表

命令 简写 作用
help h 按模块列出命令类
help class 查看某一类型的具体命令
list l 查看代码,可跟行号或函数名
quit q 退出gdb
run r 全速运行程序
run arg[1] arg[2] 调试时命令行传参 run aa bb cc
start 单步执行,运行程序,停在第一行执行语句
next n 逐过程执行代码,跳到下一行代码继续执行
nexti ni 逐过程执行机器指令,跳到下一行代码继续执行
step s 逐语句执行代码,遇见函数,跳到函数内执行
stepi si 逐语句执行机器指令,遇见函数,跳到函数内执行
backtrace bt 查看函数的调用栈帧和层级关系
info i 查看GDB内部局部变量的的数值 info breakpoints //(可以简写成 i b)  查看断点信息表
info locals //查看局部变量
frame f 切换函数的栈帧
finish fin 跳出当前循环(跳出当前栈)
util 运行到指定行结束
return  结束当前函数,返回到函数调用点, 并决定返回值 return 233
set 设置变量的值 set var n=100
set args 设置main函数的参数值(在start、run之前) set args aa bb cc
print p 打印变量和地址 p *数组名 @10 // 列出数组前10个元素
p 数组名[n] @m// 从下标n开始向后打印m个元素
print type ptype 查看变量的类型
break b 设置断点,可根据行号和函数名 b 20 if i = 5 设置条件断点
break * 0x1234 在地址0x1234处设置断点
delete d 删除断点 d breakpoints NUM
display 设置观察变量 display testvar
undisplay 取消观察变量 undisplay 观察变量的编号
continue c 继续全速运行剩下的代码
enable breakpoints 启用断点
disable breakpoints 禁用断点
x 查看内存 x /20xw 显示20个单元,16进制,4字节每单元
watch 被设置观察点的变量发生修改时,打印显示
core file ulimit -c 1024 开启core文件, 调试时 gdb a.out core
tui enable/disable 打开关闭tui界面
disas 反汇编当前函数 disas foo 反汇编函数foo
info watch i watch 显示观察点
info frame 有关栈帧的信息
info registers 所有寄存器的值

其他一些常用命令

  • set disassembly-flavor intel 转换为intel格式的汇编
  • set disassembly-flavor att 转换为att格式的汇编
  • set solib-search-path <path> 设置动态库路径
  • set solib-absolute-prefix <path> 设置绝对路径系统动态库路径
  • layout asm 显示汇编窗口
  • layout regs 显示寄存器窗口
  • layout src 显示源码窗口

多线程调试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
## 列出所有线程
info threads

## 切换线程(<num>根据上面命令列出的线程id选择)
thread <num>

## 查看所有线程的堆栈
thread apply all bt
thread name [name]#为当前线程设置命名
thread applay [command] # 为当前线程应用对应的命令

## 查找符合描述的线程,查找条件一般按照线程命名查找,
## Will display thread ids whose name, target ID, or extra info matches REGEXP
thread find

set scheduler-locking [ off | on | step]
##off 不锁定任何线程, on表示只有当前被调试的线程继续执行, step 表示单步执行的时候,只有当前线程执行
show scheduler-locking # 查看设置

多进程调试

1
2
3
4
5
6
7
8
9
10
11
12
13
## 查看当前所有进程
info inferiors
## 切换进程
inferiors <num>

## 跟踪父进程还是子进程
set follow-fork-mode [child | parent]

## 调试某个进程时,是否断开对其他进程的调试
set detach-on-fork [on | off]

## 当gdb调用函数的时候,接收到信号的处理方式(默认停止栈帧)
set unwindonsignal on

反向调试(gdb7.0以上)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
## 开启记录
record
## 关闭记录
record stop

## 反向单步
reverse-step/reverse-stepi

## 反向逐过程
reverse-next/reverse-nexti

## 反向运行到调用当前函数的地方
reverse-finish

## 设置程序运行方向,能够像正常调试方式一样反向调试
set exec-direction [forward | reverse]
## e.g. 设置了reverse就不用上面的reverse前缀了, n/s直接反向

断点的储存与恢复

这个文件本质上存的是命令列表
同理,也可以将一些命令写入文件,用source命令加载
类似批处理

1
2
3
4
## 存储断点到文件
save breakpoints mybreak.list
## 从文件加载断点
source mybreak.list

可以把一些设置写在 ~/.gdbinit 中,以便让gdb下次启动时自动加载你的设置

0x02 gdb增强

gef 是一组用于 X86、ARM、MIPS、PowerPCSPARC 的命令集,可让 GDB 为漏洞利用开发人员提供更多更酷炫的功能。它主要由漏洞利用开发人员和逆向工程师使用,使用 Python API 为 GDB 提供附加功能,以协助动态分析和漏洞利用开发过程.

gef 的主要功能包括:

  • 一个GDB 脚本(用python写的,有1w多行代码)
  • 完全与操作系统无关,无依赖
  • 提供种类繁多的命令来彻底改变您在 GDB 中的体验。
  • 完整的python3 支持
  • 提供GDB python API 来自定义插件

安装

gef 是一个开箱即用的工具,可以通过下面命令通过脚本安装

1
bash -c "$(curl -fsSL http://gef.blah.cat/sh)"

也可以通过git安装

1
2
git clone https://github.com/hugsy/gef.git
echo source `pwd`/gef/gef.py >> ~/.gdbinit

为了更好的体验,可以安装一些依赖(可选)

1
pip3 install capstone unicorn keystone-engine ropper

只需确保您使用的pip是与编译 GDB 时所用的 Python 版本相对应的版本。

用户写基于gef的插件

GEF 的构建还为外部脚本提供了坚实的基础。该存储库gef-extras是一个开放的存储库,任何人都可以通过 GEFAPI 自由提交自己的命令来扩展 GDB

1
2
3
4
5
6
7
8
9
## via the install script
$ bash -c "$(wget https://github.com/hugsy/gef/raw/master/scripts/gef-extras.sh -O -)"

## manually
## clone the repo
$ https://github.com/hugsy/gef-extras.git
## specify gef to load this directory
$ gdb -ex 'gef config gef.extra_plugins_dir "/path/to/gef-extras/scripts"' -ex 'gef save' -ex quit
[+] Configuration saved

gef使用小技巧

  1. 将输出重定向到一个tty

若是从tmux会话中启动的gdb可以用下面命令 自动分屏指定tty

1
(gdb) gef tmux-setup

更加通用的办法是 手工指定tty
首先开一个新终端并查看其tty

1
2
$ tty
/dev/pts/0

然后在配置gefcontext.redirect 选项为上一步的输出结果

1
gef➤ gef config context.redirect /dev/pts/0

RERFERENCES

[1] https://hugsy.github.io/gef/