OllyDBG - 第四章 | 記憶體斷點
在OllyDBG - 第三章 | 函數參考,在看程序的過程中,發現ESI
暫存器值不知是從什麼地方產生的,要弄清這個問題必須要找到生成這個 ESI 值的計算部分。
這篇文章主要是學習使用記憶體斷點的功能,來去找出這個值是如何計算的。目的程式依舊是使用CrackHead.exe
。
註:在寫這篇文章之前,我都會實際操作過一遍,基本上已經把很多坑都找出來了⋯
檔案下載
目的程式:
crackhead.7z
OD外掛插件:(Ollydbg
對64位系统不相容問題)
[Stealth64 v1.3.7z](/download/ollydbg/chapter4/Stealth64 v1.3.7z)
解壓縮密碼:
1 | morosedog |
安裝、設定插件
- 下載
Stealth64 v1.3.7z
,並解壓縮 - 將
Stealth64.dll
放到OllyDBG
下的plugin
目錄裡面 - 啟動
OllyDBG
- 點擊
外掛
選擇Stealth64
->Options
- 勾選
x64 Compatibility mode
,並點擊OK
註:因為OllyDBG與64位元不相容,會造成
Alt + F9
動作只有走一條匯編指令。
使用OllyDBG分析
在開始之前,建議先複習一下OllyDBG - 第三章 | 函數參考。
啟動
OllyDBG
按下快捷鍵
F3
選擇
CrackHead.exe
F9
運行程式註:基本上因為有
UDD
紀錄,所以入口點註解和斷點基本上都是還在的。點擊
shit
選擇Try It
點擊
Check It
會被中斷在
00401323 |. E8 4C010000 call <jmp.&USER32.GetWindowTextA> ; \GetWindowTextA
此時往上捲動看一下ESI是在哪裡被賦予值
會發現
00401310 |. 8B35 9C334000 mov esi,dword ptr ds:[40339C]
這邊賦予ESI值 (記住40339C
)我們選中
00401310 |. 8B35 9C334000 mov esi,dword ptr ds:[40339C]
觀察資訊視窗
ds:[0040339C]=00000000
(這裡可能不一樣)對這條右鍵選擇
資料視窗中跟隨位址
- 看到資料視窗會發現好像都還沒有被賦予任何值,但是有一個 03 (請先記住他)
F8
後高亮在00401328 |. E8 A5000000 call 004013D2
- 此時注意資料視窗會看到很眼熟的
12345666
會寫入了- 在這邊我可以假設是在執行
00401323 |. E8 4C010000 call <jmp.&USER32.GetWindowTextA> ; \GetWindowTextA
的時候去取值並寫入這個記憶體位置
- 在這邊我可以假設是在執行
這時候說明了,在某一個地方已經對該記憶體進行寫入的動作,我們可以利用這章節所要教學的記憶體斷點
來找出計算ESI的地方
- 按下
Alt + B
先把斷點停用
- 按下
Crtl + F2
重新開始 - 在資料視窗按下右鍵選擇
轉到
->運算式
- 會開啟
輸入要在資料視窗中跟隨的運算式
- 輸入剛剛的
40339C
,並按下確定
- 選擇
40339C
前面四個位元組註:記憶體斷點的特性就是不管你選幾個位元組,OllyDBG 都會配置 4096 位元組的記憶體區。這裡我就選從 40339C 位址處開始的四個位元組,主要是為了讓大家提前瞭解一下硬體斷點的設法,因為硬體斷點最多只能選 4 個位元組。
- 選中部分會顯示為灰色
- 選好以後鬆開滑鼠左鍵,在我們選中的灰色部分上右鍵選擇
斷點
->記憶體寫入
註:經由上面的操作,我們的記憶體斷點就設好了(這裡還有個要注意的地方:記憶體斷點只在目前除錯的進程中有效,就是說你如果重新載入程式的話記憶體斷點就自動移除了。且記憶體斷點每一時刻只能有一個。就是說你不能像按 F2 鍵那樣同時設定多個斷點)
- 另外注意一下
004033EC 00 .
F9
執行程式- 會中斷到
77BB6B82 881C01 mov byte ptr ds:[ecx+eax],bl
- 注意一下領空位置為
ntdll.dll
系統領空,我們現在要考慮返回到程式領空 - 返回前我們看一下資料視窗
004033EC 03 .
- 會發現原本是
00
被寫入了變為03
- 會發現原本是
Alt + F9
執行到使用者代碼- 會到
0040144E |. 893D 9C334000 mov dword ptr ds:[40339C],edi
- 此時我們捲動捲軸往上看,參照左邊的黑色線框看頭在哪裡,可以發現在
0040140C /$ 60 pushad
- 我們
F2
下斷點在0040140C /$ 60 pushad
- 這時候我們再往上捲軸,可看到很眼熟在OllyDBG - 第三章 | 函數參考分析的代碼,原來這段代碼就在我們分析的下面
- 按下
Crtl + F2
重新開始 F9
執行程式- 中斷在剛剛的
0040140C /$ 60 pushad
- 以下是參考教學文章中所註解的 (因為我還不熟組合語言)
(這邊以下看不懂也沒關係,不要氣餒,目前先盡量了解OD工具的功能和使用…因為我也很多問號)
1 | 0040140C /$ 60 pushad |
通過上面的分析,可以大概知道基本算法:
GetDriveTypeA 函數取得磁碟類型參數
GetVolumeInformationA 函數取得這個 crackme 程式所在分區的標簽
註:如我把這個 Crackme 程式放在 F:/OD教學/crackhead/ 目錄下,而我 F 盤設定的標簽是 GAME,則這裡取得的就是 GAME,ASCII 碼為「47414D45」。
但我們發現一個問題:假如原來我們在資料視窗中看到的位址 40339C 處的 16 進位代碼是「47414D45」,即「GAME」,但經由位址 00401442 處的那條 MOV EBX,DWORD PTR DS:[ESI] 指令後,我們卻發現 EBX 中的值是「454D4147」,正好把我們上面那個「47414D45」反過來了。為什麼會這樣呢?如果大家對 x86系列 CPU 的存儲模式瞭解的話,這裡就容易理解了。我們知道「GAME」有四個位元組,即 ASCII 碼為「47414D45」。
系統存儲的原則為「高高低低」,即低位元組存放在位址較低的位元組單元中,高位元組存放在位址較高的位元組單元中。
比如一個字由兩個位元組群組成,像這樣:12 34 ,這裡的高位元組就是 12 ,低位元組就是 34。
上面的那條指令 MOV EBX,DWORD PTR DS:[ESI] 等同於 MOV EBX,DWORD PTR DS:[40339C]。
注意這裡是 DWORD,即「雙字」,由 4 個連續的位元組構成。而取位址為 40339C 的雙字單元中的內容時,我們應該得到的是「454D4147」,即由高位元組到低位元組順序的值。因此經由 MOV EBX,DWORD PTR DS:[ESI] 這條指令,就是把從位址 40339C 開始處的值送到 EBX,所以我們得到了「454D4147」。
好了,這裡弄清楚了,我們再接著談這個程式的算法。前面我們已經說了取磁碟類型參數做迴圈次數,再取標簽值 ASCII 碼的逆序作為數值,有了這兩個值就開始計算了。
現在我們把磁碟類型值作為 n,標簽值 ASCII 碼的逆序數值作為 a,最後得出的結果作為 b,有這樣的計算過程:
1 | 第一次:b a * n |
還記得上一篇我們的分析嗎?看這一句:00401405 |. 81F6 53757A79 xor esi,797A7553 ; 把ESI中的值與797A7553H異或
這裡算出來的 b 最後還要和 797A7553H 異或一下才是真正的註冊碼。
只要你對寫程式有所瞭解,這個註冊機就很好寫了。如果用彙編來寫這個註冊機的話就更簡單了,很多內容可以直接照抄。
其他
到此已經差不多了,最後還有幾個東西也說一下吧:
- 上面用到了兩個 API 函數,一個是 GetDriveTypeA,還有一個是 GetVolumeInformationA,關於這兩個函數的具體用法我就不多說了,大家可以查一下 MSDN。
這裡只要大家注意函數參數傳遞的次序,即呼叫約定。先看一下這裡:
1 | 00401419 |. 6A 00 PUSH 0 ; /pFileSystemNameSize NULL |
把上面代碼後的 OllyDBG 自動加入的注解與 MSDN 中的函數原型比較一下:
1 | BOOL GetVolumeInformation( |
大家應該看出來點什麼了吧?函數呼叫是先把最後一個參數壓棧,參數壓棧順序是從後往前。這就是一般比較常見的 stdcall 呼叫約定。
我在前面的 00401414 位址處的那條 MOV BYTE PTR DS:[4033EC],AL 指令後加的注解是「磁碟類型參數送記憶體位址4033EC」。為什麼這樣寫?大家把前一句和這一句合起來看一下:
1
20040140F |. E8 B4000000 CALL <JMP.&KERNEL32.GetDriveTypeA> ; /GetDriveTypeA
00401414 |. A2 EC334000 MOV BYTE PTR DS:[4033EC],AL ; 磁碟類型參數送記憶體位址4033EC位址 0040140F 處的那條指令是呼叫 GetDriveTypeA 函數,一般函數呼叫後的返回值都儲存在 EAX 中,所以位址 00401414 處的那一句 MOV BYTE PTR DS:[4033EC],AL 就是傳遞返回值。查一下 MSDN 可以知道 GetDriveTypeA 函數的返回值有這幾個:
1
2
3
4
5
6
7
8Value 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上面那個「返回在EAX中的值」是我加的,我這裡返回的是 3,即磁碟不可從磁碟機上移除。
通過分析這個程式的算法,我們發現這個註冊算法是有漏洞的。如果我的分區沒有標簽的話,則標簽值為 0,最後的註冊碼就是 797A7553H,即十進位 2038068563。而如果你的標簽和我一樣,且磁碟類型一樣的話,註冊碼也會一樣,並不能真正做到一機一碼。
總結
一大堆看不懂!!對!這就是我的感覺!但是工具上的功能和使用又了解更多了,這才是我目前要的。這篇文章絕對不會只是看一遍。希望後面可以漸入佳境。再次強調看懂「組合語言」很重要。
註:以上參考了
看雪論壇 的 OllyDBG 入门系列(四)-内存断点