文章目錄
- abex'crackme-2
- 1. Visual Basic文件的特征
- 1.1. VB專用引擎
- 1.2. 本地代碼與偽代碼
- 1.3. 事件處理程序
- 1.4. 未文檔化的結構體
- 2. 開始調試
- 2.1. 間接調用
- 2.2. RT_MainStruct結構體
- 2.3. ThunRTMain()函數
- 3. 分析crackme
- 3.1. 檢索字符串
- 3.2. 查找字符串地址
- 3.3. 生成Serial的算法
- 3.4. 預測代碼
- 3.5. 讀取name字符串的代碼
- 3.6. 加密循環
- 3.7. 加密方法
- 函數的調用約定
- 1. 函數調用約定
- 1.1. cdecl
- 1.2. stdcall
- 1.3. fastcall
- 視頻講座-Tut.ReverseMe1
- 1. 運行
- 2. 分析
- 2.1. 去除消息框
- 2.2. 打補丁去除消息框
- 2.2.1. 第一次嘗試
- 2.2.2. 第二次嘗試
- 2.2.3. 如何查看401C17 函數的參數個數
- 2.3. 查找注冊碼
思維導圖

abex’crackme-2
運行這個程序
要求我們輸入正確的賬戶與序列號
1. Visual Basic文件的特征
Visual Basic(VB)文件現在已經不太常見了,尤其是在現代開發環境中。微軟早在 2008 年就停止了對 VB6(經典 Visual Basic)的官方支持,而 VB.NET 作為 .NET 生態的一部分,雖然仍然在維護,但在開發者社區中的使用率已經大幅下降
此crackme是由 Visual Basic編寫而成的。所以我們可以先了解一下其特征
1.1. VB專用引擎
VB文件使用名為MSVBVM60.dll
(MicrosoftVisual BasicVirtual Machine6.0)的VB專用引擎(也稱為TheThunder Runtime Engine)。
舉個使用VB引擎的例子,顯示消息框時,VB代碼中要調用 MsgBox
函數。其實,VB編輯器真正調用的是 MSVBVM60.dll
里的 rtcMsgBox
函數,在該函數內部通過調用 user32.dll
里的 MessageBoxW
函數(Win32API)來工作(也可以在VB代碼中直接調用 user32.dll
里的 MessageBoxW
。
1.2. 本地代碼與偽代碼
根據使用的編譯選項的不同,VB文件可以編譯為本地代碼(Ncode)與偽代碼(Pcode)。
本地代碼一般使用易于調試器解析的IA-32指令
偽代碼是一種解釋器(Interpreter)語言,它使用由VB引擎實現虛擬機并可自解析的指令(字節碼)。因此,若想準確解析VB的偽代碼,就需要分析VB引擎并實現模擬器。
1.3. 事件處理程序
VB主要用來編寫GUI程序,IDE用戶界面本身也最適合于GUI編程。由于VB程序采用Windows操作系統的事件驅動方式工作,所以在main
或WinMain
中并不存在用戶代碼(希望調試的代碼),用戶代碼存在于各個事件處理程序(eventhandler)之中。
就上述abex’crackme ,用戶代碼在點擊Check按鈕時觸發的事件處理程序內。
1.4. 未文檔化的結構體
VB中使用的各種信息(Dialog、Control、Form、Module、Function等)以結構體形式保存在文件內部。由于微軟未正式公開這種結構體信息,所以調試VB文件會難一些。
2. 開始調試
首先在EP代碼中找到調用VB引擎的主函數 ThunRTMain()
00401232 $- FF25 A0104000 jmp dword ptr ds:[0x4010A0] ; msvbvm60.ThunRTMain
00401238 > $ 68 141E4000 push 0x401E14
0040123D . E8 F0FFFFFF call 00401232 ; <jmp.&MSVBVM60.#100>
分析一下幾條命令
00401238
是EP的地址、命令 push 0x401E14
把 RT_MainStruct
結構體的地址 401E14
壓入棧。
然后 call 00401232
調用 401232
處的指令。 即會跳轉到VB引擎的主函數 ThunRTMain()
(前面壓入棧的401E14 的值作為 ThunRTMain()
的參數)
這就是VB文件的全部啟動代碼,非常簡單
2.1. 間接調用
40123D
地址處的 CALL 401232
命令用于調用 ThunRTMain
函數,這里使用了較為特別的技法。不是直接轉到 MSVBVM60.dll
里的 ThunRTMain
函數,而是通過中間 401232
地址處的 JMP
命令跳轉。
這是VC++ 、VB編譯器中常用的間接調用法
4010A0
地址是IAT(Import Address Table,導入地址表)區域,包含著MSVBVM60.ThunRTMain
函數的實際地址
2.2. RT_MainStruct結構體
要注意的是 ThunRTMain
函數的參數 RT_MainStruct
結構體。這里,RT_MainStruct
結構體存在于 401E14
地址處,如圖所示。
微軟未公開 RT_MainStruct
,但是有國外的逆向分析高手已經完成了對 RTMainStruct
結構體的分析,并公布在網絡上。
RT_MainStruct
結構體的成員是其他結構體的地址。也就是說,VB引擎通過參數傳遞過來的 RT_MainStruct
結構體獲取程序運行需要的所有信息。此處省略對RT_MainStruct結構體的詳細說明。
2.3. ThunRTMain()函數
圖中顯示了 ThunRTMain
代碼的開始部分,可以看到內存地址完全不同了。這是 MSVBVM60.dII
模塊的地址區域。換言之,我們分析的不是程序代碼,而是VB引擎代碼(現在還不需要分析如此龐大的代碼)
3. 分析crackme
當前我們很難直接去分析 RT_MainStruct
結構體。 需要找一個更簡單的方法
我們可以通過程序提示的字符串入手。因為程序會提示錯誤后的信息
3.1. 檢索字符串
查找->所有參考字符串
雙擊跳轉
我們這里找到了消息提示的調用函數
根據邏輯判斷。看到是會根據我們的 name
生成一個序列號。然后判斷是否正確,然后回顯信息
這里我們往上翻 去找一下那個判斷語句,而判斷語句一般就是一個跳轉命令
通過上面 call dword ptr ds:[0x401058]
命令調用 __vbaVarTstEq
函數比較(TEST命令)返回值(AX)后,由403332
地址的條件轉移指令(JE指令)決定執行“真”代碼還是“假”代碼。
TEST:邏輯比較(Logical Compare)
與bit-wiselogical“AND’一樣(僅改變EFLAGS寄存器而不改變操作數的值)若2個操作數中一個為0,則AND運算結果被置為0->ZF=1。
JE:條件轉移指令(Jumpif equal)
若ZF=1,則跳轉。
3.2. 查找字符串地址
我們現在已經找到了比較函數 __vbaVarTstEq
的地址,那么上面的兩個push 就是傳入的函數參數
我們先調試到比較函數地址 403329
處
我們觀察上面的兩處指令
這里 SS:[EBP-44]
指的是棧內地址。它恰好就是函數聲明中的局部對象的地址(局部對象存儲再棧區)
下面信息顯示了棧內地址為 19f288
我們可以通過 選中地址,然后在數據框中跟隨地址 即可查看對應的內存地址
查看存儲在棧中的內存地址
這里與C++的string類一樣,VB 字符串使用可變長度的字符串類型。 如圖,直接顯示的不是字符串,而是16字節大小的數據(這就是VB中使用的字符串對象)
不同的值被這樣統一起來,僅方框顯示的值是不同的,看上去就像內存地址一樣(可變長度字符串類型內部持有實際動態分配的字符串緩存地址)
我們右鍵選擇ASCII數據類型 即可查看到我們實際輸入的值與 進行對比的值
切回來選擇這個
如圖
EAX 19f298
是我們輸入的值 123456 字符串所在的地址是 66145c
EDX 19f288
是實際的序列號 C795D8D6 字符串所在地址是 6615c4
我們可以查看一下
VB默認使用基于Unicode的可變長度字符串對象。可變長度字符串對象會根據需要在內部隨時動態分配/釋放內存。因此,每次運行時字符串的地址會有所不同。此外,調試時無法一眼看全實際字符串,這也是調試的困難之一。
下面我們運行程序 輸入對應的名字與序列號即可彈出正確的信息
3.3. 生成Serial的算法
這里我們探討一下生成序列號的算法。 我們只知道了 c1trus
對應的序列號是 C795D8D6
那我們想批量生成序列號,肯定需要知道其算法
查找函數開始部分
我們上面找到了一個條件轉移的代碼。 那個應該就是check后進行的操作。當我們點擊check后,這個計算序列號的函數就會被調用執行。然后就會進行對比,并彈出消息框
所以我們應該往上一點點找函數開始的部分。
而且結合 [[07.棧幀]]中的知識。我們知道在執行函數之前會生成棧幀
通常命令是 PUSH EBP
然后 MOV EBP,ESP
那我們就著重看這個命令,也可以直接查找。
很容易就找到了生成棧幀的代碼
可以發現上面有大量的 nop命令
VB文件的函數之間存在著NOP指令(圖8-14的402ECC~402ECF地址區)。NOP:NoOperation,不執行任何動作的指令(只消耗CPU時鐘)。
為了方便分析代碼。我們先在此處下斷點
3.4. 預測代碼
如果你有經驗,就可以預測出生成序列號的方法,若是win32API程序
則一般有一下特點
- 讀取Name字符串(使用
GetWindowsText
、GetDlgItemText
等API) - 啟動循環,對字符進行加密(
XOR
ADD
SUB
等)
VB引擎編寫的文件也有類似的規律,如果正確的化,我們從斷點處開始調試,查找到讀取name
字符串的那部分后,緊接著就是加密循環
3.5. 讀取name字符串的代碼
開始調試后我們會遇到好幾個 call
直到調試到第四個 call
上面的 lea edx,dword ptr ss:[ebp-0x88]
指令把函數的局部對象 ss:[ebp-0x88]
地址傳遞給了函數的參數。 我們可以看一下這個地址
因為要查找的是Name字符串,在VB中,字符串使用字符串對象(這與C語言使用char數組不同)我們很難認出實際的字符串,所以需要調整一下地址視圖的模式
然后繼續調試到 call
函數之后
這里我們可以可以看到 name
字符串的值了。 其地址就是 19F244
就是 EBP-88
3.6. 加密循環
繼續調試就會遇到以下循環。一系列的循環語句
簡單講解上述循環的動作原理,就像在鏈表中使用next指針引用下一個元素一樣,
__vbaVarForInit
、__vbaVarForNext
可以使逆向分析人員在字符串對象中逐個引I用字符。并且設置loopcount(EBX)使其按指定次數運轉循環。
實測僅使用接收的Name字符串中的前4個字符。在代碼內檢查字符串的長度,若少于4個字符,就會彈出錯誤消息框。
3.7. 加密方法
這里太難整了。我先跳過了。等以后再來分析
函數的調用約定
1. 函數調用約定
CallingConvention譯成中文是“函數調用約定”,它是對函數調用時如何傳遞參數的一種約定。
通過前面的學習已經知道,調用函數前要先把參數壓人棧然后再傳遞給函數。棧就是定義在進程中的一段內存空間,向下(低地址方向)擴展,且其大小被記錄在PE頭中。也就是說,進程運行時確定棧內存的大小(與malloc/new動態分配內存不同)。
提問1.函數執行完成后,棧中的參數如何處理?
回答1.不用管。
由于只是臨時使用存儲在棧中的值,即使不再使用,清除工作也會浪費CPU資源。下一次再向棧存人其他值時,原有值會被自然覆蓋掉,并且棧內存是固定的,所以既不能也沒必要釋放內存。
提問2.函數執行完畢后,ESP值如何變化?
回答2.ESP值要恢復到函數調用之前,這樣可引用的棧大小才不會縮減。棧內存是固定的,ESP用來指示棧的當前位置,若ESP指向棧底,則無法再使用該棧。函數調用后如何處理ESP,這就是函數調用約定要解決的問題。主要的函數調用約定如下。
cdecl
stdcall
fastcall
應用程序的調試中,cdecl與stdcall的區別非常明顯。不管采用哪種方式,通過棧來傳遞參數的基本概念都是一樣的。
調用者——調用函數的一方。
被調用者——被調用的函數。
比如在main
函數中調用printf
函數時,調用者為main
,被調用者為printf
1.1. cdecl
cdecl是主要在C語言中使用的方式,調用者負責處理棧
示例代碼
#include "stdio.h"
int add(int a ,int b)
{return a+b;
}
int main(int argc , char* argv[])
{return add(1,2);
}
編譯后進行調試
從圖中
401013~40101C
地址間的代碼可以發現,add
函數的參數1、2
以逆序方式壓人棧,
調用 add
函數(401000)后,使用 ADD ESP,8
命令整理棧。調用者 main
函數直接清理其壓人棧的函數參數,這樣的方式即是cdecl。
cdecl方式的好處在于,它可以像C語言的
printf
函數一樣,向被調用函數傳遞長度可變的參數。這種長度可變的參數在其他調用約定中很難實現。
1.2. stdcall
stdcall方式常用于Win32API,該方式由被調用者清理棧。前面講解過C語言默認的函數調用方式為cdecl。若想使用stdcall方式編譯源碼,只要使用 _stdcall
關鍵字即可
示例代碼
#include "stdio.h"
int _stdcall add(int a ,int b)
{return a+b;
}
int main(int argc , char* argv[])
{return add(1,2);
}
生成exe然后進行調試
從圖中的代碼可以看到,在 main
函數中調用 add
函數后,省略了清理棧的代碼(ADD ESP,8
)。棧的清理工作由 add
函數中最后(40100A)的 RETN 8
命令來執行。RETN 8
命令的含義為 RETN+POP 8
字節,即返回后使ESP增加到指定大小
像這樣在被調用者 add
函數內部清理棧的方式即為stdcall方式。
stdcall方式的好處在于,被調用者函數內部存在著棧清理代碼,與每次調用函數時都要用 ADD ESP,XXX
命令的cdecl方式相比,代碼尺寸要小。
雖然Win32API是使用C語言編寫的庫,但它使用的是stdcall方式,而不是C語言默認的cdecl方式。這是為了獲得更好的兼容性,使C語言之外的其他語言(Delphi(Pascal)、VisualBasic等)也能直接調用API。
1.3. fastcall
fastcall方式與stdcall方式基本類似,但該方式通常會使用寄存器(而非棧內存)去傳遞那些需要傳遞給函數的部分參數(前2個)。若某函數有4個參數,則前2個參數分別使用ECX、EDX寄存器傳遞。
顧名思義,fastcall方式的優勢在于可以實現對函數的快速調用(從CPU的立場看,訪問寄存器的速度要遠比內存快得多)。單從函數調用本身來看,fastcall方式非常快,但是有時需要額外的系統開銷來管理ECX、EDX寄存器。倘若調用函數前ECX與EDX中存有重要數據,那么使用它們前必須先備份。此外,如果函數本身很復雜,需要把ECX、EDX寄存器用作其他用途時,也需要將它們中的參數值存儲到另外某個地方。
視頻講座-Tut.ReverseMe1
一位名叫Lena的人在 http://www.tuts4you.com/ 公示板上貼了40個crackme講座,以幫助初學者學習代碼逆向分析技術。這些講座非常受歡迎,因為所有講座都以Flash視頻形式呈現出來,且讓學習者感到非常親切。各位可以鏈接到tuts4you網站觀看這些視頻講座,這對學習代碼逆向分析技術非常有幫助。
1. 運行
運行要破解的程序 給我們了兩條信息
就是說叫我們 去除提示框 并找到正確的注冊碼
2. 分析
2.1. 去除消息框
打開后發現也是用VB編寫的軟件,因為我們在EP中發現了 ThunRTMain
而且我們知道VB中調用消息框的函數是 rtcMsgBox
然后我們在調用模塊中去查找,可以發現有三次調用此函數
在每一處調用都設置一個斷點
然后運行程序
發現程序在 402CFE
處斷了下來,此時應該就是要調用函數進行顯示消息了。那么上面多半就有要顯示的消息。果不其然,上面確實可以發現將要顯示的消息
然后我們繼續運行,我們找一下程序中點擊 nag?
按鈕后的斷點在哪里
發現還是斷在了 402CFE
處。那么我們只要對這一處進行修復即可
2.2. 打補丁去除消息框
2.2.1. 第一次嘗試
我們先修改 402CFE
處的call命令
原來的
修改后的
402CFE
地址處的ADD ESP,14
命令的含義是,按照傳遞給rtcMsgBox
參數的大小(14)清理棧。并用NOP填充其余2個字節,以保證代碼不會亂(原來CALL命令的大小為5字節,ADD命令用3個字節,還余下2個字節)。
看上去沒有什么問題,但結果卻“發生錯誤”。原因在于沒有正確處理 rtcMsgBox
函數的返回值(EAX寄存器)。
如圖,在 402CFE
地址處調用 rtcMsgBox
函數后,402D0C
地址處將返回值(EAX)存儲到特定變量(EBP-9C)。此處消息框的返回值應該是1(表示“確定”按鈕)。若存儲的為1之外的值,則表示程序終止。那么最好試試其他方法。
(1)可以修改402CFE地址處的指令,如下所示。
ADD ESP,14
(Instruction:83C414)
MOV EAX,1
(Instruction:B801000000)以上兩行匯編代碼產生的結果與調用
rtcMsgBox
函數后用戶按“確定”按鈕的結果相同(棧與返回值相同)。之所以沒有這樣做是因為指令長度不合適。源文件402CFE
地址處的命令長度為5字節,但上面2行匯編命令的長度為8個字節,因此會侵占到后面的代碼。(2)x86(IA-32)系統中使用EAX寄存器傳遞函數的返回值。
2.2.2. 第二次嘗試
在斷點處向上看可以發現 402C17
處表示函數開始的棧幀
402CFE
的 rtcMsgBox
函數調用代碼也是屬于其他函數內部的代碼。所以如果上層函數無法調用,或直接返回,最終將不會調用 rtcMsgBox
函數。像下面這樣修改401C17
處的指令(使用AssembleSpace指令)。
原來的
修改后
要根據傳遞給函數的參數大小跳轉棧 (RETN XX)
然后運行 發現就去除了消息框
2.2.3. 如何查看401C17 函數的參數個數
首先找到 402C17
處函數的返回地址,在 402C17
下個斷點,并運行到這里
然后可以在棧空間看到返回的地址是 7401E5A9
goto過去看返回地址上一條命令。即執行此命令后就完成 402C17
函數。并返回地址到 7401E5A9
如圖所示 返回地址 (7401E5A9)
。該代碼區域是 MSVBVM50.dll
模塊區域。執行 7401E5A7
地址處的 CALL EAX
指令后即返回 7401E5A9
地址處。再次運行調試器(Ctrl+F2),在 7401E5A7
地址處設置斷點后運行程序(F9),可以得知EAX的值為402656
此時會進入一個函數內部,并跳轉到 402656
地址處,
然后我們使用 ctrl+f9
發現最后會回到 402C17
地址處
所以調用 call eax
就是在調用 402c17
地址處的函數,那么我們只要確認 CALL EAX
命令(7401E5A7)調用前后的棧地址即可得知 402C17
函數參數的個數(因使用的是stdcall調用方式,所以棧由被調用者負責清理)
調用之前的地址 19fab8
調用之后的地址 19fab4
相差4
2.3. 查找注冊碼
首先先定位到注冊碼判斷的命令位置處
字符串找關鍵字符
跳轉過去,往上找判斷語句
這里發現調用了 __vbaStrCmp
函數。 __vbaStrCmp
API是VB中用于比較字符串的函數。這里大概率就是與上面壓入棧中的參數 I'mlena151
進行比較
我們輸入進行驗證
成功了
PS:筆記僅記錄自己學習。里面有大量書中原文。如有侵權,請聯系我刪除。