通過x64dbg腳本功能修復IAT表
[PEDIY.華章 Crackme 競賽 2009] [第十回] –ninejs
https://bbs.pediy.com/thread-97892-1.htm
感興趣可自行去下載,看到下面評論也不少在說會dump但不會修復IAT表,故開此貼簡單介紹一下剛學的方法。
1. 尋找OEP
通過棧平衡應該可以很快定位到OEP位置,這種基礎操作不再贅述。
確定入口點在0x0047148B,此處下硬件執行斷點,方便下次快速定位。

2. dump
直接先 dump 一手,有問題再慢慢修。
x64dbg 下方控制臺可輸入scylla指令,調出 scylla 工具,直接點上方圖標也可以。


確認 OEP 地址是否正確,然后點擊 dump,成功后可以看到源文件目錄下多了一個后綴為xxx_dump的文件,雙擊運行。
不出意外的話是無法正常運行的,這時候不要慌,將此文件拖入 x64dbg,咱們一步步看問題出在哪了。
3. 定位問題
◆直接 F9 讓程序跑起來,等它拋異常就行了,先瞟一眼異常。

◆查看棧頂到底是哪個函數出了問題。

◆好家伙,第一個 call 就卡住了。

◆可以看到他這里報的錯是無法訪問,也就是 call 的地址有問題,那么這個0x475080的地址里存放的到底是啥呢,咱也不曉得,咱也不敢問,咱們只能去源文件過一眼他是怎么跑的。

◆源文件這個地址 call 進去是完全沒有任何問題的,而且這個地址一下串的老遠,離譜的是這個地址還是屬于用戶模塊,這就有點讓人懷疑人生,一番苦思冥想,只能得出一個結論,這是他自己申請的內存頁,并往其中寫入了執行代碼,咱們入口住 dump 出來是沒有的。先不管其他,咱們先把這條 call 追到底,看看他到底在干嘛,幾步一跟就到底了,可以看到他是拿ret下方的4個字節作為返回地址跳過去,這個地址是系統函數GetVersion,就是說這個函數用自己的算法變相的調用了GetVersion,至此我猜測該程序修改了IAT表的值,把表中本該跳轉到函數的地址全部替換成了自己的跳轉方式。
這里咱們去看一眼 IAT 表,內存窗口跳轉至地址0x475080,確實是 IAT 表的結構,就是地址全被改寫成了他自己的函數,這種情況我們直接用工具是獲取不到導入函數的,只能把表還原才能修復。

4. 修復 IAT 表
手動修復
就是把系統函數地址填回去,比如上面的call [0x475080],我們知道這個 call 等于調了GetVersion,我們就把GetVersion的地址填到0x475080里,下次運行時他就可以直接跳轉到系統函數地址。
我本來是這么修的,但是真的好慢,花了半個小時僅把運行需要的函數給修復了,還有很多沒用到的都沒修,主要是單步跟著真的很累,有的地方還被混肴了,跳來跳去看著都煩,但是這種手動修的方法肯定是可行的,但成本太高,這還是程序導入函數比較少的情況,再多點人不得累死。
然后下面就要說一下 x64dbg 的腳本功能,真的是太舒服了。
腳本修復
關于腳本的所有指令都在官方的在線文檔里:x64dbg 文檔(https://help.x64dbg.com/en/latest/index.html)
幸好在科銳上個階段項目就是自己寫個調試器,x64dbg 的很多指令看著還挺親切。
下面進入正題,這里先貼一下王老師課上寫的腳本,沒有對比就沒有傷害,我自己寫的留在最后得好好炫耀一下。
// 運行到OEP
bph 0x47148b // 設定硬件執行斷點
g
// 導入表范圍
// 表范圍可以通過內存頁跳轉至 0x475080 直接看出來
impStart = 0x00475000
impEnd = 0x00475120
WHILEBEGIN:
//取出一項
mov itaItem, dword:[impStart]
//跳過0
cmp itaItem, 0
jz WHILECONTINUE
//設為新的EIP
mov eip, itaItem
//單步,直到遇到ret
SETBEGIN:
sti // 置單步
// 判斷是否到達ret
mov code, byte:[eip]
// ret的機器碼為0xC3
cmp code, 0xC3
jnz SETBEGIN
//從棧頂取出API地址,存入IAT對應項
mov apiAddr, dword:[esp]
mov dword:[impStart],apiAddr
WHILECONTINUE:
add impStart, 4
cmp impStart, impEnd
jb WHILEBEGIN
ret
這個 x64dbg 的腳本語法和匯編還挺像,里面可以用變量,可以用條件跳,可以添加段聲明,這里簡單說一下老王的思路。
他首先確定了 IAT 表的范圍,然后直接對 IAT 表進行遍歷,把 EIP 依次設為表中的地址開始跑,每跑完一次就把獲取到的地址寫回,跑完即可把 IAT 表修復
但他這個判定方法很奇葩,還記得我們上面說的call [0x475080],這個函數是通過ret下方的四個字節作為跳板跳到系統 API,他這就是吃定了每個函數都用這種方法跳轉。
但現實可能真的這么簡單嗎?然后課堂上就被打臉了,有的地方用的是jmp xxx,有的地方是call reg,這種跳轉的,腳本根本無法正常獲取到地址,最后老王只能灰溜溜的手動修復了剩下的部分。
欸嘿,是時候到我表演真正的技術了!經過我花了數小時仔仔細細,認認真真把 x64dbg 手冊翻了個遍,終于我悟了,老王他不行,我行!開玩笑哈,我就是吹水比他強。
首先咱們還是基于老王這個思想進行架構,遍歷沒問題,修改EIP沒問題,但置單步和這個判定邏輯咱們得改,自己置單步實在是太慢了,我們可以用 x64dbg 的 api,讓他幫我們跑,這樣效率會更高,比如說像“跟蹤”菜單里的“步進直到條件滿足”,直接在這里寫暫停條件肯定比我們自己置單步判斷條件來的快,我這里主要用到了一個重要 API:RunToParty,這個函數的功能是運行到指定模塊,參數0為用戶模塊,參數1為系統模塊,我的想法很簡單,我管你是用什么方式跳轉的,反正你肯定要跳轉到系統模塊,只要在這個時候斷下,我們就能拿到系統函數的首地址,我本來這里用的是模塊地址范圍做邏輯判斷的,正好看到有這么一個函數,倒省了我一番心思,ok,上代碼。
$ArrayPtr = 475000 $ArrayEnd = 475120 Loop: cmp dword:[$ArrayPtr], 0 // 跳過模塊空隙 je Next EIP = dword:[$ArrayPtr] RunToParty 1 // 執行到系統模塊斷下 dword:[$ArrayPtr] = EIP // 取當前地址 Next: $ArrayPtr += 4 // 指向下一塊地址 cmp $ArrayPtr, $ArrayEnd // 判斷是否結束 jne Loop ret
我的這種寫法 x64dbg 也是支持的,是不是十分的簡潔明了,運行完腳本之后,可以看到 IAT 表已經被完美修復。

收尾


這時候我們再打開scylla工具,確定好 OEP,直接點擊 IAT AutoSearch,然后點擊 Get Imports,此時表已經能被加載出來,點擊右側的 Fix Dump,選擇我們之前 dump 出來無法正常運行的文件,大功告成,新生成的xxx_dump_SCY已經能夠完美運行,拖進 IDA 也可以清楚的看到調用的函數,收工!