最近学弟也开始学pwn了,回忆当初自己学pwn时,在调试这方面屡屡碰壁,决定出一个gdb调试指南,便于新生们更好的入门gdb调试
作者废话:
对于pwn题来说,调试是一个入门的关卡,会调试的pwn学者才算真正的入门pwn,但调试并不是一种技巧或一种理论并不是一蹴而就或者幡然醒悟,他是我们在学习pwn过程中将我们所学到的理论进行实践并积累经验的过程,你会在调试的过程中发现你学到的哪些知识(例如:栈溢出、shellcode、栈迁移等等)是如何实现的,并感叹前辈们设计出如此精妙的手法进行攻击,在漫漫的学习历程中你会一边觉得调试很无聊总是对着一堆代码进行调试;但同时你又会感觉很有趣,你利用自己一点点构造的程序输入,一点点将程序腐蚀达到了自己想要的效果,像攻城一样,看似坚不可摧的程序其实也不堪一击,pwn的枯燥在于此,pwn的乐趣也在于此,初学者莫要丧气,多尝试多思考。
文中将包含常用gdb调试指令,以及基础题型调试的关注重点和注意事项
该文章如果需要会持续更新
前言:
GDB是linux下非常好用且强大的调试工具,能够使让用户在程序运行的过程中观察内部结构与内存的使用情况,掌握gdb对于二进制pwn手的学习是入门的要求。
GDB在pwn中主要帮助你完四个方面:
1 | 1.启动程序,按照我们定义的输入进行运行程序(比如exp) |
基本指令的掌握
gdb中的指令其实有很多很多,但我们只需要记住常用的,不要去死记硬背,在调试中慢慢就熟练了,对于不常用的可以做个笔记保存下来(这些是我平时比较常用的,其他可以以后在补充)
也可以看这个文章学习更多
首先启动程序gdb
1 | gdb 目标二进制程序 |
1 | 示例gdb text1 |
进入gdb模式
以下在gdb状态下(介绍常用)
1.直接运行程序 r
不会进入调试界面,跟直接运行程序差不多
2.单补执行运行程序,停在第一执行语句 start
会进入调试的界面
3.单步调试next(简写n)
每进行一次next的话,程序就会对应走一步(c语言级的断点定位)
每进行一次ni,程序就会对应走一步(汇编级别的断点定位)
你就明白,要想走的精细一点(慢一点)就用ni,走的快一点用n;比较建议用ni,因为更好观察程序结构与内存的变化
4.单步调试step(简写s)
s与si的区别同n与ni
s与n和si和ni的区别就在于s和s会进入函数的内部
我们用s,进入了函数的内部,更精确的调试
用n的话不会进入函数的内部
5.结束当前函数finish(简写fini)
与s相反,s是进入函数,finish是跳出这个函数
此时在puts函数内部
进行执行finish,跳出函数
6.设定程序在运行中停止的位置break(简写b)
下断点
如何c过去就会停在那个位置
7.继续运行程序continue/c
可以跳到断点处如上
在c让程序继续进行
8.查看栈中的内容stack
9.查看地址中的内容以及符号表
例如栈中的
用tele查看对应的内容
或者说看的内容
10.打印值以及地址print(简写p)
前置知识的简略介绍我就到这里了,因为我这篇文章主要是针对题目的调试,网上有对gdb知识以及调试指令的文章很多也都很好,可以多去看看
gdb指令更加详细的分类与介绍https://www.cnblogs.com/ve1kcon/p/17812420.html
在gdb调试中一般要注意三个区域
区域1:当前状态下各个寄存器中存储的值(我称之为寄存器区)
区域2:当前执行步骤所执行的汇编指令以及该指令附近的汇编语句(我称为程序汇编区)
区域3:从rsp往下部分栈中的情况(我称为栈区)
介绍如何调试pwn的exp或payload
gdb.attach()
这是pwntools用于在exp脚本方便调试的部分
1 | context.terminal = ["tmux","splitw","-h"]#产生左右分屏,不带的话是上下分屏比较难受 |
运行脚本得到
注意:要在tmux模式下才可以
安装 tmux
更新软件包列表:
打开终端并运行以下命令,以确保你的包管理器是最新的:
1 | sudo apt update |
安装 tmux:
使用以下命令安装 tmux
:
1 | sudo apt install tmux |
验证安装:
安装完成后,可以通过以下命令检查 tmux
的版本,以确认安装成功:
1 | tmux -V |
启动 tmux
:
直接终端输入tmux
1 | tmux |
但是我是不用gdb.attach的,因为毕竟只是pwntools的函数,很多大佬都有自己的函数库将其进行了修饰以及添加各种功能
我一直在用我学长的函数库
tools库
功能很多不在此介绍,本文主要讲述gdb调试,感兴趣的可以自行了解
https://zikh26.github.io/posts/ad411136.html#debug
稍微介绍一下这个库
调试的时候扔要在tmux模式下进行,只支撑python3运行
想要调试的时候
1 | python3 exp |
会进入分三屏的操作
当不想用调试模式的时候,无需进入exp中将debug(p)注释
只需要
1 | python3 exp 2 |
就可以直接运行程序
你可以把想要下的断点直接下在exp中debug中
例如
接下来讲述本文正题
ret2text调试
前言:
ret2text为pwn中最基本的入门题,一般都会给出后门函数,主要注重的是对栈溢出的理解,当读入的内容大小超过读入位置(栈上)设定的内存大小就会造成栈溢出,导致覆盖到返回地址
(简略:只针对于ret2text的话,你读入能覆盖到rbp下方就行,哪里就是返回地址)
具备知识:
1.栈溢出
2.寄存器(重点是rsp栈顶寄存器,rbp栈底寄存器)
3.汇编指令(建议理解好leave,ret的含义以及函数中参数对应的意义)
4.栈对齐(执行system(“/bin/sh”)有时会因为没有栈对齐而导致无法通过,需要在返回地址处先进行一次ret在跳到后门函数)
调试注意点:
1.读入位置(距离栈底rbp的偏移)
2.覆盖栈的情况
3.是否覆盖到返回地址(rbp的下方)
4.调试后门函数是否需要栈对齐
例题:buuctf 中的rip
漏洞的重点
gets函数在遇到回车符\n之前是不限制读入的,所以存在栈溢出
1.然后第一步查看读入位置(距离栈底rbp的距离)
可以在ida中粗略的看一下,但建议在gdb中好好看看,增加理解,后期的对于栈溢出的话直接看ida差不多就行
在ida中看
在gdb中看
因为该题的话只有一次读入,用exp中的调试(gdb.attach)会直接进入到读入函数
而不太好看读入位置
我直接用gdb启动了,然后下断点
先找到我们要查看的函数
在ida中用tab键和空格找到这个位置,这个地址就是我们要停在的位置
然后启动程序(后续不会在写这一步了)
1 | gdb 目标程序 |
下断点到gets函数利用 b *0x401162(记得加个0x转化为16进制,ida那个地址是16进制的)
其实也可以直接用b gets去定位,但是会进入函数中,该题不建议
下好断点直接c过去就行,下面是c过去的效果,正好停在了我们想要看的函数
每个函数的参数要求是不同的,对于gets函数的话rdi的位置存储的是读入位置
stack 30一下,我们算一下读入位置距离rbp的大小
当然可以直接看rbp寄存器(寄存器区),但我觉得直接看栈更形象
利用x/gx 地址1-地址2(一般地址1为大地址)计算偏移
x/gx该指令可以记住,有其他计算指令,我挺喜欢用的
计算得到偏移0xf,与ida中查看的一样,所以我们一般看读入位置与rbp的偏移可以直接在ida中看
现在完成第一步了,我写的比较详细,便于新生理解
简略:对于ret2text来说,直接看ida中距离rbp的偏移即可找到需要读入的大小
2.覆盖栈的情况
我们已经找到偏移大小了,那么我们就可以读入内容覆盖返回地址了(偏移为0xf)
我们先读入0xf的内容看覆盖栈的效果
读入前
读入0xf(15)个a后(0x61对应的acill码代表a)
马上覆盖到rbp了
用x/8gx 看字节更加详细,现在马上到rbp,如果我们在多读一个字节呢
读入了0x10(16)个a后,rbp被写入了一个字节
现在我们就可以填充rbp
记住那个偏移0xf是到rbp的位置,你的目标是rbp下方,所以填充rbp
而64位程序下rbp是8个字节,32位程序下ebp是4个字节(32位程序下栈底寄存器是ebp)
所以你想要覆盖到返回地址就要
$$
覆盖rbp大小=距离rbp(或ebp)的偏移+rbp(或ebp)的大小
$$
$$
覆盖返回地址大小=覆盖rbp大小+控制返回地址的大小
$$
读入覆盖rbp的大小,查看覆盖rbp效果
读入后,现在已经把rbp覆盖了,在往后读就是返回地址了
3.查看是否覆盖了返回地址
在读入覆盖返回地址
fun的地址希望你们已经会找了
读入后,就把rbp下方覆盖掉了(返回地址)
覆盖返回地址后,查看一下运行的效果
(你要记住,这个在return处查看只是对于最基本的ret2text,你要多思考为啥会在这里进行返回,这不是一种固定思维,你要多思考,建议把leave ret搞清楚,以后的题并不是都是填充在rbp下面就是返回地址的)
跳到断点处,其实你也可以直接ni过去查看程序走向就行
覆盖返回地址前的效果
覆盖后,你会发现后续的程序走向已经被改变,改变成我们刚刚覆盖的返回地址(后门函数的位置)
好了,不要以为这就结束了,你别忘了还有第四步,调试返回地址后程序能否正常执行完会不会卡住,是否需要栈对齐
4.查看执行system(‘/bin/sh’)是否需要栈对齐
现在的情况是吧返回地址覆盖为后门函数后
依旧跳到刚刚那个位置
然后用ni一步步执行汇编
查看程序执行流的情况,会不会卡在哪里
执行到这里按道理来说就要获得shell(得到目标权限)了,system(‘/bin/sh’)参数也正确,但是我们在ni一下你会发现
程序卡在了这个汇编指令(xmm 寄存器要求内存地址对齐,对齐由内存位数决定,本文中为16字节对齐。)
对于这种情况的话我们不能直接执行system(‘/bin/sh’)
我们要现执行一次ret进行栈对齐
ret可以直接去ida中
也可以用ROPgadget –binary 程序 | grep ‘ret’
稍微改动一下exp
这样你在尝试调试一下你就会发现可以绕过那个命令,具体想要搞清楚为什么,去学学栈对齐
然后你就会发现程序可以正常执行完了
这就是大致ret2text的调试过程,如有什么问题后续会在补充
最终的exp
1 | from tools import * |
- 本文作者: NEWYM
- 本文链接: http://example.com/2024/11/06/GDB调试指南/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!