好了,我們已經得到足夠的信息了。現在我們在菜單 查看->斷點 上點擊一下,打開斷點窗口(也可以通過組合鍵 ALT+B 或點擊工具欄上那個“B”圖標打開斷點窗口):

為什么要做這一步,而不是把這個斷點刪除呢?這里主要是為了保險一點,萬一分析錯誤,我們還要接著分析,要是把斷點刪除了就要做一些重復工作了。還是先禁用一下,如果經過實際驗證證明我們的分析是正確的,再刪不遲。現在我們把斷點禁用,在 OllyDBG 中按 F9 鍵讓程序運行。輸入我們經分析得出的內容:
用戶名:Registered User
注冊碼:GFX-754-IER-954
點擊“Register now !”按鈕,呵呵,終于成功了:

OllyDBG 入門系列(三)-函數參考
作者:CCDebuger
現在進入第三篇,這一篇我們重點講解怎樣使用 OllyDBG 中的函數參考(即名稱參考)功能。仍然選擇 crackmes.cjb.net 鏡像打包中的一個名稱為 CrackHead 的crackme。老規矩,先運行一下這個程序看看:

呵,竟然沒找到輸入注冊碼的地方!別急,我們點一下程序上的那個菜單“Shit”(真是 Shit 啊,呵呵),在下拉菜單中選“Try It”,會來到如下界面:

我們點一下那個“Check It”按鈕試一下,哦,竟然沒反應!我再輸個“78787878”試試,還是沒反應。再試試輸入字母或其它字符,輸不進去。由此判斷注冊碼應該都是數字,只有輸入正確的注冊碼才有動靜。用 PEiD 檢測一下,結果為 MASM32 / TASM32,怪不得程序比較小。信息收集的差不多了,現在關掉這個程序,我們用 OllyDBG 載入,按 F9 鍵直接讓它運行起來,依次點擊上面圖中所說的菜單,使被調試程序顯示如上面的第二個圖。先不要點那個“Check It”按鈕,保留上圖的狀態。現在我們沒有什么字串好參考了,我們就在 API 函數上下斷點,來讓被調試程序中斷在我們希望的地方。我們在 OllyDBG 的反匯編窗口中右擊鼠標,在彈出菜單中選擇 查找->當前模塊中的名稱 (標簽),或者我們通過按 CTR+N 組合鍵也可以達到同樣的效果(注意在進行此操作時要在 OllyDBG 中保證是在當前被調試程序的領空,我在第一篇中已經介紹了領空的概念,如我這里調試這個程序時 OllyDBG 的標題欄顯示的就是“[CPU - 主線程, 模塊 - CrackHea]”,這表明我們當前在被調試程序的領空)。通過上面的操作后會彈出一個對話框,如圖:

對于這樣的編輯框中輸注冊碼的程序我們要設斷點首選的 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 函數上右擊,在彈出菜單上選擇“查找輸入函數參考”(或者按回車鍵),將會出現下面的對話框:

看上圖,我們可以把兩條都設上斷點。這個程序只需在第一條指令設斷點就可以了。好,我們現在按前面提到的第一條方法,就是“在每個參考上設置斷點”,這樣上圖中的兩條指令都會設上斷點。斷點設好后我們轉到我們調試的程序上來,現在我們在被我們調試的程序上點擊那個“Check It”按鈕,被 OllyDBG 斷下:
00401323 |. E8 4C010000 CALL ; 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 ; 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.GetWindowTextA
0040147A $- FF25 30204000 JMP DWORD PTR DS:[] ; USER32.LoadCursorA
00401480 $- FF25 1C204000 JMP DWORD PTR DS:[] ; USER32.LoadIconA
00401486 $- FF25 20204000 JMP DWORD PTR DS:[] ; USER32.LoadMenuA
0040148C $- FF25 24204000 JMP DWORD PTR DS:[] ; 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,再按右鍵,在彈出菜單上選“修改寄存器”,我們會看到這樣一個窗口:

可能你的顯示跟我不一樣,因為這個 crackme 中已經說了每個機器的序列號不一樣。關掉上面的窗口,再對信息窗口中的 EAX=FF439EBE 做同樣操作:

由上圖我們知道了原來前面分析的對我們輸入的注冊碼進行處理后的結果就是把字符格式轉為數字格式。我們原來輸入的是字串“12345666”,現在轉換為了數字 12345666。這下就很清楚了,隨便在上面那個修改 ESI 圖中顯示的有符號或無符號編輯框中復制一個,粘貼到我們調試的程序中的編輯框中試一下:

呵呵,成功了。且慢高興,這個 crackme 是要求寫出注冊機的。我們先不要求寫注冊機,但注冊的算法我們要搞清楚。還記得我在前面說到的那個 ESI 寄存器值的問題嗎?現在看看我們上面的分析,其實對做注冊機來說是沒有多少幫助的。要搞清注冊算法,必須知道上面那個 ESI 寄存器值是如何產生的,這弄清楚后才能真正清楚這個 crackme 算法。今天就先說到這里。