逆向工程核心原理(1)逆向基礎

Muling_m發表於2023-03-16

所有資源可以在官網下載

程式碼逆向工程

逆向分析法

靜態分析

不執行程式碼,觀察外部特徵,獲取其檔案型別、大小、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開啟,視窗如下:

image-20230308160553332 image-20230308160657713

偵錯程式停下的位置為程式的入口地址。

image-20230308160849646

在入口點呼叫了0040270c函式,並跳轉到了0040104f。

跟蹤0040270c

F7(步進)進入0040270c

image-20230308161432584

右側區域有Od提供的註釋,紅色是呼叫的API函式名稱,這裡看到的是VS自動新增的一些啟動函式呼叫,F8步過即可。

直到RETN返回仍然沒有看到main和MessageBox

跟蹤0040104f

F7步入,如下:

image-20230308162040903

這個部分為VS啟動函式部分,跟蹤它們可以找到main的位置。

相同IDE生成的啟動函式都大致相同,在熟悉後可以比較快速地找到啟動函式並定位程式碼

不斷跟蹤,F7進入後Ctrl+F9退出嘗試,找到呼叫了MessageBox的Main函式位置

image-20230309153140803

設定camp

  • 使用Goto(Ctrl+G)跳轉指標到40104f,再使用F4執行到指標位置

  • F2設定BP,開啟Alt+B可以看到BP列表,雙擊一個BP即可跳轉到任一個斷點(只是高亮位置移到該點),也可直接除錯,會自動停止在斷點處。

  • 使用‘;’可以進行註釋,右鍵->查詢->註釋可以查到註釋的位置。

    image-20230309154144225 image-20230309154120667
  • 使用‘:’可以新增標籤,

    如下為在40104f處新增標籤

    image-20230309154607833 image-20230309154641666

    右鍵->查詢->所有使用者定義的標籤可以檢視標籤

查詢指定的程式碼

一般來說main函式不會出現在EP位置上,EP上一般是開發工具新增的一些啟動函式。

程式碼執行法

在程式功能明確、程式碼量不大的情況下可以使用,逐條執行指令來找到需要查詢的指令位置。

比如呼叫MessageBox的程式碼,一直按F8步過,一定會有一個指令執行後出現訊息框。彈出訊息對話方塊的函式為main函式。

字串檢索

右鍵->查詢->所有被引用的字串可以檢視所有程式引用的字串。

image-20230309161414518

雙擊”Hello World!”可以定位到MessageBox函式。

在記憶體Dump視窗使用Ctrl+G可以進一步檢視004092A0處。

API檢索法(1):呼叫程式碼中設定斷點

右鍵->查詢->所有模組間呼叫

程式輸出時,需要呼叫Win32 API,觀察一個程式後,就可以大致瞭解其呼叫的APi函式有哪些。

比如程式跳出訊息框,可以斷定呼叫了MessageBox API。

這個方法可以找到程式呼叫的所有API列表。

image-20230309162338165

API檢索法(2):API程式碼中設定斷點

右鍵->查詢->所有

對於壓縮器/保護器,列出API呼叫列表比較麻煩

image-20230309171606232

在這種情況下,可以在DLL程式碼庫載入到程式記憶體後直接向DLL程式碼庫新增斷點。

API是作業系統提供的一系列函式,存放在C:\Windows\systems32資料夾內

所以程式執行操作時需要向OS提出請求使用API,然後API對應的DLL檔案才會被載入進來。

(Alt+M)開啟記憶體對映選單,可以看到user32庫被載入到了記憶體。

image-20230309172206119

使用查詢所有模組中的名稱可以列出被載入的DLL中的所有API,單擊按名稱排序,再鍵盤輸入MessageBoxW可以找到MessageBox

image-20230309173105641

雙擊即可定位到其程式碼部分。

image-20230309173237760

在函式起始位置使用F2設定斷點,然後繼續執行,會自動停在這一行。

image-20230309173508549

此時,ESP=0019FF70,此值指向堆疊位置中存放返回地址值為00401014,也即呼叫此函式後的下一句。

image-20230309173800936

打補丁修改程式字串

打補丁常常用於修復程式中的BUG/新增新功能,打補丁的物件可以是檔案、記憶體、程式的程式碼、資料等

Ctrl+F2重新除錯,F9執行到main函式位置。

直接修改字串緩衝區

該方法操作簡單,但對新內容的長度有限制

“Hello World!”儲存在4092A0處,只要修改這段即可。

跳轉後Ctrl+E開啟編輯視窗,修改字串即可。

image-20230310100825151

在Dump視窗,右鍵->複製到可執行檔案即可將修改儲存到exe檔案中,在彈出的hex視窗中右鍵儲存檔案。

其他區域新增新字串並傳給訊息函式

再次執行到main函式處。

在401007處有一條Push命令,將字串傳給訊息函式。

再次找到HelloWorld處4092A0,往後一直拖,會看到一個未填充區域。

選擇合適的位置如409F50處寫入新字串。

再轉到程式碼401007處,空格開啟彙編視窗,輸入PUSH 409F50

image-20230310103428825 image-20230310103631084

小端序標記法

image-20230310104521988

IA-32暫存器

IA-32支援以下暫存器

通用暫存器



ESP指示棧頂位置,EBP指示棧的及地址,函式呼叫時用於儲存ESP的值,返回時再還給ESP。ESI和EDI主要用於記憶體複製。

段暫存器

狀態/控制暫存器

EFLAGS 標誌暫存器
image-20230310112826490
image-20230310112845836

指令指標暫存器 EIP


作用

  • 區域性變數儲存

  • 傳參

  • 儲存返回地址

特徵

棧結構如下:

image-20230310132241127

ESP為棧頂指標,初始在棧底(高地址),PUSH使ESP自減移到棧頂方向。

引數入棧

傳參時,引數逆序入棧(相對C語言),即從右向左

操作示例

  • OD開啟stack.exe。初始時棧頂位置在0019FF74處
image-20230310215122098
  • 執行push 0x100,ESP=ESP-4,

    image-20230310215247583

棧幀

介紹

利用EBP訪問棧內的區域性變數、引數、函式返回地址等內容。

呼叫函式時,先將棧頂指標ESP賦給EBP,無論ESP如何變化,EBP不變,能方便訪問到區域性變數、引數、返回地址等值。

image-20230311231218136

除錯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
    image-20230311233245555

  • 執行mov ebp,esp後,棧空間大致如下

image-20230311235135674
  • 可以右鍵->地址->相對於EBP,方便觀察
    image-20230311235356400

  • call 時自動push返回地址並跳轉,call後自動pop地址並跳轉

abex crackme#2-VB檔案

執行

image-20230312165952839

這個程式要求找出程式的序列號,輸入Name和Serial,按Check,如下:

image-20230312170116323

VB檔案

這個程式由VB編寫而成,除錯前需要先學習相關特徵。

VB專用引擎

使用VB專用引擎MSVBVM60.dll。

比如VB程式碼中呼叫MsgBox()實現訊息框,其真正呼叫的是MSVBVM60.dll中的rtcMsgBox(),該函式再呼叫user32.dll中的MessageBoxW()。

原生程式碼和虛擬碼

根據編譯選項,VB檔案可以編譯為原生程式碼(N code)與虛擬碼(P code),原生程式碼使用易於除錯的IA-32指令,而虛擬碼是解釋性語言,使用VB引擎實現虛擬機器並自解析指令。

要想準確解析VB虛擬碼,需要分析VB引擎並實現模擬器。

image-20230312170709633

事件處理程式

VB主要用於編寫GUI程式,採用Windows的事件驅動方式工作,在main和WinMain中不存在使用者程式碼,使用者程式碼在各個事件處理程式中。

本程式的用於程式碼就在Check程式碼觸發的事件處理程式內。

未文件化的結構體

VB中各種資訊以結構體形式存在檔案內部,這些結構體並沒有被微軟公開。

除錯

image-20230312195534006

先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處。

image-20230312200307086

雖然微軟沒有公佈,但在國外有逆向高手完成了對其的分析。

該結構體用於獲取程式執行需要的所有資訊。

ThunRTMain

進入主函式,如下:

image-20230312200455692

記憶體地址完全不同,因為這裡是MSVBVM60.dll的地址區域。

分析crackme

  • 檢索字串
    image-20230312200849332

  • 雙擊 wrong serial可以轉到程式碼,這種程式碼一般分為兩個部分,即序列號正確和錯誤兩種,判斷後透過跳轉輸出正確/錯誤的訊息框。

image-20230312200927413
  • 向上拖動程式碼,可以找到條件轉移程式碼
image-20230313125030999
  • 此處呼叫了__vbaVarTstEq()函式,比較(TEST)返回值AX後,如果AX為0,則跳轉到輸出正確訊息,否則不跳轉輸出錯誤訊息。
image-20230312201257150
  • 該函式為字串比較函式,前面兩個push語句應當是傳參,即被比較的字串。

  • F4執行到403329處,輸入字串,點選check。edx=0019F1AC,eax=0019F1BC

    image-20230312201516074

  • dump區轉到19f1AC。類似於C++中的stringg型別,VB中的字串使用可變長度的字串型別。(字串物件)
image-20230312202532432
  • 兩串內容只有中間4個位元組不同,推斷這串是地址。使用右鍵->長型->ASCII資料地址,可以對地址對應的字串進行顯示。

可以看出EDX(EBP-0x44)為正確serial值,而EAX(EBP-0x34)為使用者輸入serial值。

  • 如下,輸入serial即可

    image-20230312204626709

Serial生成演算法

預測程式碼

如果win32 API程式,應當有如下步驟:

  • 讀取Name字串(GetWindowText、GetDlgItemText 等API)

  • 啟動迴圈,對字元運算

VB程式碼也應當是類似的原理。所以可以從程式碼開始處(指check回撥函式的開始)除錯,找到讀取name字串的部分,緊接著就會出現加密迴圈。

找到name讀取程式碼

由於需要呼叫API函式,所以可以以call指令為主要注意物件。可以注意到如下call語句。

image-20230312225356326

先將ebp-0x88的位置賦給edx,然後將此地址作為引數傳入,除錯跳過該call後會發現ebp-0x88位置出現name,判斷此call為name讀取函式。

找到加密迴圈

書上並沒有詳細說明加密迴圈的部分是如何找出來的,所以對於書上的這部分,我並沒有太理解,以下是自己的分析過程。

既然是迴圈,肯定會有一個向上的jmp過程,主要找到符合這種特徵的指令即可。翻找jmp指令,可以看到4032A0處有一個jmp向上跳轉。

image-20230315124526833

看看跳轉到的部分,可以看到jmp到的位置為test 接jmp,這段主要判斷eax是否為0,如果為0就跳到前面那個jmp的後一條指令,這樣的指令完美符合迴圈的特徵。可以判斷4032A0為迴圈末尾,403197為迴圈判斷部分。

image-20230315124632639

後面不會啦~

函式呼叫約定

主要函式呼叫約定如下:

  • cdecl
  • stdcall
  • fastcall
  • this
  • naked

cdecl

C語言中常用的呼叫方式,呼叫者處理棧。使用push從右至左傳參。

cdecl方式好處在於可以傳遞長度可變的引數(如printf)。

stdcall

常用於win32 API,被呼叫者處理棧。引數傳遞方式相同。在C語言中需要在函式前新增“_stdcall”關鍵字以使用stdcall方式編譯。

image-20230315105742309

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,彈出訊息框

    image-20230315133331048

要求去除所有的nag(大概就是訊息框吧)並取得註冊碼。

  • 點選確定,介面如下:

    image-20230315133419767
  • 隨意輸入,如下

    image-20230315133504477

去除訊息框

觀察

  • 先嚐試去除訊息框,查詢所有的程式碼間呼叫。這個一般呼叫rtcMsgBox(從外面的dll檔案再到熟悉的啟動函式可以判斷是vb程式),可以找到三個呼叫。右鍵選擇在每個rtcMsgBox上設定斷點,直接F9執行,偵錯程式自動停止,這個點(402CFE)就是msgbox的位置。需要注意視窗主介面有一個“Nag?”按鈕,可以嘗試再次F9執行,發現它執行跳轉到402CFE,所以只需要對一個進行修改。
image-20230315135320442

方法一

  • 可以執行msgbox函式試試,觀察暫存器:

這樣可以知道msgbox傳入了0x14位元組的引數。

  • 空格修改彙編,如下。nop是為了填補空缺,因為原來的call佔用5位元組,而add指令佔用3位元組,為了不影響後面的指令,必須填補這兩位元組的空缺。

  • 這樣修改後,msgbox會被越過,且棧會平衡,但仍然有一個問題,就是返回值並沒有處理。msgbox會使用eax返回1,表示使用者按下確定,這裡並沒有處理eax,導致返回值為0,所以程式會在執行時認為按下了否,程式自動退出。

    大家可能會想到這樣的程式碼:

    add esp,0x14
    mov eax,1
    

    這個程式碼是可行的,但是由於程式碼長度只有5,所以新增一個mov指令會超出這個限制,導致後面的程式碼被侵佔。

方法二

  • 直接向上翻找到函式入口,修改push ebp

    image-20230315143002263
  • 這個0x4可以由兩個地方確定,一個是函式末尾使用了retn 0x4,一個是返回地址處的call前後esp變化了0x4。

  • 這樣修改後直接跳過該函式的執行過程,訊息框自然就被去除了。

找到註冊碼

  • 查詢所有引用字串,可以找到“Yep”
    image-20230315152830773

這個應當是註冊碼輸入正確時執行的指令。

  • 向前找到je

    image-20230315152933346

這個jmp指令應當為對註冊碼正確與否的判斷。

這裡要求di!=si,可以向上繼續尋找,

  • 在前面有個strcmp函式,其push 的兩個引數分別為“I'mlena151”和使用者輸入的註冊碼(ebp-0x58)。基本可以猜測註冊碼即為“I'mlena151”。

相關文章