http://www.pediy.com/kssd/pediy08/pediy8-793.htm
通過掛鈎API函數
給加了保護的應用程式內嵌補丁
原著:SUB Z3R0
主題:Spirit Of The Wind
論壇:Seek'n Destroy TeaM 2oo6
目 錄
殼的原理和內嵌補丁的原理 ……………………………………………2
簡易的掛鈎方法 …………………………………………………………3
較難的掛鈎方法 …………………………………………………………4
關於我的方法 ……………………………………………………………5
用掛鈎方法做ASProtect 2.xx 殼的內嵌補丁 ……………………… 6
用掛鈎方法做tElock 0.98+ 殼的內嵌補丁 ………………………… 8
用掛鈎方法做Armadillo 4.xx 殼的內嵌補丁 ……………………… 10
用掛鈎方法做Execryptor 2.xx 殼的內嵌補丁 ………………………12
用掛鈎方法做TheMida 1.xx殼的內嵌補丁 ……………………………15
介紹:
要瞭解內嵌補丁的含義必須弄明白下面這些問題:
問題:一個加殼程式或加保護程式通常對原程式做了什嗎?
回答:加殼程式把應用程式原始的代碼壓縮(如Aspack、Upx殼)或者加密(如tElock、TheMida殼)或者兼有二者(如Asprotector、Armadillo殼),然後把他自己的代碼添加到結果檔案,最後把EP(進入點)改成他自已代碼的EP。
問題:加殼程式的代碼有些什麼操作?
回答:1. 給自己分配一些記憶體(也許沒有這一步)
2. 解密出他的代碼並寫入分配的記憶體(自我解密)
3. 解密出程式原始的代碼並寫入原始的映像基址處(通常是400000h)
4. 有些殼還要去除API函數【輸入表】、或者類比API函數的功能。
5. 修複輸入表(或重新導向對API函數的調用到類比API函數的地址)
6. 跳轉到OEP(原始進入點)
注意:第二步可能發生兩次以上,這使得正常的內嵌補丁更難做。現在你瞭解殼的原理,接下來要瞭解內嵌補丁的原理,問題是類似的:
問題:什麼是內嵌補丁?
回答:內嵌補丁是一種不需要先把一個加了殼的程式脫殼就能打補丁的技巧!
問題:它是怎麼做到的?
回答:為了做到這一點,我們必須在殼代碼寫完程式原始的代碼之後、還沒運行到我們想要修改的那部分應用之前,插入一段代碼。內嵌補丁最重要的問題之一就是擷取CRC值,通常可以用API函數(CreateFile - ReadFile、CreateFileMapping- MapViewOfFile)讀檔案得到它。
(想瞭解更多,請看Lena教程和ARTeam初學者Ollydebug教程)
(想學習內嵌補丁的進階技巧,請看JohnWho的ARMA/ASPR內嵌補丁教程)
問題:我們怎樣掛鈎一個API函數呢?
回答:我知道有兩個方法:
簡易方法:
1. 讀取API函數開頭的5位元組或6位元組,儲存於某地址X處。
2. 把API函數開頭的5位元組或6位元組改寫為一個跳轉指令【注】,跳到地址X。
3. 在X+5或X+6這個地址開始寫我們的代碼。
4. 代碼最後寫入一個跳轉指令,跳到這個地址——“API函數地址”加5或加6。
【原注】可以使用下面這些指令實現跳轉:jmp [xxxx] (6 byte) 或 jmp xxxx (5 byte) 或 push xxxx ret (6 byte)
我的函數的任務:把[EAX]處5位元組寫到[EBX];把“跳轉到[EBX]”這條指令寫到[EAX];把“跳轉到[EAX+5]”這條指令寫到[EBX+10]。
[圖1]
這個方法是對的,但不適用於所有的API函數以及所有的Windows平台。
較難的方法:
1. 讀取API函數開頭5、6位元組並儲存好。
2. 把API函數開頭5、6位元組改寫成一個跳轉到地址X的指令。【可以是PUSH/RET方式】
3. 在地址X那裡寫入我們的代碼,接著寫入一些代碼(功能是:先儲存API函數原始的返回地址,再把API函數的返回地址改為我們代碼中的地址Y),再還原API函數開頭5位元組或6位元組,最後寫入一個跳轉指令跳到API函數。
4. 地址Y那裡的代碼必須再一次執行第2步,然後跳轉到原始的返回地址。
[圖2]
【第1,2步是在程式原始進入點完成,第3步在截獲時執行,它把API變回原樣了,所以第4步必須再一次做第2步即Install
Hook Again,以便下次截獲。API函數的原始返回地址是在每次截獲時讀自DWORD PTR SS:[ESP]】
這個方法較難,但它適用於所有的API函數以及所有的Windows平台。
問題:你的方法是什嗎?
回答:我的方法是教你用掛鈎截獲API函數以挫敗CRC檢查以及給程式打補丁(在程式的原始代碼已被解密或解壓出之後)。
問題:怎麼操作?
回答:我做內嵌補丁的方法分為以下幾個步驟:
第一步:用以下技巧之一挫敗CRC檢查:
a) 掛鈎ReadFile或MapViewOfFile,把原始位元組重新寫回緩衝區。(對於Asprotector殼我們用這個辦法)
【緩衝區指從MapViewOfFile返回時EAX+128處】
b) 掛鈎最近一次做CRC檢查的API函數調用,把它計算出來的CRC值改寫成程式原始的CRC值。(我們常用這個辦法)
c) 對於有記憶體檢查的殼,我們首先應該改正記憶體資料。(對於有TheMida保護的目標程式我們用這個辦法)
第二步:掛鈎如GetModuleHandleA(對於Delphi/C++程式)、GetVersion (用於C++程式)、ThunRTMain(用於VB程式)等API函數,它們會在到達OEP後不久被調用;打上補丁。(有些案例中,我們必須用計數器數它的調用次數來接近OEP)
(如果加殼程式沒有去除輸入表,你也可以通過比較GetModuleHandleA的返回地址,如果返回地址小於“程式碼片段首地址+程式碼片段大小”之和,你就能知道,這次調用是來自程式的原始代碼,也就是到打補丁的時候了。)
【"程式碼片段"指程式原始代碼】
現在用一些目標程式實踐一下:
JohnWho InLineMe :
1, ASProtect 2.11
sND! 2oo5 UnPackMes :
2, TELock 0.98+
3, Armadillo 4.20
5, TheMida 1.3.x
6, ExECryptor 2.1.70
用掛鈎方法做ASProtect 2.xx殼的內嵌補丁:
用ToPo開啟我們的目標程式(JohnWho InLineMe)…
[圖3]
【計有815位元組是閑置的,可以在這裡寫補丁代碼】
有815位元組足夠了!所以我們將使用已有的節段 … 空閑記憶體位址是4898D1 … 所以我們把程式EP改為位移地址898D1。
想想,在這個案例中我們必須做點什麼:
1. 空閑記憶體會被ASPR改變,所以我們得分配一些記憶體用於安裝我們的鉤子。
2. 在分配的記憶體中寫入我們的代碼並修正地址。【即代碼重定位】
[圖4]
3. 掛鈎MapViewOfFile函數以挫敗CRC:
我們將掛鈎MapViewOfFile … 修改AccessMode為1 ( File_Map_Copy ) (使之變為可以修改的) … 修改MapViewOfFile函數的返回地址,使之返回時來到我們的代碼(執行過MapViewOfFile函數之後,我們就修補它的緩衝區——緩衝區地址放在EAX) … 然後我們比較一下檔案映像的EP和我們的EP(898D1),把檔案映像的EP重寫為殼的OEP(1000)以修正CRC。
4. 掛鈎GetModuleHandle函數以去掉nag屏:
第一次以NULL為參數的GetModuleHandle調用是來自程式碼片段,指的是應用程式的原始代碼 … 這意味著程式在記憶體裡已經被脫殼了,正是打補丁的好時機。[圖5]
運行程式 … 好!煩人的東西去掉了!
用掛鈎方法做tElock 0.98+殼的內嵌補丁:
先把檔案備一份,再用ToPo開啟我們的目標(用SnD UnpackMe吧?我們要修補它的標題) …
給檔案增加一個400位元組的新節 …
修改EP為新增加空間的位移地址:66BD6
我們必須先找到CRC:
用Olly開啟原始檔案備份 … 下CloseHandle函數參考斷點 … 運行並等到olly斷下 … 按Ctrl+F9來到Ret … 再按F8看代碼:
[圖6]
在4659DE處下斷點,運行 … 抄下EAX的值(這是原始的CRC) …
現在想想對於此案例我們需要做點什麼:
1. tElock殼保護的應用程式他們的輸入表沒有GetProcAddress … 所以我們只好從kernel32的輸出表找該函數的地址(需要一點專業技術!) … 你也可以在他們的輸入表中手工增添GetProcAddress或者用LordPE也行 …
2. 從上面的圖片見到,我們可以在第一次調用CloseHandle的返回地址那裡寫入"mov eax,04e468f32 | jmp 4659DE"來通過CRC檢查。
3. 掛鈎GetModuleHandle來打補丁:
第一次以NULL為參數的GetModuleHandle調用是來自程式碼片段,指的是應用程式的原始代碼 … 這意味著程式在記憶體裡已經被脫殼了,正是打補丁的好時機。
這段代碼先找到GetProcAddress,再掛鈎CloseHandle和GetModuleHandle …
[圖7]
掛鈎回呼函數 …
[圖8]
現在運行它
[圖9]
呵呵!標題被修改了 … 啊!如果讓teddy知道,他會殺了我的!
用掛鈎方法做Armadillo 4.xx殼(沒有hmemcpy2的)的內嵌補丁:
我確信這個方法同樣適用於hmemcpy2(但我還沒時間測試!)
用ToPo開啟我們的目標(還是用SnD UnpackMe吧?我們要再一次修補它的標題) …
給檔案增加一個300位元組的新節 …
修改EP為新增加空間的位移地址:13B000
我們先得找到到CRC:
用olly開啟原始檔案備份 … 下OutputDebugStringA函數參考斷點 … 運行程式第2次在這個函數斷點停下 … 按Ctrl+F9來到Ret … 然後按F8看代碼:
[圖10]
在堆棧視窗點滑鼠右鍵,到功能表項目“地址”下選擇“相對於EBP” … 現在堆棧視窗是跟隨EBP:
[圖11]
圖中顯示出5個CRC值 … 都抄下來 … 後面做內嵌補丁要用到它們。
(你也可以用Armadillo CRC Finder 1.3 By AvAtAr找到這些CRC)
現在該想想我們的目的:
1. 掛鈎OutputDebugStringA,用計數器檢測到第二次調用,重寫那些CRC。
2. 掛鈎GetModuleHandle,用計數器檢測到第5次調用,因為這一次的參數是NULL,也就是說調用源靠近OEP。
[圖12]
現在運行程式看它的標題:
[圖13]
[圖14]
用掛鈎方法做Execryptor 2.xx殼的內嵌補丁:
用ToPo開啟我們的目標(還是用SnD UnpackMe吧?我們要再一次修補它的標題) …
給檔案增加一個300位元組的新節 …
在本案例是在到達EP之前檢查CRC!它是在TLS-CallBack計算 …
所以修改TLSCallBack到我們添加的代碼。
我們得先找到Execryptor那些CRC值:
設定第一次暫停於系統斷點
[圖15]
用olly開啟原始檔案備份(我確信你已經備了一份!) … 下_lread函數參考斷點 … 按Shift+F9鍵14次(你那裡可能不一樣吧?我的意思是最後一次_lread調用) … 按Ctrl+F9來到Ret … 然後按F8看代碼:
[圖16]
在堆棧視窗點滑鼠右鍵,到功能表項目“地址”下選擇“相對於EBP” … 現在堆棧視窗跟隨EBP:
[圖17]
抄下這些CRC值。
現在我們要做什麼呢?
1. 掛鈎_lread函數,用計數器檢測到第14次調用時重寫那些CRC值。
2. 掛鈎GetModuleHandle函數給訊息框標題打補丁:
第一次以NULL為參數的GetModuleHandle調用是來自程式碼片段,指的是應用程式的原始代碼 … 這意味著打補丁的好時機來了。
請看代碼和注釋加深理解:
[圖18]
[圖19]
運行程式看看:
[圖20]
用掛鈎方法做TheMida 1.xx殼(沒有ring0級驅動程式的)的內嵌補丁:
用ToPo開啟我們的目標(SnD UnpackMe) …
給檔案增加一個550位元組的新節(我曾經增加.themida節尺寸並使用它) …
修改EP為新增加空間的位移地址,TheMida殼沒有任何CRC檢查(這讓我們的工作輕鬆不少),但是它有少許記憶體檢查!
但是還有一些更厲害的難題!它的輸入表中沒有GetModuleHandle和GetProcAddress!
現在我們能做什嗎?
1. 我們探索一下PEB_LDR_DATA就可以取得kernel32的ImageBase值!然後你可以通過kernel32的輸出表擷取GetModuleHandle和GetProcAddress函數地址(逆向是家常便飯!這個會難一點了?)。或者你就乾脆(手工或用LoadPE)把需要用的API函數添加到程式的輸入表。
2. 首先在記憶體裡複原PE頭中的EP!(以便通過記憶體檢查)。
3. 掛鈎GetModuleHandle給訊息框標題打補丁:
第一次發現GetModuleHandle函數調用的返回地址值小於10000000h時就說明這次調用來自代碼節段,所以這時就應該打補丁了。
請看下一頁圖片加深理解—〉
(注意:在我的案例中我沒有向我的檔案添加任何節 … 我只是增加.Themida節尺寸以擷取空閑空間為我所用 … 所以中你會看到我分配一些記憶體以安裝我的鉤子(因為themida殼代碼會試圖在那裡寫東西))
[圖21]
[圖22]
【安裝好掛鈎、還原殼的EP和殼節尺寸之後跳到殼的入口】
[圖23]
我想5個例子夠了!本文就寫到這吧!(還有問題或疑問?我會在SND論壇儘可能地回答。)
感謝:
Teddy Rogers(感謝提供UnpackMes及幫我發布本文)
JohnWho(感謝他漂亮的CRC過關技巧)
致意:
Soda + Sina , Joker + LordXp , Magic , 和所有 SnD / Shabgard / UnReal 朋友們。
SUB Z3R0 | Spirit Of The Wind | - SnD TeaM 2oo6
SnD TeaM 2oo6 論壇 -> http://www.tuts4you.com/forum/