所有資源可以在官網下載
程式碼逆向工程
逆向分析法
靜態分析
不執行程式碼,觀察外部特徵,獲取其檔案型別、大小、PE頭、Import/Export API、字串、是否執行時解壓縮、註冊資訊、除錯資訊、數字證書、反彙編程式碼等
動態分析
在程式檔案執行的過程中對程式碼進行分析。
一般首先靜態分析,再進行動態分析。
逆向Hello World
程式碼如下:
#include<windows.h>
#include<tchar.h>
int _tmain(int argc,TCHAR *argv[])
{
MessageBox(NULL, L"Hello World!", L"Muling", MB_OK);
return 0;
}
偵錯程式與彙編
分析二進位制檔案時,需要使用偵錯程式(Debugger)的反彙編模組,將二進位制程式碼轉換為彙編程式碼。
除錯HelloWorld.exe
目標
目標:除錯HelloWorld.exe可執行檔案,在彙編程式碼中找到main函式中的MessageBox函式的呼叫。
開啟檔案
Ollydbg開啟,視窗如下:
偵錯程式停下的位置為程式的入口地址。
在入口點呼叫了0040270c函式,並跳轉到了0040104f。
跟蹤0040270c
F7(步進)進入0040270c
右側區域有Od提供的註釋,紅色是呼叫的API函式名稱,這裡看到的是VS自動新增的一些啟動函式呼叫,F8步過即可。
直到RETN返回仍然沒有看到main和MessageBox
跟蹤0040104f
F7步入,如下:
這個部分為VS啟動函式部分,跟蹤它們可以找到main的位置。
相同IDE生成的啟動函式都大致相同,在熟悉後可以比較快速地找到啟動函式並定位程式碼
不斷跟蹤,F7進入後Ctrl+F9退出嘗試,找到呼叫了MessageBox的Main函式位置
設定camp
-
使用Goto(Ctrl+G)跳轉指標到40104f,再使用F4執行到指標位置
-
F2設定BP,開啟Alt+B可以看到BP列表,雙擊一個BP即可跳轉到任一個斷點(只是高亮位置移到該點),也可直接除錯,會自動停止在斷點處。
-
使用‘;’可以進行註釋,右鍵->查詢->註釋可以查到註釋的位置。
-
使用‘:’可以新增標籤,
如下為在40104f處新增標籤
右鍵->查詢->所有使用者定義的標籤可以檢視標籤
查詢指定的程式碼
一般來說main函式不會出現在EP位置上,EP上一般是開發工具新增的一些啟動函式。
程式碼執行法
在程式功能明確、程式碼量不大的情況下可以使用,逐條執行指令來找到需要查詢的指令位置。
比如呼叫MessageBox的程式碼,一直按F8步過,一定會有一個指令執行後出現訊息框。彈出訊息對話方塊的函式為main函式。
字串檢索
右鍵->查詢->所有被引用的字串可以檢視所有程式引用的字串。
雙擊”Hello World!”可以定位到MessageBox函式。
在記憶體Dump視窗使用Ctrl+G可以進一步檢視004092A0處。
API檢索法(1):呼叫程式碼中設定斷點
右鍵->查詢->所有模組間呼叫
程式輸出時,需要呼叫Win32 API,觀察一個程式後,就可以大致瞭解其呼叫的APi函式有哪些。
比如程式跳出訊息框,可以斷定呼叫了MessageBox API。
這個方法可以找到程式呼叫的所有API列表。
API檢索法(2):API程式碼中設定斷點
右鍵->查詢->所有
對於壓縮器/保護器,列出API呼叫列表比較麻煩
在這種情況下,可以在DLL程式碼庫載入到程式記憶體後直接向DLL程式碼庫新增斷點。
API是作業系統提供的一系列函式,存放在C:\Windows\systems32
資料夾內
所以程式執行操作時需要向OS提出請求使用API,然後API對應的DLL檔案才會被載入進來。
(Alt+M)開啟記憶體對映選單,可以看到user32庫被載入到了記憶體。
使用查詢所有模組中的名稱可以列出被載入的DLL中的所有API,單擊按名稱排序,再鍵盤輸入MessageBoxW可以找到MessageBox
雙擊即可定位到其程式碼部分。
在函式起始位置使用F2設定斷點,然後繼續執行,會自動停在這一行。
此時,ESP=0019FF70,此值指向堆疊位置中存放返回地址值為00401014,也即呼叫此函式後的下一句。
打補丁修改程式字串
打補丁常常用於修復程式中的BUG/新增新功能,打補丁的物件可以是檔案、記憶體、程式的程式碼、資料等
Ctrl+F2重新除錯,F9執行到main函式位置。
直接修改字串緩衝區
該方法操作簡單,但對新內容的長度有限制
“Hello World!”儲存在4092A0處,只要修改這段即可。
跳轉後Ctrl+E開啟編輯視窗,修改字串即可。
在Dump視窗,右鍵->複製到可執行檔案即可將修改儲存到exe檔案中,在彈出的hex視窗中右鍵儲存檔案。
其他區域新增新字串並傳給訊息函式
再次執行到main函式處。
在401007處有一條Push命令,將字串傳給訊息函式。
再次找到HelloWorld處4092A0,往後一直拖,會看到一個未填充區域。
選擇合適的位置如409F50處寫入新字串。
再轉到程式碼401007處,空格開啟彙編視窗,輸入PUSH 409F50
小端序標記法
IA-32暫存器
IA-32支援以下暫存器
通用暫存器
ESP指示棧頂位置,EBP指示棧的及地址,函式呼叫時用於儲存ESP的值,返回時再還給ESP。ESI和EDI主要用於記憶體複製。
段暫存器
狀態/控制暫存器
EFLAGS 標誌暫存器
指令指標暫存器 EIP
棧
作用
-
區域性變數儲存
-
傳參
-
儲存返回地址
特徵
棧結構如下:
ESP為棧頂指標,初始在棧底(高地址),PUSH使ESP自減移到棧頂方向。
引數入棧
傳參時,引數逆序入棧(相對C語言),即從右向左
操作示例
- OD開啟stack.exe。初始時棧頂位置在0019FF74處
- 執行push 0x100,ESP=ESP-4,
棧幀
介紹
利用EBP訪問棧內的區域性變數、引數、函式返回地址等內容。
呼叫函式時,先將棧頂指標ESP賦給EBP,無論ESP如何變化,EBP不變,能方便訪問到區域性變數、引數、返回地址等值。
除錯stackframe.exe
//StackFrame.exe
#include<stdio.h>
long add(long a,long b)
{
long x = a, y = b;
return x + y;
}
int main(int argc,char* argv[])
{
long a = 1, b = 2;
printf("%d\n",add(a,b));
return 0;
}
-
Ctrl+G到401000
-
執行
mov ebp,esp
後,棧空間大致如下
-
可以右鍵->地址->相對於EBP,方便觀察
-
call 時自動push返回地址並跳轉,call後自動pop地址並跳轉
abex crackme#2-VB檔案
執行
這個程式要求找出程式的序列號,輸入Name和Serial,按Check,如下:
VB檔案
這個程式由VB編寫而成,除錯前需要先學習相關特徵。
VB專用引擎
使用VB專用引擎MSVBVM60.dll。
比如VB程式碼中呼叫MsgBox()實現訊息框,其真正呼叫的是MSVBVM60.dll中的rtcMsgBox(),該函式再呼叫user32.dll中的MessageBoxW()。
原生程式碼和虛擬碼
根據編譯選項,VB檔案可以編譯為原生程式碼(N code)與虛擬碼(P code),原生程式碼使用易於除錯的IA-32指令,而虛擬碼是解釋性語言,使用VB引擎實現虛擬機器並自解析指令。
要想準確解析VB虛擬碼,需要分析VB引擎並實現模擬器。
事件處理程式
VB主要用於編寫GUI程式,採用Windows的事件驅動方式工作,在main和WinMain中不存在使用者程式碼,使用者程式碼在各個事件處理程式中。
本程式的用於程式碼就在Check程式碼觸發的事件處理程式內。
未文件化的結構體
VB中各種資訊以結構體形式存在檔案內部,這些結構體並沒有被微軟公開。
除錯
先push一個RT_MainStruct結構體的地址,然後call 呼叫401232處的JMP,跳轉到VB引擎的主函式ThunRTMain(),push的值作為其引數。
間接呼叫
40123D處Call 401232呼叫ThunRTMain()函式,使用了間接呼叫,透過401232處的JMO跳轉,而不是直接call MSVBVM60.dll中的ThunRTMain()函式。
401232處的JMP如下:
4010A0為IAT(匯入地址表)區域,包含MSVBVM60.ThunRTMain()函式的實際地址。
RT_MainStruct
該結構體在401E14處。
雖然微軟沒有公佈,但在國外有逆向高手完成了對其的分析。
該結構體用於獲取程式執行需要的所有資訊。
ThunRTMain
進入主函式,如下:
記憶體地址完全不同,因為這裡是MSVBVM60.dll的地址區域。
分析crackme
-
檢索字串
-
雙擊 wrong serial可以轉到程式碼,這種程式碼一般分為兩個部分,即序列號正確和錯誤兩種,判斷後透過跳轉輸出正確/錯誤的訊息框。
- 向上拖動程式碼,可以找到條件轉移程式碼
- 此處呼叫了__vbaVarTstEq()函式,比較(TEST)返回值AX後,如果AX為0,則跳轉到輸出正確訊息,否則不跳轉輸出錯誤訊息。
-
該函式為字串比較函式,前面兩個push語句應當是傳參,即被比較的字串。
-
F4執行到403329處,輸入字串,點選check。edx=0019F1AC,eax=0019F1BC
- dump區轉到19f1AC。類似於C++中的stringg型別,VB中的字串使用可變長度的字串型別。(字串物件)
- 兩串內容只有中間4個位元組不同,推斷這串是地址。使用右鍵->長型->ASCII資料地址,可以對地址對應的字串進行顯示。
可以看出EDX(EBP-0x44)為正確serial值,而EAX(EBP-0x34)為使用者輸入serial值。
-
如下,輸入serial即可
Serial生成演算法
預測程式碼
如果win32 API程式,應當有如下步驟:
-
讀取Name字串(GetWindowText、GetDlgItemText 等API)
-
啟動迴圈,對字元運算
VB程式碼也應當是類似的原理。所以可以從程式碼開始處(指check回撥函式的開始)除錯,找到讀取name字串的部分,緊接著就會出現加密迴圈。
找到name讀取程式碼
由於需要呼叫API函式,所以可以以call指令為主要注意物件。可以注意到如下call語句。
先將ebp-0x88的位置賦給edx,然後將此地址作為引數傳入,除錯跳過該call後會發現ebp-0x88位置出現name,判斷此call為name讀取函式。
找到加密迴圈
書上並沒有詳細說明加密迴圈的部分是如何找出來的,所以對於書上的這部分,我並沒有太理解,以下是自己的分析過程。
既然是迴圈,肯定會有一個向上的jmp過程,主要找到符合這種特徵的指令即可。翻找jmp指令,可以看到4032A0處有一個jmp向上跳轉。
看看跳轉到的部分,可以看到jmp到的位置為test 接jmp,這段主要判斷eax是否為0,如果為0就跳到前面那個jmp的後一條指令,這樣的指令完美符合迴圈的特徵。可以判斷4032A0為迴圈末尾,403197為迴圈判斷部分。
後面不會啦~
函式呼叫約定
主要函式呼叫約定如下:
- cdecl
- stdcall
- fastcall
- this
- naked
cdecl
C語言中常用的呼叫方式,呼叫者處理棧。使用push從右至左傳參。
cdecl方式好處在於可以傳遞長度可變的引數(如printf)。
stdcall
常用於win32 API,被呼叫者處理棧。引數傳遞方式相同。在C語言中需要在函式前新增“_stdcall”關鍵字以使用stdcall方式編譯。
RETN 8表示返回後pop8個位元組,將ESP增加到合適的大小。
fastcall
被呼叫者平衡棧。ECX、EDX傳遞第1、2個引數,其他引數與stdcall相同。常用於核心程式,這種呼叫方式比較快。
this
被呼叫者平衡棧。ECX傳遞this,其餘引數相同。C++成員函式常用。
naked
被呼叫者平衡棧。傳參方式相同。使用此呼叫方式,編譯器自動為函式生成如下的進入和退出程式碼。
PUSH EBP
MOV EBP,ESP
......
POP EBP
RET 8
CRACKME
程式分析
-
開啟exe,彈出訊息框
要求去除所有的nag(大概就是訊息框吧)並取得註冊碼。
-
點選確定,介面如下:
-
隨意輸入,如下
去除訊息框
觀察
- 先嚐試去除訊息框,查詢所有的程式碼間呼叫。這個一般呼叫rtcMsgBox(從外面的dll檔案再到熟悉的啟動函式可以判斷是vb程式),可以找到三個呼叫。右鍵選擇在每個rtcMsgBox上設定斷點,直接F9執行,偵錯程式自動停止,這個點(402CFE)就是msgbox的位置。需要注意視窗主介面有一個“Nag?”按鈕,可以嘗試再次F9執行,發現它執行跳轉到402CFE,所以只需要對一個進行修改。
方法一
- 可以執行msgbox函式試試,觀察暫存器:
這樣可以知道msgbox傳入了0x14位元組的引數。
-
空格修改彙編,如下。nop是為了填補空缺,因為原來的call佔用5位元組,而add指令佔用3位元組,為了不影響後面的指令,必須填補這兩位元組的空缺。
-
這樣修改後,msgbox會被越過,且棧會平衡,但仍然有一個問題,就是返回值並沒有處理。msgbox會使用eax返回1,表示使用者按下確定,這裡並沒有處理eax,導致返回值為0,所以程式會在執行時認為按下了否,程式自動退出。
大家可能會想到這樣的程式碼:
add esp,0x14 mov eax,1
這個程式碼是可行的,但是由於程式碼長度只有5,所以新增一個mov指令會超出這個限制,導致後面的程式碼被侵佔。
方法二
-
直接向上翻找到函式入口,修改push ebp
-
這個0x4可以由兩個地方確定,一個是函式末尾使用了retn 0x4,一個是返回地址處的call前後esp變化了0x4。
-
這樣修改後直接跳過該函式的執行過程,訊息框自然就被去除了。
找到註冊碼
- 查詢所有引用字串,可以找到“Yep”
這個應當是註冊碼輸入正確時執行的指令。
-
向前找到je
這個jmp指令應當為對註冊碼正確與否的判斷。
這裡要求di!=si
,可以向上繼續尋找,
- 在前面有個strcmp函式,其push 的兩個引數分別為“I'mlena151”和使用者輸入的註冊碼(ebp-0x58)。基本可以猜測註冊碼即為“I'mlena151”。