CTF-All-In-One

2.3.1 GDB

gdb 的组成架构

img

gdb 基本工作原理

gdb 通过系统调用 ptrace 来接管一个进程的执行。ptrace 系统调用提供了一种方法使得父进程可以观察和控制其它进程的执行,检查和改变其核心映像以及寄存器。它主要用来实现断点调试和系统调用跟踪。ptrace 系统调用的原型如下:

#include <sys/ptrace.h>
long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);

gdb 的三种调试方式

注意,在你将 gdb attach 到一个进程时,可能会出现这样的问题:

gdb-peda$ attach 9091
Attaching to process 9091
ptrace: Operation not permitted.

这是因为开启了内核参数 ptrace_scope

$ cat /proc/sys/kernel/yama/ptrace_scope
1

1 表示 True,此时普通用户进程是不能对其他进程进行 attach 操作的,当然你可以用 root 权限启动 gdb,但最好的办法还是关掉它:

# echo 0 > /proc/sys/kernel/yama/ptrace_scope

断点的实现

断点的功能是通过内核信号实现的,在 x86 架构上,内核向某个地址打入断点,实际上就是往该地址写入断点指令 INT 3,即 0xCC。目标程序运行到这条指令之后会触发 SIGTRAP 信号,gdb 捕获这个信号,并根据目标程序当前停止的位置查询 gdb 维护的断点链表,若发现在该地址确实存在断点,则可判定为断点命中。

gdb 基本操作

使用 -tui 选项可以将代码显示在一个漂亮的交互式窗口中。

break – b

info

disable – dis

禁用断点,参数使用空格分隔。不带参数时禁用所有断点。

enable

启用断点,参数使用空格分隔。不带参数时启用所有断点。

breakpointsdisable 中的描述。

clear

在指定行或函数处清除断点。参数可以是行号,函数名称或 * 跟一个地址。

delete – d

删除断点。参数使用空格分隔。不带参数时删除所有断点。

tbreak

设置临时断点。参数形式同 break 一样。当第一次命中时被删除。

watch

为表达式设置观察点。每当一个表达式的值改变时,观察点就会停止执行您的程序。

另外 rwatch 表示在访问时停止,awatch 表示在访问和改变时都停止。

step – s

单步执行程序,直到到达不同的源码行。

reverse-step

反向步进程序,直到到达另一个源码行的开头。

next – n

单步执行程序,执行完子程序调用。

step 不同,如果当前的源代码行调用子程序,则此命令不会进入子程序,而是继续执行,将其视为单个源代码行。

reverse-next

反向步进程序,执行完子程序调用。

如果要执行的源代码行调用子程序,则此命令不会进入子程序,调用被视为一个指令。

return

您可以使用 return 命令取消函数调用的执行。如果你给出一个表达式参数,它的值被用作函数的返回值。

finish – fin

执行直到选定的栈帧返回。

until – u

执行程序直到大于当前栈帧或当前栈帧中的指定位置(与 break 命令相同的参数)的源码行。此命令常用于通过一个循环,以避免单步执行。

continue – c

在信号或断点之后,继续运行被调试的程序。

如果从断点开始,可以使用数字 N 作为参数,这意味着将该断点的忽略计数设置为 N - 1(以便断点在第 N 次到达之前不会中断)。

求表达式 expr 的值并打印。可访问的变量是所选栈帧的词法环境,以及范围为全局或整个文件的所有变量。

x

检查内存。

display

每次程序停止时打印表达式 expr 的值。

fmt 用于指定显示格式。对于格式 is,或者包括单位大小或单位数量,将表达式 addr 添加为每次程序停止时要检查的内存地址。

disassemble – disas

反汇编命令。

undisplay

取消某些表达式在程序停止时自动显示。参数是表达式的编号(使用 info display 查询编号)。不带参数表示取消所有自动显示表达式。

disable display

禁用某些表达式在程序停止时自动显示。禁用的显示项目被再次启用。参数是表达式的编号(使用 info display 查询编号)。不带参数表示禁用所有自动显示表达式。

enable display

启用某些表达式在程序停止时自动显示。参数是重新显示的表达式的编号(使用 info display 查询编号)。不带参数表示启用所有自动显示表达式。

help – h

打印命令列表。

attach

挂接到 GDB 之外的进程或文件。将进程 ID 或设备文件作为参数。

run – r

启动被调试的程序。可以直接指定参数,也可以用 set args 设置(启动所需的)参数。还允许使用 >, <, 或 >> 进行输入和输出重定向。

甚至可以运行一个脚本,如:

run `python2 -c 'print "A"*100'`

backtrace – bt

打印整个栈的回溯。

注意:使用 gdb 调试时,会自动关闭 ASLR,所以可能每次看到的栈地址都不变。

ptype

打印类型 TYPE 的定义。

参数可以是由 typedef 定义的类型名, 或者 struct STRUCT-TAG 或者 class CLASS-NAME 或者 union UNION-TAG 或者 enum ENUM-TAG

set follow-fork-mode

当程序 fork 出一个子进程的时候,gdb 默认会追踪父进程(set follow-fork-mode parent),但也可以使用命令 set follow-fork-mode child 让其追踪子进程。

另外,如果想要同时追踪父进程和子进程,可以使用命令 set detach-on-fork off(默认为on),这样就可以同时调试父子进程,在调试其中一个进程时,另一个进程被挂起。如果想让父子进程同时运行,可以使用 set schedule-multiple on(默认为off)。

但如果程序是使用 exec 来启动了一个新的程序,可以使用 set follow-exec-mode new(默认为same) 来新建一个 inferior 给新程序,而父进程的 inferior 仍然保留。

thread apply all bt

打印出所有线程的堆栈信息。

generate-core-file

将调试中的进程生成内核转储文件。

directory – dir

设置查找源文件的路径。

或者使用 gdb 的 -d 参数,例如:gdb a.out -d /search/code/

gdb-peda

当 gdb 启动时,它会在当前用户的主目录中寻找一个名为 .gdbinit 的文件;如果该文件存在,则 gdb 就执行该文件中的所有命令。通常,该文件用于简单的配置命令。但是 .gdbinit 的配置十分繁琐,因此对 gdb 的扩展通常用插件的方式来实现,通过 python 的脚本可以很方便的实现需要的功能。

PEDA(Python Exploit Development Assistance for GDB)是一个强大的 gdb 插件。它提供了高亮显示反汇编代码、寄存器、内存信息等人性化的功能。同时,PEDA 还有一些实用的新命令,比如 checksec 可以查看程序开启了哪些安全机制等等。

安装

安装 peda 需要的软件包:

$ sudo apt-get install nasm micro-inetd
$ sudo apt-get install libc6-dbg vim ssh

安装 peda:

$ git clone https://github.com/longld/peda.git ~/peda
$ echo "source ~/peda/peda.py" >> ~/.gdbinit
$ echo "DONE! debug your program with gdb and enjoy"

如果系统为 Arch Linux,则可以直接安装:

$ yaourt -S peda

peda命令

使用 PEDA 和 Python 编写 gdb 脚本

更多资料

http://ropshell.com/peda/

GEF/pwndbg

除了 PEDA 外还有一些优秀的 gdb 增强工具,特别是增加了一些查看堆的命令,可以看情况选用。

参考资料