软件调试基础
文章目錄
- PE文件格式
- 文件格式
- 加殼
- 虛擬內存
- Windows安全模式
- 虛擬內存
- 進程空間
- PE文件和虛擬內存的映射
- 基本概念
- 映射關系
- LordPE
- 查看導入表信息
- IAT表信息
- PEView
- 調試分析工具
- OllyDbg
- 基本調試方法
- 快捷鍵
- 跟蹤
- IDA PRO
- 反匯編窗口
- 其它窗口
- PE文件代碼注入示例
- 演示內容及實驗環境
- 用OllyDBG打開掃雷程序
- 在空白代碼區編寫要注入的代碼
- 編輯和注入代碼
- Message函數
- 構造相關字符串
- 構造函數調用的代碼
- 掛接代碼及完成跳轉
- 掛接代碼
- 保存修改
- 修改程序入口點完成跳轉
- 軟件破解示例
- 使用OllyDBG破解
- 破解方式一:修改分支語句
- 破解方式二:修改函數返回值
- 總結
PE文件格式
文件格式
可執行文件之所以可以被操作系統加載且運行,是因為它們遵循相同的規范。PE(Portable Executable)是Win32平臺下可執行文件遵守的數據格式。常見的可執行文件(.exe文件和.dll文件)都是典型的PE文件。
一個可執行文件不光包含了二進制機器碼,還會自帶許多其他信息,如字符串、菜單、圖標、位圖、字體等。PE文件格式規定了所有的這些信息在可執行文件中如何組織。在程序被執行時,操作系統會按照PE文件格式的約定去相應的地方準確地定位各種類型的資源,并分別裝入內存的不同區域。
PE文件格式把可執行文件分成若干個數據節(section),不同的資源被存放在不同的節中。一個典型的PE文件中包含的節如下:
- rsrc
存放程序的資源,如圖標、菜單等。
- text
由編譯器產生,存放著二進制的機器代碼,也是我們反匯編和調試的對象。
- idata
可執行文件所使用的動態鏈接庫等外來函數與文件的信息, 即輸入表
- data
初始化的數據塊,如宏定義、全局變量、靜態變量等。
如果是正常編譯出的標準PE文件,其節信息往往是大致相同的。但這些section的名字只是為了方便人的記憶與使用,使用Microsoft Visual C++中的編譯指示符:
#pragma data_seg()
可以把代碼中的任意部分編譯到PE的任意節中,節名也可以自己定義,如果可執行文件經過了“加殼”處理,PE的節信息就會變得非常“古怪”。在Crack和反病毒分析中需要經常處理這類古怪的PE文件。
加殼
全稱應該是可執行程序資源壓縮,是保護文件的常用手段。 加殼過的程序可以直接運行,但是不能查看源代碼。要經過脫殼才可以查看源代碼。
加殼其實是利用特殊的算法,對EXE、DLL文件里的代碼、資源進行壓縮、加密。類似WINZIP 的效果,只不過這個壓縮之后的文件,可以獨立運行。附加在原程序上的解壓程序通過Windows加載器載入內存后,先于原始程序執行,得到控制權,執行過程中對原始程序進行解密、還原,還原完成后再把控制權交還給原始程序,執行原來的代碼部分。
加上外殼后,原始程序代碼在磁盤文件中一般是以加密后的形式存在,只在執行時在內存中還原,這樣就可以比較有效地防止對程序文件的非法修改和靜態反編譯
加殼工具通常分為壓縮殼和加密殼兩類。
- 壓縮殼的特點是減小軟件體積大小,加密保護不是重點。
- 加密殼種類比較多,不同的殼側重點不同,一些殼單純保護程序,另一些殼提供額外的功能,如提供注冊機制、使用次數、時間限制等。
虛擬內存
Windows安全模式
為了防止用戶程序訪問并篡改操作系統的關鍵部分,Windows使用了2種處理器存取模式:用戶模式和內核模式。用戶程序運行在用戶模式,而操作系統代碼(如系統服務和設備驅動程序)則運行在內核模式。在內核模式下程序可以訪問所有的內存和硬件,并使用所有的處理器指令。操作系統程序比用戶程序有更高的權限,使得系統設計者可以確保用戶程序不會意外的破壞系統的穩定性。
虛擬內存
Windows的內存可以被分為兩個層面:物理內存和虛擬內存。其中,物理內存非常復雜,需要進入Windows內核級別ring0才能看到。通常,在用戶模式下,用調試器看到的內存地址都是虛擬內存。
用戶編制程序時使用的地址稱為虛擬地址或邏輯地址,其對應的存儲空間稱為虛擬內存或邏輯地址空間;而計算機物理內存的訪問地址則稱為實地址或物理地址,其對應的存儲空間稱為物理存儲空間或主存空間。程序進行虛地址到實地址轉換的過程稱為程序的再定位。
進程空間
在Windows系統中,在運行PE文件時,操作系統會自動加載該文件到內存,并為其映射出4GB的虛擬存儲空間,然后繼續運行,這就形成了所謂的進程空間。用戶的PE文件被操作系統加載進內存后,PE對應的進程支配了自己獨立的4GB虛擬空間。在這個空間中定位的地址稱為虛擬內存地址(Virtual Address,VA)。
到了現在,系統運行在X64架構的硬件上,可訪問的內存也突破了以前4GB的限制,但是獨立的進程擁有獨立的虛擬地址空間的內存管理機制還在沿用。
PE文件和虛擬內存的映射
在調試漏洞時,可能經常需要做這樣兩種操作:
- 靜態反匯編工具看到的PE文件中某條指令的位置是相對于磁盤文件而言的,即所謂的文件偏移,我們可能還需要知道這條指令在內存中所處的位置,即虛擬內存地址。
- 反之,在調試時看到的某條指令的地址是虛擬內存地址,我們也經常需要回到PE文件中找到這條指令對應的機器碼。
基本概念
相對虛擬地址(RVA)
相對虛擬地址是內存地址相對于映射基址的偏移量。
虛擬內存地址(VA)
PE文件中的指令被裝入內存后的地址。
文件偏移地址(File Offset)
數據在PE文件中的地址叫文件偏移地址,這是文件在磁盤上存放時相對于文件開頭的偏移。
裝載基址(Image Base)
PE裝入內存時的基地址。默認情況下,EXE文件在內存中的基地址是0x00400000,DLL文件是0x10000000。
這些位置可以通過修改編譯選項更改。
映射關系
VA=Image Base+RVA
由于文件數據的存放單位與內存數據存放單位不同而造成一些差異:
- PE文件中的數據按照磁盤數據標準存放,以0x200字節為基本單位進行組織。當一個數據節(section)不足0x200字節時,不足的地方將被0x00填充:當一個數據節超過0x200字節時,下一個0x200塊將分配給這個節使用。因此PE數據節的大小永遠是0x200的整數倍。
- 當代碼裝入內存后,將按照內存數據標準存放,并以0x1000字節為基本單位進行組織。類似的,不足將被補全,若超出將分配下一個0x1000為其所用。因此,內存中的節總是0x1000的整數倍。
LordPE
LordPE是一款功能強大的PE文件分析、修改、脫殼軟件。LordPE是查看PE格式文件信息的首選工具,并且可以修改相關信息。
VOffset是RVA(相對虛擬地址),ROffset是文件偏移。也就是,在系統進程中,代碼(.text節)將被加載到0x400000+0x11000=0x411000的虛擬地址中(裝載基址+RVA)。而在文件中,可以使用二進制文件打開,看到對應的代碼在0x1000位置處。
查看導入表信息
導入表在文件里的偏移地址ROffset為0x24000,RVA是0x25000。打開目錄表可以看到,可以看到輸入表的RVA確實是0x25000。點左側按鈕L可以查看具體輸入表里的內容。
IAT表信息
IAT(Import Address Table:輸入函數地址表)
每個API函數在對應的進程空間中都有其相應的入口地址。眾所周知,操作系統動態庫版本的更新,其包含的API函數入口地址通常也會改變。由于入口地址的不確定性,程序在不同的電腦上很有可能會出錯,為了解決程序的兼容問題,操作系統就必須提供一些措施來確保程序可以在其他版本的Windows操作系統,以及DLL版本下也能正常運行。這時IAT表就應運而生了。
基于導入表可以定位IAT的具體信息,相關工具可以幫助直接查看IAT表的相關內容。
PEView
直觀顯示PE文件內容
調試分析工具
OllyDbg
是一種具有可視化界面的 32 位匯編—分析調試器,適合動態調試。 OllyDBG版的發布版本是個ZIP 壓縮包,解壓就可以使用了。
基本調試方法
OllyDBG 有兩種方式來載入程序進行調試
- 一種是點擊菜單文件,打開(快捷鍵是F3)來打開可執行文件進行調試
- 另一種是點擊菜單文件,附加到一個己運行的進程上進行調試,要附加的程序必須己運行。
快捷鍵
- F2設置斷點。
- F7單步步入。功能同單步步過(F8)類似,區別是遇到 CALL 等子程序時會進入其中,進入后首先會停留在子程序的第一條指令上。
- F8單步步過。執行一條指令,遇到 CALL 等子程序不進入其代碼。
- F4運行到選定位置。
- F9運行
- CTR+F9執行到返回。此命令在執行到一個ret(返回指令)指令時暫停,常用于從系統領空返回到我們調試的程序領空。
- ALT+F9執行到用戶代碼。可用于從系統領空快速返回到我們調試的程序領空。
跟蹤
使用調試功能時通常會碰到在斷點處無法定位入口的情況,即無法確定前序執行指令,通過Trace(跟蹤)功能可以記錄調試過程中執行的指令,用于分析前序執行指令。Trace記錄可選擇是否記錄寄存器的值。
IDA PRO
簡稱IDA(Interactive Disassembler),是一個世界頂級的交互式反匯編工具,是逆向分析的主流工具。
IDA使用File菜單中的Open選項,可以打開一個計劃逆向分析的可執行文件,打開的過程是需要耗費一些時間的。IDA會對可執行文件進行分析。一旦打開成功,會提示你是否進入Proximity view。通常都會點Yes,按默認選項進入。
反匯編窗口
也叫IDA-View窗口,是操作和分析二進制文件的主要工具。
反匯編窗口有三種顯示格式:
- 面向文本的列表視圖(Text view)
- 基于圖形的視圖(Graphic View)
- 優化視圖(Proximity view)將顯示函數及其調用關系
視圖間可以切換:在上圖的Proximity view視圖中,點選一個塊,比如_main函數塊,在其上點右鍵,可以看到Text view和Graph view等選項。通過右鍵可以實現不同視圖的切換。
圖形視圖:將一個函數分解為許多基本塊,類似程序流程圖類似,生動的顯示該函數由一個塊到另一個塊的控制流程。
如下圖所示的_main函數的圖形視圖:
文本視圖:文本視圖則呈現一個程序的完整反匯編代碼清單(而在圖形模式下一次只能顯示一個函數),用戶只有通過這個窗口才能查看一個二進制文件的數據部分。如下圖所示的文本視圖:
通常虛擬地址以[區域名稱]:[虛擬地址]這種格式顯示,如.txt:0040110C0。
實線箭頭表示非條件跳轉,虛線箭頭則表示條件跳轉。如果一個跳轉將控制權交給程序中的某個地址,這時會使用粗線,出現這類逆向流程,通常表示程序中存在循環。
其它窗口
通過菜單Views,Open subviews可以打開更多的窗口。
Names窗口:列舉二進制文件的所有全局名稱。名稱是指對一個程序虛擬地址的符號描述。 Names窗口顯示的名稱采用了顏色和字母編碼,其編碼方案如下:
- F常規函數
- A字符串數據
- L庫函數
- I導入的名稱,通常為共享庫導入的函數名稱
- D數據,已命名數據的位置通常表示全局變量
Strings窗口:顯示從二進制文件中提取出的字符串,以及每個字符串所在的地址。與雙擊Names窗口中的名稱得到的結果類似,雙擊Strings窗口中的任何字符串,反匯編窗口將跳轉到該字符串所在的地址。將Strings窗口與交叉引用結合,可以迅速定義感興趣的字符串,并追蹤到程序中任何引用該字符串的位置。
Function name窗口:該窗口顯示所有的函數。點擊函數名稱,可以快速導航到反匯編視圖中的該函數區域。該窗口中的條目如下:
這一行信息指出:用戶可以在二進制文件中虛擬地址為00401040的.text部分中找到_main函數,該函數長度為0x50字節。
Function call窗口:函數調用(Function call)窗口將顯示所有函數的調用關系。如下圖:
反編譯:新版本的IDA增加了反編譯功能,加強了分析能力。
在IDA View窗口下制定匯編代碼,按快捷鍵F5,IDA會將當前所在位置的匯編代碼編譯成C/C++形式的代碼,并在Pseudocode窗口中顯示,如下圖所示。
PE文件代碼注入示例
演示內容及實驗環境
利用PE文件輸入表API實現代碼注入:讓目標程序運行之前,先運行我們注入的代碼,注入的代碼將運行PE文件輸入表里包含的API。
目標PE文件為Windows XP下的掃雷程序,使用的工具包括OllyDBG和LordPE。
掃雷游戲程序位置:在Windows下找到附件里的掃雷游戲,右鍵屬性可以看到具體文件的位置,即C:\WINDOWS\system32\winmine.exe。
用OllyDBG打開掃雷程序
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-vVPKOYNS-1655188292069)(attachment:48ea5e2f8ed501e9e4e16c59bdde8403)]
程序會停下來,自動停下來的這一行代碼位置就是程序入口點。可以通過LordPE文件來查看,得知程序入口點的RVA是0x00003E21,同時也可以看到裝載基址是0x01000000(掃雷程序是C++語言編寫);也可以通過右側寄存器EIP的值0x01003E21(VA)可以觀察到注釋信息里,提示是ModuleEntryPoint。
反匯編區域繼續往下翻頁,可以看到相關的導入表動態鏈接庫及其相關函數的信息。
在空白代碼區編寫要注入的代碼
在代碼區可以找到大量的空白代碼區域,如果我們往這里頭植入代碼,直接修改PE文件相關跳轉地址(入口點或特定函數的IAT的跳轉地址),就可以實現相關的植入代碼的執行。
本實驗:演示讓掃雷程序運行之前,先運行我們注入的代碼,注入的代碼將調用PE文件輸入表里包含的MessageBox函數,彈出對話框,顯示相關信息。
編輯和注入代碼
Message函數
int MessageBox(HWND hWnd, // handle to owner windowLPCTSTR lpText, // text in message boxLPCTSTR lpCaption, // message box titleUINT uType // message box style );- hWnd:消息框所屬窗口的句柄,如果為NULL,消息框則不屬于任何窗口。
- lpText:字符串指針,所指字符串會在消息框中顯示。
- lpCaption:字符串指針,所指字符串將成為消息框的標題。
- uType:消息框的風格(單按鈕、多按鈕等),NULL代表默認風格。
系統中并不存在真正的MessageBox函數,調用最終都將由系統按照參數中的字符串的類型選擇“A”類函數(ASCII)或者“W”類函數(UNICODE)調用,我們使用MessageBoxA.
構造相關字符串
計劃注入的代碼功能為:彈出對話框,顯示“You are Injected!”。
在代碼空白區域每一行位置,點鼠標右鍵,選擇 編輯->二進制編輯。
在彈出的編輯界面里,輸入ASCII碼“PE Inject”,將“保持代碼空間大小”去掉選中狀態,狀態如下:
按快捷鍵CTRL+A(分析),顯示為ASCII碼。再加入另一條語句“You are Injected!” 。
上面的每個語句后面都留了一行00。因為,字符串后面是需要結束符0x00的。
構造函數調用的代碼
push 0(默認風格); push 0x01004AA7(標題字符串地址); push 0x01004A9C(內容字符串地址); push 0(窗口歸屬); call MessageBoxA //參數壓入棧中的順序是從右向左注意,直接雙擊要修改的當前行,就進入修改匯編代碼的狀態,如下:
我們輸入的匯編指令call MessageBoxA之所以后面能成功運行,也是因為PE文件的輸入表里已經有這個函數的入口地址了。以上代碼完成輸入后,結果如下:
掛接代碼及完成跳轉
掛接代碼
我們首先繼續輸入一條指令jmp 0x01003E21。這句話意思是我們運行完我們注入的彈出對話框之后,會跳轉到我們原來的這個PE文件的入口點,繼續運行。結果如下圖:
保存修改
上述修改是在原始文件副本里修改的,如果要保存修改,需要:
到此,文件修改完畢,但是如果直接運行這個掃雷程序,并沒有發生任何變化。** 因為,我們只是編輯了一段代碼,只有這些代碼被運行了才算真正被注入。**
修改程序入口點完成跳轉
利用LordPE文件,我們更改一下程序入口點,為我們的程序的起始位置,即我們編輯的代碼段的第一個push 0的位置,地址為0x01004ABA,因為只需要更改RVA,就修改為0x00004ABA即可,如下圖:
保存后運行,可以看到彈出右側對話框,之后出現掃雷程序。
軟件破解示例
本節將對一個簡單的密碼驗證程序,使用OllyDBG進行破解。具體程序如下:·
#include <iostream> using namespace std; #define password "12345678" bool verifyPwd(char * pwd) {int flag;flag=strcmp(password, pwd);return flag==0; } void main() {bool bFlag;char pwd[1024];printf("please input your password:\n");while (1){scanf("%s",pwd);bFlag=verifyPwd(pwd);if (bFlag){printf("passed\n");break;}else{printf("wrong password, please input again:\n");}} }破解對象是該程序生成的Debug模式的exe程序。
對得到的exe程序(假定不知道上面的源代碼),有多種方式實現破解:
- 使用OllyDBG
通過運行程序,觀察關鍵信息,通過對關鍵信息定位,來得到關鍵分支語句,通過對該分支語句進行修改,達到破解的目的;
- 另一種方式
可以通過IDA Pro來觀察代碼結構,確定函數入口地址,對函數體返回值進行更改。(給出的程序實例中使用的貌似還是OllyDBG)
運行程序,輸入一個密碼,發現運行結果如下:
使用OllyDBG破解
在OllyDBG中,為了盡快定位到分支語句處,在反匯編窗口,點右鍵,選擇“查找→所有引用的字符串”功能:
然后,使用快捷鍵,Ctrl+F打開搜索窗口,輸入wrong,點確定后,將定位出錯信息的哪一行代碼:
雙擊這一行代碼,就會定位反匯編中的相應代碼處:
破解方式一:修改分支語句
觀察反匯編語言,可知核心分支判斷在于:
Test eax,eax Jz short 0041364b如果jz條件成立,則跳轉到0041364b處,即顯示錯誤密碼分支語句中。如果將jz該指令改為jnz,則程序截然相反。輸入了錯誤密碼,將進入驗證成功的分支中。
雙擊jz密碼一行,對其進行修改:
注意:
此時并沒有真正修改二進制文件中的有關代碼,如果想要修改二進制文件中的代碼,需要在反匯編窗口,點右鍵,選擇“編輯->復制當前修改到可執行文件”。保存后的可執行文件,將是破解后的文件。
破解方式二:修改函數返回值
更改函數。通過分析匯編語句,可知,驗證命令使用的是verifyPwd函數,點右鍵選擇跟隨,逐步進入該函數:
函數的返回值通過eax寄存器來完成的,核心語句即sete al。
對于函數中的代碼:
被解釋成匯編語言:
Mov dword ptr [ebp-8], eax //將strcmp函數調用后的返回值(存在eax中)賦值給變量flag Xor eax, eax //將eax的值清空 Cmp dword ptr [ebp-8], 0 //將flag的值與0進行比較,即flag==0; //注意cmp運算的結果只會影響一些狀態寄存器的值 Sete al //sete是根據狀態寄存器的值,如果相等,則設置,如果不等,則不設置要想更改該語句,在cmp dword ptr [ebp-8], 0處開始更改,將其更改為:mov al,01。取消保持代碼空間大小,如果新代碼超長,將無法完成更改。
并將sete al改為NOP。
得到結果如下:
運行結果校驗破解正確性。
總結
本章介紹了軟件安全調試的基礎知識,包括PE文件格式、虛擬內存,以及介紹了LordPE、OllyDBG、IDA Pro等用于逆向分析的工具。還給出了PE文件代碼注入和軟件破解兩個示例,后續如果有時間會分享這兩個實驗的錄制視頻,演示每一步實驗細節。
軟件安全系列預期會有8輯:
- 軟件安全預備知識
- 軟件調試基礎
- 軟件漏洞篇
- 漏洞利用篇
- 漏洞挖掘篇
- 滲透測試篇
- WEB安全基礎
- WEB滲透實戰
內容參考自《軟件安全:漏洞利用及滲透測試》,書中對于PE文件結構講述并不到位,后續會發一篇博文,講述PE文件結構有關內容。
總結
- 上一篇: Kmeans 算法 收敛
- 下一篇: Android事件分发之ACTION_C