使用GDB和GEF进行调试

Posted HacTF

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用GDB和GEF进行调试相关的知识,希望对你有一定的参考价值。

使用GDB进行调试

这是编译ARM二进制文件和使用GDB进行基本调试的简单介绍。在您按照教程进行操作时,您可能需要按照自己的习惯使用ARM程序集。在这种情况下,你要么需要一个备用的ARM设备,或者你只是按照在这短短的步骤建立自己的实验室环境中虚拟机操作方法

您可以使用第7部分 - 堆栈和函数中的以下代码来熟悉GDB的基本调试。

.section .text
.global _start

_开始:
    按{r11,lr} / *开始序幕。将帧指针和LR保存到堆栈* /
    添加r11,sp,#0 / *设置堆栈框架的底部* /
    sub sp,sp,#16 / *序幕结束。在堆栈上分配一些缓冲区* /
    mov r0,#1 / *设置局部变量(a = 1)。这也可以作为设置最大功能的第一个参数* /
    mov r1,#2 / *设置局部变量(b = 2)。这也可以作为设置最大功能的第二个参数* /
    bl最大/ *呼叫/分支功能最大* /
    sub sp,r11,#0 / *结尾的开始。重新调整堆栈指针* /
    流行{r11,pc} / *结语结尾。从堆栈中恢复帧指针,通过直接加载到PC,跳转到先前保存的LR * /

最大:
    按{r11} / *开始序幕。将帧指针保存到堆栈* /
    添加r11,sp,#0 / *设置堆栈框架的底部* /
    sub sp,sp,#12 / *序幕结束。在堆栈上分配一些缓冲区* /
    cmp r0,r1 / *执行if(a <b)* /
    movlt r0,r1 / *如果r0小于r1,将r1存入r0 * /
    添加sp,r11,#0 / *结尾的开始。重新调整堆栈指针* /
    弹出{r11} / *恢复帧指针* /
    bx lr / *结语结尾。通过LR寄存器跳回主站* /

就个人而言,我更喜欢使用GEF作为GDB扩展。它给了我一个更好的概述和有用的功能。您可以在这里试用:GEF -  GDB增强功能

将上面的代码保存在一个名为max.s的文件中,并使用以下命令进行编译:

$ as max.s -o max.o
$ ld max.o -o max

调试器是一个强大的工具,可以:

  • 崩溃后加载内存转储(验尸调试)
  • 附加到正在运行的进程(用于服务器进程)
  • 启动一个程序并进行调试

针对二进制文件,核心文件或进程ID启动GDB:

  • 附加到进程:$ gdb -pid $(pidof <process>)
  • 调试一个二进制文件:$ gdb ./file
  • 检查核心(崩溃)文件:$ gdb -c ./core.3243
$ gdb max

如果您安装了GEF,则会释放您的gef>提示符。

这是你如何获得帮助:

  • (gdb)h
  • (gdb)apropos <search-term>
gef> apropos寄存器
收集 - 指定要在追踪点收集的一个或多个数据项目
核心文件 - 使用FILE作为核心转储来检查内存和寄存器
info all-registers  - 所有寄存器及其内容的列表
info r  - 整数寄存器及其内容的列表
信息寄存器 - 整数寄存器及其内容的列表
维护打印烹饪寄存器 - 打印包括烹饪值的内部寄存器配置
维护打印原始寄存器 - 打印内部寄存器配置,包括原始值
维护打印寄存器 - 打印内部寄存器配置
维护打印远程寄存器 - 打印包括每个寄存器的内部寄存器配置
p  - 打印EXP表达式的值
打印 - 表达EXP的打印值
寄存器 - 在一个显示全部细节
设置may-write-registers  - 设置写入寄存器的权限
设置观察者 - 设置gdb是否在观察者模式下控制劣势
显示may-write-registers  - 显示写入寄存器的权限
显示观察者 - 显示gdb是否在观察者模式下控制劣势
tui reg float  - 仅显示浮点寄存器
tui reg general  - 只显示通用寄存器
tui reg system  - 只显示系统寄存器

断点命令:

  • break(或者只是b)<function-name>
  • 打破<line-number>
  • 打破文件名:功能
  • 中断文件名:行号
  • 打破* <地址>
  • break + <offset>  
  • 打破 - <偏移>
  • tbreak(设置临时断点)
  • del <number>  (删除断点编号x)
  • 删除(删除所有断点)
  • 删除<range>(删除断点范围)
  • 禁用/启用<断点编号或范围>(不删除断点,只是启用/禁用它们)
  • 继续(或只是c) - (继续执行,直到下一个断点)
  • 继续<number>(继续,但是忽略当前的断点编号时间,对循环内的断点很有用。
  • 完成(继续结束功能)
gef> break _start 
gef> info break
Num Type Disp Enb Address什么
1个断点保持y 0x00008054 <_start>
 断点已经达到了一次
gef> del 1
 gef> break * 0x0000805c
断点2在0x805c
gef> break _start

这将删除第一个断点并在指定的内存地址设置一个断点。当你运行这个程序时,它会在这个确切的位置中断。如果不删除第一个断点,只是设置一个新断点并运行,它将在第一个断点处断开。

开始和停止:

  • 从程序开始处开始执行程序
    • [R
    • 运行<command-line-argument>
  • 停止程序执行
  • 退出GDB调试器
    • 放弃
    • q
gef>运行

技术分享

现在我们的程序正好打破了我们想要的地方,现在是检查内存的时候了。命令“x”以各种格式显示存储器内容。

语法:x / < count > < format > < unit >
格式单元
x - 十六进制 b - 字节
d - 十进制 h - 半字(2字节)
 - 指示 w - 字(4字节)
t - 二进制(二) g - 巨词(8字节)
o - 八进制  
 - 无符号  
s - 字符串  
c - 字符  
gef> x / 10i $ pc
=> 0x8054 <_start>:push {r11,lr}
 0x8058 <_start + 4>:添加r11,sp,#0
 0x805c <_start + 8>:sub sp,sp,#16
 0x8060 <_start + 12>:mov r0,#1
 0x8064 <_start + 16>:mov r1,#2
 0x8068 <_start + 20>:bl 0x8074 <max>
 0x806c <_start + 24>:sub sp,r11,#0
 0x8070 <_start + 28>:pop {r11,pc}
 0x8074 <max>:push {r11}
 0x8078 <max + 4>:加上r11,sp,#0
gef> x / 16xw $ pc
0x8068 <_start + 20>:0xeb000001 0xe24bd000 0xe8bd8800 0xe92d0800
0x8078 <max + 4>:0xe28db000 0xe24dd00c 0xe1500001 0xb1a00001
0x8088 <max + 20>:0xe28bd000 0xe8bd0800 0xe12fff1e 0x00001741
0x8098:0x61656100 0x01006962 0x0000000d 0x01080206

单步执行代码的命令:

  • 进入下一行代码。将步入一个功能
    • 步骤1
    • 小号
    • 步骤<步骤数量>
  • 执行下一行代码。不会输入功能
    • nexti
    • ?
    • 下一个<number>
  • 继续处理,直到达到指定的行号,函数名称,地址,文件名:函数或文件名:行号
    • 直到
    • 直到<line-number>
  • 显示当前行号和您所在的功能
    • 哪里
gef> nexti 5
...
0x8068 <_start + 20> bl 0x8074 <max> < -  $ pc
0x806c <_start + 24> sub sp,r11,#0
0x8070 <_start + 28> pop {r11,pc}
0x8074 <max> push {r11}
0x8078 <max + 4>添加r11,sp,#0
0x807c <max + 8> sub sp,sp,#12
0x8080 <max + 12> cmp r0,r1
0x8084 <max + 16> movlt r0,r1
0x8088 <max + 20>添加sp,r11,#0

信息寄存器ir检查寄存器

gef> info寄存器
r0 0x1 1
r1 0x2 2
r2 0x0 0
r3 0x0 0
r4 0x0 0
r5 0x0 0
r6 0x0 0
r7 0x0 0
r8 0x0 0
r9 0x0 0
r10 0x0 0
r11 0xbefff7e8 3204446184
r12 0x0 0
sp 0xbefff7d8 0xbefff7d8
lr 0x0 0
pc 0x8068 0x8068 <_start + 20>
cpsr 0x10 16

命令“信息寄存器”给你当前的寄存器状态。我们可以看到通用寄存器r0-r12和专用寄存器SP,LR和PC,包括状态寄存器CPSR。函数的前四个参数通常存储在r0-r3中。在这种情况下,我们手动将值移到r0和r1。

显示进程内存映射:

gef> info proc map
过程10225
映射地址空间:

 开始地址结束地址大小偏移量objfile
     0x8000 0x9000 0x1000 0 / home / pi / lab / max
 0xb6fff000 0xb7000000 0x1000 0 [sigpage]
 0xbefdf000 0xbf000000 0x21000 0 [stack]
 0xffff0000 0xffff1000 0x1000 0 [矢量]

用“反汇编”命令查看max函数的反汇编输出。

gef>反汇编max
 汇编代码功能最大的转储:
 0x00008074 <+0>:push {r11}
 0x00008078 <+4>:添加r11,sp,#0
 0x0000807c <+8>:sub sp,sp,#12
 0x00008080 <+12>:cmp r0,r1
 0x00008084 <+16>:movlt r0,r1
 0x00008088 <+20>:添加sp,r11,#0
 0x0000808c <+24>:pop {r11}
 0x00008090 <+28>:bx lr
 汇编器转储结束。

GEF特定命令(更多命令可以使用命令“gef”查看):

  • 将所有加载的ELF图像的所有部分转储到进程内存中
    • X档案
  • proc map的增强版本在映射页面中包含RWX属性
    • 的VMMap
  • 内存属性在给定的地址
    • 信佛
  • 检查内置于运行二进制文件中的编译器级保护
    • checksec
gef> xfiles
     开始结束名称文件
0x00008054 0x00008094 .text / home / pi / lab / max
0x00008054 0x00008094 .text / home / pi / lab / max
0x00008054 0x00008094 .text / home / pi / lab / max
0x00008054 0x00008094 .text / home / pi / lab / max
0x00008054 0x00008094 .text / home / pi / lab / max
0x00008054 0x00008094 .text / home / pi / lab / max
0x00008054 0x00008094 .text / home / pi / lab / max
0x00008054 0x00008094 .text / home / pi / lab / max
0x00008054 0x00008094 .text / home / pi / lab / max
0x00008054 0x00008094 .text / home / pi / lab / max
gef>  vmmap
     开始结束偏移Perm路径
0x00008000 0x00009000 0x00000000 rx / home / pi / lab / max
0xb6fff000 0xb7000000 0x00000000 rx [sigpage]
0xbefdf000 0xbf000000 0x00000000 rwx [stack]
0xffff0000 0xffff1000 0x00000000 rx [矢量]
gef> xinfo 0xbefff7e8
---------------------------------------- [xinfo:0xbefff7e8] ----- -----------------------------------
找到0xbefff7e8
页面:0xbefdf000  - > 0xbf000000(大小= 0x21000)
权限:rwx
路径名称:[stack]
偏移(从页面):+ 0x207e8
Inode:0
gef> checksec
[+] checksec for‘/ home / pi / lab / max‘
金丝雀:没有
NX支持:是的
PIE支持:没有
RPATH:没有
RUNPATH:没有
部分RelRO:没有
完整的RelRO:没有
 
故障排除

为了使GDB的调试更高效,知道某些分支/跳转将带给我们的位置是非常有用的。某些(较新的)GDB版本解析分支指令的地址并向我们显示目标函数的名称。例如,GDB的以下输出缺少这个功能:

...
0x000104f8 <+72>:bl 0x10334
0x000104fc <+76>:mov r0,#8
0x00010500 <+80>:bl 0x1034c
0x00010504 <+84>:mov r3,r0
...

这是GDB(native,没有gef)的输出,它具有我正在谈论的功能:

0x000104f8 <+72>:bl 0x10334 <free @ plt>
0x000104fc <+76>:mov r0,#8
0x00010500 <+80>:bl 0x1034c <malloc @ plt> 
0x00010504 <+84>:mov r3,r0

如果你在GDB中没有这个特性,你可以更新Linux源代码(并且希望他们的代码库中已经有了一个更新的GDB)或者自己编译一个更新的GDB。如果您选择自行编译GDB,则可以使用以下命令:

cd / tmp
wget https://ftp.gnu.org/gnu/gdb/gdb-7.12.tar.gz
tar vxzf gdb-7.12.tar.gz
sudo apt-get更新
sudo apt-get install libreadline-dev python-dev texinfo -y
cd gdb-7.12
./configure --prefix = / usr --with-system-readline --with-python && make -j4
sudo make -j4 -C gdb / install
gdb --version

我用上面提供的命令在Raspbian(jessie)上下载,编译和运行GDB,没有任何问题。这些命令也将取代以前的GDB版本。如果你不想要,那么跳过以install为结尾的命令而且,我在QEMU中模拟Raspbian的时候做了这个,所以花了我很长时间(小时),因为仿真环境中的资源(CPU)有限。我使用GDB版本7.12,但是即使使用更新的版本,您也很有可能成功(请点击这里查看其他版本)。

以上是关于使用GDB和GEF进行调试的主要内容,如果未能解决你的问题,请参考以下文章

如何将多个输入从文件重定向到在gdb中调试的二进制文件?

linux 下面怎么用gdb调试多个.c文件?

GDB调试汇编分析

Linux C/C++代码 使用gdb进行coredump调试

Linux调试工具gdb的使用

GDB调试