每個人在偵錯中快速查詢所需程式碼時都有不同的方法,但是最基本最常用的有下面幾種。
學習這4種方法前我們需要思考一個問題。我們知道,執行HelloWorld.exe程式會彈出一個訊息方塊,顯示「Hello
World!」資訊。固然是因為我們編寫了程式碼,可在這種情形下,只要執行一下程式,不論是誰都能輕鬆意識到這一點。如果你熟悉win32 api的開發,看到彈出的訊息方塊就會想到,這是呼叫MessageBox()
API的結果。應用程式的功能非常明確,只要執行一下程式,就能大致推測出其內部結構。不過前提是你已經具備了開發和分析程式碼的經驗。
我們需要查詢的是main()函數中呼叫MessageBox()函數的程式碼。在偵錯程式中偵錯HelloWorld.exe(F8)時,main()函數的MessageBox()函數在某個時刻就會被呼叫執行,彈出訊息對話方塊,顯示「Hello World!」這條資訊。
上面就是程式碼執行法的基本原理,當程式功能非常明確時,我們可以逐條執行指令來查詢需要查詢的位置。但是程式碼執行法僅適用於被偵錯的程式碼量不大、且程式功能明確的情況。倘若被偵錯的程式碼量很大且比較複雜時,此種方法就不再適用了。下面使用程式碼執行法來查詢程式碼中的main()函數。從「大本營」(40104F)開始,按F8鍵逐行執行命令,在某個時刻彈出訊息對話方塊,顯示「Hello World!」資訊。按Ctrl+F2鍵再次載入待偵錯的可執行檔案並重新偵錯,不斷按F8鍵,某個時刻一定會彈出訊息對話方塊。彈出訊息對話方塊時呼叫的函數即為main()函數。如圖2-20所示,地址401144處有一條函數呼叫指令「CALL00401000」,被呼叫的函數地址為401000,按F7鍵(Step Into)進入被呼叫的函數,可以發現該函數就是我們要查詢的main()函數。
如上圖,地址40100E處有一條呼叫MessageBox() API的語句。401002與401007處分別有一條PUSH語句,他把訊息對話方塊的標題與顯示字串(Title = 「www.reversecore.com」, Text = 「Hello World!」)儲存到棧(Stack)中,並作為引數傳遞給MessageBox W()函數。
這樣我們就準確的查詢到了main()函數。
win32應用程式中,API函數的引數是通過棧傳遞的。VC++中預設字串是使用Unicode碼錶示的,並且,處理字串的API函數也全部變更為Unicode系列函數。
右鍵選單-search for-all referenced text strings
OllyDbg初次載入待偵錯的程式時,都會先經歷一個預分析過程。此過程中會檢視程序記憶體,程式中參照的字串和呼叫的API都會被摘錄出來,整理到另外的一個列表中,這樣的列表對偵錯時相當有用的。使用all referenced text strings命令會彈出一個視窗,其中列出了程式程式碼參照的字串。
地址401007處有一條PUSH 004092A0命令,該命令中參照的004092A0處即是字串「Hello World!」。雙擊字串,遊標定位到main()函數中呼叫MessageBox W()函數的程式碼處。
在OllyDbg的value視窗,然後按Ctrl+G命令,可以進一步檢視位於記憶體4092A0地址處的字串。首先使用滑鼠單擊value視窗,然後按Ctrl+G快捷鍵,開啟Enter expression to follow in Dump視窗。(如果你的資料視窗不是這樣的,右鍵單機選擇HEX)
ascii前兩行即使「Hello World!」字串,它是以unicode碼形式表示的,並且字串的後面被填充上了NULL值(記住這塊null值的區域,我們以後還會再講的)。
VC++中,static字串會被預設儲存為Unicode碼形式,static字串是指在程式內部被寫死的字串。
上圖我們還需要注意的是4092A0這個地址,它與我們之前看到的程式碼區域地址比如401XXX是不一樣的。在HelloWorld程序中,409XXX地址空間被用來儲存程式使用的資料。大家要清楚一點:程式碼和資料所在的區域是彼此分開的。
我們後面會慢慢學習程式碼和資料在檔案裡是怎麼儲存,以及怎麼載入到記憶體的,這些是windows PE檔案格式的相關內容,我們暫時先不管他。
3.1在呼叫程式碼中設定斷點
右鍵單擊-search for-all intermodular calls
windows 程式設計中,如果想讓顯示器顯示內容,則需要使用win32API向OS請求顯示輸出。換句話說,應用程式向顯示器輸出內容時,需要在程式內部呼叫win32 API。認真觀察一個程式的功能後,我們能夠大致推測出它在執行時呼叫的WIN32 API,則會為程式調式帶來極大便利。以HelloWorld.exe為例,它在執行時會彈出一個訊息視窗,由此我們可以推斷出該程式呼叫了user_32.MessageBox W() API。(敏感詞所以加了下劃線)
在OllyDbg的預分析中,不僅可以分析出程式中使用的字串,還可以摘錄出程式執行時呼叫的API函數列表。如果只想檢視程式程式碼中呼叫了哪些API函數,可以直接使用all intermodular calls命令。如下圖視窗列出了程式中呼叫的所有API。
可以看到呼叫MessageBox W()的程式碼,該函數位於40100E地址處,他是user_32.MessageBox W() API。雙擊它,遊標就會定位到呼叫它的地址處(40100E)。觀察一個程式的行為特徵,若能事先推測出程式碼中使用的API,則使用上述方法能夠幫助我們快速查詢到需要的部分。
對於程式中呼叫的API,OllyDbg如何準確摘錄出他們的名稱呢?首先,他不是通過檢視原始碼來摘取的,如果想要了解其中的原理,我們需要理解PE檔案格式的IAT(Import
Address Table,匯入地址表)結構。我們會在後面的文章中提到這些內容。
3.2在API程式碼中設定斷點
滑鼠右鍵選單-search for - name in all calls
OllyDbg並不能為所有可執行檔案都列出API函數呼叫列表。使用壓縮器、保護器工具對可執行檔案進行壓縮或保護之後,檔案結構就會改變,此時OllyDbg就無法列出API呼叫列表了(甚至連偵錯都會變得十分困難)。
壓縮器(Run time Packer,執行時壓縮器)
壓縮器是一個實用壓縮工具,能夠壓縮可執行檔案的程式碼、資料、資源等,與普通壓縮不同,它壓縮後的檔案本身就是一個可執行檔案。
保護器
保護器不僅具有壓縮功能,還新增了反偵錯、反模擬、反轉儲等功能,能夠有效保護程序。如果想仔細分析保護器,我們還需要具有更高階的逆向知識。
這種情況下,DLL程式碼庫被載入到程序記憶體後,我們可以直接向DLL程式碼庫新增斷點。API是作業系統對使用者應用程式提供的一系列函數,他們實現於C:\Windows\system32資料夾中的 *.dll檔案(如kernel32.dll、user_32.dll、gdi32.dll、advapi32.dll、ws2_32.dll等)內部。簡單的說,我們編寫的應用程式執行某種操作時(如各種I/O操作),必須使用OS提供的API向OS提出請求,然後與被呼叫API對應的系統DLL檔案就會被載入到應用程式的程序記憶體。
在OllyDbg選單中選擇 View-Memory選單(Alt+M),開啟記憶體對映視窗。如下圖,記憶體對映視窗中顯示了一部分HelloWorld.exe程序記憶體。在圖底部可以看到user_32庫被載入到了記憶體。
使用OllyDbg中的Name in all modules命令可以列出被載入的DLL檔案中提供的所有API。使用Name in all moudules命令開啟All names視窗,單機Name欄目按名稱排序,通過鍵盤敲出MessageBox W後,遊標會自動定位到MessageBox W上。
USER_32模組中有一個Export型別的MessageBoxW函數(不同環境下函數地址不同)。雙擊MessageBoxW函數後就會顯示其程式碼,它實現於USER_32.dll庫中,如圖
觀察MessageBoxW函數的地址空間可以發現,它與HelloWorld.exe使用的地址空間完全不同。在函數起始地址上按F2鍵,設定好斷點後按F9繼續執行。
如果HelloWorld.exe應用程式中呼叫了MessageBoxW() API,則偵錯時程式執行到該處就會暫停。
與預測的一樣,程式執行到MessageBoxW程式碼的斷點處就停了下來,此時暫存器視窗中的ESP值為19FF18
它是程序棧的地址。在右下角的棧視窗中可以看到更詳細的資訊
我們會在後面的教學中詳細說明函數呼叫以及站動作原理,現在可以暫時忽略
如上圖,ESP值的12FF18處對應一個返回地址401014,HelloWorld.exe的main()函數呼叫完MessageBoxW函數後,程式執行流將返回到該地址處。按Ctrl+F9快捷鍵使程式執行到MessageBoxW函數的RETN命令處,然後按F7鍵也可以返回到401014地址處。地址401014的上方就是地址40100E,它正是呼叫MessageBoxW函數的地方。
以上是我所掌握的幾種方法,大家還有其他的思路嗎?