使用WinDbg除錯Windows核心(二)
0×01使用斷點跟蹤資料
斷點通常用在暫停某個我們感興趣的執行程式碼,例如當某個函式被呼叫時,我們還可以使用WinDbg斷點命令字串跟蹤一些資訊。在這裡,我們將著眼於跟蹤特定使用者模式程序的有趣資訊,即特定資料NOTEPAD.EXE寫入到磁碟的一個例子。在開始前,我們需要在我們的虛擬機器中開啟一個記事本例項。我們現在可以建立一個斷點來攔截檔案系統寫入記事本的資料,並同時顯示要寫入磁碟的資料。
第一步,我們需要找到Notepad.exe程序的一些資訊,以確保我們只在這個程序裡斷下,而不是在每個程序都斷下。而我們需要尋找的資訊是一個指向EPROCESS結構的指標。該EPROCESS結構是用來表示一個程序的主核心資料結構。你可以看到包含“DT _EPROCESS”(在EPROCESS結構dump型別)的資訊。為了找到一個給定的過程的EPROCESS結構,我們可以呼叫!Process擴充套件命令。該擴充套件命令列印目標系統中當前活動程序的資訊。我們過濾篩選出notepad的程序,並且只顯示最低限度的資訊:
該EPROCESS的指標為藍色突出“PROCESS”欄位,我們接下來就會用到這個值。
我們要設定在核心中設定NtWriteFile斷點。這是系統的呼叫,所有使用者模式寫入磁碟會呼叫該函式。通過在此處設定斷點,我們可以看到系統所有寫入磁碟資料的過程。這樣就顯得很煩了,所以我們將使用上述EPROCESS值,要求從我們選擇的程序上下文中斷NtWriteFile函式。我們可以使用命令如下:
bp /p fffffa800295d060 nt!NtWriteFile "da poi(@rsp+30); g"
這就只在我們的程序中設定(通過/ P使用我們的EPROCESS值)NT!NtWriteFile(NT為核心模組的名稱)斷點。當斷點命中時,引號後的命令將執行,這裡由分號來分隔每個命令。這裡已經使用了notepad寫顯示資料的命令,然後使用“g”重新啟動虛擬機器執行。但是,為什麼“da poi (*@ rsp+ 30)”的顯示結果為寫入緩衝區資料?
要理解這一部分,我們需要看看NtWriteFile的函式原型:
NTSTATUS NtWriteFile(
_In_ HANDLE FileHandle,
_In_opt_ HANDLE Event,
_In_opt_ PIO_APC_ROUTINE ApcRoutine,
_In_opt_ PVOID ApcContext,
_Out_ PIO_STATUS_BLOCK IoStatusBlock,
_In_ PVOID Buffer,
_In_ ULONG Length,
_In_opt_ PLARGE_INTEGER ByteOffset,
_In_opt_ PULONG Key
);
來源於這裡
在這個函式的原型中,我們感興趣的是Buffer引數。這些緩衝區的資料調通過函式呼叫最後寫入磁碟。在微軟64位呼叫約定中,前四個引數由暫存器( RCX ,RDX,R8和R9 )傳遞的,剩下的引數是通過堆疊來傳遞的。雖然前4個引數在暫存器中傳遞呼叫約定要求在棧上分配空間(這就是所謂的Home Space) 。由於Buffer是第6個引數,在Home Space和第五個引數之後。這意味著,在堆疊中斷點斷下後,堆疊中是這樣的:
於是命令da poi(@ rsp+ 30)取出暫存器RSP中的值,加上30h剛好指向第6個引數,然後使用POI()引用該值(POI()類似於C語言中的*,返回一個指標大小的值)。最後,我們將這個地址傳入da(顯示ASCII)。我們可以在監視緩衝區資料,因為我們知道記事本儲存的是純文字而不是二進位制檔案。執行這個斷點,並在記事本中儲存一些文字,WinDbg的輸出如下:
使用這種技術,可以通過核心跟蹤各種有趣的資訊。
0×02更先進的命令用法
通過除錯來操縱一些資料,經常可以獲得更多的有意義的結果。一個很好的例子來說明其中的一些技術是系統服務描述表(SSDT)。該SSDT形成了所有的系統呼叫而產生的系統呼叫表。核心匯出的SSDT是具有以下格式符號KeServiceDescriptorTable的結構:
typedef struct _KSERVICE_DESCRIPTOR_TABLE {
PULONG ServiceTableBase; // Pointer to function/offset table (the table itself is exported as KiServiceTable)
PULONG ServiceCounterTableBase;
ULONG NumberOfServices; // The number of entries in ServiceTableBase
PUCHAR ParamTableBase;
} KSERVICE_DESCRIPTOR_TABLE,*PKSERVICE_DESCRIPTOR_TABLE;
在Windows的32位版本,ServiceTableBase是一個指向函式指標陣列的指標。在64位中稍微有點複雜,ServiceTableBase指向陣列偏移值為32位處,全部都是相對於KiServiceTable在儲存器中的表的位置,這使得視覺化使用常用的記憶體顯示命令(如dds)是不可能的。相反,我們將不得不使用一些WinDbgs更高階的命令在列表中迭代,資料操縱到一個更合適的形式。
讓我們先來看看在記憶體中是如何偏移,我們可以用dd(顯示DWORD)命令列出陣列偏移值。使用/c 1選項指示偵錯程式每行顯示一個DWORD:
kd> dd /c 1 KiServiceTable
fffff800`02692300 040d9a00
fffff800`02692304 02f55c00
fffff800`02692308 fff6ea00
fffff800`0269230c 02e87805
fffff800`02692310 031a4a06
fffff800`02692314 03116a05
...
這些值通過左移4位並和其他資料編碼,最終至少顯示四位。為了形成我們需要的每個值的絕對儲存器地址,需要右移4位移,並加上KiServiceTable的地址。我們希望在表的每個入口點都這樣做,並輸出與絕對地址相關聯的符號。要做到這一點,我們可以使用.foreach命令迭代,使用.printf顯示符號。下面是一個命令的實現,會對每個部分進行解釋和說明:
.foreach /ps 1 /pS 1 ( offset {dd /c 1 nt!KiServiceTable L poi(nt!KeServiceDescriptorTable+10)}){ .printf "%y\n", ( offset >>> 4) + nt!KiServiceTable }
.foreach——該步驟指定每個令牌(在我們的例子中,我們使用dd命令來提供令牌)。這些引數/ PS 1和/ PS 1使得foreach每秒跳過一個令牌。我們這樣做是因為dd命令輸出<地址> <值>,我們只對當前值感興趣。這些選項每次跳過令牌地址。
偏移——宣告一個名為offset變數,該變數儲存當前foreach迭代的令牌(當前的偏移值)
dd ——執行dd命令顯示DWORD的偏移列表,這些將被.foreach進行迭代。/C 1確保每行只輸出一個DWORD。Nt!KiServiceTable是我們將要顯示的地址(這是偏移陣列)。”L poi(nt!KeServiceDescriptorTable+10)”描述了要顯示多少個值。在這種情況下,我們從指向我們的結構體NumberOfServices的KeServiceDescriptorTable開始處取出16個位元組(10H),POI(),然後間接引用實際地址儲存的值,例如表中有效入口的值。
.printf ——printf命令讓我們執行格式化的列印。這裡我們使用格式化字串%y向給定的記憶體地址列印符號。當我們傳遞一個引數“(offset>>> 4)+!NT KiServiceTable”,這是當前偏移值右移4位,並新增到KiServiceTable的地址。我們使用>>>operator而不是>>operator,來保持的符號位,因為一些值是代表負偏移。
如果標誌設定正確,上述命令的輸出結果應該是這樣:
kd> .foreach /ps 1 /pS 1 ( offset {dd /c 1 nt!KiServiceTable L poi(nt!KeServiceDescriptorTable+10)}){ .printf "%y\n", ( offset >>> 4) + nt!KiServiceTable }
nt!NtMapUserPhysicalPagesScatter (fffff800`02a9fca0)
nt!NtWaitForSingleObject (fffff800`029878c0)
nt!NtCallbackReturn (fffff800`026891a0)
nt!NtReadFile (fffff800`0297aa80)
nt!NtDeviceIoControlFile (fffff800`029ac7a0)
nt!NtWriteFile (fffff800`029a39a0)
結果應該顯示可供使用者態程式碼的主要核心系統呼叫一個合適的SSDT功能列表。
0×03總結
希望現在你開始有信心去使用偵錯程式,並去探索作業系統。通過結合在這個系列文章你就可以開始梳理出Windows的內部工作原理,它可以用來幫助發現問題和漏洞,以及列出了一些列安全相關的技術和命令。配合這些知識以及偵錯程式來了解Windows如何構建以及使用者模式和核心模式互動的API層是很重要的。