提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
GDB作为Linux平台下的动态调试工具,对安全测试人员来说和Windows下的Ollydebug、ImmunityDebug实质来说差不多,对于不太习惯命令行的同学来说,建议先在Windows平台上熟悉一个动态调试器操作的基本逻辑和功能再来Linux平台上学习使用GDB*(尽管使用习惯上的差距确实让人会觉得很痛苦) *。
vi ~/.gdbinit 可以设置一些gdb内容
一、如何开始调试
gbd和其他调试器一样,有多种开始的方式,这里我们重点讲gdb直接运行、attach这两种,调试coredump文件的这种目前应该还不会用到。
- 直接运行:如果我们需要从头开始对一个程序进行调试,可以在程序路径下使用gdb file_name 的方式进入调试界面
- Attach:很多时候我们需要调试一个正在运行中的程序,就需要使用attach。
首先我们需要确认程序的PID,有2中方式:
1. 在路径下使用pidof filename 查看
2. 使用ps -ef |grep filename 查看
然后就可以使用gdb attach pid 调试程序
二、常用指令
命令名称 命令缩写 命令说明
run r 运行一个待调试的程序
continue c 让暂停的程序继续运行
next n 运行到下一行
step s 单步执行,遇到函数会进入
until u 运行到指定行停下来
finish fi 结束当前调用函数,回到上一层调用函数处
return return 结束当前调用函数并返回指定值,到上一层函数调用处
jump j 将当前程序执行流跳转到指定行或地址
print p 打印变量或寄存器值
backtrace bt 查看当前线程的调用堆栈
frame f 切换到当前调用线程的指定堆栈
thread thread 切换到指定线程
break b 添加断点
tbreak tb 添加临时断点
delete d 删除断点
enable enable 启用某个断点
disable disable 禁用某个断点
watch watch 监视某一个变量或内存地址的值是否发生变化
list l 显示源码
info i 查看断点 / 线程等信息
ptype ptype 查看变量类型
disassemble dis 查看汇编代码
set args set args 设置程序启动命令行参数
show args show args 查看设置的命令行参数
接下来以这段代码为例,
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
char sh[]="/bin/sh";
int init_func(){
setvbuf(stdin,0,2,0);
setvbuf(stdout,0,2,0);
setvbuf(stderr,0,2,0);
return 0;
}
int func_test(char *cmd){
system(cmd);
return 0;
}
int main(){
char a[8] = {};
char b[8] = {};
//char a[1] = {'b'};
puts("input:");
gets(a);
printf(a);
if(b[0]=='a'){
func_test(sh);
}
return 0;
}
1.断点与开始
断点
break命令(简写为b)用于添加断点,可以使用以下几种方式添加断点:
break FunctionName,在函数的入口处添加一个断点;
break LineNo,在当前文件行号为LineNo处添加断点;需要-g
break FileName:LineNo,在FileName文件行号为LineNo处添加一个断点;需要-g
break FileName:FunctionName,在FileName文件的FunctionName函数的入口处添加断点;
break -/+offset,在当前程序暂停位置的前/后 offset 行处下断点;
break *address
break … if cond,下条件断点;
这里补充说明下,在学习gdb时最好在使用gcc编译程序时加上-g参数带入调试信息,这样在调试时可以关联到源码的相关信息(不过在一些需要逆向的场景下本来也不会有源码,也可以选择提前适应通过标记函数地址的方式命名函数,这个过程也可以借助静态分析工具,比如IDA pro完成)
断点的查看、禁用/启用、删除
info b
delete b_id
disable b_id
enable b_id
i b中 Enb字段代表当前断点的激活状态,当为n时,该断点并不会生效
开始运行
run 与 start
run命令直接从头运行程序直至结束或遇到断点,而start命令则首先在main函数的起点设置一个断点,然后开始执行,便于从程序的起始处进行调试。下面就能看到,我们并没有对main下断点,当使用start时会自动停在main函数入口
当我们再使用run时,会先提示是否从头开始执行,选择y后我们看到程序停在了断点4,也就是puts函数中
继续运行
continue
run 和 start都是从头开始执行程序,那如何快速执行到下一个断点尼?continue
next 单步步过 step 单步步入
ni si汇编语言的单步步过和单步步入,这里有Windows动态调试经验的应该知道汇编的步过逻辑就是rip指向下个指令,遇到call往里走还是要靠si
finish 跳出函数:当我们步入到puts函数中不想继续执行的时候可以直接用finish函数跳出来回到main函数中
看点什么
disassemble
基本用法
直接使用,可以查看当前运行指令附近的汇编代码
查看目标地址附近的汇编代码
查看目标函数的汇编代码
与源码对照
info
info能查看的东西很多,暂时先写一些可能会比较常用的,有机会的话再试着遍历一遍吧
info args 显示当前函数的入参
info locals 显示局部变量
info b 显示断点信息
info reg 显示当前寄存器状态
info symbol 显示符号所在文件及其归属的section
info address 反查地址属于哪个符号
监视器 watch
打印print
内存查看X
X指令的f\u\t参数本质上是一种开关,当上一次输入确定后,下次只需要输入要查看的对象(addr)和显示的内存单元数量即可
这里只写几种常见搭配,有机会再遍历一下
x/[n][fu][t]addr
x/20i $rip 显示rip以下的20行汇编指令
x/i 0x5555555552e7:显示0x5555555552e7这个地址上的汇编指令
x/20b $rbp-0x10 显示[rbp-0x10]后20个字节的内容
x/20b 0x5555555552e7:显示0x5555555552e7指针指向的地址后20个字节的值
x/[n][fu][t]addr
n: (可选)要显示的内存单元数量。默认单位是byte
f: (可选)显示格式,如x(十六进制)、d(十进制)、u(无符号)、o(八进制)、t(二进制),a(地址),c(字符),s(字符串)等。
u: (可选)单位大小,如b(字节)、h(半字,即2字节)、w(字,通常4字节,在x86-64上是8字节)、g(巨型字,通常是8字节)。
t: (可选)当格式为s(字符串)时,限制输出的字符串长度。
addr: 要查看的内存地址。
用set做点手脚
set *addr = value
set *((unsigned int$ebp)) = value