OllyDBG 入門系列(三)-函數參考

 OllyDBG 入門系列(三)-函數參考

作者:CCDebuger

現在進入第三篇,這一篇我們重點講解怎樣使用 OllyDBG 中的函數參考(即名稱參考)功能。仍然選擇 crackmes.cjb.net 鏡像打包中的一個名稱爲 CrackHead 的crackme。老規矩,先運行一下這個程序看看:
http://bbs.pediy.com/upload/2006/4/image/2_crackme_gui.gif
呵,竟然沒找到輸入註冊碼的地方!別急,我們點一下程序上的那個菜單“Shit”(真是 Shit 啊,呵呵),在下拉菜單中選“Try It”,會來到如下界面:
http://bbs.pediy.com/upload/2006/4/image/2_crackme_check.gif 
我們點一下那個“Check It”按鈕試一下,哦,竟然沒反應!我再輸個“78787878”試試,還是沒反應。再試試輸入字母或其它字符,輸不進去。由此判斷註冊碼應該都是數字,只有輸入正確的註冊碼纔有動靜。用 PEiD 檢測一下,結果爲 MASM32 / TASM32,怪不得程序比較小。信息收集的差不多了,現在關掉這個程序,我們用 OllyDBG 載入,按 F9 鍵直接讓它運行起來,依次點擊上面圖中所說的菜單,使被調試程序顯示如上面的第二個圖。先不要點那個“Check It”按鈕,保留上圖的狀態。現在我們沒有什麼字串好參考了,我們就在 API 函數上下斷點,來讓被調試程序中斷在我們希望的地方。我們在 OllyDBG 的反彙編窗口中右擊鼠標,在彈出菜單中選擇 查找->當前模塊中的名稱 (標籤),或者我們通過按 CTR+N 組合鍵也可以達到同樣的效果(注意在進行此操作時要在 OllyDBG 中保證是在當前被調試程序的領空,我在第一篇中已經介紹了領空的概念,如我這裏調試這個程序時 OllyDBG 的標題欄顯示的就是“[CPU - 主線程, 模塊 - CrackHea]”,這表明我們當前在被調試程序的領空)。通過上面的操作後會彈出一個對話框,如圖:
http://bbs.pediy.com/upload/2006/4/image/2_name.gif
對於這樣的編輯框中輸註冊碼的程序我們要設斷點首選的 API 函數就是 GetDlgItemText 及 GetWindowText。每個函數都有兩個版本,一個是 ASCII 版,在函數後添加一個 A 表示,如 GetDlgItemTextA,另一個是 UNICODE 版,在函數後添加一個 W 表示。如 GetDlgItemTextW。對於編譯爲 UNCODE 版的程序可能在 Win98 下不能運行,因爲 Win98 並非是完全支持 UNICODE 的系統。而 NT 系統則從底層支持 UNICODE,它可以在操作系統內對字串進行轉換,同時支持 ASCII 和 UNICODE 版本函數的調用。一般我們打開的程序看到的調用都是 ASCII 類型的函數,以“A”結尾。又跑題了,呵呵。現在回到我們調試的程序上來,我們現在就是要找一下我們調試的程序有沒有調用 GetDlgItemTextA 或 GetWindowTextA 函數。還好,找到一個 GetWindowTextA。在這個函數上右擊,在彈出菜單上選擇“在每個參考上設置斷點”,我們會在 OllyDBG 窗口最下面的那個狀態欄裏看到“已設置 2 個斷點”。另一種方法就是那個 GetWindowTextA 函數上右擊,在彈出菜單上選擇“查找輸入函數參考”(或者按回車鍵),將會出現下面的對話框:
http://bbs.pediy.com/upload/2006/4/image/2_ref_getw.gif 
看上圖,我們可以把兩條都設上斷點。這個程序只需在第一條指令設斷點就可以了。好,我們現在按前面提到的第一條方法,就是“在每個參考上設置斷點”,這樣上圖中的兩條指令都會設上斷點。斷點設好後我們轉到我們調試的程序上來,現在我們在被我們調試的程序上點擊那個“Check It”按鈕,被 OllyDBG 斷下:

00401323 |. E8 4C010000         CALL <JMP.&USER32.GetWindowTextA>           ; GetWindowTextA
00401328 |. E8 A5000000         CALL CrackHea.004013D2                      ; 關鍵,要按F7鍵跟進去
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 是關鍵,必須仔細跟蹤。而註冊成功則會顯示一個對話框,標題是“Crudd's Crack Head”,對話框顯示的內容是“Now write a keygen and tut and you're done.”現在我按一下 F8,準備步進到 00401328 地址處的那條 CALL CrackHea.004013D2 指令後再按 F7 鍵跟進去。等等,怎麼回事?怎麼按一下 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

原來是跳到另一個斷點了。這個斷點我們不需要,按一下 F2 鍵刪掉它吧。刪掉 00401474 地址處的斷點後,我再按 F8 鍵,呵,完了,跑到 User32.dll 的領空了。看一下 OllyDBG 的標題欄:“[CPU - 主線程, 模塊 - USER32],跑到系統領空了,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                     ; 關鍵,要按F7鍵跟進去
0040132D |. 3BC6               CMP EAX,ESI                                ; 比較
0040132F |. 75 42              JNZ SHORT CrackHea.00401373                ; 不等則完蛋

光標停在 00401328 地址處的那條指令上。現在我們按 F7 鍵跟進:

004013D2 /$ 56                PUSH ESI                                    ; ESI入棧
004013D3 |. 33C0              XOR EAX,EAX                                 ; EAX清零
004013D5 |. 8D35 C4334000     LEA ESI,DWORD PTR DS:[4033C4]               ; 把註冊碼框中的數值送到ESI
004013DB |. 33C9              XOR ECX,ECX                                 ; ECX清零
004013DD |. 33D2              XOR EDX,EDX                                 ; EDX清零
004013DF |. 8A06              MOV AL,BYTE PTR DS:[ESI]                    ; 把註冊碼中的每個字符送到AL
004013E1 |. 46                INC ESI                                     ; 指針加1,指向下一個字符
004013E2 |. 3C 2D             CMP AL,2D                                   ; 把取得的字符與16進制值爲2D的字符(即“-”)比較,這裏主要用於判斷輸入的是不是負數
004013E4 |. 75 08             JNZ SHORT CrackHea.004013EE                 ; 不等則跳
004013E6 |. BA FFFFFFFF       MOV EDX,-1                                  ; 如果輸入的是負數,則把-1送到EDX,即16進制FFFFFFFF
004013EB |. 8A06              MOV AL,BYTE PTR DS:[ESI]                    ; 取“-”號後的第一個字符
004013ED |. 46                INC ESI                                     ; 指針加1,指向再下一個字符
004013EE |> EB 0B             JMP SHORT CrackHea.004013FB
004013F0 |> 2C 30             SUB AL,30                                   ; 每位字符減16進制的30,因爲這裏都是數字,如1的ASCII碼是“31H”,減30H後爲1,即我們平時看到的數值
004013F2 |. 8D0C89            LEA ECX,DWORD PTR DS:[ECX+ECX*4]            ; 把前面運算後保存在ECX中的結果乘5再送到ECX
004013F5 |. 8D0C48            LEA ECX,DWORD PTR DS:[EAX+ECX*2]            ; 每位字符運算後的值與2倍上一位字符運算後值相加後送ECX
004013F8 |. 8A06              MOV AL,BYTE PTR DS:[ESI]                    ; 取下一個字符
004013FA |. 46                INC ESI                                     ; 指針加1,指向再下一個字符
004013FB |> 0AC0              OR AL,AL
004013FD |.^ 75 F1            JNZ SHORT CrackHea.004013F0                 ; 上面一條和這一條指令主要是用來判斷是否已把用戶輸入的註冊碼計算完
004013FF |. 8D040A            LEA EAX,DWORD PTR DS:[EDX+ECX]              ; 把EDX中的值與經過上面運算後的ECX中值相加送到EAX
00401402 |. 33C2              XOR EAX,EDX                                 ; 把EAX與EDX異或。如果我們輸入的是負數,則此處功能就是把EAX中的值取反
00401404 |. 5E                POP ESI                                     ; ESI出棧。看到這條和下一條指令,我們要考慮一下這個ESI的值是哪裏運算得出的呢?
00401405 |. 81F6 53757A79     XOR ESI,797A7553                            ; 把ESI中的值與797A7553H異或
0040140B /. C3                RETN


這裏留下了一個問題:那個 ESI 寄存器中的值是從哪運算出來的?先不管這裏,我們接着按 F8 鍵往下走,來到 0040140B 地址處的那條 RETN 指令(這裏可以通過在調試選項的“命令”標籤中勾選“使用 RET 代替 RETN”來更改返回指令的顯示方式),再按一下 F8,我們就走出 00401328 地址處的那個 CALL 了。現在我們回到了這裏:

0040132D |. 3BC6             CMP EAX,ESI                                  ; 比較
0040132F |. 75 42            JNZ SHORT CrackHea.00401373                  ; 不等則完蛋

光標停在了 0040132D 地址處的那條指令上。根據前面的分析,我們知道 EAX 中存放的是我們輸入的註冊碼經過計算後的值。我們來看一下信息窗口:

ESI=E6B5F2F9
EAX=FF439EBE

左鍵選擇信息窗口中的 ESI=E6B5F2F9,再按右鍵,在彈出菜單上選“修改寄存器”,我們會看到這樣一個窗口:
http://bbs.pediy.com/upload/2006/4/image/2_mod_esi.gif 
可能你的顯示跟我不一樣,因爲這個 crackme 中已經說了每個機器的序列號不一樣。關掉上面的窗口,再對信息窗口中的 EAX=FF439EBE 做同樣操作:
http://bbs.pediy.com/upload/2006/4/image/2_mod_eax.gif 
由上圖我們知道了原來前面分析的對我們輸入的註冊碼進行處理後的結果就是把字符格式轉爲數字格式。我們原來輸入的是字串“12345666”,現在轉換爲了數字 12345666。這下就很清楚了,隨便在上面那個修改 ESI 圖中顯示的有符號或無符號編輯框中複製一個,粘貼到我們調試的程序中的編輯框中試一下:
http://bbs.pediy.com/upload/2006/4/image/2_success.gif 
呵呵,成功了。且慢高興,這個 crackme 是要求寫出註冊機的。我們先不要求寫註冊機,但註冊的算法我們要搞清楚。還記得我在前面說到的那個 ESI 寄存器值的問題嗎?現在看看我們上面的分析,其實對做註冊機來說是沒有多少幫助的。要搞清註冊算法,必須知道上面那個 ESI 寄存器值是如何產生的,這弄清楚後才能真正清楚這個 crackme 算法。今天就先說到這裏,關於如何追出 ESI 寄存器的值我就留到下一篇 OllyDBG 入門系列(四)-內存斷點 中再講吧。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章