上一篇博客是对栈溢出的基本学习,做了几个题练习了一下,师兄给的建议是多用gdb,更直观,动态也可以查看寄存器和栈的变化
一、如何使用gdb
1.peda插件的安装
为了方便查看堆栈和寄存器,最好是安装peda插件,命令如下
sudo apt install gdb //安装gdb
git clone https://github.com/longld/peda.git ~/peda //使用git命令从GitHub克隆peda插件到本地
echo "source ~/peda/peda.py" >> ~/.gdbinit //加入配置文件中,每次启动GDB时都会自动加载peda
cat ~/.gdbinit //查看是否添加成功
这里我是之前就以及安装好gdb了
克隆
配置
查看配置文件,里面确实存在peda插件
2.gdb简介
GDB(GUN Debugger)是一个用来调试C/C++程序的功能强大的调试器,是Linux系统开发C/C++最常用的调试器
GDB主要功能:
1>设置断点(断点可以是条件表达式)
2>使程序在指定的代码行上暂停执行,便于观察
3>单步执行程序,便于调试
4>查看程序中变量值的变化
5>动态改变程序的执行环境
6>分析崩溃程序产生的core文件
3.常用命令
(gdb)help(h) # 查看命令帮助,具体命令查询在gdb中输入help + 命令
(gdb)run(r) # 重新开始运行文件(run-text:加载文本文件,run-bin:加载二进制文件)
(gdb)start # 单步执行,运行程序,停在第一行执行语句
(gdb)list(l) # 查看原代码(list-n,从第n行开始查看代码。list+ 函数名:查看具体函数)
(gdb)set # 设置变量的值
(gdb)next(n) # 单步调试(逐过程,函数直接执行)
(gdb)step(s) # 单步调试(逐语句:跳入自定义函数内部执行)
(gdb)backtrace(bt) # 查看函数的调用的栈帧和层级关系
(gdb)frame(f) # 切换函数的栈帧
(gdb)info(i) # 查看函数内部局部变量的数值
(gdb)finish # 结束当前函数,返回到函数调用点
(gdb)continue(c) # 继续运行
(gdb)print(p) # 打印值及地址
(gdb)quit(q) # 退出gdb
(gdb)break+num(b) # 在第num行设置断点
(gdb)info breakpoints # 查看当前设置的所有断点
(gdb)delete breakpoints num(d) # 删除第num个断点
(gdb)display # 追踪查看具体变量值
(gdb)undisplay # 取消追踪观察变量
(gdb)watch # 被设置观察点的变量发生修改时,打印显示
(gdb)i watch # 显示观察点
(gdb)enable breakpoints # 启用断点
(gdb)disable breakpoints # 禁用断点
(gdb)x # 查看内存x/20xw 显示20个单元,16进制,4字节每单元
(gdb)run argv[1] argv[2] # 调试时命令行传参
(gdb)set follow-fork-mode child # Makefile项目管理:选择跟踪父子进程(fork())
后面那些有括号的是命令的简化版
Linux·gdb用法详解-CSDN博客具体命令可以参考这篇文章,非常详细
4.调试
准备
gcc -g test.c -o test.out //进行编译
gdb ./test.out //进行调试
二、ret2text
1.简介
ret2text就是执行程序中已有的代码,例如程序中写有system等系统的调用函数,在这种攻击方法中,攻击者可以控制程序执行若干不相邻的代码段(即gadgets),这就是我们常说的ROP(Return-Oriented Programming)
补充:
ROP主要思想是在栈缓冲区溢出的基础上,利用程序中已有的小片段 (gadgets) 来改变某些寄存器或者变量的值,从而控制程序的执行流程
有以下一些概念:
1.rop:在栈缓冲区溢出的基础上,利用程序中已有的小片段 (gadgets) 来改变某些寄存器或者变量的值,从而控制程序的执行流程。
2.gadgets:在程序中的指令片段,有时我们为了达到我们执行命令的目的,需要多个gadget来完成我们的功能。gadget最后一般都有ret,因为我们需要将程序控制权(EIP)给下一个gadget。即让程序自动持续的选择堆栈中的指令依次执行。
3.ropgadgets:一个pwntools的一个命令行工具,用来具体寻找gadgets的。例如:我们从pop、ret序列当中寻找其中的eax
ROPgadget --binary ./7.exe --only "pop|ret" | grep "eax"
4、在linux系统中,函数的调用是有一个系统调用号的。例如execve("/bin/sh",null,null)函数其系统调用号是11,即十六进制0xb。
2.攻击原理
通过篡改栈帧上的返回地址为程序中已有的后门函数。攻击者需要知道对应返回代码的位置,并构造合适的payload来触发栈溢出,从而改变返回地址,执行后门函数。
3.主要过程
确定溢出点:首先要找到程序中的溢出点,通常是通过分析程序的反汇编代码来定位。常见的溢出点包括gets(buf)、strcpy(dest,sec)、scanf("%s",buf)、strcat(buf,buf2) 、read(0,buf,size)等函数调用。
构造ROP链:一旦找到溢出点,攻击者需要构造一个ROP链。ROP链是由一系列的"gadgets"组成,这些gadgets是程序中已有的、以ret指令结尾的指令序列。攻击者通过这些gadgets来控制程序的执行流程。
覆盖返回地址:在构造好ROP链后,攻击者会通过溢出覆盖函数的返回地址,使其指向ROP链的起始地址。这样,当函数执行完毕后,控制流会跳转到ROP链,执行攻击者控制的代码。
执行特定代码:ROP链的最终目的是执行特定的代码。在ret2text攻击中,攻击者通常会选择执行程序已有的代码段(.text段)中的函数或代码片段。例如,如果攻击者想要执行system("/bin/sh"),他们会在ROP链中插入相应的gadgets来调用system函数。
绕过安全机制:在某些情况下,程序可能启用了如PIE(位置无关执行)和Canary(栈保护)等安全机制。攻击者需要通过格式化字符串漏洞等技巧来泄露栈上的信息,从而绕过这些安全机制。
三、练习
云曦24秋季学期期末考-pwn-真正的签到题
先检查一下文件,发现没有缓冲区溢出
nc连接看看
发现不行
用ida打开附件,可以看到有个give_flag
在下面的start_shell中得知输入69470可执行give_flag
得到flag
NSSCTF-pwn-[SWPUCTF 2021 新生赛]gift_pwn
例行发现可能存在栈溢出
用ida打开,在gift函数中发现存在后门
查看main函数,这个是程序的入口,从这里可以看程序的执行流程,会发现执行了vlun函数
查看vuln,看图会发现read读取64个字节,超过了栈的大小
双击buf,查看一下栈内,0x10就是数组的大小,然后后面就是保存的rbp(32位是ebp)以及返回地址。s是存储的rbp,代表这个函数结束以后rbp要被修改为什么。r是返回地址,代表这个函数结束以后程序会跳转到哪个地址继续执行,大多数情况下都是利用这个返回地址来进行操作。
栈的生长方向是由高地址往低地址增长的,可以看到左边是变量所在的相对位置,以s为基准。在read读入字符的时候,第一个字符会被放在相对地址是0x10的位置,第二个字符会被放在0x0F的位置,当读入的字符超过了buf的长度,那么会接着向下(高地址)存放,这个时候就会覆盖掉原来保存在高地址的s和r,以让程序跳转到不同的地方。
read函数可以读入0x64个字符,而buf的长度为16,s为8,r为8,可以修改s和r,(在32位程序中s和r为4,64位程序中s和r为8,一个字符长度是一个字节,一个字节的长度是8位(即长度为8的二进制数),32/8 = 4,64/8 = 8)
目的是为了跳转到gift函数处,那么r是返回地址,也就是说要让r的地址成为gift起始地址
找到gift函数地址
然后构造payload
payload = b'a' * 16 + b'a' * 8
payload += p64(0x4005B6)
接下来写exp
from pwn import *
p = remote("node4.anna.nssctf.cn",28035)
payload = b'a' * 16 + b'a' * 8
payload += p64(0x4005B6)
p.send(payload)
p.interactive()
得到flag