gdb_tips
前言
学长推荐了个有趣的github项目,就此来回顾学习一下gdb的调试技巧:100-gdb-tips/src/index.md at master · hellogcc/100-gdb-tips
信息显示
显示gdb版本信息
1 | gdb --version |
显示gdb版权相关信息
1 | show/info copying #gdb权限信息 |
调用gdb时不显示提示信息
Invoking GDB (Debugging with GDB)
1 | gdb -q(--quiet) |
为了每次使用gdb时不显示提示信息
可以在.bashrc里面设置gdb
1 | alias gdb="gdb -q" #~/.bashrc 添加 |
输出信息多时不会暂停输出
Screen Size (Debugging with GDB)
还没遇到输出多的调试信息而暂停输出的情况,不过记下来
1 | set pagination off |
pwndbg设置输出重定向
pwndbg使用链接:https://github.com/pwndbg/pwndbg/blob/dev/FEATURES.md#context
我们可以通过 set context-output /path/to/file 输出重定向到文件(包括其它tty),同时保留其它输出。
1 | set context-output /dev/pts/x |
快捷更新~/.gdbinit
1 |
|
函数
列出函数名
demo
1 |
|
usage
1 | info functions |
调试技巧
- start:程序从头开始重新启动
- run(r):执行程序,遇到断点将停止,等待用户输入。
- continue (c ):继续运行程序,直到遇到下一个断点或错误。
- next( n):单步跟踪程序,当遇到函数调用时,也不进入此函数体;此命令同 step 的主要区别是,step 遇到用户自定义的函数,将步进到函数中去运行,而 next 则直接调用函数,不会进入到函数体内。
- nexti(ni):同next,单步一条机器指令,不进入函数。
- step (s):单步调试如果有函数调用,则进入函数;与命令n不同,n是不进入调用的函数的
- stepi(si):同step,单步一条机器指令。
- until:用于退出循环体了。
- until N: 执行运行程序,直到当前行前面已经运行了N行代码。
- finish(fini): 退出正在执行的函数。
- quit(q):退出gdb。
- info frame(i frame/f):打印函数堆栈帧信息。
- info registers(i registers/r):查看函数寄存器信息。
- disasseble func:查看函数反汇编代码。
- up N:向上切换函数栈帧。
- down N:向下切换函数栈帧。
- set debug entry-values 1:打印尾调用堆栈帧信息。
- frame N:选择函数堆栈帧(N为层数),也可以使用frame addr
n & ni 的区别
前者用于源代码级别的调试,后者用于机器码级别的调试。
(个人调试时喜欢使用ni/si指令😍)
执行n后,跳过了sum = x+y对应的汇编语句
而执行ni后,会执行下一条机器码
s & si 的区别
s:在n的基础上能够步入函数
si:在ni的基础上能够步入函数
直接执行函数
前提是需要加载调试符号!!!
如果未编译程序时没有包含调试符号(-g
选项),GDB 无法识别函数的签名和类型信息。
没有设置-g选项时
func
函数是以非调试符号(Non-debugging symbols)的形式存在
手动加载调试信息
指明类型进行调试
1 | p (void)func(1, 5) |
断点
调试技巧
- 匿名空间设置断点 b Foo::foo b (anonymous namespace)::bar
- 程序地址上打断点 b *address
- 在程序入口处打断点 去除符号表后可以 先start 执行到__libc_start_main找main函数的调用地址
- 在文件行号上打断点 文件编译时-g添加了调试信息,那么可以直接 b N进行源代码级别的行号设置断点
- 保存已经设置的断点 save breakpoints file-name-to-save
- 查看断点 info breakpoints (i b)
- 设置临时断点(生效一次)tbreak(tbr) –> enable once 断点编号
- 设置条件断点 b 10 if i==101
- 禁用断点 disable 断点编号(批量禁止disable num1-num2)
- 启用断点 enable 断点编号
- 忽略前N次命中:ignore 断点编号 次数
- 删除断点 delete [断点] (单delete删除全部)
gcc -s 和 strip命令区别
gcc作为编译器/链接器,-s选项是在链接时完成的。用来删除符号表和重定向信息。
strip则是对已经编译生成的目标文件进行删减。有对应的命令选项来进行删除,比如-g选项能删除对应gcc -g添加的调试信息而保留符号表。
一般在实际开发中,经常需要对ELF格式二进制文件进行strip操作,删除部分section:减少size,节省空间;去掉symbol table,增加逆向工程难度😭
(nm
命令是 Linux
下的一个强大的文件分析工具,用于检查和分析二进制文件、库文件、可执行文件中的符号表。)
提取调试信息:objcopy –only-keep-debug program program.debug
gdb加载调试信息:symbol-file program.debug
去除符号表后调试程序时调用__libc_start_main的原因
去除符号表后,调试器不能找到main的入口地址,只能依赖程序启动的默认流程。
在 Linux 程序中,程序启动通常经过以下步骤:
- **程序入口点 (
_start
)**:- 这是 ELF 文件的默认入口,由操作系统加载程序后首先执行。
_start
通常初始化运行时环境(如栈、堆等),并调用__libc_start_main
。
- **
__libc_start_main
**:- 一个标准的 C 程序入口函数,它负责:
- 初始化全局变量。
- 执行构造函数(
.init_array
中的函数)。 - 最终调用
main
函数。
- 一个标准的 C 程序入口函数,它负责:
- 调用
main
函数:- 当
__libc_start_main
完成初始化后,它会跳转到main
函数,开始程序的主要逻辑。
- 当
直接调试去除符号表的程序将调用libc_start_main
加载符号表后再启动断在main函数
strip使用方法
选项 | 描述 |
---|---|
-I --input-target=<bfdname> |
假设输入文件的格式为 <bfdname> |
-O --output-target=<bfdname> |
创建格式为 <bfdname> 的输出文件 |
-F --target=<bfdname> |
将输入和输出格式都设置为 <bfdname> |
-p --preserve-dates |
将修改/访问时间戳复制到输出文件 |
-D --enable-deterministic-archives |
生成确定性输出时剥离归档文件(默认行为) |
-U --disable-deterministic-archives |
禁用 -D 行为 |
-R --remove-section=<name> |
从输出中移除名称为 <name> 的段 |
--remove-relocations <name> |
从 <name> 段移除重定位信息 |
-s --strip-all |
移除所有符号和重定位信息 |
-g -S -d --strip-debug |
移除所有调试符号和段 |
--strip-dwo |
移除所有 DWO 段 |
--strip-unneeded |
移除重定位信息不需要的符号 |
--only-keep-debug |
仅保留调试信息,移除其他信息 |
-M --merge-notes |
移除注释段中冗余的条目(默认行为) |
--no-merge-notes |
不尝试移除冗余注释 |
-N --strip-symbol=<name> |
不复制名称为 <name> 的符号 |
--keep-section=<name> |
不剥离名称为 <name> 的段 |
-K --keep-symbol=<name> |
不剥离名称为 <name> 的符号 |
--keep-file-symbols |
不剥离文件符号 |
-w --wildcard |
允许符号匹配中使用通配符 |
-x --discard-all |
移除所有非全局符号 |
-X --discard-locals |
移除所有编译器生成的符号 |
-v --verbose |
列出所有被修改的目标文件 |
-V --version |
显示此程序的版本号 |
-h --help |
显示帮助信息 |
观察点
gdb可以使用“watch
”命令设置观察点,也就是当一个变量值发生变化时,程序会停下来。
调试技巧
- 设置观察点watch a ,wacth (type)adress, info watchpoints disable、enable、delete
- 设置观察点只针对特定线程生效 watch expr thread threadnum, wa a thread 2
- 设置读观察点 rwatch 当发生读取变量行为时,程序就会暂停住。
- 设置读写观察点 awacth 发生读取变量或改变变量值的行为时,程序就会暂停住。
Catchpoint
Breakpoints能让程序执行到暂停流程,包括Breakpoints、Watchpoints、Catchpoints。
Catchpoints是一种特殊的Breakpoints,当某种特殊的事件产生后停止程序执行。
除了设置Catchpoints,其他对Catchpoints的管理方式类似于Breakpoints。
调试技巧
- catch 事件 (事件发生后暂停)
- 设置命令触发一次 tcatch
- 为系统调用设置catchpoint catch syscall [name | number] 当系统调用发生后,gdb会暂停程序的运行。
通过为ptrace调用设置catchpoint破解anti-debugging的程序
ptrace系统调用
Linux沙箱入门——ptrace从0到1-安全客 - 安全资讯平台
提供父进程观察和控制另一个进程执行的机制,同时提供查询和修改另一进程的核心影像与寄存器的能力。主要用于执行断点调试和系统调用跟踪。
1 |
|
可以使用 catch syscall ptrace来对系统调用进行监测,调用ptrace后来修改返回值
不过也可以直接nop掉或者是修改其操作码实现破解,也能使用LD_PRELOAD来劫持ptrace函数的调用
通过catchpoint实现破解是基于调试技术实现,后面三种方法则是对文件或文件执行时的patch
1 | long ptrace(int request, int pid, int addr, int data) |
LD_PRELOAD用于动态库的加载,动态库加载的优先级最高,一般情况下,其加载顺序为LD_PRELOAD>LD_LIBRARY_PATH>/etc/ld.so.cache>/lib>/usr/lib
LD_PRELOAD 是一个环境变量,用于在运行 Linux 程序时指定一个或多个共享库,这些库会在其他库之前加载。这使得开发者可以替换或增强现有库的功能。以下是对 LD_PRELOAD 的详细解释:
- 函数重载:通过提供自定义实现,重载某些库函数。例如,可以替换标准库函数以添加日志、监控或调试功能。
- 调试和分析:用于调试程序,允许开发者在不修改源代码的情况下跟踪函数调用。
- 安全性:可以用来实现安全检查,例如在访问敏感资源时进行权限验证。
要使用 LD_PRELOAD,可以在终端中设置这个环境变量,然后运行程序。例如:
1 | export LD_PRELOAD=/path/to/your/library.so |
工作原理:
- 当程序启动时,动态链接器(ld.so)会检查 LD_PRELOAD 环境变量,并加载指定的共享库。
- 这些库的函数实现会优先于系统库的相应函数,允许开发者插入自定义代码。
1 | gcc -fpic -c -ldl hack.c |
打印
参考:GDB调试指令 - noahze - 博客园 这位师傅写得很详细
常用的一些打印命令
print(p) & display & x
/fmt | 功 能 |
---|---|
/x | 以十六进制的形式打印出整数。 |
/d | 以有符号、十进制的形式打印出整数。 |
/u | 以无符号、十进制的形式打印出整数。 |
/o | 以八进制的形式打印出整数。 |
/t | 以二进制的形式打印出整数。 |
/f | 以浮点数的形式打印变量或表达式的值。 |
/c | 以字符形式打印变量或表达式的值。 |
hexdump
1 | hex/hexdump addr N -->打印从addr开始的N字节长度内容 |
打印内存地址
1 | n:为正整数,表示需要打印的内存单元个数 |
打印长字符串
1 | show print elements # 显示字符串最大打印长度 |
打印堆相关信息
含义 | 命令 |
---|---|
查看分配的堆 | heap [-h] [-v] [-s] [addr] (chunk头开始) |
查看各种bins | bins [addr] |
16进制查看 | hexdump 地址 显示的字节数 |
查看各个bins的情况 | heapinfo (常用) |
查看堆的基地址 | heapbase |
查看用户使用的堆的使用情况 | parseheap (chunk头开始)(par简写)(常用) |
查看chunks内存 | vis_heap_chunks |
查找fake_chunks | find_fake_fast &main_arena |
打印结构体相关信息
含义 | 命令 |
---|---|
输出变量类型 | ptype |
格式化输出显示结构体 | p |
set print pretty on 如果打开了这个选项,那么当显示字符串时,遇到结束符则停止显示。这个选项默认为off。
set print pretty on 如果打开printf pretty这个选项,那么当GDB显示结构体时会比较漂亮
set print union 设置显示结构体时,是否显式其内的联合体数据。
**也可以将一片内存自行转义成为结构体p (struct _IO_FILE_plus )addr <– 用于查看伪造的_IO_FILE_plus结构体
打印其它信息
含义 | 命令 |
---|---|
查看区段 | vmmap |
搜索字符串 | searchmem 字符串 |
查看子命令帮助 | help x |
转换为intel格式 | set disassembly-flavor intel |
转换为att格式 | set disassembly-flavor att |
查看符号地址 | info address 符号 |
查看puts的got表地址 | info address puts@got.plt |
查看libc中system函数地址 | info address system |
查看内存映射 | info proc mappings |
用栈的方式查看 | telescope 地址 |
显示当前gdb断点信息 | info breakpoints (i b) |
修改内存
1 | 修改寄存器 set $rax=0x111 |
无符号 | 修改字节数 |
---|---|
set {unsigned char}addr =value | 1字节 |
set {unsigned short}addr =value | 2字节 |
set {unsigned int}addr =value | 4字节 |
set {unsigned long long}addr =value | 8字节 |
有符号 | 修改字节数 |
---|---|
set {char}addr =value | 1字节 |
set {short}addr =value | 2字节 |
set {int}addr = value | 4字节 |
set {long long}addr =value | 8字节 |
多进程/线程调试
这位师傅写的很详细 –> GDB 调试多进程或者多线程应用-CSDN博客
follow-fork-mode
默认设置下, 在调试多进程程序时 GDB 只会调试主进程. 但是 GDB > V7.0 支持多进程的分别以及同时调试, 换句话说, GDB 可以同时调试多个程序. 只需要设置 follow-fork-mode (默认值 parent) 和 detach-on-fork (默认值 on )即可.
follow-fork-mode | detach-on-fork | 说明 |
---|---|---|
parent | on | 只调试主进程( GDB 默认) |
child | on | 只调试子进程 |
parent | off | 同时调试两个进程, gdb 跟主进程, 子进程 block 在 fork 位置 |
child | off | 同时调试两个进程, gdb 跟子进程, 主进程 block 在 fork 位置 |
常用命令
1 | set follow-fork-mode [parent|child] |
GDB默认支持调试多线程, 跟主线程, 子线程
block在
create thread
1 | 查询线程 |
Attach进程
- gdb attach pid
- gdb demo3 pid
- detach命令脱离进程