網絡安全編程:調試API函數之產生斷點
Windows中有些API函數是專門用來進行調試的,被稱作Debug API,或者是調試API。利用這些函數可以進行調試器的開發,調試器通過創建有調試關系的父子進程來進行調試,被調試進程的底層信息、即時的寄存器、指令等信息都可以被獲取,進而用來分析。
OllyDbg調試器的功能非常強大,雖然有眾多的功能,但是其基礎的實現就是依賴于調試API。調試API函數的個數雖然不多,但是合理使用會產生非常大的作用。調試器依賴于調試事件,調試事件有著非常復雜的結構體。調試器有著固定的流程,由于實時需要等待調試事件的發生,其過程是一個調試循環體,非常類似于SDK開發程序中的消息循環。無論是調試事件還是調試循環,對于調試或者說調試器來說,其最根本、最核心的部分是中斷,或者說其最核心的部分是可以捕獲中斷。
產生中斷的方法是設置斷點。常見的產生中斷的斷點方法有3種,分別是中斷斷點、內存斷點和硬件斷點。下面介紹這3種斷點的不同。
中斷斷點,這里通常指的是匯編語言中的int 3指令,CPU執行該指令時會產生一個斷點,因此也常稱之為INT3斷點。現在演示如何使用int 3來產生一個斷點,代碼如下:
- int main(int argc, char* argv[])
- {
- __asm int 3
- return 0;
- }
代碼中使用了asm,在asm后面可以使用匯編指令。如果想添加一段匯編指令,方法是asm{}。通過asm可以在C語言中進行內嵌匯編語言。在__asm后面直接使用的是int 3指令,這樣會產生一個異常,稱為斷點中斷異常。對這段簡單的代碼進行編譯連接,并且運行。運行后出現錯誤對話框,如圖1所示。
圖1 異常對話框
圖1所示的異常對話框中通過鏈接“請單擊此處”可以打開詳細的異常報告。如果電腦與此處顯示的對話框不同,請依次進行如下設置:在“我的電腦”上單擊右鍵,在彈出的菜單中選擇“屬性”,打開“屬性”對話框,選擇“高級”選項卡,選擇“錯誤報告”按鈕,打開“錯誤匯報”界面,在該界面上選擇“啟用錯誤匯報”單選按鈕,然后單擊確定。通過這樣的設置,就可以啟動“異常對話框”了。對于分析程序的BUG、挖掘軟件的漏洞,彈出異常對話框界面是非常有用的。
這個對話框可能常常見到,而且見到以后多半會很讓人郁悶,通常情況是直接單擊“不發送”按鈕,然后關閉這個對話框。在這里,這個異常是通過int 3導致的,不要忙著關掉它。通常在寫自己的軟件時如果出現這樣的錯誤,應該去尋找更多的幫助信息來修正錯誤。單擊“請單擊此處”鏈接,出現如圖2所示的對話框。
圖2 “異常基本信息”對話框
彈出“異常基本信息”對話框,因為這個對話框給出的信息實在太少了,繼續單擊“要查看關于錯誤報告的技術信息”后面的“請單擊此處”鏈接,打開如圖3所示的對話框。
圖3 “錯誤報告內容”對話框
通常情況下,在這個報告中只關心兩個內容,一是Code,二是Address。在圖3中,Code后面的值為0x80000003,Address后面的值為0x0000000000401028。Code的值為產生異常的異常代碼,Address是產生異常的地址。在Winnt.h中定義了關于Code的值,在這里0x80000003的定義為STATUS_BREAKPOINT,也就是斷點中斷。在Winnt.h中的定義為:
- #define STATUS_BREAKPOINT ((DWORD)0x80000003L)
可以看出,這里給的Address是一個VA(虛擬地址),用OD打開這個程序,直接按F9鍵運行,如圖4和圖5所示。
圖4 在OD中運行后被斷下
圖5 OD狀態欄提示
從圖4中可以看到,程序執行停在了00401029位置處。從圖5看到,INT3命令位于00401028位置處。再看一下圖3中Address后面的值,為00401028。這也就證明了在系統的錯誤報告中可以給出正確的出錯地址(或產生異常的地址)。這樣在以后寫程序的過程中可以很容易地定位到自己程序中有錯誤的位置。
在OD中運行自己的int 3程序時,可能OD不會停在00401029地址處,也不會給出類似圖4的提示。在實驗這個例子的時候需要對OD進行設置,在菜單中選擇“選項”→“調試設置”,打開“調試選項”對話框,選擇“異常”選項卡,取消“INT3中斷”復選框的選中狀態,這樣就可以按照該例子進行測試了。
回到中斷斷點的話題上,中斷斷點是由int 3產生的,那么要如何通過調試器(調試進程)在被調試進程中設置中斷斷點呢?看圖4中00401028地址處,在地址值的后面、反匯編代碼的前面,中間那一列的內容是匯編指令對應的機器碼。可以看出,INT3對應的機器碼是0xCC。如果想通過調試器在被調試進程中設置INT3斷點的話,那么只需要把要中斷的位置的機器碼改為0xCC即可。當調試器捕獲到該斷點異常時,修改為原來的值即可。
內存斷點的方法同樣是通過異常產生的。在Win32平臺下,內存是按頁進行劃分的,每頁的大小為4KB。每一頁內存都有其各自的內存屬性,常見的內存屬性有只讀、可讀寫、可執行、可共享等。內存斷點的原理就是通過對內存屬性的修改,本該允許進行的操作無法進行,這樣便會引發異常。
在OD中關于內存斷點有兩種,一種是內存訪問,另一種是內存寫入。用OD隨便打開一個應用程序,在其“轉存窗口”(或者叫“數據窗口”)中隨便選中一些數據點后單擊右鍵,在彈出的菜單中選擇“斷點”命令,在“斷點”子命令下會看到“內存訪問”和“內存寫入”兩種斷點,如圖6所示。
圖6 內存斷點類型
下面通過簡單例子來看如何產生一個內存訪問異常,代碼如下:
- #include <Windows.h>
- #define MEMLEN 0x100
- int main(int argc, char* argv[])
- {
- PBYTE pByte = NULL;
- pByte = (PBYTE)malloc(MEMLEN);
- if ( pByte == NULL )
- {
- return -1;
- }
- DWORD dwProtect = 0;
- VirtualProtect(pByte, MEMLEN, PAGE_READONLY, &dwProtect);
- BYTE bByte = '\xCC';
- memcpy(pByte, (const char *)&bByte, MEMLEN);
- free(pByte);
- return 0;
- }
這個程序中使用了VirtualProtect()函數,該函數與VirtualProtectEx()函數類似,不過VirtualProtect()是用來修改當前進程的內存屬性的。
對這個程序編譯連接,并運行起來。熟悉的出錯界面又出現在眼前,如圖7所示。
圖7 “異常基本信息”對話框
按照前面介紹的步驟打開“錯誤報告內容”對話框,如圖8所示。
圖8 “錯誤報告內容”對話框
按照上面的分析方法來看一下Code和Address這兩個值。Code后面的值為0xc0000005,這個值在Winnt.h中的定義如下:
- #define STATUS_ACCESS_VIOLATION ((DWORD)0xC0000005L)
這個值的意義表示訪問違例。Address后面的值為0x0000000000403093,這個值是地址,但是這里的地址根據程序來考慮,值是用malloc()函數申請的,用于保存數據的堆地址,而不是用來保存代碼的地址。這個地址就不進行測試了,因為是動態申請,很可能每次不同,因此大家了解就可以了。
硬件斷點是由硬件進行支持的,它是硬件提供的調試寄存器組。通過這些硬件寄存器設置相應的值,然后讓硬件斷在需要下斷點的地址。在CPU上有一組特殊的寄存器,被稱作調試寄存器。該調試寄存器有8個,分別是DR0—DR7,用于設置和管理硬件斷點。調試寄存器DR0—DR3用于存儲所設置硬件斷點的內存地址,由于只有4個調試寄存器可以用來存放地址,因此最多只能設置4個硬件斷點。寄存器DR4和DR5是系統保留的,并沒有公開其用處。調試寄存器DR6被稱為調試狀態寄存器,記錄了上一次斷點觸發所產生的調試事件類型信息。調試寄存器DR7用于設置觸發硬件斷點的條件,比如硬件讀斷點、硬件訪問斷點或硬件執行斷點。