一、OllyDBG 的安裝與配置
OllyDBG 1.10 版的發(fā)布版本是個(gè) ZIP 壓縮包,只要解壓到一個(gè)目錄下,運(yùn)行 OllyDBG.exe 就可以了。漢化版的發(fā)布版本是個(gè) RAR 壓縮包,同樣只需解壓到一個(gè)目錄下運(yùn)行 OllyDBG.exe 即可:
OllyDBG 中各個(gè)窗口的功能如上圖。簡(jiǎn)單解釋一下各個(gè)窗口的功能,更詳細(xì)的內(nèi)容可以參考 TT 小組翻譯的中文幫助:
反匯編窗口:顯示被調(diào)試程序的反匯編代碼,標(biāo)題欄上的地址、HEX 數(shù)據(jù)、反匯編、注釋可以通過在窗口中右擊出現(xiàn)的菜單 界面選項(xiàng)->隱藏標(biāo)題 或 顯示標(biāo)題 來進(jìn)行切換是否顯示。用鼠標(biāo)左鍵點(diǎn)擊注釋標(biāo)簽可以切換注釋顯示的方式。
寄存器窗口:顯示當(dāng)前所選線程的 CPU 寄存器內(nèi)容。同樣點(diǎn)擊標(biāo)簽 寄存器 (FPU) 可以切換顯示寄存器的方式。
信息窗口:顯示反匯編窗口中選中的第一個(gè)命令的參數(shù)及一些跳轉(zhuǎn)目標(biāo)地址、字串等。
數(shù)據(jù)窗口:顯示內(nèi)存或文件的內(nèi)容。右鍵菜單可用于切換顯示方式。
堆棧窗口:顯示當(dāng)前線程的堆棧。
要調(diào)整上面各個(gè)窗口的大小的話,只需左鍵按住邊框拖動(dòng),等調(diào)整好了,重新啟動(dòng)一下 OllyDBG 就可以生效了。
啟動(dòng)后我們要把插件及 UDD 的目錄配置為絕對(duì)路徑,點(diǎn)擊菜單上的 選項(xiàng)->界面,將會(huì)出來一個(gè)界面選項(xiàng)的對(duì)話框,我們點(diǎn)擊其中的目錄標(biāo)簽:
因?yàn)槲疫@里是把 OllyDBG 解壓在 F:\OllyDBG 目錄下,所以相應(yīng)的 UDD 目錄及插件目錄按圖上配置。還有一個(gè)常用到的標(biāo)簽就是上圖后面那個(gè)字體,在這里你可以更改 OllyDBG 中顯示的字體。上圖中其它的選項(xiàng)可以保留為默認(rèn),若有需要也可以自己修改。修改完以后點(diǎn)擊確定,彈出一個(gè)對(duì)話框,說我們更改了插件路徑,要重新啟動(dòng) OllyDBG。在這個(gè)對(duì)話框上點(diǎn)確定,重新啟動(dòng)一下 OllyDBG,我們?cè)俚浇缑孢x項(xiàng)中看一下,會(huì)發(fā)現(xiàn)我們?cè)仍O(shè)置好的路徑都已保存了。有人可能知道插件的作用,但對(duì)那個(gè) UDD 目錄不清楚。我這簡(jiǎn)單解釋一下:這個(gè) UDD 目錄的作用是保存你調(diào)試的工作。比如你調(diào)試一個(gè)軟件,設(shè)置了斷點(diǎn),添加了注釋,一次沒做完,這時(shí) OllyDBG 就會(huì)把你所做的工作保存到這個(gè) UDD 目錄,以便你下次調(diào)試時(shí)可以繼續(xù)以前的工作。如果不設(shè)置這個(gè) UDD 目錄,OllyDBG 默認(rèn)是在其安裝目錄下保存這些后綴名為 udd 的文件,時(shí)間長(zhǎng)了就會(huì)顯的很亂,所以還是建議專門設(shè)置一個(gè)目錄來保存這些文件。
另外一個(gè)重要的選項(xiàng)就是調(diào)試選項(xiàng),可通過菜單 選項(xiàng)->調(diào)試設(shè)置 來配置:
新手一般不需更改這里的選項(xiàng),默認(rèn)已配置好,可以直接使用。建議在對(duì) OllyDBG 已比較熟的情況下再來進(jìn)行配置。上面那個(gè)異常標(biāo)簽中的選項(xiàng)經(jīng)常會(huì)在脫殼中用到,建議在有一定調(diào)試基礎(chǔ)后學(xué)脫殼時(shí)再配置這里。
除了直接啟動(dòng) OllyDBG 來調(diào)試外,我們還可以把 OllyDBG 添加到資源管理器右鍵菜單,這樣我們就可以直接在 .exe 及 .dll 文件上點(diǎn)右鍵選擇"用Ollydbg打開"菜單來進(jìn)行調(diào)試。要把 OllyDBG 添加到資源管理器右鍵菜單,只需點(diǎn)菜單 選項(xiàng)->添加到瀏覽器,將會(huì)出現(xiàn)一個(gè)對(duì)話框,先點(diǎn)擊"添加 Ollydbg 到系統(tǒng)資源管理器菜單",再點(diǎn)擊"完成"按鈕即可。要從右鍵菜單中刪除也很簡(jiǎn)單,還是這個(gè)對(duì)話框,點(diǎn)擊"從系統(tǒng)資源管理器菜單刪除 Ollydbg",再點(diǎn)擊"完成"就行了。
OllyDBG 支持插件功能,插件的安裝也很簡(jiǎn)單,只要把下載的插件(一般是個(gè) DLL 文件)復(fù)制到 OllyDBG 安裝目錄下的 PLUGIN 目錄中就可以了,OllyDBG 啟動(dòng)時(shí)會(huì)自動(dòng)識(shí)別。要注意的是 OllyDBG 1.10 對(duì)插件的個(gè)數(shù)有限制,最多不能超過 32 個(gè),否則會(huì)出錯(cuò)。建議插件不要添加的太多。
到這里基本配置就完成了,OllyDBG 把所有配置都放在安裝目錄下的 ollydbg.ini 文件中。
二、基本調(diào)試方法
OllyDBG 有三種方式來載入程序進(jìn)行調(diào)試,一種是點(diǎn)擊菜單 文件->打開 (快捷鍵是 F3)來打開一個(gè)可執(zhí)行文件進(jìn)行調(diào)試,另一種是點(diǎn)擊菜單 文件->附加 來附加到一個(gè)已運(yùn)行的進(jìn)程上進(jìn)行調(diào)試。注意這里要附加的程序必須已運(yùn)行。第三種就是用右鍵菜單來載入程序(不知這種算不算)。一般情況下我們選第一種方式。比如我們選擇一個(gè) test.exe 來調(diào)試,通過菜單 文件->打開 來載入這個(gè)程序,OllyDBG 中顯示的內(nèi)容將會(huì)是這樣:
調(diào)試中我們經(jīng)常要用到的快捷鍵有這些:
F2:設(shè)置斷點(diǎn),只要在光標(biāo)定位的位置(上圖中灰色條)按F2鍵即可,再按一次F2鍵則會(huì)刪除斷點(diǎn)。(相當(dāng)于 SoftICE 中的 F9)
F8:?jiǎn)尾讲竭^。每按一次這個(gè)鍵執(zhí)行一條反匯編窗口中的一條指令,遇到 CALL 等子程序不進(jìn)入其代碼。(相當(dāng)于 SoftICE 中的 F10)
F7:?jiǎn)尾讲饺?。功能同單步步過(F8)類似,區(qū)別是遇到 CALL 等子程序時(shí)會(huì)進(jìn)入其中,進(jìn)入后首先會(huì)停留在子程序的第一條指令上。(相當(dāng)于 SoftICE 中的 F8)
F4:運(yùn)行到選定位置。作用就是直接運(yùn)行到光標(biāo)所在位置處暫停。(相當(dāng)于 SoftICE 中的 F7)
F9:運(yùn)行。按下這個(gè)鍵如果沒有設(shè)置相應(yīng)斷點(diǎn)的話,被調(diào)試的程序?qū)⒅苯娱_始運(yùn)行。(相當(dāng)于 SoftICE 中的 F5)
CTR+F9:執(zhí)行到返回。此命令在執(zhí)行到一個(gè) ret (返回指令)指令時(shí)暫停,常用于從系統(tǒng)領(lǐng)空返回到我們調(diào)試的程序領(lǐng)空。(相當(dāng)于 SoftICE 中的 F12)
ALT+F9:執(zhí)行到用戶代碼??捎糜趶南到y(tǒng)領(lǐng)空快速返回到我們調(diào)試的程序領(lǐng)空。(相當(dāng)于 SoftICE 中的 F11)
上面提到的幾個(gè)快捷鍵對(duì)于一般的調(diào)試基本上已夠用了。要開始調(diào)試只需設(shè)置好斷點(diǎn),找到你感興趣的代碼段再按 F8 或 F7 鍵來一條條分析指令功能就可以了。
字串參考
上一篇是使用入門,現(xiàn)在我們開始正式進(jìn)入破解。今天的目標(biāo)程序是看雪兄《加密與解密》第一版附帶光盤中的 crackmes.cjb.net 鏡像打包中的 CFF Crackme #3,采用用戶名/序列號(hào)保護(hù)方式。原版加了個(gè) UPX 的殼。剛開始學(xué)破解先不涉及殼的問題,我們主要是熟悉用 OllyDBG 來破解的一般方法。我這里把殼脫掉來分析,附件是脫殼后的文件,直接就可以拿來用。先說一下一般軟件破解的流程:拿到一個(gè)軟件先別接著馬上用 OllyDBG 調(diào)試,先運(yùn)行一下,有幫助文檔的最好先看一下幫助,熟悉一下軟件的使用方法,再看看注冊(cè)的方式。如果是序列號(hào)方式可以先輸個(gè)假的來試一下,看看有什么反應(yīng),也給我們破解留下一些有用的線索。如果沒有輸入注冊(cè)碼的地方,要考慮一下是不是讀取注冊(cè)表或 Key 文件(一般稱 keyfile,就是程序讀取一個(gè)文件中的內(nèi)容來判斷是否注冊(cè)),這些可以用其它工具來輔助分析。如果這些都不是,原程序只是一個(gè)功能不全的試用版,那要注冊(cè)為正式版本就要自己來寫代碼完善了。有點(diǎn)跑題了,呵呵。獲得程序的一些基本信息后,還要用查殼的工具來查一下程序是否加了殼,若沒殼的話看看程序是什么編譯器編的,如 VC、Delphi、VB 等。這樣的查殼工具有 PEiD 和 FI。有殼的話我們要盡量脫了殼后再來用 OllyDBG 調(diào)試,特殊情況下也可帶殼調(diào)試。下面進(jìn)入正題:
我們先來運(yùn)行一下這個(gè) crackme(用 PEiD 檢測(cè)顯示是 Delphi 編的),界面如圖:
這個(gè) crackme 已經(jīng)把用戶名和注冊(cè)碼都輸好了,省得我們動(dòng)手^_^。我們?cè)谀莻€(gè)"Register now !"按鈕上點(diǎn)擊一下,將會(huì)跳出一個(gè)對(duì)話框:
好了,今天我們就從這個(gè)錯(cuò)誤對(duì)話框中顯示的"Wrong Serial, try again!"來入手。啟動(dòng) OllyDBG,選擇菜單 文件->打開 載入 CrackMe3.exe 文件,我們會(huì)停在這里:
我們?cè)诜磪R編窗口中右擊,出來一個(gè)菜單,我們?cè)?/span> 查找->所有參考文本字串 上左鍵點(diǎn)擊:
當(dāng)然如果用上面那個(gè) 超級(jí)字串參考+ 插件會(huì)更方便。但我們的目標(biāo)是熟悉 OllyDBG 的一些操作,我就盡量使用 OllyDBG 自帶的功能,少用插件。好了,現(xiàn)在出來另一個(gè)對(duì)話框,我們?cè)谶@個(gè)對(duì)話框里右擊,選擇"查找文本"菜單項(xiàng),輸入"Wrong Serial, try again!"的開頭單詞"Wrong"(注意這里查找內(nèi)容要區(qū)分大小寫)來查找,找到一處:
在我們找到的字串上右擊,再在出來的菜單上點(diǎn)擊"反匯編窗口中跟隨",我們來到這里:
見上圖,為了看看是否還有其他的參考,可以通過選擇右鍵菜單查找參考->立即數(shù),會(huì)出來一個(gè)對(duì)話框:
分別雙擊上面標(biāo)出的兩個(gè)地址,我們會(huì)來到對(duì)應(yīng)的位置:
00440F79 |. BA 8C104400 MOV EDX,CrackMe3.0044108C ; ASCII "Wrong Serial,try again!"
00440F7E |. A1 442C4400 MOV EAX,DWORD PTR DS:[442C44]
00440F83 |. 8B00 MOV EAX,DWORD PTR DS:[EAX]
00440F85 |. E8 DEC0FFFF CALL CrackMe3.0043D068
00440F8A |. EB 18 JMP SHORT CrackMe3.00440FA4
00440F8C |> 6A 00 PUSH 0
00440F8E |. B9 80104400 MOV ECX,CrackMe3.00441080 ; ASCII "Beggar off!"
00440F93 |. BA 8C104400 MOV EDX,CrackMe3.0044108C ; ASCII "Wrong Serial,try again!"
00440F98 |. A1 442C4400 MOV EAX,DWORD PTR DS:[442C44]
00440F9D |. 8B00 MOV EAX,DWORD PTR DS:[EAX]
00440F9F |. E8 C4C0FFFF CALL CrackMe3.0043D068
我們?cè)诜磪R編窗口中向上滾動(dòng)一下再看看:
00440F2C |. 8B45 FC MOV EAX,DWORD PTR SS:[EBP-4]
00440F2F |. BA 14104400 MOV EDX,CrackMe3.00441014 ; ASCII "Registered User"
00440F34 |. E8 F32BFCFF CALL CrackMe3.00403B2C ; 關(guān)鍵,要用F7跟進(jìn)去
00440F39 |. 75 51 JNZ SHORT CrackMe3.00440F8C ; 這里跳走就完蛋
00440F3B |. 8D55 FC LEA EDX,DWORD PTR SS:[EBP-4]
00440F3E |. 8B83 C8020000 MOV EAX,DWORD PTR DS:[EBX+2C8]
00440F44 |. E8 D7FEFDFF CALL CrackMe3.00420E20
00440F49 |. 8B45 FC MOV EAX,DWORD PTR SS:[EBP-4]
00440F4C |. BA 2C104400 MOV EDX,CrackMe3.0044102C ; ASCII "GFX-754-IER-954"
00440F51 |. E8 D62BFCFF CALL CrackMe3.00403B2C ; 關(guān)鍵,要用F7跟進(jìn)去
00440F56 |. 75 1A JNZ SHORT CrackMe3.00440F72 ; 這里跳走就完蛋
00440F58 |. 6A 00 PUSH 0
00440F5A |. B9 3C104400 MOV ECX,CrackMe3.0044103C ; ASCII "CrackMe cracked successfully"
00440F5F |. BA 5C104400 MOV EDX,CrackMe3.0044105C ; ASCII "Congrats! You cracked this CrackMe!"
00440F64 |. A1 442C4400 MOV EAX,DWORD PTR DS:[442C44]
00440F69 |. 8B00 MOV EAX,DWORD PTR DS:[EAX]
00440F6B |. E8 F8C0FFFF CALL CrackMe3.0043D068
00440F70 |. EB 32 JMP SHORT CrackMe3.00440FA4
00440F72 |> 6A 00 PUSH 0
00440F74 |. B9 80104400 MOV ECX,CrackMe3.00441080 ; ASCII "Beggar off!"
00440F79 |. BA 8C104400 MOV EDX,CrackMe3.0044108C ; ASCII "Wrong Serial,try again!"
00440F7E |. A1 442C4400 MOV EAX,DWORD PTR DS:[442C44]
00440F83 |. 8B00 MOV EAX,DWORD PTR DS:[EAX]
00440F85 |. E8 DEC0FFFF CALL CrackMe3.0043D068
00440F8A |. EB 18 JMP SHORT CrackMe3.00440FA4
00440F8C |> 6A 00 PUSH 0
00440F8E |. B9 80104400 MOV ECX,CrackMe3.00441080 ; ASCII "Beggar off!"
00440F93 |. BA 8C104400 MOV EDX,CrackMe3.0044108C ; ASCII "Wrong Serial,try again!"
00440F98 |. A1 442C4400 MOV EAX,DWORD PTR DS:[442C44]
00440F9D |. 8B00 MOV EAX,DWORD PTR DS:[EAX]
00440F9F |. E8 C4C0FFFF CALL CrackMe3.0043D068
大家注意看一下上面的注釋,我在上面標(biāo)了兩個(gè)關(guān)鍵點(diǎn)。有人可能要問,你怎么知道那兩個(gè)地方是關(guān)鍵點(diǎn)?其實(shí)很簡(jiǎn)單,我是根據(jù)查看是哪條指令跳到"wrong serial,try again"這條字串對(duì)應(yīng)的指令來決定的。如果你在 調(diào)試選項(xiàng)->CPU 標(biāo)簽中把"顯示跳轉(zhuǎn)路徑"及其下面的兩個(gè)"如跳轉(zhuǎn)未實(shí)現(xiàn)則顯示灰色路徑"、"顯示跳轉(zhuǎn)到選定命令的路徑"都選上的話,就會(huì)看到是從什么地方跳到出錯(cuò)字串處的:
我們?cè)谏蠄D中地址 00440F2C 處按 F2 鍵設(shè)個(gè)斷點(diǎn),現(xiàn)在我們按 F9 鍵,程序已運(yùn)行起來了。我在上面那個(gè)編輯框中隨便輸入一下,如 CCDebuger,下面那個(gè)編輯框我還保留為原來的"754-GFX-IER-954",我們點(diǎn)一下那個(gè)"Register now !"按鈕,呵,OllyDBG 跳了出來,暫停在我們下的斷點(diǎn)處。我們看一下信息窗口,你應(yīng)該發(fā)現(xiàn)了你剛才輸入的內(nèi)容了吧?我這里顯示是這樣:
堆棧 SS:[0012F9AC]=00D44DB4, (ASCII "CCDebuger")
EAX=00000009
上面的內(nèi)存地址 00D44DB4 中就是我們剛才輸入的內(nèi)容,我這里是 CCDebuger。你可以在 堆棧 SS:[0012F9AC]=00D44DB4, (ASCII "CCDebuger") 這條內(nèi)容上左擊選擇一下,再點(diǎn)右鍵,在彈出菜單中選擇"數(shù)據(jù)窗口中跟隨數(shù)值",你就會(huì)在下面的數(shù)據(jù)窗口中看到你剛才輸入的內(nèi)容。而 EAX=00000009 指的是你輸入內(nèi)容的長(zhǎng)度。如我輸入的 CCDebuger 是9個(gè)字符。如下圖所示:
現(xiàn)在我們來按 F8 鍵一步步分析一下:
00440F2C |. 8B45 FC MOV EAX,DWORD PTR SS:[EBP-4] ; 把我們輸入的內(nèi)容送到EAX,我這里是"CCDebuger"
00440F2F |. BA 14104400 MOV EDX,CrackMe3.00441014 ; ASCII "Registered User"
00440F34 |. E8 F32BFCFF CALL CrackMe3.00403B2C ; 關(guān)鍵,要用F7跟進(jìn)去
00440F39 |. 75 51 JNZ SHORT CrackMe3.00440F8C ; 這里跳走就完蛋
當(dāng)我們按 F8 鍵走到 00440F34 |. E8 F32BFCFF CALL CrackMe3.00403B2C 這一句時(shí),我們按一下 F7 鍵,進(jìn)入這個(gè) CALL,進(jìn)去后光標(biāo)停在這一句:
我們所看到的那些 PUSH EBX、 PUSH ESI 等都是調(diào)用子程序保存堆棧時(shí)用的指令,不用管它,按 F8 鍵一步步過來,我們只關(guān)心關(guān)鍵部分:
00403B2C /$ 53 PUSH EBX
00403B2D |. 56 PUSH ESI
00403B2E |. 57 PUSH EDI
00403B2F |. 89C6 MOV ESI,EAX ; 把EAX內(nèi)我們輸入的用戶名送到 ESI
00403B31 |. 89D7 MOV EDI,EDX ; 把EDX內(nèi)的數(shù)據(jù)"Registered User"送到EDI
00403B33 |. 39D0 CMP EAX,EDX ; 用"Registered User"和我們輸入的用戶名作比較
00403B35 |. 0F84 8F000000 JE CrackMe3.00403BCA ; 相同則跳
00403B3B |. 85F6 TEST ESI,ESI ; 看看ESI中是否有數(shù)據(jù),主要是看看我們有沒有輸入用戶名
00403B3D |. 74 68 JE SHORT CrackMe3.00403BA7 ; 用戶名為空則跳
00403B3F |. 85FF TEST EDI,EDI
00403B41 |. 74 6B JE SHORT CrackMe3.00403BAE
00403B43 |. 8B46 FC MOV EAX,DWORD PTR DS:[ESI-4] ; 用戶名長(zhǎng)度送EAX
00403B46 |. 8B57 FC MOV EDX,DWORD PTR DS:[EDI-4] ; "Registered User"字串的長(zhǎng)度送EDX
00403B49 |. 29D0 SUB EAX,EDX ; 把用戶名長(zhǎng)度和"Registered User"字串長(zhǎng)度相減
00403B4B |. 77 02 JA SHORT CrackMe3.00403B4F ; 用戶名長(zhǎng)度大于"Registered User"長(zhǎng)度則跳
00403B4D |. 01C2 ADD EDX,EAX ; 把減后值與"Registered User"長(zhǎng)度相加,即用戶名長(zhǎng)度
00403B4F |> 52 PUSH EDX
00403B50 |. C1EA 02 SHR EDX,2 ; 用戶名長(zhǎng)度值右移2位,這里相當(dāng)于長(zhǎng)度除以4
00403B53 |. 74 26 JE SHORT CrackMe3.00403B7B ; 上面的指令及這條指令就是判斷用戶名長(zhǎng)度最少不能低于4
00403B55 |> 8B0E MOV ECX,DWORD PTR DS:[ESI] ; 把我們輸入的用戶名送到ECX
00403B57 |. 8B1F MOV EBX,DWORD PTR DS:[EDI] ; 把"Registered User"送到EBX
00403B59 |. 39D9 CMP ECX,EBX ; 比較
00403B5B |. 75 58 JNZ SHORT CrackMe3.00403BB5 ; 不等則完蛋
根據(jù)上面的分析,我們知道用戶名必須是"Registered User"。我們按 F9 鍵讓程序運(yùn)行,出現(xiàn)錯(cuò)誤對(duì)話框,點(diǎn)確定,重新在第一個(gè)編輯框中輸入"Registered User",再次點(diǎn)擊那個(gè)"Register now !"按鈕,被 OllyDBG 攔下。因?yàn)榈刂?/span> 00440F34 處的那個(gè) CALL 我們已經(jīng)分析清楚了,這次就不用再按 F7 鍵跟進(jìn)去了,直接按 F8 鍵通過。我們一路按 F8 鍵,來到第二個(gè)關(guān)鍵代碼處:
00440F49 |. 8B45 FC MOV EAX,DWORD PTR SS:[EBP-4] ; 取輸入的注冊(cè)碼
00440F4C |. BA 2C104400 MOV EDX,CrackMe3.0044102C ; ASCII "GFX-754-IER-954"
00440F51 |. E8 D62BFCFF CALL CrackMe3.00403B2C ; 關(guān)鍵,要用F7跟進(jìn)去
00440F56 |. 75 1A JNZ SHORT CrackMe3.00440F72 ; 這里跳走就完蛋
大家注意看一下,地址 00440F51 處的 CALL CrackMe3.00403B2C 和上面我們分析的地址 00440F34 處的 CALL CrackMe3.00403B2C 是不是匯編指令都一樣?。窟@說明檢測(cè)用戶名和注冊(cè)碼是用的同一個(gè)子程序。而這個(gè)子程序 CALL 我們?cè)谏厦嬉呀?jīng)分析過了。我們執(zhí)行到現(xiàn)在可以很容易得出結(jié)論,這個(gè) CALL 也就是把我們輸入的注冊(cè)碼與 00440F4C 地址處指令后的"GFX-754-IER-954"作比較,相等則 OK。好了,我們已經(jīng)得到足夠的信息了?,F(xiàn)在我們?cè)诓藛?/span> 查看->斷點(diǎn) 上點(diǎn)擊一下,打開斷點(diǎn)窗口(也可以通過組合鍵 ALT+B 或點(diǎn)擊工具欄上那個(gè)"B"圖標(biāo)打開斷點(diǎn)窗口):
為什么要做這一步,而不是把這個(gè)斷點(diǎn)刪除呢?這里主要是為了保險(xiǎn)一點(diǎn),萬(wàn)一分析錯(cuò)誤,我們還要接著分析,要是把斷點(diǎn)刪除了就要做一些重復(fù)工作了。還是先禁用一下,如果經(jīng)過實(shí)際驗(yàn)證證明我們的分析是正確的,再刪不遲?,F(xiàn)在我們把斷點(diǎn)禁用,在 OllyDBG 中按 F9 鍵讓程序運(yùn)行。輸入我們經(jīng)分析得出的內(nèi)容:
用戶名:Registered User
注冊(cè)碼:GFX-754-IER-954
點(diǎn)擊"Register now !"按鈕,呵呵,終于成功了:
函數(shù)參考
現(xiàn)在進(jìn)入第三篇,這一篇我們重點(diǎn)講解怎樣使用 OllyDBG 中的函數(shù)參考(即名稱參考)功能。仍然選擇 crackmes.cjb.net 鏡像打包中的一個(gè)名稱為 CrackHead 的crackme。老規(guī)矩,先運(yùn)行一下這個(gè)程序看看:
呵,竟然沒找到輸入注冊(cè)碼的地方!別急,我們點(diǎn)一下程序上的那個(gè)菜單"Shit"(真是 Shit 啊,呵呵),在下拉菜單中選"Try It",會(huì)來到如下界面:
我們點(diǎn)一下那個(gè)"Check It"按鈕試一下,哦,竟然沒反應(yīng)!我再輸個(gè)"78787878"試試,還是沒反應(yīng)。再試試輸入字母或其它字符,輸不進(jìn)去。由此判斷注冊(cè)碼應(yīng)該都是數(shù)字,只有輸入正確的注冊(cè)碼才有動(dòng)靜。用 PEiD 檢測(cè)一下,結(jié)果為 MASM32 / TASM32,怪不得程序比較小。信息收集的差不多了,現(xiàn)在關(guān)掉這個(gè)程序,我們用 OllyDBG 載入,按 F9 鍵直接讓它運(yùn)行起來,依次點(diǎn)擊上面圖中所說的菜單,使被調(diào)試程序顯示如上面的第二個(gè)圖。先不要點(diǎn)那個(gè)"Check It"按鈕,保留上圖的狀態(tài)?,F(xiàn)在我們沒有什么字串好參考了,我們就在 API 函數(shù)上下斷點(diǎn),來讓被調(diào)試程序中斷在我們希望的地方。我們?cè)?/span> OllyDBG 的反匯編窗口中右擊鼠標(biāo),在彈出菜單中選擇 查找->當(dāng)前模塊中的名稱 (標(biāo)簽),或者我們通過按 CTR+N 組合鍵也可以達(dá)到同樣的效果(注意在進(jìn)行此操作時(shí)要在 OllyDBG 中保證是在當(dāng)前被調(diào)試程序的領(lǐng)空,我在第一篇中已經(jīng)介紹了領(lǐng)空的概念,如我這里調(diào)試這個(gè)程序時(shí) OllyDBG 的標(biāo)題欄顯示的就是"[CPU - 主線程, 模塊 - CrackHea]",這表明我們當(dāng)前在被調(diào)試程序的領(lǐng)空)。通過上面的操作后會(huì)彈出一個(gè)對(duì)話框,如圖:
對(duì)于這樣的編輯框中輸注冊(cè)碼的程序我們要設(shè)斷點(diǎn)首選的 API 函數(shù)就是 GetDlgItemText 及 GetWindowText。每個(gè)函數(shù)都有兩個(gè)版本,一個(gè)是 ASCII 版,在函數(shù)后添加一個(gè) A 表示,如 GetDlgItemTextA,另一個(gè)是 UNICODE 版,在函數(shù)后添加一個(gè) W 表示。如 GetDlgItemTextW。對(duì)于編譯為 UNCODE 版的程序可能在 Win98 下不能運(yùn)行,因?yàn)?/span> Win98 并非是完全支持 UNICODE 的系統(tǒng)。而 NT 系統(tǒng)則從底層支持 UNICODE,它可以在操作系統(tǒng)內(nèi)對(duì)字串進(jìn)行轉(zhuǎn)換,同時(shí)支持 ASCII 和 UNICODE 版本函數(shù)的調(diào)用。一般我們打開的程序看到的調(diào)用都是 ASCII 類型的函數(shù),以"A"結(jié)尾。又跑題了,呵呵?,F(xiàn)在回到我們調(diào)試的程序上來,我們現(xiàn)在就是要找一下我們調(diào)試的程序有沒有調(diào)用 GetDlgItemTextA 或 GetWindowTextA 函數(shù)。還好,找到一個(gè) GetWindowTextA。在這個(gè)函數(shù)上右擊,在彈出菜單上選擇"在每個(gè)參考上設(shè)置斷點(diǎn)",我們會(huì)在 OllyDBG 窗口最下面的那個(gè)狀態(tài)欄里看到"已設(shè)置 2 個(gè)斷點(diǎn)"。另一種方法就是那個(gè) GetWindowTextA 函數(shù)上右擊,在彈出菜單上選擇"查找輸入函數(shù)參考"(或者按回車鍵),將會(huì)出現(xiàn)下面的對(duì)話框:
看上圖,我們可以把兩條都設(shè)上斷點(diǎn)。這個(gè)程序只需在第一條指令設(shè)斷點(diǎn)就可以了。好,我們現(xiàn)在按前面提到的第一條方法,就是"在每個(gè)參考上設(shè)置斷點(diǎn)",這樣上圖中的兩條指令都會(huì)設(shè)上斷點(diǎn)。斷點(diǎn)設(shè)好后我們轉(zhuǎn)到我們調(diào)試的程序上來,現(xiàn)在我們?cè)诒晃覀冋{(diào)試的程序上點(diǎn)擊那個(gè)"Check It"按鈕,被 OllyDBG 斷下:
00401323 |. E8 4C010000 CALL <JMP.&USER32.GetWindowTextA> ; GetWindowTextA
00401328 |. E8 A5000000 CALL CrackHea.004013D2 ; 關(guān)鍵,要按F7鍵跟進(jìn)去
0040132D |. 3BC6 CMP EAX,ESI ; 比較
0040132F |. 75 42 JNZ SHORT CrackHea.00401373 ; 不等則完蛋
00401331 |. EB 2C JMP SHORT CrackHea.0040135F
00401333 |. 4E 6F 77 20 7> ASCII "Now write a keyg"
00401343 |. 65 6E 20 61 6> ASCII "en and tut and y"
00401353 |. 6F 75 27 72 6> ASCII "ou're done.",0
0040135F |> 6A 00 PUSH 0 ; Style = MB_OK|MB_APPLMODAL
00401361 |. 68 0F304000 PUSH CrackHea.0040300F ; Title = "Crudd's Crack Head"
00401366 |. 68 33134000 PUSH CrackHea.00401333 ; Text = "Now write a keygen and tut and you're done."
0040136B |. FF75 08 PUSH DWORD PTR SS:[EBP+8] ; hOwner
0040136E |. E8 19010000 CALL <JMP.&USER32.MessageBoxA> ; MessageBoxA
從上面的代碼,我們很容易看出 00401328 地址處的 CALL CrackHea.004013D2 是關(guān)鍵,必須仔細(xì)跟蹤。而注冊(cè)成功則會(huì)顯示一個(gè)對(duì)話框,標(biāo)題是"Crudd's Crack Head",對(duì)話框顯示的內(nèi)容是"Now write a keygen and tut and you're done."現(xiàn)在我按一下 F8,準(zhǔn)備步進(jìn)到 00401328 地址處的那條 CALL CrackHea.004013D2 指令后再按 F7 鍵跟進(jìn)去。等等,怎么回事?怎么按一下 F8 鍵跑到這來了:
00401474 $- FF25 2C204000 JMP DWORD PTR DS:[<&USER32.GetWindowText> ; USER32.GetWindowTextA
0040147A $- FF25 30204000 JMP DWORD PTR DS:[<&USER32.LoadCursorA>] ; USER32.LoadCursorA
00401480 $- FF25 1C204000 JMP DWORD PTR DS:[<&USER32.LoadIconA>] ; USER32.LoadIconA
00401486 $- FF25 20204000 JMP DWORD PTR DS:[<&USER32.LoadMenuA>] ; USER32.LoadMenuA
0040148C $- FF25 24204000 JMP DWORD PTR DS:[<&USER32.MessageBoxA>] ; USER32.MessageBoxA
原來是跳到另一個(gè)斷點(diǎn)了。這個(gè)斷點(diǎn)我們不需要,按一下 F2 鍵刪掉它吧。刪掉 00401474 地址處的斷點(diǎn)后,我再按 F8 鍵,呵,完了,跑到 User32.dll 的領(lǐng)空了??匆幌?/span> OllyDBG 的標(biāo)題欄:"[CPU - 主線程, 模塊 - USER32],跑到系統(tǒng)領(lǐng)空了,OllyDBG 反匯編窗口中顯示代碼是這樣:
77D3213C 6A 0C PUSH 0C
77D3213E 68 A021D377 PUSH USER32.77D321A0
77D32143 E8 7864FEFF CALL USER32.77D185C0
怎么辦?別急,我們按一下 ALT+F9 組合鍵,呵,回來了:
00401328 |. E8 A5000000 CALL CrackHea.004013D2 ; 關(guān)鍵,要按F7鍵跟進(jìn)去
0040132D |. 3BC6 CMP EAX,ESI ; 比較
0040132F |. 75 42 JNZ SHORT CrackHea.00401373 ; 不等則完蛋
光標(biāo)停在 00401328 地址處的那條指令上?,F(xiàn)在我們按 F7 鍵跟進(jìn):
004013D2 /$ 56 PUSH ESI ; ESI入棧
004013D3 |. 33C0 XOR EAX,EAX ; EAX清零
004013D5 |. 8D35 C4334000 LEA ESI,DWORD PTR DS:[4033C4] ; 把注冊(cè)碼框中的數(shù)值送到ESI
004013DB |. 33C9 XOR ECX,ECX ; ECX清零
004013DD |. 33D2 XOR EDX,EDX ; EDX清零
004013DF |. 8A06 MOV AL,BYTE PTR DS:[ESI] ; 把注冊(cè)碼中的每個(gè)字符送到AL
004013E1 |. 46 INC ESI ; 指針加1,指向下一個(gè)字符
004013E2 |. 3C 2D CMP AL,2D ; 把取得的字符與16進(jìn)制值為2D的字符(即"-")比較,這里主要用于判斷輸入的是不是負(fù)數(shù)
004013E4 |. 75 08 JNZ SHORT CrackHea.004013EE ; 不等則跳
004013E6 |. BA FFFFFFFF MOV EDX,-1 ; 如果輸入的是負(fù)數(shù),則把-1送到EDX,即16進(jìn)制FFFFFFFF
004013EB |. 8A06 MOV AL,BYTE PTR DS:[ESI] ; 取"-"號(hào)后的第一個(gè)字符
004013ED |. 46 INC ESI ; 指針加1,指向再下一個(gè)字符
004013EE |> EB 0B JMP SHORT CrackHea.004013FB
004013F0 |> 2C 30 SUB AL,30 ; 每位字符減16進(jìn)制的30,因?yàn)檫@里都是數(shù)字,如1的ASCII碼是"31H",減30H后為1,即我們平時(shí)看到的數(shù)值
004013F2 |. 8D0C89 LEA ECX,DWORD PTR DS:[ECX+ECX*4] ; 把前面運(yùn)算后保存在ECX中的結(jié)果乘5再送到ECX
004013F5 |. 8D0C48 LEA ECX,DWORD PTR DS:[EAX+ECX*2] ; 每位字符運(yùn)算后的值與2倍上一位字符運(yùn)算后值相加后送ECX
004013F8 |. 8A06 MOV AL,BYTE PTR DS:[ESI] ; 取下一個(gè)字符
004013FA |. 46 INC ESI ; 指針加1,指向再下一個(gè)字符
004013FB |> 0AC0 OR AL,AL
004013FD |.^ 75 F1 JNZ SHORT CrackHea.004013F0 ; 上面一條和這一條指令主要是用來判斷是否已把用戶輸入的注冊(cè)碼計(jì)算完
004013FF |. 8D040A LEA EAX,DWORD PTR DS:[EDX+ECX] ; 把EDX中的值與經(jīng)過上面運(yùn)算后的ECX中值相加送到EAX
00401402 |. 33C2 XOR EAX,EDX ; 把EAX與EDX異或。如果我們輸入的是負(fù)數(shù),則此處功能就是把EAX中的值取反
00401404 |. 5E POP ESI ; ESI出棧??吹竭@條和下一條指令,我們要考慮一下這個(gè)ESI的值是哪里運(yùn)算得出的呢?
00401405 |. 81F6 53757A79 XOR ESI,797A7553 ; 把ESI中的值與797A7553H異或
0040140B \. C3 RETN
這里留下了一個(gè)問題:那個(gè) ESI 寄存器中的值是從哪運(yùn)算出來的?先不管這里,我們接著按 F8 鍵往下走,來到 0040140B 地址處的那條 RETN 指令(這里可以通過在調(diào)試選項(xiàng)的"命令"標(biāo)簽中勾選"使用 RET 代替 RETN"來更改返回指令的顯示方式),再按一下 F8,我們就走出 00401328 地址處的那個(gè) CALL 了?,F(xiàn)在我們回到了這里:
0040132D |. 3BC6 CMP EAX,ESI ; 比較
0040132F |. 75 42 JNZ SHORT CrackHea.00401373 ; 不等則完蛋
光標(biāo)停在了 0040132D 地址處的那條指令上。根據(jù)前面的分析,我們知道 EAX 中存放的是我們輸入的注冊(cè)碼經(jīng)過計(jì)算后的值。我們來看一下信息窗口:
ESI=E6B5F2F9
EAX=FF439EBE
左鍵選擇信息窗口中的 ESI=E6B5F2F9,再按右鍵,在彈出菜單上選"修改寄存器",我們會(huì)看到這樣一個(gè)窗口:
可能你的顯示跟我不一樣,因?yàn)檫@個(gè) crackme 中已經(jīng)說了每個(gè)機(jī)器的序列號(hào)不一樣。關(guān)掉上面的窗口,再對(duì)信息窗口中的 EAX=FF439EBE 做同樣操作:
由上圖我們知道了原來前面分析的對(duì)我們輸入的注冊(cè)碼進(jìn)行處理后的結(jié)果就是把字符格式轉(zhuǎn)為數(shù)字格式。我們?cè)瓉磔斎氲氖亲执?/span>"12345666",現(xiàn)在轉(zhuǎn)換為了數(shù)字 12345666。這下就很清楚了,隨便在上面那個(gè)修改 ESI 圖中顯示的有符號(hào)或無(wú)符號(hào)編輯框中復(fù)制一個(gè),粘貼到我們調(diào)試的程序中的編輯框中試一下:
呵呵,成功了。且慢高興,這個(gè) crackme 是要求寫出注冊(cè)機(jī)的。我們先不要求寫注冊(cè)機(jī),但注冊(cè)的算法我們要搞清楚。還記得我在前面說到的那個(gè) ESI 寄存器值的問題嗎?現(xiàn)在看看我們上面的分析,其實(shí)對(duì)做注冊(cè)機(jī)來說是沒有多少幫助的。要搞清注冊(cè)算法,必須知道上面那個(gè) ESI 寄存器值是如何產(chǎn)生的,這弄清楚后才能真正清楚這個(gè) crackme 算法。今天就先說到這里,關(guān)于如何追出 ESI 寄存器的值我就留到下一篇-內(nèi)存斷點(diǎn) 中再講吧。
內(nèi)存斷點(diǎn)
還記得上一篇的內(nèi)容嗎?在那篇文章中我們分析后發(fā)現(xiàn)一個(gè) ESI 寄存器值不知是從什么地方產(chǎn)生的,要弄清這個(gè)問題必須要找到生成這個(gè) ESI 值的計(jì)算部分。今天我們的任務(wù)就是使用 OllyDBG 的內(nèi)存斷點(diǎn)功能找到這個(gè)地方,搞清楚這個(gè)值是如何算出來的。這次分析的目標(biāo)程序還是上一篇的那個(gè) crackme,附件我就不再上傳了,用上篇中的附件就可以了。下面我們開始:
還記得我們上篇中所說的關(guān)鍵代碼的地方嗎?溫習(xí)一下:
00401323 |. E8 4C010000 CALL <JMP.&USER32.GetWindowTextA> ; GetWindowTextA
00401328 |. E8 A5000000 CALL CrackHea.004013D2 ; 關(guān)鍵,要按F7鍵跟進(jìn)去
0040132D |. 3BC6 CMP EAX,ESI ; 比較
0040132F |. 75 42 JNZ SHORT CrackHea.00401373 ; 不等則完蛋
我們重新用 OllyDBG 載入目標(biāo)程序,F9運(yùn)行來到上面代碼所在的地方(你上次設(shè)的斷點(diǎn)應(yīng)該沒刪吧?),我們向上看看能不能找到那個(gè) ESI 寄存器中最近是在哪里賦的值。哈哈,原來就在附近?。?/span>
我們現(xiàn)在知道 ESI 寄存器的值是從內(nèi)存地址 40339C 中送過來的,那內(nèi)存地址 40339C 中的數(shù)據(jù)是什么時(shí)候產(chǎn)生的呢?大家注意,我這里信息窗口中顯示的是 DS:[0040339C]=9FCF87AA,你那可能是 DS:[0040339C]=XXXXXXXX,這里的 XXXXXXXX 表示的是其它的值,就是說與我這里顯示的 9FCF87AA 不一樣。我們按上圖的操作在數(shù)據(jù)窗口中看一下:
從上圖我們可以看出內(nèi)存地址 40339C 處的值已經(jīng)有了,說明早就算過了。現(xiàn)在怎么辦呢?我們考慮一下,看情況程序是把這個(gè)值算出來以后寫在這個(gè)內(nèi)存地址,那我們要是能讓 OllyDBG 在程序開始往這個(gè)內(nèi)存地址寫東西的時(shí)候中斷下來,不就有可能知道目標(biāo)程序是怎么算出這個(gè)值的嗎?說干就干,我們?cè)?/span> OllyDBG 的菜單上點(diǎn) 調(diào)試->重新開始,或者按 CTR+F2 組合鍵(還可以點(diǎn)擊工具欄上的那個(gè)有兩個(gè)實(shí)心左箭頭的圖標(biāo))來重新載入程序。這時(shí)會(huì)跳出一個(gè)"進(jìn)程仍處于激活狀態(tài)"的對(duì)話框(我們可以在在調(diào)試選項(xiàng)的安全標(biāo)簽下把"終止活動(dòng)進(jìn)程時(shí)警告"這條前面的勾去掉,這樣下次就不會(huì)出現(xiàn)這個(gè)對(duì)話框了),問我們是否要終止進(jìn)程。這里我們選"是",程序被重新載入,我們停在下面這一句上:
00401000 >/$ 6A 00 PUSH 0 ; pModule = NULL
現(xiàn)在我們就要來設(shè)內(nèi)存斷點(diǎn)了。在 OllyDBG 中一般我們用到的內(nèi)存斷點(diǎn)有內(nèi)存訪問和內(nèi)存寫入斷點(diǎn)。內(nèi)存訪問斷點(diǎn)就是指程序訪問內(nèi)存中我們指定的內(nèi)存地址時(shí)中斷,內(nèi)存寫入斷點(diǎn)就是指程序往我們指定的內(nèi)存地址中寫東西時(shí)中斷。更多關(guān)于斷點(diǎn)的知識(shí)大家可以參考 論壇精華7->基礎(chǔ)知識(shí)->斷點(diǎn)技巧->斷點(diǎn)原理 這篇 Lenus 兄弟寫的《如何對(duì)抗硬件斷點(diǎn)之一 --- 調(diào)試寄存器》文章,也可以看這個(gè)帖:http://bbs.pediy.com/showthread.php?threadid=10829。根據(jù)當(dāng)前我們調(diào)試的具體程序的情況,我們選用內(nèi)存寫入斷點(diǎn)。還記得前面我叫大家記住的那個(gè) 40339C 內(nèi)存地址嗎?現(xiàn)在我們要用上了。我們先在 OllyDBG 的數(shù)據(jù)窗口中左鍵點(diǎn)擊一下,再右擊,會(huì)彈出一個(gè)如下圖所示的菜單。我們選擇其中的轉(zhuǎn)到->表達(dá)式(也可以左鍵點(diǎn)擊數(shù)據(jù)窗口后按 CTR+G 組合鍵)。如下圖:
現(xiàn)在將會(huì)出現(xiàn)這樣一個(gè)對(duì)話框:
我們?cè)谏厦婺莻€(gè)編輯框中輸入我們想查看內(nèi)容的內(nèi)存地址 40339C,然后點(diǎn)確定按鈕,數(shù)據(jù)窗口中顯示如下:
我們可以看到,40339C 地址開始處的這段內(nèi)存里面還沒有內(nèi)容。我們現(xiàn)在在 40339C 地址處后面的 HEX 數(shù)據(jù)或 ASCII 欄中按住左鍵往后拖放,選擇一段。內(nèi)存斷點(diǎn)的特性就是不管你選幾個(gè)字節(jié),OllyDBG 都會(huì)分配 4096 字節(jié)的內(nèi)存區(qū)。這里我就選從 40339C 地址處開始的四個(gè)字節(jié),主要是為了讓大家提前了解一下硬件斷點(diǎn)的設(shè)法,因?yàn)橛布帱c(diǎn)最多只能選 4 個(gè)字節(jié)。選中部分會(huì)顯示為灰色。選好以后松開鼠標(biāo)左鍵,在我們選中的灰色部分上右擊:
經(jīng)過上面的操作,我們的內(nèi)存斷點(diǎn)就設(shè)好了(這里還有個(gè)要注意的地方:內(nèi)存斷點(diǎn)只在當(dāng)前調(diào)試的進(jìn)程中有效,就是說你如果重新載入程序的話內(nèi)存斷點(diǎn)就自動(dòng)刪除了。且內(nèi)存斷點(diǎn)每一時(shí)刻只能有一個(gè)。就是說你不能像按 F2 鍵那樣同時(shí)設(shè)置多個(gè)斷點(diǎn))?,F(xiàn)在按 F9 鍵讓程序運(yùn)行,呵,OllyDBG 中斷了!
7C932F39 8808 MOV BYTE PTR DS:[EAX],CL ; 這就是我們第一次斷下來的地方
7C932F3B 40 INC EAX
7C932F3C 4F DEC EDI
7C932F3D 4E DEC ESI
7C932F3E ^ 75 CB JNZ SHORT ntdll.7C932F0B
7C932F40 8B4D 10 MOV ECX,DWORD PTR SS:[EBP+10]
上面就是我們中斷后反匯編窗口中的代碼。如果你是其它系統(tǒng),如 Win98 的話,可能會(huì)有所不同。沒關(guān)系,這里不是關(guān)鍵。我們看一下領(lǐng)空,原來是在 ntdll.dll 內(nèi)。系統(tǒng)領(lǐng)空,我們現(xiàn)在要考慮返回到程序領(lǐng)空。返回前我們看一下數(shù)據(jù)窗口:
現(xiàn)在我們轉(zhuǎn)到反匯編窗口,右擊鼠標(biāo),在彈出菜單上選擇斷點(diǎn)->刪除內(nèi)存斷點(diǎn),這樣內(nèi)存斷點(diǎn)就被刪除了。
現(xiàn)在我們來按一下 ALT+F9 組合鍵,我們來到下面的代碼:
00401431 |. 8D35 9C334000 LEA ESI,DWORD PTR DS:[40339C] ; ALT+F9返回后來到的位置
00401437 |. 0FB60D EC334000 MOVZX ECX,BYTE PTR DS:[4033EC]
0040143E |. 33FF XOR EDI,EDI
我們把反匯編窗口往上翻翻,呵,原來就在我們上一篇分析的代碼下面???
現(xiàn)在我們?cè)?/span> 0040140C 地址處那條指令上按 F2 設(shè)置一個(gè)斷點(diǎn),現(xiàn)在我們按 CTR+F2 組合鍵重新載入程序,載入后按 F9 鍵運(yùn)行,我們將會(huì)中斷在我們剛才在 0040140C 地址下的那個(gè)斷點(diǎn)處:
0040140C /$ 60 PUSHAD
0040140D |. 6A 00 PUSH 0 ; /RootPathName = NULL
0040140F |. E8 B4000000 CALL <JMP.&KERNEL32.GetDriveTypeA> ; \GetDriveTypeA
00401414 |. A2 EC334000 MOV BYTE PTR DS:[4033EC],AL ; 磁盤類型參數(shù)送內(nèi)存地址4033EC
00401419 |. 6A 00 PUSH 0 ; /pFileSystemNameSize = NULL
0040141B |. 6A 00 PUSH 0 ; |pFileSystemNameBuffer = NULL
0040141D |. 6A 00 PUSH 0 ; |pFileSystemFlags = NULL
0040141F |. 6A 00 PUSH 0 ; |pMaxFilenameLength = NULL
00401421 |. 6A 00 PUSH 0 ; |pVolumeSerialNumber = NULL
00401423 |. 6A 0B PUSH 0B ; |MaxVolumeNameSize = B (11.)
00401425 |. 68 9C334000 PUSH CrackHea.0040339C ; |VolumeNameBuffer = CrackHea.0040339C
0040142A |. 6A 00 PUSH 0 ; |RootPathName = NULL
0040142C |. E8 A3000000 CALL <JMP.&KERNEL32.GetVolumeInformationA> ; \GetVolumeInformationA
00401431 |. 8D35 9C334000 LEA ESI,DWORD PTR DS:[40339C] ; 把crackme程序所在分區(qū)的卷標(biāo)名稱送到ESI
00401437 |. 0FB60D EC334000 MOVZX ECX,BYTE PTR DS:[4033EC] ; 磁盤類型參數(shù)送ECX
0040143E |. 33FF XOR EDI,EDI ; 把EDI清零
00401440 |> 8BC1 MOV EAX,ECX ; 磁盤類型參數(shù)送EAX
00401442 |. 8B1E MOV EBX,DWORD PTR DS:[ESI] ; 把卷標(biāo)名作為數(shù)值送到 EBX
00401444 |. F7E3 MUL EBX ; 循環(huán)遞減取磁盤類型參數(shù)值與卷標(biāo)名值相乘
00401446 |. 03F8 ADD EDI,EAX ; 每次計(jì)算結(jié)果再加上上次計(jì)算結(jié)果保存在EDI中
00401448 |. 49 DEC ECX ; 把磁盤類型參數(shù)作為循環(huán)次數(shù),依次遞減
00401449 |. 83F9 00 CMP ECX,0 ; 判斷是否計(jì)算完
0040144C |.^ 75 F2 JNZ SHORT CrackHea.00401440 ; 沒完繼續(xù)
0040144E |. 893D 9C334000 MOV DWORD PTR DS:[40339C],EDI ; 把計(jì)算后值送到內(nèi)存地址40339C,這就是我們后來在ESI中看到的值
00401454 |. 61 POPAD
00401455 \. C3 RETN
通過上面的分析,我們知道基本算法是這樣的:先用 GetDriveTypeA 函數(shù)獲取磁盤類型參數(shù),再用 GetVolumeInformationA 函數(shù)獲取這個(gè) crackme 程序所在分區(qū)的卷標(biāo)。如我把這個(gè) Crackme 程序放在 F:\OD教程\crackhead\ 目錄下,而我 F 盤設(shè)置的卷標(biāo)是 GAME,則這里獲取的就是 GAME,ASCII 碼為"47414D45"。但我們發(fā)現(xiàn)一個(gè)問題:假如原來我們?cè)跀?shù)據(jù)窗口中看到的地址 40339C 處的 16 進(jìn)制代碼是"47414D45",即"GAME",但經(jīng)過地址 00401442 處的那條 MOV EBX,DWORD PTR DS:[ESI] 指令后,我們卻發(fā)現(xiàn) EBX 中的值是"454D4147",正好把我們上面那個(gè)"47414D45"反過來了。為什么會(huì)這樣呢?如果大家對(duì) x86系列 CPU 的存儲(chǔ)方式了解的話,這里就容易理解了。我們知道"GAME"有四個(gè)字節(jié),即 ASCII 碼為"47414D45"。我們看一下數(shù)據(jù)窗口中的情況:
0040339C 47 41 4D 45 00 00 00 00 00 00 00 00 00 00 00 00 GAME............
大家可以看出來內(nèi)存地址 40339CH 到 40339FH 分別按順序存放的是 47 41 4D 45。
如下圖:
系統(tǒng)存儲(chǔ)的原則為"高高低低",即低字節(jié)存放在地址較低的字節(jié)單元中,高字節(jié)存放在地址較高的字節(jié)單元中。比如一個(gè)字由兩個(gè)字節(jié)組成,像這樣:12 34 ,這里的高字節(jié)就是 12 ,低字節(jié)就是 34。上面的那條指令 MOV EBX,DWORD PTR DS:[ESI] 等同于 MOV EBX,DWORD PTR DS:[40339C]。注意這里是 DWORD,即"雙字",由 4 個(gè)連續(xù)的字節(jié)構(gòu)成。而取地址為 40339C 的雙字單元中的內(nèi)容時(shí),我們應(yīng)該得到的是"454D4147",即由高字節(jié)到低字節(jié)順序的值。因此經(jīng)過 MOV EBX,DWORD PTR DS:[ESI] 這條指令,就是把從地址 40339C 開始處的值送到 EBX,所以我們得到了"454D4147"。好了,這里弄清楚了,我們?cè)俳又勥@個(gè)程序的算法。前面我們已經(jīng)說了取磁盤類型參數(shù)做循環(huán)次數(shù),再取卷標(biāo)值 ASCII 碼的逆序作為數(shù)值,有了這兩個(gè)值就開始計(jì)算了?,F(xiàn)在我們把磁盤類型值作為 n,卷標(biāo)值 ASCII 碼的逆序數(shù)值作為 a,最后得出的結(jié)果作為 b,有這樣的計(jì)算過程:
第一次:b = a * n
第二次:b = a * (n - 1) + b
第三次:b = a * (n - 2) + b
…
第 n 次:b = a * 1 + b
可得出公式為 b = a * [n + (n - 1) + (n - 2) + … + 1] = a * [n * (n + 1) / 2]
還記得上一篇我們的分析嗎?看這一句:
00401405 |. 81F6 53757A79 XOR ESI,797A7553 ; 把ESI中的值與797A7553H異或
這里算出來的 b 最后還要和 797A7553H 異或一下才是真正的注冊(cè)碼。只要你對(duì)編程有所了解,這個(gè)注冊(cè)機(jī)就很好寫了。如果用匯編來寫這個(gè)注冊(cè)機(jī)的話就更簡(jiǎn)單了,很多內(nèi)容可以直接照抄。
到此已經(jīng)差不多了,最后還有幾個(gè)東西也說一下吧:
1、上面用到了兩個(gè) API 函數(shù),一個(gè)是 GetDriveTypeA,還有一個(gè)是 GetVolumeInformationA,關(guān)于這兩個(gè)函數(shù)的具體用法我就不多說了,大家可以查一下 MSDN。這里只要大家注意函數(shù)參數(shù)傳遞的次序,即調(diào)用約定。先看一下這里:
00401419 |. 6A 00 PUSH 0 ; /pFileSystemNameSize = NULL
0040141B |. 6A 00 PUSH 0 ; |pFileSystemNameBuffer = NULL
0040141D |. 6A 00 PUSH 0 ; |pFileSystemFlags = NULL
0040141F |. 6A 00 PUSH 0 ; |pMaxFilenameLength = NULL
00401421 |. 6A 00 PUSH 0 ; |pVolumeSerialNumber = NULL
00401423 |. 6A 0B PUSH 0B ; |MaxVolumeNameSize = B (11.)
00401425 |. 68 9C334000 PUSH CrackHea.0040339C ; |VolumeNameBuffer = CrackHea.0040339C
0040142A |. 6A 00 PUSH 0 ; |RootPathName = NULL
0040142C |. E8 A3000000 CALL <JMP.&KERNEL32.GetVolumeInformationA> ; \GetVolumeInformationA
把上面代碼后的 OllyDBG 自動(dòng)添加的注釋與 MSDN 中的函數(shù)原型比較一下:
BOOL GetVolumeInformation(
LPCTSTR lpRootPathName, // address of root directory of the file system
LPTSTR lpVolumeNameBuffer, // address of name of the volume
DWORD nVolumeNameSize, // length of lpVolumeNameBuffer
LPDWORD lpVolumeSerialNumber, // address of volume serial number
LPDWORD lpMaximumComponentLength, // address of system's maximum filename length
LPDWORD lpFileSystemFlags, // address of file system flags
LPTSTR lpFileSystemNameBuffer, // address of name of file system
DWORD nFileSystemNameSize // length of lpFileSystemNameBuffer
);
大家應(yīng)該看出來點(diǎn)什么了吧?函數(shù)調(diào)用是先把最后一個(gè)參數(shù)壓棧,參數(shù)壓棧順序是從后往前。這就是一般比較常見的 stdcall 調(diào)用約定。
2、我在前面的 00401414 地址處的那條 MOV BYTE PTR DS:[4033EC],AL 指令后加的注釋是"磁盤類型參數(shù)送內(nèi)存地址4033EC"。為什么這樣寫?大家把前一句和這一句合起來看一下:
0040140F |. E8 B4000000 CALL <JMP.&KERNEL32.GetDriveTypeA> ; \GetDriveTypeA
00401414 |. A2 EC334000 MOV BYTE PTR DS:[4033EC],AL ; 磁盤類型參數(shù)送內(nèi)存地址4033EC
地址 0040140F 處的那條指令是調(diào)用 GetDriveTypeA 函數(shù),一般函數(shù)調(diào)用后的返回值都保存在 EAX 中,所以地址 00401414 處的那一句 MOV BYTE PTR DS:[4033EC],AL 就是傳遞返回值。查一下 MSDN 可以知道 GetDriveTypeA 函數(shù)的返回值有這幾個(gè):
Value Meaning 返回在EAX中的值
DRIVE_UNKNOWN The drive type cannot be determined. 0
DRIVE_NO_ROOT_DIR The root directory does not exist. 1
DRIVE_REMOVABLE The disk can be removed from the drive. 2
DRIVE_FIXED The disk cannot be removed from the drive. 3
DRIVE_REMOTE The drive is a remote (network) drive. 4
DRIVE_CDROM The drive is a CD-ROM drive. 5
DRIVE_RAMDISK The drive is a RAM disk. 6
上面那個(gè)"返回在EAX中的值"是我加的,我這里返回的是 3,即磁盤不可從驅(qū)動(dòng)器上刪除。
3、通過分析這個(gè)程序的算法,我們發(fā)現(xiàn)這個(gè)注冊(cè)算法是有漏洞的。如果我的分區(qū)沒有卷標(biāo)的話,則卷標(biāo)值為 0,最后的注冊(cè)碼就是 797A7553H,即十進(jìn)制 2038068563。而如果你的卷標(biāo)和我一樣,且磁盤類型一樣的話,注冊(cè)碼也會(huì)一樣,并不能真正做到一機(jī)一碼。
消息斷點(diǎn)及 RUN 跟蹤
找了幾十個(gè)不同語(yǔ)言編寫的 crackme,發(fā)現(xiàn)只用消息斷點(diǎn)的話有很多并不能真正到達(dá)我們要找的關(guān)鍵位置,想想還是把消息斷點(diǎn)和 RUN 跟蹤結(jié)合在一起講,更有效一點(diǎn)。關(guān)于消息斷點(diǎn)的更多內(nèi)容大家可以參考 jingulong 兄的那篇《幾種典型程序Button處理代碼的定位》的文章,堪稱經(jīng)典之作。今天仍然選擇 crackmes.cjb.net 鏡像打包中的一個(gè)名稱為 cycle 的 crackme。按照慣例,我們先運(yùn)行一下這個(gè)程序看看:
我們輸入用戶名 CCDebuger,序列號(hào) 78787878,點(diǎn)上面那個(gè)"Check"按鈕,呵, 沒反應(yīng)!看來是要注冊(cè)碼正確才有動(dòng)靜?,F(xiàn)在關(guān)掉這個(gè) crackme,用 PEiD 查一下殼,原來是 MASM32 / TASM32 [Overlay]。啟動(dòng) OllyDBG 載入這個(gè)程序,F9讓它運(yùn)行。這個(gè)程序按我們前面講的采用字串參考或函數(shù)參考的方法都很容易斷下來。但我們今天主要學(xué)習(xí)的是消息斷點(diǎn)及 RUN 跟蹤,就先用消息斷點(diǎn)來斷這個(gè)程序吧。在設(shè)消息斷點(diǎn)前,有兩個(gè)內(nèi)容我們要簡(jiǎn)單了解一下:首先我們要了解的是消息。Windows 的中文翻譯就是"窗口",而 Windows 上面的應(yīng)用程序也都是通過窗口來與用戶交互的?,F(xiàn)在就有一個(gè)問題,應(yīng)用程序是如何知道用戶作了什么樣的操作的?這里就要用到消息了。Windows 是個(gè)基于消息的系統(tǒng),它在應(yīng)用程序開始執(zhí)行后,為該程序創(chuàng)建一個(gè)"消息隊(duì)列",用來存放該程序可能創(chuàng)建的各種不同窗口的信息。比如你創(chuàng)建窗口、點(diǎn)擊按鈕、移動(dòng)鼠標(biāo)等等,都是通過消息來完成的。通俗的說,Windows 就像一個(gè)中間人,你要干什么事是先通知它,然后它才通過傳遞消息的方式通知應(yīng)用程序作出相應(yīng)的操作。說到這,又有個(gè)問題了,在 Windows 下有多個(gè)程序都在運(yùn)行,那我點(diǎn)了某個(gè)按鈕,或把某個(gè)窗口最大化,Windows 知道我是點(diǎn)的哪個(gè)嗎?這里就要說到另一個(gè)內(nèi)容:句柄(handle)了。句柄一般是個(gè) 32 位的數(shù),表示一個(gè)對(duì)象。Windows 通過使用句柄來標(biāo)識(shí)它代表的對(duì)象。比如你點(diǎn)擊某個(gè)按鈕,Windows 就是通過句柄來判斷你是點(diǎn)擊了那一個(gè)按鈕,然后發(fā)送相應(yīng)的消息通知程序。說完這些我們?cè)倩氐轿覀冋{(diào)試的程序上來,你應(yīng)該已經(jīng)用 OllyDBG 把這個(gè) crackme 載入并按 F9 鍵運(yùn)行了吧?現(xiàn)在我們輸入用戶名"CCDebuger",序列號(hào)"78787878",先不要點(diǎn)那個(gè)"Check"按鈕,我們來到 OllyDBG 中,點(diǎn)擊菜單 查看->窗口(或者點(diǎn)擊工具欄上那個(gè)"W"的圖標(biāo)),我們會(huì)看到以下內(nèi)容:
我們?cè)谶x中的條目上點(diǎn)右鍵,再選擇上圖所示的菜單項(xiàng),會(huì)來到下面這個(gè)窗口:
現(xiàn)在我們點(diǎn)擊圖上的那個(gè)下拉菜單,呵,原來里面的消息真不少。這么多消息我們選哪個(gè)呢?注冊(cè)是個(gè)按鈕,我們就在按下按鈕再松開時(shí)讓程序中斷。查一下 MSDN,我們知道這個(gè)消息應(yīng)該是 WM_LBUTTON_UP,看字面意思也可以知道是左鍵松開時(shí)的消息:
從下拉菜單中選中那個(gè) 202 WM_LBUTTON_UP,再按確定按鈕,我們的消息斷點(diǎn)就設(shè)好了?,F(xiàn)在我們還要做一件事,就是把 RUN 跟蹤打開。有人可能要問,這個(gè) RUN 跟蹤是干什么的?簡(jiǎn)單的說,RUN 跟蹤就是把被調(diào)試程序執(zhí)行過的指令保存下來,讓你可以查看被調(diào)試程序運(yùn)行期間干了哪些事。RUN 跟蹤會(huì)把地址、寄存器的內(nèi)容、消息以及已知的操作數(shù)記錄到 RUN 跟蹤緩沖區(qū)中,你可以通過查看 RUN 跟蹤的記錄來了解程序執(zhí)行了那些指令。在這還要注意一個(gè)緩沖區(qū)大小的問題,如果執(zhí)行的指令太多,緩沖區(qū)滿了的話,就會(huì)自動(dòng)丟棄前面老的記錄。我們可以在調(diào)試選項(xiàng)->跟蹤中設(shè)置:
現(xiàn)在我們回到 OllyDBG 中,點(diǎn)擊菜單調(diào)試->打開或清除 RUN 跟蹤(第一次點(diǎn)這個(gè)菜單是打開 RUN 跟蹤,在打開的情況下點(diǎn)擊就是清除 RUN 跟蹤的記錄,對(duì) RUN 跟蹤熟悉時(shí)還可以設(shè)置條件),保證當(dāng)前在我們調(diào)試的程序領(lǐng)空,在反匯編窗口中點(diǎn)擊右鍵,在彈出菜單中選擇 RUN 跟蹤->添加所有函數(shù)過程的入口:
我們可以看到 OllyDBG 把識(shí)別出的函數(shù)過程都在前面加了灰色條:
現(xiàn)在我們回到那個(gè) crackme 中按那個(gè)"Check"按鈕,被 OllyDBG 斷下了:
這時(shí)我們點(diǎn)擊菜單查看->內(nèi)存,或者點(diǎn)擊工具欄上那個(gè)"M"按鈕(也可以按組合鍵 ALT+M),來到內(nèi)存映射窗口:
為什么在這里設(shè)訪問斷點(diǎn),我也說一下。我們可以看一下常見的 PE 文件,沒加過殼的用 PEiD 檢測(cè)是這樣:
點(diǎn)一下 EP 段后面那個(gè)">"符號(hào),我們可以看到以下內(nèi)容:
看完上面的圖我們應(yīng)該了解為什么在 401000 處的代碼段下訪問斷點(diǎn)了,我們這里的意思就是在消息斷點(diǎn)斷下后,只要按 F9 鍵運(yùn)行時(shí)執(zhí)行到程序代碼段的指令我們就中斷,這樣就可以回到程序領(lǐng)空了(當(dāng)然在 401000 處所在的段不是絕對(duì)的,我們主要是要看程序的代碼段在什么位置,其實(shí)在上面圖中 OllyDBG 內(nèi)存窗口的"包含"欄中我們就可以看得很清楚了)。設(shè)好訪問斷點(diǎn)后我們按 F9 鍵,被 OllyDBG 斷下:
現(xiàn)在我們先不管,按 F9 鍵(或者按 CTR+F12 組合鍵跟蹤步過)讓程序運(yùn)行,再點(diǎn)擊菜單查看->RUN 跟蹤,或者點(diǎn)擊工具欄上的那個(gè)"…"符號(hào),打開 RUN 跟蹤的記錄窗口看看:
我們現(xiàn)在再來看看統(tǒng)計(jì)的情況:
在地址 401082 處的那條指令上雙擊一下,來到以下位置:
現(xiàn)在我們?cè)诘刂?/span> 4010A6 處的那條指令上按 F2,刪除所有其它的斷點(diǎn),點(diǎn)菜單調(diào)試->關(guān)閉 RUN 跟蹤,現(xiàn)在我們就可以開始分析了:
004010E2 |. 8BFE MOV EDI,ESI ; 用戶名送 EDI
004010E4 |. 03F8 ADD EDI,EAX
004010E6 |. FC CLD
004010E7 |. F3:A4 REP MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[ESI]
004010E9 |. 33C9 XOR ECX,ECX ; 清零,設(shè)循環(huán)計(jì)數(shù)器
004010EB |. BE 71214000 MOV ESI,cycle.00402171 ; 注冊(cè)碼送ESI
004010F0 |> 41 INC ECX
004010F1 |. AC LODS BYTE PTR DS:[ESI] ; 取注冊(cè)碼的每個(gè)字符
004010F2 |. 0AC0 OR AL,AL ; 判斷是否為空
004010F4 |. 74 0A JE SHORT cycle.00401100 ; 沒有則跳走
004010F6 |. 3C 7E CMP AL,7E ; 判斷字符是否為非ASCII字符
004010F8 |. 7F 06 JG SHORT cycle.00401100 ; 非ASCII字符跳走
004010FA |. 3C 30 CMP AL,30 ; 看是否小于30H,主要是判斷是不是數(shù)字或字母等
004010FC |. 72 02 JB SHORT cycle.00401100 ; 小于跳走
004010FE |.^ EB F0 JMP SHORT cycle.004010F0
00401100 |> 83F9 11 CMP ECX,11 ; 比較注冊(cè)碼位數(shù),必須為十進(jìn)制17位
00401103 |. 75 1A JNZ SHORT cycle.0040111F
00401105 |. E8 E7000000 CALL cycle.004011F1 ; 關(guān)鍵,F7跟進(jìn)去
0040110A |. B9 01FF0000 MOV ECX,0FF01
0040110F |. 51 PUSH ECX
00401110 |. E8 7B000000 CALL cycle.00401190 ; 關(guān)鍵,跟進(jìn)去
00401115 |. 83F9 01 CMP ECX,1
00401118 |. 74 06 JE SHORT cycle.00401120
0040111A |> E8 47000000 CALL cycle.00401166 ; 注冊(cè)失敗對(duì)話框
0040111F |> C3 RETN
00401120 |> A1 68214000 MOV EAX,DWORD PTR DS:[402168]
00401125 |. 8B1D 6C214000 MOV EBX,DWORD PTR DS:[40216C]
0040112B |. 33C3 XOR EAX,EBX
0040112D |. 3305 82214000 XOR EAX,DWORD PTR DS:[402182]
00401133 |. 0D 40404040 OR EAX,40404040
00401138 |. 25 77777777 AND EAX,77777777
0040113D |. 3305 79214000 XOR EAX,DWORD PTR DS:[402179]
00401143 |. 3305 7D214000 XOR EAX,DWORD PTR DS:[40217D]
00401149 |.^ 75 CF JNZ SHORT cycle.0040111A ; 這里跳走就完蛋
0040114B |. E8 2B000000 CALL cycle.0040117B ; 注冊(cè)成功對(duì)話框
寫到這準(zhǔn)備跟蹤算法時(shí),才發(fā)現(xiàn)這個(gè) crackme 還是挺復(fù)雜的,具體算法我就不寫了,實(shí)在沒那么多時(shí)間詳細(xì)跟蹤。有興趣的可以跟一下,注冊(cè)碼是17位,用戶名采用復(fù)制的方式擴(kuò)展到 16 位,如我輸入"CCDebuger",擴(kuò)展后就是"CCDebugerCCDebug"。大致是先取擴(kuò)展后用戶名的前 8 位和注冊(cè)碼的前 8 位,把用戶名的前四位和后四位分別與注冊(cè)碼的前四位和后四位進(jìn)行運(yùn)算,算完后再把擴(kuò)展后用戶名的后 8 位和注冊(cè)碼的后 8 位分兩部分,再與前面用戶名和注冊(cè)碼的前 8 位計(jì)算后的值進(jìn)行異或計(jì)算,最后結(jié)果等于 0 就成功。注冊(cè)碼的第 17 位我尚未發(fā)現(xiàn)有何用處。對(duì)于新手來說,可能這個(gè) crackme 的難度大了一點(diǎn)。沒關(guān)系,我們主要是學(xué)習(xí) OllyDBG 的使用,方法掌握就可以了。
最后說明一下:
1、這個(gè)程序在設(shè)置了消息斷點(diǎn)后可以省略在代碼段上設(shè)訪問斷點(diǎn)那一步,直接打開 RUN 跟蹤,消息斷點(diǎn)斷下后按 CTR+F12 組合鍵讓程序執(zhí)行,RUN 跟蹤記錄中就可以找到關(guān)鍵地方。
2、對(duì)于這個(gè)程序,你可以不設(shè)消息斷點(diǎn),在輸入用戶名和注冊(cè)碼后先不按那個(gè)"Check"按鈕,直接打開 RUN 跟蹤,添加"所有函數(shù)過程的入口"后再回到程序中點(diǎn)"Check"按鈕,這時(shí)在 OllyDBG 中打開 RUN 跟蹤記錄同樣可以找到關(guān)鍵位置。
匯編功能
今天我們的目標(biāo)程序是 MyUninstaller 1.34 版。這是一個(gè)非常小的程序卸載工具,VC6編寫,大小只有61K。我拿到的這個(gè)是上次閃電狼兄弟給我的,附帶在里面的簡(jiǎn)體中文語(yǔ)言文件是由六芒星制作的。這個(gè)程序有個(gè)毛?。壕褪窃诹谐龅目尚遁d程序上雙擊查看屬性時(shí),彈出的屬性窗口的字體非常難看,應(yīng)該就是系統(tǒng)字體(SYSTEM_FONT):
我們今天的目標(biāo)就是利用 OllyDBG 的匯編功能把上面顯示的字體改成我們常見的9號(hào)(小五)宋體。首先我們用 OllyDBG 載入程序,按 CTR+N 組合鍵查找一下有哪些 API 函數(shù),只發(fā)現(xiàn)一個(gè)和設(shè)置字體相關(guān)的 CreateFontIndirectA。現(xiàn)在我們按鼠標(biāo)右鍵,選擇"在每個(gè)參考上設(shè)置斷點(diǎn)",關(guān)掉名稱對(duì)話框,F9運(yùn)行,程序已經(jīng)運(yùn)行起來了。我們?cè)诔绦虻牧斜砜蛑须S便找一項(xiàng)雙擊一下,很不幸,那個(gè)字體難看的界面又出現(xiàn)了,OllyDBG 沒有任何動(dòng)作??梢妱?chuàng)建這個(gè)窗口的時(shí)候根本沒調(diào)用 CreateFontIndirectA,問題現(xiàn)在就變得有點(diǎn)復(fù)雜了。先點(diǎn)確定把這個(gè)字體難看的對(duì)話框關(guān)閉,現(xiàn)在我們從另一個(gè)方面考慮:既然沒有調(diào)用設(shè)置字體的函數(shù),那我們來看看這個(gè)窗口是如何創(chuàng)建的,跟蹤窗口創(chuàng)建過程可能會(huì)找到一些對(duì)我們有用的信息?,F(xiàn)在我們?cè)倩氐轿覀冋{(diào)試程序的領(lǐng)空,按 CTR+N 看一下,發(fā)現(xiàn) CreateWindowExA 這個(gè) API 函數(shù)比較可疑。我們?cè)?/span> CreateWindowExA 函數(shù)的每個(gè)參考上設(shè)上斷點(diǎn),在 MyUninstaller 的列表框中再隨便找一項(xiàng)雙擊一下,被 OllyDBG 斷下:
00408F5E |. FF15 98B24000 |CALL DWORD PTR DS:[<&USER32.CreateWindowExA>] ; \斷在這里
上下翻看一下代碼:
00408F3B |. 50 |PUSH EAX ; |hInst
00408F3C |. 8B45 C0 |MOV EAX,DWORD PTR SS:[EBP-40] ; |
00408F3F |. 6A 00 |PUSH 0 ; |hMenu = NULL
00408F41 |. 03C6 |ADD EAX,ESI ; |
00408F43 |. FF75 08 |PUSH DWORD PTR SS:[EBP+8] ; |hParent
00408F46 |. FF75 D0 |PUSH DWORD PTR SS:[EBP-30] ; |Height
00408F49 |. 57 |PUSH EDI ; |Width
00408F4A |. 50 |PUSH EAX ; |Y
00408F4B |. FF75 BC |PUSH DWORD PTR SS:[EBP-44] ; |X
00408F4E |. FF75 EC |PUSH DWORD PTR SS:[EBP-14] ; |Style
00408F51 |. 68 80DE4000 |PUSH myuninst.0040DE80 ; |WindowName = ""
00408F56 |. 68 DCD94000 |PUSH myuninst.0040D9DC ; |Class = "STATIC"
00408F5B |. FF75 D4 |PUSH DWORD PTR SS:[EBP-2C] ; |ExtStyle
00408F5E |. FF15 98B24000 |CALL DWORD PTR DS:[<&USER32.CreateWindowExA>] ; \斷在這里
00408F64 | 6A 00 |PUSH 0 ; 第一處要修改的地方
00408F66 | 8945 F4 |MOV DWORD PTR SS:[EBP-C],EAX
00408F69 |. E8 A098FFFF |CALL <myuninst.sub_40280E>
00408F6E |. 50 |PUSH EAX ; |hInst
00408F6F |. 8B45 DC |MOV EAX,DWORD PTR SS:[EBP-24] ; |
00408F72 |. 6A 00 |PUSH 0 ; |hMenu = NULL
00408F74 |. 03F0 |ADD ESI,EAX ; |
00408F76 |. FF75 08 |PUSH DWORD PTR SS:[EBP+8] ; |hParent
00408F79 |. FF75 CC |PUSH DWORD PTR SS:[EBP-34] ; |Height
00408F7C |. 53 |PUSH EBX ; |Width
00408F7D |. 56 |PUSH ESI ; |Y
00408F7E |. FF75 D8 |PUSH DWORD PTR SS:[EBP-28] ; |X
00408F81 |. FF75 E8 |PUSH DWORD PTR SS:[EBP-18] ; |Style
00408F84 |. 68 80DE4000 |PUSH myuninst.0040DE80 ; |WindowName = ""
00408F89 |. 68 D4D94000 |PUSH myuninst.0040D9D4 ; |Class = "EDIT"
00408F8E |. FF75 B8 |PUSH DWORD PTR SS:[EBP-48] ; |ExtStyle
00408F91 |. FF15 98B24000 |CALL DWORD PTR DS:[<&USER32.CreateWindowExA>] ; \CreateWindowExA
00408F97 | 8945 F0 |MOV DWORD PTR SS:[EBP-10],EAX ; 第二處要修改的地方
00408F9A | 8B45 F8 |MOV EAX,DWORD PTR SS:[EBP-8]
00408F9D |. FF30 |PUSH DWORD PTR DS:[EAX] ; /<%s>
00408F9F |. 8D85 B0FEFFFF |LEA EAX,DWORD PTR SS:[EBP-150] ; |
00408FA5 |. 68 D0D94000 |PUSH myuninst.0040D9D0 ; |format = "%s:"
00408FAA |. 50 |PUSH EAX ; |s
00408FAB |. FF15 90B14000 |CALL DWORD PTR DS:[<&MSVCRT.sprintf>] ; \sprintf
00408FB1 |. 8B35 84B24000 |MOV ESI,DWORD PTR DS:[<&USER32.SetWindowTextA>] ; USER32.SetWindowTextA
00408FB7 |. 83C4 0C |ADD ESP,0C
00408FBA |. 8D85 B0FEFFFF |LEA EAX,DWORD PTR SS:[EBP-150]
00408FC0 |. 50 |PUSH EAX ; /Text
00408FC1 |. FF75 F4 |PUSH DWORD PTR SS:[EBP-C] ; |hWnd
00408FC4 |. FFD6 |CALL ESI ; \SetWindowTextA
00408FC6 |. 8D85 ACFAFFFF |LEA EAX,DWORD PTR SS:[EBP-554]
00408FCC |. 50 |PUSH EAX ; /Arg3
00408FCD |. FF75 FC |PUSH DWORD PTR SS:[EBP-4] ; |Arg2
00408FD0 |. FF35 00EF4000 |PUSH DWORD PTR DS:[40EF00] ; |Arg1 = 00BEADCC
00408FD6 |. E8 1884FFFF |CALL <myuninst.sub_4013F3> ; \sub_4013F3
00408FDB |. 83C4 0C |ADD ESP,0C
00408FDE |. 50 |PUSH EAX
00408FDF |. FF75 F0 |PUSH DWORD PTR SS:[EBP-10]
00408FE2 |. FFD6 |CALL ESI
00408FE4 |. FF45 FC |INC DWORD PTR SS:[EBP-4]
00408FE7 |. 8345 F8 14 |ADD DWORD PTR SS:[EBP-8],14
00408FEB |. 837D FC 0F |CMP DWORD PTR SS:[EBP-4],0F
00408FEF |.^ 0F8C 32FFFFFF \JL <myuninst.loc_408F27>
00408FF5 |. 5F POP EDI
00408FF6 |. 5E POP ESI
00408FF7 |. 5B POP EBX
00408FF8 |. C9 LEAVE
00408FF9 \. C3 RETN
我想上面的代碼我不需多做解釋,OllyDBG 自動(dòng)給出的注釋已經(jīng)夠清楚的了。我們雙擊 MyUninstaller 列表框中的的某項(xiàng)查看屬性時(shí),彈出的屬性窗口上的 STATIC 控件和 EDIT 控件都是由 CreateWindowExA 函數(shù)創(chuàng)建的,然后再調(diào)用 SetWindowTextA 來設(shè)置文本,根本沒考慮控件上字體顯示的問題,所以我們看到的都是系統(tǒng)默認(rèn)的字體。我們要設(shè)置控件上的字體,可以考慮在 CreateWindowExA 創(chuàng)建完控件后,在使用 SetWindowTextA 函數(shù)設(shè)置文本之前調(diào)用相關(guān)字體創(chuàng)建函數(shù)來選擇字體,再調(diào)用 SendMessageA 函數(shù)發(fā)送 WM_SETFONT 消息來設(shè)置控件字體。思路定下來后,我們就開始來實(shí)施。首先我們看一下這個(gè)程序中的導(dǎo)入函數(shù),CreateFontIndirectA 這個(gè)字體創(chuàng)建函數(shù)已經(jīng)有了,再看看 SendMessageA,呵呵,不錯(cuò),原程序也有這個(gè)函數(shù)。這樣我們就省事了。有人可能要問,如果原來并沒有這兩個(gè)導(dǎo)入函數(shù),那怎么辦呢?其實(shí)這也很簡(jiǎn)單,我們可以直接用 LordPE 來在程序中添加我們需要的導(dǎo)入函數(shù)。我這里用個(gè)很小的 PE 工具 zeroadd 來示范一下,這個(gè)程序里面沒有 CreateFontIndirectA 和 SendMessageA 函數(shù)(這里還有個(gè)問題說一下,其實(shí)我們編程時(shí)調(diào)用這兩個(gè)函數(shù)時(shí)都是直接寫 CreateFontIndirect 及 SendMessage,一般不需指定。但在程序中寫補(bǔ)丁代碼時(shí)我們要指定這是什么類型的函數(shù)。這里在函數(shù)后面加個(gè)"A"表示這是 ASCII 版本,同樣 UNICODE 版本在后面加個(gè)"W",如 SendMessageW。在 Win9X 下我們一般都用 ASCII 版本的函數(shù),UNICODE 版本的函數(shù)很多在 Win9X 下是不能運(yùn)行的。而NT 系統(tǒng)如 WinXP 一般都是 UNICODE 版本的,但如果我們用了 ASCII 版本的函數(shù),系統(tǒng)會(huì)自動(dòng)轉(zhuǎn)換調(diào)用 UNICODE 版本。這樣我們寫補(bǔ)丁代碼的時(shí)候就可以直接指定為 ASCII 版本的函數(shù),可以兼容各個(gè)系統(tǒng)):我們用 LordPE 的 PE 編輯器載入 zeroadd 程序,選擇"目錄",再在彈出的目錄表對(duì)話框中選擇輸入表后面的那個(gè)"..."按鈕,會(huì)彈出一個(gè)對(duì)話框:
因?yàn)?/span> SendMessageA 在 USER32.dll 中,我們?cè)谟益I菜單中點(diǎn)擊按鈕"添加導(dǎo)入表",來到下面:
按上面的提示完成后點(diǎn)"確定",我們回到原先的那個(gè)"輸入表"對(duì)話框:
從上圖中我們可以看出多出了一個(gè) USER32.dll,這就是我們添加 SendMessageA 的結(jié)果。這也是用工具添加的一個(gè)缺點(diǎn)。我們一般希望把添加的函數(shù)直接放到已存在的 DLL 中,而不是多出來一個(gè),這樣顯得不好看。但用工具就沒辦法,LordPE 默認(rèn)是建一個(gè) 1K 的新區(qū)段來保存添加后的結(jié)果,由此出現(xiàn)了上圖中的情況。如果你對(duì) PE 結(jié)構(gòu)比較熟悉的話,也可以直接用 16進(jìn)制編輯工具來添加你需要的函數(shù),這樣改出來的東西好看。如果想偷懶,就像我一樣用工具吧,呵呵。在上圖中我還標(biāo)出了要注意 FirstThunk 及那個(gè) ThunkRVA 的值,并且要把"總是查看 FirstThunk"那個(gè)選項(xiàng)選上。有人可能不理解其作用,我這里也解釋一下:一般講述 PE 格式的文章中對(duì) FirstThunk 的解釋是這樣的:FirstThunk 包含指向一個(gè) IMAGE_THUNK_DATA 結(jié)構(gòu)數(shù)組的 RVA 偏移地址,當(dāng)把 PE 文件裝載到內(nèi)存中時(shí),PE裝載器將查找 IMAGE_THUNK_DATA 和 IMAGE_IMPORT_BY_NAME 這些結(jié)構(gòu)數(shù)組來決定導(dǎo)入函數(shù)的地址,隨后用導(dǎo)入函數(shù)真實(shí)地址來替代由 FirstThunk 指向的 IMAGE_THUNK_DATA 數(shù)組里的元素值。這樣說起來還是讓人不明白,我舉個(gè)例子:比如你有個(gè)很要好的朋友,他是個(gè)大忙人,雖然你知道他的家庭住址,可他很少回家。如果你哪天想找他,直接去他家,很可能吃個(gè)閉門羹,找不到他人。怎么辦?幸好你有他的手機(jī)號(hào)碼,你就給他撥了一個(gè)電話:"小子,你在哪呢?",他告訴你:"我正在XXX飯店喝酒呢!"這時(shí)你怎么辦?(當(dāng)然是殺到他說的那家飯店去蹭飯了!^_^)這里的 ThunkRVA 就相當(dāng)于你朋友的手機(jī)號(hào)碼, SendMessageA 就相當(dāng)于你那個(gè)朋友。而 FirstThunk 就是你手機(jī)里的號(hào)碼分組。你把你的多個(gè)朋友都放在 FirstThunk 這樣的號(hào)碼分組里,每個(gè) ThunkRVA 就是你一個(gè)朋友的手機(jī)號(hào)碼。你要找他們,就是通過 ThunkRVA 這樣的手機(jī)號(hào)碼來和他們聯(lián)系,直接去他家找他你很可能要碰壁。而移動(dòng)或聯(lián)通就相當(dāng)于操作系統(tǒng),他們負(fù)責(zé)把你的手機(jī)號(hào)碼和你的朋友對(duì)應(yīng)上。而 FirstThunk 這樣的號(hào)碼分組還有一個(gè)好處就是你可以不記你某個(gè)朋友的具體號(hào)碼,只要記得 FirstThunk 號(hào)碼分組的值,你的朋友會(huì)按順序在里面排列。比如上圖中 USER32.dll 中的第一個(gè)函數(shù)是 SendMessageA,它的 ThunkRVA 值就是 FirstThunk 值。如果還有第二個(gè)函數(shù),比如是 MessageBoxA,它的值就是 FirstThunk 值加上 4,其余類推。你只要記住各個(gè)函數(shù)的位置,也可以通過 FirstThunk 加上位置對(duì)應(yīng)值來找到它。當(dāng)然這比不上直接看 ThunkRVA 來得方便。說了上面這些,我們就要考慮怎么在程序中調(diào)用了。你可能會(huì)說,我在 OllyDBG 中直接在我們要修改的程序中這樣調(diào)用:CALL SendMessageA。哦,別這樣。這等于我上面說的都是廢話,會(huì)讓我感到傷心的。你這里的 CALL SendMessageA 就相當(dāng)于也不跟你朋友打個(gè)招呼就直接去他家找他,很有可能你會(huì)乘興而去,敗興而歸。別忘了他的手機(jī)號(hào)碼,我們只有通過號(hào)碼才知道他到底在什么地方。我們應(yīng)該這樣:CALL DWORD PTR [40B01A],這里的 40B01A 就是上面的 SendMessageA 在程序載入后的所在的地方,由基址 00400000 加上 ThunkRVA 0000B01A 得到的。這就是你要找的人所在的地方,不管他跑到哪,你有他的手機(jī)號(hào)碼就能找到他。同樣道理,你只要記住了 ThunkRVA 值,就按這個(gè)來調(diào)用你需要的函數(shù),在別的 Windows 系統(tǒng)下也是沒有問題的。系統(tǒng)會(huì)自動(dòng)把你要找到函數(shù)和 ThunkRVA 值對(duì)應(yīng)上。而你在 OllyDBG 中寫 CALL SendMessageA,可能你在你的系統(tǒng)上成功了,可放到別的系統(tǒng)下就要出錯(cuò)了。為什么?因?yàn)槟阏业娜艘呀?jīng)不在原來的位置了,他跑到別的地方去了。你還到老地方找他,當(dāng)然看不見人了。說了這么多廢話,也不知大家聽明白了沒有,別越聽越糊涂就行了。總之一句話,別像 CALL SendMessageA 這樣直接調(diào)用某個(gè)函數(shù),而應(yīng)該通過 ThunkRVA 值來調(diào)用它。下面我們回到我們要修改的 MyUninstaller 上來,先用 LordPE 打開看一下,呵呵,原來 CreateFontIndirectA 和 SendMessageA 原程序里面都有了,省了我們不少事情??匆幌逻@兩個(gè)函數(shù)的 ThunkRVA 值,CreateFontIndirectA 在 GDI32.dll 里面,ThunkRVA 值是 0000B044,這樣我們就知道在程序中調(diào)用它的時(shí)候就是 CALL DWORD PTR [0040B044]。同樣,SendMessageA 的ThunkRVA 值是 0000B23C,調(diào)用時(shí)應(yīng)該是這樣:CALL DWORD PTR [0040B23C]。了解了這些東西我們就來考慮怎么寫代碼了。首先我們來看一下 CreateFontIndirectA 和 SendMessageA 這兩個(gè)函數(shù)的定義:
CreateFontIndirectA:
HFONT CreateFontIndirect(
CONST LOGFONT *lplf // pointer to logical font structure
);
CreateFontIndirect的返回值就是字體的句柄。
對(duì)于這個(gè)函數(shù)我們需要的參數(shù)就是給它一個(gè) LOGFONT 的字體結(jié)構(gòu)指針,我們只要在要修改程序的空白處建一個(gè)標(biāo)準(zhǔn)的9號(hào)(小五)宋體的 LOGFONT 字體結(jié)構(gòu),再把指針給 CreateFontIndirectA 就可以了。
SendMessageA:
LRESULT SendMessage(
HWND hWnd, // handle of destination window
UINT Msg, // message to send
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter
);
上面的第一個(gè)參數(shù)是窗口句柄,我們知道 CreateWindowExA 返回的就是窗口句柄,我們可以直接拿來用。第二個(gè)消息參數(shù)我們這里是設(shè)置字體,選WM_SETFONT,這個(gè)值是 30H。第三個(gè)參數(shù)是字體句柄,可以由上面的 CreateFontIndirectA 獲得。第四個(gè)參數(shù)我們不需要,留空?,F(xiàn)在我們準(zhǔn)備開始寫代碼,首先我們要在程序中建一個(gè)標(biāo)準(zhǔn)9號(hào)宋體的 LOGFONT,以便于我們調(diào)用。對(duì)于 LOGFONT,我們?cè)賮砜匆幌露x:
typedef struct tagLOGFONT { // lf
LONG lfHeight;
LONG lfWidth;
LONG lfEscapement;
LONG lfOrientation;
LONG lfWeight;
BYTE lfItalic;
BYTE lfUnderline;
BYTE lfStrikeOut;
BYTE lfCharSet;
BYTE lfOutPrecision;
BYTE lfClipPrecision;
BYTE lfQuality;
BYTE lfPitchAndFamily;
TCHAR lfFaceName[LF_FACESIZE];
} LOGFONT;
這樣我們的標(biāo)準(zhǔn)9號(hào)宋體的 LOGFONT 值應(yīng)該是32字節(jié),16進(jìn)制就像這樣:F4FFFFFF000000000000000000000000900100000000008600000000CBCECCE5?,F(xiàn)在在程序中找個(gè)空地。我們用 PEiD 來幫助我們尋找,用 PEiD 打開程序,點(diǎn) EP 段后面的那個(gè) > 號(hào),隨便選擇一個(gè)區(qū)段右擊,選"搜索全0處"(原版好像是cave什么的):
我們看到 PEiD 把搜索到的空間都給我們列出來了:
現(xiàn)在我們用 WinHEX 打開我們要修改的程序,轉(zhuǎn)到偏移 9815 處,從 9815 處選擇 32 字節(jié)(16進(jìn)制是0X20)的一個(gè)選塊,把光標(biāo)定位到 9815 處,右鍵選擇菜單 剪貼板數(shù)據(jù)->寫入(從當(dāng)前位置覆寫),隨后的格式選擇 ASCII Hex,把我們 LOGFONT 的 16 進(jìn)制值
F4FFFFFF000000000000000000000000900100000000008600000000CBCECCE5
寫入保存?,F(xiàn)在我們用 OllyDBG 載入已添加了 LOGFONT 數(shù)據(jù)的程序,先轉(zhuǎn)到 VA 40A415 處(從上圖中看到的)往下看一下:
因?yàn)?/span> SendMessageA 還要用到一個(gè)窗口句柄,我們可以通過前面的 CreateWindowExA 來獲得?,F(xiàn)在我們就把前一張圖中的 .rdata 區(qū)段中的地址 0040C56E 作為我們保存窗口句柄 HWND 值的臨時(shí)空間。一切就緒,開始寫代碼。先回顧一下我們最先說的那兩個(gè)要修改的地方:
第一個(gè)要改的地方:
00408F5E |. FF15 98B24000 |CALL DWORD PTR DS:[<&USER32.CreateWindowExA>] ; \CreateWindowExA
00408F64 6A 00 PUSH 0 ; 修改前
00408F66 8945 F4 MOV DWORD PTR SS:[EBP-C],EAX
00408F69 |. E8 A098FFFF |CALL <myuninst.sub_40280E>
修改后:
00408F5E |. FF15 98B24000 |CALL DWORD PTR DS:[<&USER32.CreateWindowExA>] ; \CreateWindowExA
00408F64 E9 D5140000 JMP myuninst.0040A43E ; 跳轉(zhuǎn)到我們的補(bǔ)丁代碼處
00408F69 |. E8 A098FFFF |CALL <myuninst.sub_40280E>
第二個(gè)要改的地方:
00408F91 |. FF15 98B24000 |CALL DWORD PTR DS:[<&USER32.CreateWindowExA>] ; \CreateWindowExA
00408F97 8945 F0 MOV DWORD PTR SS:[EBP-10],EAX ; 改這里
00408F9A 8B45 F8 MOV EAX,DWORD PTR SS:[EBP-8]
00408F9D |. FF30 |PUSH DWORD PTR DS:[EAX] ; /<%s>
00408F9F |. 8D85 B0FEFFFF |LEA EAX,DWORD PTR SS:[EBP-150] ; |
00408FA5 |. 68 D0D94000 |PUSH myuninst.0040D9D0 ; |format = "%s:"
00408FAA |. 50 |PUSH EAX ; |s
00408FAB |. FF15 90B14000 |CALL DWORD PTR DS:[<&MSVCRT.sprintf>] ; \sprintf
00408FB1 |. 8B35 84B24000 |MOV ESI,DWORD PTR DS:[<&USER32.SetWindowTextA>] ; USER32.SetWindowTextA
修改后:
00408F91 |. FF15 98B24000 |CALL DWORD PTR DS:[<&USER32.CreateWindowExA>] ; \CreateWindowExA
00408F97 E9 D4140000 JMP myuninst.0040A470 ; 跳到我們的第二部分補(bǔ)丁代碼處
00408F9C 90 NOP
00408F9D |. FF30 |PUSH DWORD PTR DS:[EAX] ; /<%s>
00408F9F |. 8D85 B0FEFFFF |LEA EAX,DWORD PTR SS:[EBP-150] ; |
00408FA5 |. 68 D0D94000 |PUSH myuninst.0040D9D0 ; |format = "%s:"
00408FAA |. 50 |PUSH EAX ; |s
00408FAB |. FF15 90B14000 |CALL DWORD PTR DS:[<&MSVCRT.sprintf>] ; \sprintf
00408FB1 |. 8B35 84B24000 |MOV ESI,DWORD PTR DS:[<&USER32.SetWindowTextA>] ; USER32.SetWindowTextA
這兩個(gè)地方的修改都是把原代碼改成跳轉(zhuǎn),跳到我們的補(bǔ)丁代碼那繼續(xù)執(zhí)行。在修改之前先把原代碼復(fù)制下來,以便恢復(fù)。我們?cè)?/span> OllyDBG 中按 CTR+G 組合鍵,來到 0040A43E 地址處,開始輸補(bǔ)丁代碼:
同樣,我們也在 0040A470 地址處輸入我們另一部分的補(bǔ)丁代碼。兩部分的補(bǔ)丁代碼分別如下:
補(bǔ)丁代碼1:
0040A43E 60 PUSHAD ; 保護(hù)現(xiàn)場(chǎng)
0040A43F A3 6EC54000 MOV DWORD PTR DS:[40C56E],EAX ; 保存窗口句柄
0040A444 68 15A44000 PUSH myuninst.0040A415 ; 傳遞字體句柄LOGFONT
0040A449 FF15 44B04000 CALL DWORD PTR DS:[<&GDI32.CreateFontIndirectA>] ; GDI32.CreateFontIndirectA
0040A44F 6A 00 PUSH 0 ; lParam 參數(shù)留空
0040A451 50 PUSH EAX ; 字體句柄LOGFONT
0040A452 6A 30 PUSH 30 ; WM_SETFONT
0040A454 8B0D 6EC54000 MOV ECX,DWORD PTR DS:[40C56E] ; 窗口句柄送ECX
0040A45A 51 PUSH ECX ; 壓入窗口句柄參數(shù)
0040A45B FF15 3CB24000 CALL DWORD PTR DS:[<&USER32.SendMessageA>] ; USER32.SendMessageA
0040A461 61 POPAD ; 恢復(fù)現(xiàn)場(chǎng)
0040A462 6A 00 PUSH 0 ; 恢復(fù)原代碼
0040A464 8945 F4 MOV DWORD PTR SS:[EBP-C],EAX
0040A467 ^ E9 FDEAFFFF JMP myuninst.00408F69 ; 返回
補(bǔ)丁代碼2:
0040A470 > \60 PUSHAD
0040A471 . A3 6EC54000 MOV DWORD PTR DS:[40C56E],EAX
0040A476 . 68 15A44000 PUSH myuninst.0040A415 ; /pLogfont = myuninst.0040A415
0040A47B . FF15 44B04000 CALL DWORD PTR DS:[<&GDI32.CreateFontIndirectA>] ; \CreateFontIndirectA
0040A481 . 6A 00 PUSH 0 ; /lParam = 0
0040A483 . 50 PUSH EAX ; |wParam
0040A484 . 6A 30 PUSH 30 ; |Message = WM_SETFONT
0040A486 . 8B0D 6EC54000 MOV ECX,DWORD PTR DS:[40C56E] ; |
0040A48C . 51 PUSH ECX ; |hWnd => NULL
0040A48D . FF15 3CB24000 CALL DWORD PTR DS:[<&USER32.SendMessageA>] ; \SendMessageA
0040A493 . 61 POPAD
0040A494 . 8945 F0 MOV DWORD PTR SS:[EBP-10],EAX
0040A497 . 8B45 F8 MOV EAX,DWORD PTR SS:[EBP-8]
0040A49A .^ E9 FEEAFFFF JMP myuninst.00408F9D
補(bǔ)丁代碼2因?yàn)榕c補(bǔ)丁代碼1類似,我就不做詳細(xì)解釋了。現(xiàn)在我們的代碼都寫完了,現(xiàn)在我們開始保存我們的工作,選中我們修改的代碼,點(diǎn)擊鼠標(biāo)右鍵,會(huì)出來一個(gè)菜單:
我們左鍵選所有修改(當(dāng)然選它了,要不然只會(huì)保存我們選定的這一部分。關(guān)于這個(gè)地方還要說一下,有的時(shí)候我們修改完程序選"復(fù)制到可執(zhí)行文件"時(shí)只有"選擇"菜單,沒有"所有修改"菜單項(xiàng)。按 OllyDBG 幫助里關(guān)于備份功能的說法,好像是受內(nèi)存塊限制的,補(bǔ)丁功能也同樣是這樣。對(duì)于備份及補(bǔ)丁功能我用的比較少,并不是很了解,這方面的內(nèi)容還是大家自己去研究吧,有什么好的心得也希望能共享一下。我遇到不能保存所有修改的情況就是先把補(bǔ)丁代碼全部復(fù)制下來,同時(shí)利用二進(jìn)制功能復(fù)制代碼,先選一段補(bǔ)丁代碼保存為文件,再用 OllyDBG 打開保存后的文件,轉(zhuǎn)到相應(yīng)位置分別把我們復(fù)制下來的補(bǔ)丁二進(jìn)制代碼粘貼上去后保存。純屬笨辦法,當(dāng)然你也可以用 HexView 這樣的工具來修改代碼),隨后會(huì)出來一個(gè)"把選中的內(nèi)容復(fù)制到可執(zhí)行文件"的對(duì)話框,我們選"全部復(fù)制",又出來一個(gè)對(duì)話框,我們?cè)谏厦纥c(diǎn)右鍵,在彈出的菜單上選"保存文件":
這時(shí)會(huì)出來一個(gè)另存文件的對(duì)話框,我們另選一個(gè)名字如 myuninst1.exe 來保存,不要直接覆蓋原文件 myuninst.exe,以便于出錯(cuò)后好修改?,F(xiàn)在關(guān)閉 OllyDBG,先不要急著運(yùn)行剛剛修改過的文件,因?yàn)槲覀冞€有個(gè)地方要改一下。大家還記得我們?cè)?/span> .rdata 中用了個(gè)地方作為我們保存臨時(shí)變量的地方吧?原先的 .rdata 段屬性設(shè)置是不可寫的,現(xiàn)在我們寫入了數(shù)據(jù),運(yùn)行時(shí)是會(huì)出錯(cuò)的。現(xiàn)在我們要修改一下 .rdata 段的屬性。用 LordPE 的 PE 編輯器打開我們修改后的程序,點(diǎn)"區(qū)段"按鈕,在彈出的對(duì)話框中點(diǎn)擊 .rdata 段,右鍵選擇彈出菜單中的"編輯區(qū)段":
在彈出的對(duì)話框中選標(biāo)志后面那個(gè)"..."按鈕:
現(xiàn)在我們把區(qū)段標(biāo)志添加一個(gè)可寫入的屬性:
完成后按確定保存我們所做的工作,運(yùn)行一下修改后的程序,呵呵,終于把字體改過來了:
如果你運(yùn)行出錯(cuò)也沒關(guān)系,用 OllyDBG 調(diào)試一下你修改后的程序,看看錯(cuò)在什么地方。這一般都是輸入補(bǔ)丁代碼時(shí)造成的,你只要看一下你補(bǔ)丁代碼運(yùn)行的情況就可以了。到這里我們的任務(wù)似乎也完成了,但細(xì)心的朋友可能會(huì)發(fā)現(xiàn)補(bǔ)丁代碼1和補(bǔ)丁代碼2前面的代碼基本上是相同的。一個(gè)兩個(gè)這樣的補(bǔ)丁還好,如果要是多的話,這樣重復(fù)就要浪費(fèi)不少空間了,況且工作量也相應(yīng)加大了。既然前面有很多代碼都是重復(fù)的,為什么我們不把這些重復(fù)的代碼做成一個(gè)子程序呢?這樣調(diào)用起來要方便的多。下面我們把前面的補(bǔ)丁代碼修改一下,我們先把補(bǔ)丁代碼1的代碼改成這樣:
0040A43E 60 PUSHAD ; 保護(hù)現(xiàn)場(chǎng)
0040A43F A3 6EC54000 MOV DWORD PTR DS:[40C56E],EAX ; 保存窗口句柄
0040A444 68 15A44000 PUSH myuninst.0040A415 ; 我們建的LOGFONT對(duì)應(yīng)指針
0040A449 FF15 44B04000 CALL DWORD PTR DS:[<&GDI32.CreateFontIndirectA>] ; GDI32.CreateFontIndirectA
0040A44F 6A 00 PUSH 0 ; lParam 參數(shù)留空
0040A451 50 PUSH EAX ; 字體句柄
0040A452 6A 30 PUSH 30 ; WM_SETFONT
0040A454 8B0D 6EC54000 MOV ECX,DWORD PTR DS:[40C56E] ; 窗口句柄
0040A45A 51 PUSH ECX ; 窗口句柄壓棧
0040A45B FF15 3CB24000 CALL DWORD PTR DS:[<&USER32.SendMessageA>] ; USER32.SendMessageA
0040A461 61 POPAD ; 恢復(fù)現(xiàn)場(chǎng)
0040A462 C3 RETN ; 返回
這樣我們的子程序代碼就寫好了?,F(xiàn)在我們?cè)僭谧映绦虼a后面寫上兩個(gè)補(bǔ)丁代碼,當(dāng)然不要忘了改前面原程序中的跳轉(zhuǎn):
修改后的補(bǔ)丁代碼1:
0040A467 E8 D2FFFFFF CALL myuninst.0040A43E ; 調(diào)用子程序
0040A46C 6A 00 PUSH 0 ; 恢復(fù)前面修改過的代碼
0040A46E 8945 F4 MOV DWORD PTR SS:[EBP-C],EAX
0040A471 ^ E9 F3EAFFFF JMP myuninst.00408F69 ; 返回繼續(xù)執(zhí)行
修改后的補(bǔ)丁代碼2:
0040A47A E8 BFFFFFFF CALL myuninst.0040A43E
0040A47F 8945 F0 MOV DWORD PTR SS:[EBP-10],EAX
0040A482 8B45 F8 MOV EAX,DWORD PTR SS:[EBP-8]
0040A485 ^ E9 13EBFFFF JMP myuninst.00408F9D
我在每個(gè)補(bǔ)丁代碼片斷間留了4個(gè)字節(jié)來分隔。同樣,我們還要修改一下我們前面的跳轉(zhuǎn):
第一個(gè)要修改跳轉(zhuǎn)的地方:
00408F5E |. FF15 98B24000 |CALL DWORD PTR DS:[<&USER32.CreateWindowExA>] ; \斷在這里
00408F64 E9 FE140000 JMP myuninst.0040A467 ; 跳到我們的第一部分補(bǔ)丁代碼處
00408F69 |. E8 A098FFFF |CALL <myuninst.sub_40280E>
第二個(gè)要修改跳轉(zhuǎn)的地方:
00408F91 |. FF15 98B24000 |CALL DWORD PTR DS:[<&USER32.CreateWindowExA>] ; \CreateWindowExA
00408F97 E9 DE140000 JMP myuninst.0040A47A ; 跳到我們的第二部分補(bǔ)丁代碼處
00408F9C 90 NOP
00408F9D |. FF30 |PUSH DWORD PTR DS:[EAX] ; /<%s>
修改好后保存,同樣不要忘了再修改一下 .rdata 區(qū)段的屬性。運(yùn)行一下,一切OK!