【翻譯】使用VMWare GDB和IDA調試Windows內核

自 http://bbs.pediy.com/showthread.php?t=135229


最近喜歡用IDA搞一些內核的東西,於是就到處找IDA關於內核方面的東東。這篇文章實在原文的基礎上進行了一定的封裝,也算是半原創的東東吧~希望大家不要拍磚撒~
VMWare的GDB調試器功能比較簡單也比較基礎,該調試器並不知道處理器和線程的任何信息(對於Windows系統),因而如果想要得到一些高等級的信息,我們需要自己做一些額外的工作。本文主要講解了如何使用IDAPython腳本來讓IDA處理已經加載的模塊列表和加載符號庫。

設置VM來進行調試

在進行這一步之前首先要保證你已經有了一個已經安裝好的Windows(32位)的操作系統。在開始調試之前,首先要拷貝你想要看到符號的模塊到系統目錄下,如果你不確定要複製那些文件,可以將如下的文件複製到虛擬機目錄下:位於System32目錄下的nt*.exe和hal.dll文件、整個System32\drivers目錄。在這裏我將文件複製到了E:\虛擬機系統\Windows 7\Shar4ed dll\目錄下。
編輯虛擬機的.vmx文件來激活GDB調試器功能:
 
圖01
在文檔末尾加入如下兩行(VM更多的功能可以查看VM的相關文檔http://wiki.osdev.org/VMWare):
debugStub.listen.guest32 = "TRUE"
debugStub.hideBreakpoints= "TRUE"
修改之後保存文件並且啓動虛擬機,等待虛擬機啓動。
 
圖02
虛擬機啓動之後啓動IDA,如果出現如圖03所示的提示窗口則直接點GO進入程序界面即可。
 名稱:  03.png查看次數: 1111文件大小:  47.7 KB
圖03
進入住界面之後執行菜單的Debugger->Attach->Remote GDB Debugger如圖04所示。
 名稱:  04.png查看次數: 1108文件大小:  17.7 KB
圖04
打開如圖05所示的調試器附加窗口。
 名稱:  05.png查看次數: 1109文件大小:  16.9 KB
圖05
在Hostname中輸入localhost,端口輸入8832。點擊確定之後將會打開如圖06所示的進程選擇窗口。
 名稱:  06.png查看次數: 1105文件大小:  19.9 KB
圖06
選擇ID爲0的進程進行附加,如果附加成功將會彈出如圖07所示的提示信息。
 名稱:  07.png查看次數: 1107文件大小:  18.1 KB
圖07
點擊OK之後將會中斷在如圖08所示的代碼處。
 
圖08
此時將會中斷在內核中地址大於0×80000000的地方,現在就可以進行單步調試了,但是沒有任何的名稱調試起來是非常不爽的,那麼我們就來收集更多的信息讓IDA的顯示看起來更加的直觀。


獲取內核模塊列表

內核模塊列表保存在一個有PsLoadedModuleList符號指向的內核列表中。爲了獲取這個列表的地址就要用到KPCR的方法,KPCR的全稱是Kernel Processor Control Region。內核用這個區域來存儲每個處理器所包含的各種信息。它被放置在fs寄存器指向的區段中(類似於應用層中的TEB)。它有一個區域叫做KdVersionBlock,這個區域指向了內核調試使用的一個結構體。而這個結構體則包含了各種內存結構的指針,其中就包括PsLoadedModuleList。

KPRC的定義
這個結構體可以在很多地方找到,其中IDA的ntddk.til文件中也有這個結構的定義。現在我們只需要知道KdVersionBlock位於KPRC結構體的的0x34處,並且它指向了DBGKD_GET_VERSION64。在DBGKD_GET_VERSION64的偏移量0x18處則可以找到PsLoadedModuleList。
現在我們就可以寫一個小的Python函數來找到這個指針的的值。爲了得到fs指向的區段的基址我們可以使用VMWare的“r”調試命令。GDB 調試器插件註冊了一個IDC函數,叫做SendGDBMonitor()來發送命令到監視器,所以我們可以使用IDAPython的Eval()函數來調用它。
fs_str = Eval(‘SendGDBMonitor("r fs")’)
返回的數據格式如下所示:
fs 0×30 base 0x82744a00 limit 0×00002008 type 0×3 s 1 dpl 0 p 1 db 1
我們需要的是在base標記之後的數值。
kpcr = int(fs_str[13:23], 16) #extract and convert as base 16 (hexadecimal) number 
然後來獲取KdVersionBlock的數值:
kdversionblock = Dword(kpcr+0×34)
最後我們來獲取PsLoadedModuleList的地址:
PsLoadedModuleList = Dword(kdversionblock+0×18)

遍歷內核模塊

現在就可以根據上面的地址來遍歷內核模塊了,PsLoadedModuleList被聲明爲PLIST_ENTRY。PLIST_ENTRY的定義如下所示(雙向鏈表):
typedef struct _LIST_ENTRY
{
     PLIST_ENTRY Flink;
     PLIST_ENTRY Blink;
} LIST_ENTRY, *PLIST_ENTRY;
所以要遍歷所有的模塊只需要跟隨Flink指針,直到我們回到開始的地方就可以了。每一個模塊的結構定義如下所示:
struct LDR_MODULE
{
  LIST_ENTRY InLoadOrderModuleList;
  LIST_ENTRY InMemoryOrderModuleList;
  LIST_ENTRY InInitializationOrderModuleList;
  PVOID BaseAddress;
  PVOID EntryPoint;
  ULONG SizeOfImage;
  UNICODE_STRING FullDllName;
  UNICODE_STRING BaseDllName;
  ULONG Flags;
  SHORT LoadCount;
  SHORT TlsIndex;
  LIST_ENTRY HashTableEntry;
  ULONG TimeDateStamp;
};
現在我們可以來編寫一個小函數遍歷這個鏈表並且爲每個模塊創建一個區段。
#get the first module
cur_mod = Dword(PsLoadedModuleList)
while cur_mod != PsLoadedModuleList and cur_mod != BADADDR:
  BaseAddress  = Dword(cur_mod+0×18)
  SizeOfImage  = Dword(cur_mod+0×20)
  FullDllName  = get_unistr(cur_mod+0×24)
  BaseDllName  = get_unistr(cur_mod+0x2C)
  #create a segment for the module
  SegCreate(BaseAddress, BaseAddress+SizeOfImage, 0, 1, saRelByte, scPriv)
  #set its name
  SegRename(BaseAddress, BaseDllName)
  #get next entry
  cur_mod = Dword(cur_mod)


加載符號庫

已經能夠獲取內核模塊列表固然不錯,但是如果不能加載符號庫那麼上面的工作也就沒有多少用處。我們可以通過IDA的File->LoadFile->PDB file手動爲每個模塊加載符號庫,但是這樣做太蛋疼了。爲什麼不讓它自動加載呢?
爲了達到我們的目的這裏就要用到PDB插件,通過查閱源代碼(SDK)我們可以發現它支持下面的三個調用代碼:
//call_code==0: user invoked ‘load pdb’ command, load pdb for the input file
//call_code==1: ida decided to call the plugin itself
//call_code==2: load pdb for an additional exe/dll
//              load_addr: netnode("$ pdb").altval(0)
//              dll_name:  netnode("$ pdb").supstr(0)
第二個調用代碼看起來正是我們需要的功能。但是,當前版本的IDAPython只包含一個非常基礎的節點類,所以沒有辦法是用Python來實現這個功能。但是我們如果查看其它的調用代碼可以看到這個插件重定義了模塊的基址(通過$ PE header)和模塊的路徑(通過get_input_file_path()),並且我們可以通過set_root_filename()函數來設置輸入文件的路徑。同樣如果我們使用第三個調用代碼那麼我們要避免出現“你是否想要加載符號庫?”(”Do you want to load the symbols?”)的提示。
#new netnode instance
penode = idaapi.netnode()
#create netnode the in database if necessary
penode.create("$PE header")
#set the imagebase (-2 == 0xFFFFFFFE)
penode.altset(0xFFFFFFFE, BaseAddress)
#set the module filename
idaapi.set_root_filename(filename)
#run the plugin
RunPlugin("pdb",3)
但是我們需要用使用前面的文件路徑(想要看到符號庫的文件)來取代內核模塊路徑:
#path to the local copy of System32 directory
local_sys32 = r" E:\虛擬機系統\Windows 7\Shar4ed dll"
if FullDllName.lower().startswith(r"\systemroot\system32"):
#translate into local filename
filename = local_sys32 + FullDllName[20:]
也可以直接運行vmmodules.py腳本,在執行的過程中將會彈出如圖09所示的提示窗口。
 名稱:  09.png查看次數: 1102文件大小:  18.2 KB
圖09
如果Input file的路徑不對則需要手工進行修改,點擊確定之後將會彈出如圖10所示的符號庫加載確認窗口。
 名稱:  10.png查看次數: 1103文件大小:  35.5 KB
圖10
點擊yes之後就開始加載符號庫了,但是比較繁瑣的是每次加載一個新的模塊的符號庫時都會彈出圖09種的確認窗口,直到所有的模塊的符號加載完畢。這也是個痛苦的過程~
在執行完腳本之後再來看下程序的內存鏡像將會是如圖11所示。
 
圖11
再來看下名稱窗口可以發現只要加載符號庫的模塊中的名稱都已經顯示出來了,如圖12所示。
 名稱:  12.png查看次數: 1098文件大小:  35.7 KB
圖12
現在看起來就好多啦,調試愉快!

發佈了2 篇原創文章 · 獲贊 2 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章