简介

下载什么就不多说了,我们直接讲使用。

GDB算是这个世界上最强的动态调试器了,而PWNDBG是GDB的一个插件,可以更好地展示GDB得到的一些内容,让我们方便理解。

GDB可以使用的功能包括且不限于:

运行

步入、步过、步出、步止

断点(设置、删除、显示)

查看内存、寄存器、各种参数

设置内存、寄存器、各种参数(加载文件)

远程调试

其他辅助功能

当我们要调试某个程序的时候,要输入:

1
>gdb [文件名]

当我们忘记命令的时候,可以输入:

1
(gdb)/(pwndbg) help

以Intel方式查看汇编代码(似乎pwndbg默认Intel汇编代码格式):

1
(gdb)/(pwndbg) set disassembly-flavor intel

上述的查看反汇编格式,可以通过修改.gdbinit来完成。

1
2
vim ~/.gdbinit
(vim) disassembly-flavor intel

接下来 我们一点一点开始讲。

程序执行与控制

执行程序及设置管理断点

当我们要执行一个程序的时候,我们需要输入run(简写r)或者start

1
2
3
(gdb)/(pwndbg) run		#程序直接跑一边,如果有断点进入断点处
(gdb)/(pwndbg) r #同上
(gdb)/(pwndbg) start #从gdb认为的入口点开始步入运行

在这之后,我们一般而言会设置断点(break,简写b)来方便调试程序:

1
2
3
(gdb)/(pwndbg) b main
(gdb)/(pwndbg) break main
(gdb)/(pwndbg) b *0x000000FF #这是一个地址

b是用来断点的,或者说是标记断点且默认处于启动状态。我们也可以用disable或者disdisa来禁用全部断点或者部分断点。启用断点则是enable或者en。而当某个断点不需要的时候,我们可以使用delete或者d来删除断点。其中每个断点都会有一个b_id号码。不过删除和禁用分别有两种语法需要注意:

1
2
3
4
5
6
(gdb)/(pwndbg) d id			#删除某id的断点
(gdb)/(pwndbg) d 1 #删除id为1的断点
(gdb)/(pwndbg) dis b id #禁用某id的断点
(gdb)/(pwndbg) dis b 1 #禁用id为1的断点
(gdb)/(pwndbg) en b id #启用某id的断点
(gdb)/(pwndbg) en b 1 #启用id为1的断点

其实pwn方向一般不会设置太多断点啥的。不过逆向倒是要学这些。

如果想是一些开有PIE的保护的话,我们则需要一个*$rebase(偏移量)函数了。例如:

1
b *$rebase(0x0f0)

步操作

1
2
3
4
5
6
7
8
9
10
11
int sum(int a,int b){
int sumnum = a + b;
return sum_num;
}
int main(){
int a;
printf("1+2=");
a = sum(1,2);
printf("%d",a);
return 0;
}

在上面这个代码里面,我们可以用GDB做一些基础的程序运行操作:

步入(**step**** 简写为****s****):**用于单步执行程序,当遇到函数调用时,会进入该函数内部,逐行执行函数的实现代码。当我运行到第 6 行的 printf 的时候,步入会进入 printf 函数中,一步步走过 printf 函数的实现代码。

步过(**next**** 简写为****n****):**同样是单步执行,但遇到函数调用时,不会进入函数内部,而是直接执行完该函数后,进入下一行代码等待执行。当我运行到第 7 行的 printf 的时候,步过会不管 printf 函数里面如何如何,直接运行完 printf 函数进入到第 8 行 sum 函数待执行。

sn都可以在后面加一个<font style="color:rgba(0, 0, 0, 0.85);">instruction</font>参数,大概使用方法就是<font style="color:rgba(0, 0, 0, 0.85);">si</font><font style="color:rgba(0, 0, 0, 0.85);">ni</font>

<font style="color:rgba(0, 0, 0, 0.85);">si</font>(step instruction):执行下一步汇编指令,会进入汇编或 C 函数内部,无论函数是否有调试信息。

<font style="color:rgba(0, 0, 0, 0.85);">ni</font>(next instruction):执行下一步汇编指令,不会进入任何函数内部,直接执行完当前汇编指令并停在下一条。

简单来说,<font style="color:rgba(0, 0, 0, 0.85);">s/n</font>适用于 C 代码层面的调试,<font style="color:rgba(0, 0, 0, 0.85);">si/ni</font>适用于更底层的汇编层面调试,核心区别在于是否进入函数、以及进入后的定位级别(C 级 vs 汇编级)。上面两个指令,一般情况下都会用nni

步出(**finish**** 简写为****fin****):**适用于已进入某个函数内部,希望快速结束该函数的执行,直接返回到调用该函数的下一行代码。当我从第八行步入到 sum 函数之后,我感觉 sum 函数不需要看,想要直接从 sum 函数回到 main 函数的时候,步出可以快速运行完 sum 函数回到 main 函数的第九行待执行。

步止(**continue**/**until**** 简写为****c**/**u 行号****):**用于让程序持续运行,执行到断点停止。如果我想运行到第 10 行等待,那么我可以设置一个断点在第十行,这样执行步止操作的时候我就可以直接运行到第十行。

数据查看与修改

寄存器数据与汇编代码查看

在指令里面,分为_操作符号_和_被操作的对象_。例如下面的:

查看(info 简写为i):用于查看对象的值。例如查看断点则是i b

寄存器(registers 简写为r):一种被查看的对象。例如查看寄存器的值可以是i r

例如查看寄存器的值可以写成(gdb)(pwndbg) i r

反汇编(disassemble 函数名/$寄存器名(eip/rip)):将某一段函数或者rip/eip指针附近的机器码反编译为汇编。也是一种操作。

1
2
(gdb)/(pwndbg) disassemble main
(gdb)/(pwndbg) disassemble $rip

内存数据查看

除了上面的i r,我还可以通过print(缩写为p)来查看寄存器的位置(也就是此寄存器的地址),例如p eax

如果我要看地址的话,就要用x指令来查询。

例如有一个movzx eax,BYTE PTR[rbp-0x20]的操作,我现在想要查询rbp-0x20这个地址里面的值,就可以用:x/20b $rbp-0x20的方式。这样就是一个byte一个byte的查询20个byte。其中语法是这样的:

1
x/[查询行数][以多少位输出][显示格式] [查询的地址]

如此这般的有:

x/20gx(64 位十六进制,显示 20 项)
x/20wx(32 位)
x/20bx(8 位)
x/20bx(字符串显示)
x/20ix(反汇编指令显示)

内存数据修改

如果我要修改这里的值,那么就用set指令,其中语法为:

1
set ([地址值])=[修改值]	#这个地址值外面的的括号必须有

例如set (rbp - 0x10) = 41

如此这般的有:

set *0x7ffffffe050=19(改写内存值)
set $rax=19(改写寄存器值)
set(char [4])0x80477a4 = "Ace"(改写内存字符串)

远程调试