《Undocumented Windows 2000 Secrets》翻譯 --- 第四章(3)
第四章 探索 Windows 2000 的內(nèi)存管理機(jī)制
翻譯: Kendiv ( [email protected] )
更新: Sunday, February 17, 2005
聲明:轉(zhuǎn)載請注明出處,并保證文章的完整性,本人保留譯文的所有權(quán)利。
Memory Spy Device 示例
微軟對 Windows NT 和 2000 說的最多的就是它們是 安全的操作系統(tǒng) 。它們不但在網(wǎng)絡(luò)環(huán)境中加入了用戶驗證系統(tǒng),同時還加強(qiáng)了系統(tǒng)的穩(wěn)健性( robustness ),以進(jìn)一步降低錯誤應(yīng)用程序危及系統(tǒng)完整性的概率,這些錯誤的程序可能使用了非法的指針或者在其內(nèi)存數(shù)據(jù)結(jié)構(gòu)以外的地方進(jìn)行了寫入操作。這些在 Windows 3.x 上都是十分讓人頭疼得問題,因為 Windows 3.x 系統(tǒng)和所有的應(yīng)用程序共享單一的內(nèi)存空間。 Windows NT 為系統(tǒng)和應(yīng)用程序內(nèi)存以及并發(fā)的進(jìn)程提供了完全獨立的內(nèi)存空間。每個進(jìn)程都有其獨立的 4GB 地址空間,如 圖 4-2 所示。無論何時發(fā)生任務(wù)切換,當(dāng)前的地址空間都會被換出( switch out ),同時另一個被映射進(jìn)來,它們各自使用不同的段寄存器、頁表和其他內(nèi)存管理結(jié)構(gòu)。這種設(shè)計避免了應(yīng)用程序無意中修改另一個程序所使用的內(nèi)存。由于每個進(jìn)程必然會要求訪問系統(tǒng)資源,所以在 4GB 空間中總是包含一些系統(tǒng)數(shù)據(jù)和代碼,并采用了一個不同的技巧來保護(hù)這些內(nèi)存區(qū)域不被惡意程序代碼所覆寫( overwritten )。
Windows 2000 的內(nèi)存分段
Windows 2000 繼承了 Windows NT 4.0 的基本內(nèi)存分段模型,默認(rèn)情況下,該模型將 4GB 地址空間劃分為相等的兩塊。低一半的地址范圍是: 0x00000000 ---- 0x7FFFFFFF ,其中包含運行于用戶模式(用 Intel 的術(shù)語來說是,是特權(quán)級 3 或 Ring 3 )的應(yīng)用程序的數(shù)據(jù)和代碼。高一半的地址范圍是: 0x80000000 --- 0xFFFFFFFF ,默認(rèn)全部保留給系統(tǒng)使用,位于這一范圍的代碼運行于內(nèi)核模式(即特權(quán)級為 0 或 Ring 0 )。特權(quán)級決定了代碼可以執(zhí)行什么操作以及可以訪問那一個塊內(nèi)存。這意味著對于低特權(quán)級的代碼來說,會被禁止執(zhí)行某些 CPU 指令或訪問某些內(nèi)存區(qū)域。例如,如果一個用戶模式下的程序觸及了任何 0x80000000 (即 4GB 地址空間中的高一半)以上的地址,系統(tǒng)會拋出一個異常并同時終止該程序的運行,不會給其任何機(jī)會。
圖 4-5. 用戶模式下不能訪問 0x80000000 以上的地址
圖 4-5 展示了程序試圖讀取 0x80000000 地址時的情況。這種嚴(yán)格的訪問限制對于系統(tǒng)的完整性來說是好事,但對于調(diào)試工具就不是什么好消息了,因為調(diào)試工具需要訪問所有可用內(nèi)存。幸運的是,存在著一個簡單的方法:采用內(nèi)核驅(qū)動程序,和系統(tǒng)本身類似,它也運行于高特權(quán)級(即 Ring 3 ),因此它們可以執(zhí)行所有的 CPU 指令,可訪問所有的內(nèi)存區(qū)域。這其中的訣竅就是將一個 Spy 驅(qū)動程序注入系統(tǒng),用它來訪問需要的內(nèi)存,并將讀到的內(nèi)容發(fā)送到它的搭檔程序,該搭檔程序會在用戶模式下等待。當(dāng)然,內(nèi)核驅(qū)動程序不能讀取虛擬內(nèi)存地址,而且得不到分頁機(jī)制的支持。因此,這樣的驅(qū)動程序必須在訪問一個地址之前小心的檢查它,以避免出現(xiàn)藍(lán)屏死機(jī)( Blue Screen Of Death , BSOD )。相對于應(yīng)用程序引發(fā)的異常(僅會終止出現(xiàn)問題的程序),驅(qū)動程序引發(fā)的異常會停止整個系統(tǒng),并強(qiáng)迫進(jìn)行重啟。
設(shè)備 I/O 控制 Dispatcher ( Device I/O Control Dispatcher )
本書光盤上有一個通用 Spy Device 的源代碼,該 Spy Device 作為內(nèi)核驅(qū)動程序?qū)崿F(xiàn)。可以在 srcw2k_spy 目錄下找到它的源代碼。這個設(shè)備基于第三章的驅(qū)動向?qū)a(chǎn)生的驅(qū)動程序骨架。其用戶模式下的接口為 w2k_spy.sys , w2k_spy.sys 采用 Win32 的設(shè)備 I/O 控制( IOCTL ),在第三章中曾簡要的談過 IOCTL 。 Spy Device 定義了一個名為 Devicew2k_spy 的設(shè)備和一個符號鏈接 DosDevicesw2k_spy ,定義符號鏈接是為了能在用戶模式下訪問該設(shè)備。非常可笑的是符號鏈接的名字空間居然是 DosDevice ,而在這兒,我們使用的可不是一個 DOS 設(shè)備驅(qū)動。這就像歷史上有名的 root ,原本是叫做石頭的 J 。安裝好符號鏈接后,驅(qū)動程序就可以被用戶模式下的任何模塊打開了,方法是:使用 Win32 API 函數(shù) CreateFile() ,路徑為 .w2k_spy 。字符串 . 是通用轉(zhuǎn)義符,表示本地設(shè)備。例如, .C : 指向本地硬盤上的 C :分區(qū)。從 SDK 的文檔中可了解 CreateFile() 的更多細(xì)節(jié)。
該驅(qū)動程序的頭文件有一部分已經(jīng)由 列表 4-2 到 列表 4-5 給出。這個文件有些像 DLL 的頭文件:它包含在編譯過程中,模塊所需的定義,而且還為客戶端程序提供了足夠的接口信息。 DLL 和驅(qū)動程序以及客戶端程序都包含相同的頭文件,但每個模塊會取出各自所需的定義以完成正確的操作。不過,頭文件的這種兩面性給內(nèi)核驅(qū)動程序帶來的麻煩要遠(yuǎn)多于給 DLL 帶來的,這都是因為微軟給驅(qū)動程序提供的特殊開發(fā)環(huán)境所致。不幸的是, DDK 中的頭文件并不能和 SDK 中的 Win32 文件兼容。至少在 C 工程,二者的頭文件是不能混合使用的。這樣的結(jié)果就是陷入了僵局,此種情況下,內(nèi)核驅(qū)動可以訪問的常量、宏和數(shù)據(jù)類型對于客戶端程序來說是卻是無法使用的。因此, w2k_spy.c 定義了一個名為 _W2K_SPY_SYS_ 的標(biāo)志常量, w2k_spy.h 通過 #ifdef…..#else…..#endif 來檢查該常量是否出現(xiàn),以決定需要補(bǔ)充哪些缺少的定義。這意味著,所有出現(xiàn)在 #ifdef _W2K_SPY_SYS_ 之后的定義僅可被驅(qū)動代碼看到,位于 #else 之后的則專用于客戶端程序。 w2k_spy.h 中條件語句之外的所有部分被這兩個模塊同時使用。
在第三章中,在討論我的驅(qū)動向?qū)r,我給出了向?qū)傻尿?qū)動程序骨架,如 列表 3-3 所示。由該驅(qū)動向?qū)傻男碌尿?qū)動工程均開始于 DeviceDispatcher() 函數(shù)。該函數(shù)接受一個設(shè)備上下文指針,以及一個指向 IRP ( I/O 請求包)的指針,該 IRP 隨后將會被分派。向?qū)У臉影宕a已經(jīng)處理了基本的 I/O 請求: IRP_MJ_CREATE 、 IRP_MJ_CLEANUP 和 IRP_MJ_CLSE ,當(dāng)客戶要關(guān)閉一個設(shè)備時,會給該設(shè)備發(fā)送這些 I/O 請求。 DeviceDispatcher() 針對這些請求只是簡單的返回 STATUS_SUCCESS ,因此設(shè)備可以被正確的打開和關(guān)閉。對于某些設(shè)備,這種動作已經(jīng)足夠,但有些設(shè)備還需要初始化和清理代碼,這些代碼多少都有些復(fù)雜。對于其他的請求,第三章中的驅(qū)動程序骨架總是返回 STATUS_NOT_IMPLEMENTED 。擴(kuò)展該骨架代碼的第一步是修改默認(rèn)的動作,以便處理更多的 I/O 請求。就像 w2k_spy.sys 的主要任務(wù)之一:通過 IOCTL 調(diào)用將在用戶模式下無法訪問的數(shù)據(jù)發(fā)送給 Win32 應(yīng)用程序,因此首先需要在 DeviceDispatcher() 中添加處理 IRP_MJ_DEVICE_CONTROL 的函數(shù)。 列表 4-6 給出了更新后的代碼。
NTSTATUS DeviceDispatcher (PDEVICE_CONTEXT pDeviceContext,
PIRP pIrp)
{
PIO_STACK_LOCATION pisl;
DWord dInfo = 0;
NTSTATUS ns = STATUS_NOT_IMPLEMENTED;
pisl = IoGetCurrentIrpStackLocation (pIrp);
switch (pisl->MajorFunction)
{
case IRP_MJ_CREATE:
case IRP_MJ_CLEANUP:
case IRP_MJ_CLOSE:
{
ns = STATUS_SUCCESS;
break;
}
case IRP_MJ_DEVICE_CONTROL:
{
ns = SpyDispatcher (pDeviceContext,
pisl->Parameters.DeviceIoControl.IoControlCode,
pIrp->AssociatedIrp.SystemBuffer,
pisl->Parameters.DeviceIoControl.InputBufferLength,
pIrp->AssociatedIrp.SystemBuffer,
pisl->Parameters.DeviceIoControl.OutputBufferLength,
&dInfo);
break;
}
}
pIrp->IoStatus.Status = ns;
pIrp->IoStatus.Information = dInfo;
IoCompleteRequest (pIrp, IO_NO_INCREMENT);
return ns;
}
列表 4-6. 為 Dispatcher 增加處理的 IRP_MJ_DEVICE_CONTROL 函數(shù)
列表 4-6 中的 IOCTL 處理代碼非常簡單,它僅調(diào)用了 SpyDispatcher() ,并將一個擴(kuò)展后的 IRP 結(jié)構(gòu)和當(dāng)前 I/O 堆棧位置作為參數(shù)傳遞給 SpyDispatcher() 。 SpyDispatcher() 在 列表 4-7 中給出,該函數(shù)需要如下的參數(shù):
l pDeviceContext 一個驅(qū)動程序的設(shè)備上下文指針。驅(qū)動程序向?qū)峁┝说幕?Device_Context 結(jié)構(gòu),該結(jié)構(gòu)中包含驅(qū)動程序和設(shè)備對象指針(參見 列表 3-4 )。不過, Spy 驅(qū)動程序在該結(jié)構(gòu)中增加了一對私有成員。
l dCode 指定了 IOCTL 編碼,以確定 Spy 設(shè)備需要執(zhí)行的命令。一個 IOCTL 編碼是一個 32 位整數(shù),它包含 4 個位域,如 圖 4-6 所示。
l pInput 指向一個輸入緩沖區(qū),用于給 IOCTL 提供輸入數(shù)據(jù)。
l dInput 輸入緩沖區(qū)的大小。
l pOutput 指向用來接收 IOCTL 輸出數(shù)據(jù)的輸出緩沖區(qū)。
l dOutput 輸出緩沖區(qū)的大小
l pdInfo 指向一個 DWORD 變量,該變量保存寫入輸出緩沖區(qū)中的字節(jié)數(shù)。
圖 4-6. 設(shè)備 I/O 控制編碼的結(jié)構(gòu)
根據(jù)所用的 IOCTL 使用的傳輸模式,輸入 / 輸出緩沖區(qū)會以不同的方式從系統(tǒng)傳遞給驅(qū)動程序。 Spy 設(shè)備使用已緩存的 I/O ( buffered I/O ),系統(tǒng)將輸入數(shù)據(jù)復(fù)制到一個安全的緩沖區(qū)(此緩沖區(qū)由系統(tǒng)自動分配)中,在返回時,將指定數(shù)目的數(shù)據(jù)從同樣的系統(tǒng)緩沖區(qū)中復(fù)制到調(diào)用者提供的輸出緩沖區(qū)中。一定要牢記:在這種情況下,輸入和輸出緩沖區(qū)是重疊的,因此 IOCTL 的處理代碼必須在向輸出緩沖區(qū)中寫入任何數(shù)據(jù)之前,保存所有它稍后可能需要使用的輸入數(shù)據(jù)。系統(tǒng) I/O 緩沖區(qū)的指針保存在 IRP 結(jié)構(gòu)中的 SystemBuffer 成員中(參見 ntddk.h )。輸入 / 輸出緩沖區(qū)的大小保存在一個不同的地方,它們是 IRP 的參數(shù)成員 DeviceIoControl 的一部分,分別為 InputBufferLength 和 OutputBufferLength 。 DeviceIoControl 子結(jié)構(gòu)還通過其 IoControlCode 成員提供了 IOCTL 編碼。有關(guān) Windows NT/2000 的 IOCTL 的傳輸模式的信息以及它們?nèi)绾蝹魅?/ 傳出數(shù)據(jù),請參考我在 Windows Developer's Journal(Schreiber 1997) 發(fā)表的文章“ A Spy Filter Driver for Windows NT ”。
NTSTATUS SpyDispatcher (PDEVICE_CONTEXT pDeviceContext,
DWORD dCode,
PVOID pInput,
DWORD dInput,
PVOID pOutput,
DWORD dOutput,
PDWORD pdInfo)
{
SPY_MEMORY_BLOCK smb;
SPY_PAGE_ENTRY spe;
SPY_CALL_INPUT sci;
PHYSICAL_ADDRESS pa;
DWORD dValue, dCount;
BOOL fReset, fPause, fFilter, fLine;
PVOID pAddress;
PBYTE pbName;
HANDLE hObject;
NTSTATUS ns = STATUS_INVALID_PARAMETER;
MUTEX_WAIT (pDeviceContext->kmDispatch);
*pdInfo = 0;
switch (dCode)
{
case SPY_IO_VERSION_INFO:
{
ns = SpyOutputVersionInfo (pOutput, dOutput, pdInfo);
break;
}
case SPY_IO_OS_INFO:
{
ns = SpyOutputOsInfo (pOutput, dOutput, pdInfo);
break;
}
case SPY_IO_SEGMENT:
{
if ((ns = SpyInputDword (&dValue,
pInput, dInput))
== STATUS_SUCCESS)
{
ns = SpyOutputSegment (dValue,
pOutput, dOutput, pdInfo);
}
break;
}
case SPY_IO_INTERRUPT:
{
if ((ns = SpyInputDword (&dValue,
pInput, dInput))
== STATUS_SUCCESS)
{
ns = SpyOutputInterrupt (dValue,
pOutput, dOutput, pdInfo);
}
break;
}
case SPY_IO_PHYSICAL:
{
if ((ns = SpyInputPointer (&pAddress,
pInput, dInput))
== STATUS_SUCCESS)
{
pa = MmGetPhysicalAddress (pAddress);
ns = SpyOutputBinary (&pa, PHYSICAL_ADDRESS_,
pOutput, dOutput, pdInfo);
}
break;
}
case SPY_IO_CPU_INFO:
{
ns = SpyOutputCpuInfo (pOutput, dOutput, pdInfo);
break;
}
case SPY_IO_PDE_ARRAY:
{
ns = SpyOutputBinary (X86_PDE_ARRAY, SPY_PDE_ARRAY_,
pOutput, dOutput, pdInfo);
break;
}
case SPY_IO_PAGE_ENTRY:
{
if ((ns = SpyInputPointer (&pAddress,
pInput, dInput))
== STATUS_SUCCESS)
{
SpyMemoryPageEntry (pAddress, &spe);
ns = SpyOutputBinary (&spe, SPY_PAGE_ENTRY_,
pOutput, dOutput, pdInfo);
}
break;
}
case SPY_IO_MEMORY_DATA:
{
if ((ns = SpyInputMemory (&smb,
pInput, dInput))
== STATUS_SUCCESS)
{
ns = SpyOutputMemory (&smb,
pOutput, dOutput, pdInfo);
}
break;
}
case SPY_IO_MEMORY_BLOCK:
{
if ((ns = SpyInputMemory (&smb,
pInput, dInput))
== STATUS_SUCCESS)
{
ns = SpyOutputBlock (&smb,
pOutput, dOutput, pdInfo);
}
break;
}
case SPY_IO_HANDLE_INFO:
{
if ((ns = SpyInputHandle (&hObject,
pInput, dInput))
== STATUS_SUCCESS)
{
ns = SpyOutputHandleInfo (hObject,
pOutput, dOutput, pdInfo);
}
break;
}
case SPY_IO_HOOK_INFO:
{
ns = SpyOutputHookInfo (pOutput, dOutput, pdInfo);
break;
}
case SPY_IO_HOOK_INSTALL:
{
if (((ns = SpyInputBool (&fReset,
pInput, dInput))
== STATUS_SUCCESS)
&&
((ns = SpyHookInstall (fReset, &dCount))
== STATUS_SUCCESS))
{
ns = SpyOutputDword (dCount,
pOutput, dOutput, pdInfo);
}
break;
}
case SPY_IO_HOOK_REMOVE:
{
if (((ns = SpyInputBool (&fReset,
pInput, dInput))
== STATUS_SUCCESS)
&&
((ns = SpyHookRemove (fReset, &dCount))
== STATUS_SUCCESS))
{
ns = SpyOutputDword (dCount,
pOutput, dOutput, pdInfo);
}
break;
}
case SPY_IO_HOOK_PAUSE:
{
if ((ns = SpyInputBool (&fPause,
pInput, dInput))
== STATUS_SUCCESS)
{
fPause = SpyHookPause (fPause);
ns = SpyOutputBool (fPause,
pOutput, dOutput, pdInfo);
}
break;
}
case SPY_IO_HOOK_FILTER:
{
if ((ns = SpyInputBool (&fFilter,
pInput, dInput))
== STATUS_SUCCESS)
{
fFilter = SpyHookFilter (fFilter);
ns = SpyOutputBool (fFilter,
pOutput, dOutput, pdInfo);
}
break;
}
case SPY_IO_HOOK_RESET:
{
SpyHookReset ();
ns = STATUS_SUCCESS;
break;
}
case SPY_IO_HOOK_READ:
{
if ((ns = SpyInputBool (&fLine,
pInput, dInput))
== STATUS_SUCCESS)
{
ns = SpyOutputHookRead (fLine,
pOutput, dOutput, pdInfo);
}
break;
}
case SPY_IO_HOOK_WRITE:
{
SpyHookWrite (pInput, dInput);
ns = STATUS_SUCCESS;
break;
}
case SPY_IO_MODULE_INFO:
{
if ((ns = SpyInputPointer (&pbName,
pInput, dInput))
== STATUS_SUCCESS)
{
ns = SpyOutputModuleInfo (pbName,
pOutput, dOutput, pdInfo);
}
break;
}
case SPY_IO_PE_HEADER:
{
if ((ns = SpyInputPointer (&pAddress,
pInput, dInput))
== STATUS_SUCCESS)
{
ns = SpyOutputPeHeader (pAddress,
pOutput, dOutput, pdInfo);
}
break;
}
case SPY_IO_PE_EXPORT:
{
if ((ns = SpyInputPointer (&pAddress,
pInput, dInput))
== STATUS_SUCCESS)
{
ns = SpyOutputPeExport (pAddress,
pOutput, dOutput, pdInfo);
}
break;
}
case SPY_IO_PE_SYMBOL:
{
if ((ns = SpyInputPointer (&pbName,
pInput, dInput))
== STATUS_SUCCESS)
{
ns = SpyOutputPeSymbol (pbName,
pOutput, dOutput, pdInfo);
}
break;
}
case SPY_IO_CALL:
{
if ((ns = SpyInputBinary (&sci, SPY_CALL_INPUT_,
pInput, dInput))
== STATUS_SUCCESS)
{
ns = SpyOutputCall (&sci,
pOutput, dOutput, pdInfo);
}
break;
}
}
MUTEX_RELEASE (pDeviceContext->kmDispatch);
return ns;
}
列表 4-7. Spy 驅(qū)動程序的內(nèi)部命令 Dispatcher
#define CTL_CODE (DeviceType, Function, Method, Access)
(( (DeviceType) << 16 ) | ( Access << 14 ) | ( (Function) << 2 ) (Method) )
列表 4-8. 用來構(gòu)建 I/O 控制編碼的 CTL_CODE() 宏
DDK 的主要頭文件 ntddk.h 和 SDK 中的 Win32 文件 winioctl.h 均定義了一個簡單但非常有用的宏 ---- CTL_CLOSE() ,如 列表 4-8 所示。該宏可方便的建立 圖 4-6 所示的 IOCTL 編碼。該編碼中的四個部分分別服務(wù)于以下四個目的:
1. DeviceType 這是一個 16 位的設(shè)備類型 ID 。 ntddk.h 列出了一對預(yù)定義的類型,由符號常量 FILE_DEVICE_* 表示。 0x0000 到 0x7FFF 保留給微軟內(nèi)部使用,開發(fā)人員可使用 0x8000 到 0xFFFF 。 Spy 驅(qū)動程序定義了它自己的設(shè)備 ID : FILE_DEVICE_SPY ,其值為 0x8000 。
2. 2 位的訪問檢查值用來確定 IOCTL 操作所需的訪問權(quán)限。可能的值有: FILE_ANY_ACCESS (0) , FILE_READ_ACCESS (1) , FILE_WRITE_ACCESS (2) 和最后兩個的組合: FILE_READ_ACCESS | FILE_WRITE_ACCESS (3) 。詳見 ntddk.h 。
3. 12 個位的 ID 表示所選擇的操作函數(shù),所選操作將由設(shè)備來執(zhí)行。 0x0000 到 0x7FFF 保留給微軟內(nèi)部使用,開發(fā)人員可使用 0x8000 到 0xFFFF 。 Spy 設(shè)備采用的 IOCTL 函數(shù) ID 位于 0x8000 到 0xFFFF 。
4. 傳輸模式占用 2 個位,可在四個可用 I/O 傳輸模式中選擇一個,這四個模式為: METHOD_BUFFERED (0) , METHOD_IN_DIRECT (1) , METHOD_OUT_DIRECT (2) 和 METHOD_NETTHER (3) ,可在 ntddk.h 中找到這些定義。 Spy 設(shè)備針對所有請求使用 METHOD_BUFFERED ,這是一個非常安全但有些慢的模式,因為數(shù)據(jù)需要在客戶端和系統(tǒng)緩沖區(qū)之間進(jìn)行復(fù)制。因為 Memory Spy 對 I/O 的處理速度并不敏感,所以選擇安全是一個不錯的注意。如果你希望知道其他模式的細(xì)節(jié),請參考我在 Windows Developer's Journal(Schreiber 1997) 發(fā)表的文章“ A Spy Filter Driver for Windows NT ”
表 4-2 列出了 w2k_spy.sys 支持的所有 IOCTL 函數(shù)。 0 到 10 的函數(shù) ID 為最基本的內(nèi)存探測函數(shù),絕大部分的任務(wù)都會用到它們;本章稍候?qū)⒂懻撍鼈儭JS嗟暮瘮?shù) ID 從 11 到 23 分屬于不同的 IOCTL 組,在下一章我們將討論它們,在下一章,我們將討論 Native API hook 和在用戶模式下調(diào)用內(nèi)核。注意某些 IOCTL 編碼需要寫入權(quán)限,由第 15 號位表示(參見 圖 4-6 )。確切的說,所有形如 0x80006nnn 的 IOCTL 命令只需讀權(quán)限,而形如 0x8000Ennn 的命令需要讀 / 寫權(quán)限。典型的要求讀權(quán)限的例子是 CreateFile() ,它通過指定 dwDesiredAccess 參數(shù)為 GENERIC_READ 和 GENERIC_WRITE 的組合來打開設(shè)備。
表 4-2 最左面的函數(shù)名稱同樣出現(xiàn)在 SpyDispatcher() (見 列表 4-7 )中那個龐大的 switch/case 語句中。這些函數(shù)首先獲取設(shè)備的 dispatcher mutex ,這樣就能保證,如果一個以上的客戶端或一個多線程的程序和設(shè)備通訊時,在同一時間只有一個請求被執(zhí)行。 MUTEX_WAIT() 是 KeWaitForMutexObject() 的外包宏( wrapper marco ), KeWaitForMutexObject() 至少需要 5 個參數(shù)。 KeWaitForMutexObject() 本身也是一個宏,它將傳入的參數(shù)向前傳遞給 KeWaitForSingleObject() 。 列表 4-9 給出了 MUTEX_WAIT() 以及它的伙伴 MUTEX_RELEASE() 和 MUTEX_INITIALIZE() 。在 mutex 對象變?yōu)橛行盘枺?signaled )狀態(tài)后,根據(jù)接收到的 IOCTL 編碼, SpyDispatcher() 會轉(zhuǎn)向不同的分支,每個分支都包含多種簡單的代碼序列。
表 4-2. w2k_spy.sys 支持的 IOCTL 函數(shù)
函數(shù)名稱
ID
IOCTL 編碼
描 述
SPY_IO_VERSION_INFO
0
0x80006000
返回 Spy 的版本信息
SPY_IO_OS_INFO
1
0x80006004
返回操作系統(tǒng)的版本信息
SPY_IO_SEGMENT
2
0x80006008
返回一個段的屬性
SPY_IO_INTERRUPT
3
0x8000600C
返回一個中斷門的屬性
SPY_IO_PHYSICAL
4
0x80006010
線性地址轉(zhuǎn)換為物理地址
SPY_IO_CPU_INFO
5
0x80006014
返回特殊 CPU 寄存器的值
SPY_IO_PDE_ARRAY
6
0x80006018
返回位于 0xC0300000 的 PDE 數(shù)組
SPY_IO_PAGE_ENTRY
7
0x8000601C
Return the PDE or PTE of a linear address
SPY_IO_MEMORY_DATA
8
0x80006020
返回內(nèi)存塊中的內(nèi)容
SPY_IO_MEMORY_BLOCK
9
0x80006024
返回內(nèi)存塊中的內(nèi)容
SPY_IO_HANDLE_INFO
10
0x80006028
從句柄中查找對象屬性
SPY_IO_HOOK_INFO
11
0x8000602C
返回有關(guān) Native API Hook 的信息
SPY_IO_HOOK_INSTALL
12
0x8000E030
安裝 Native API Hook
SPY_IO_HOOK_REMOVE
13
0x8000E034
移除一個 Native API Hook
SPY_IO_HOOK_PAUSE
14
0x8000E038
暫停 / 恢復(fù) Hook 協(xié)議
SPY_IO_HOOK_FILTER
15
0x8000E03C
允許 / 禁止 Hook 協(xié)議過濾器
SPY_IO_HOOK_RESET
16
0x8000E040
清除 Hook 協(xié)議
SPY_IO_HOOK_READ
17
0x8000E044
從 Hook 協(xié)議中讀取數(shù)據(jù)
SPY_IO_HOOK_WRITE
18
0x8000E048
向 Hook 協(xié)議中寫入輸入
SPY_IO_MODULE_INFO
19
0x8000E04C
返回已加載模塊的信息
SPY_IO_PE_HEADER
20
0x8000E050
返回 IMAGE_NT_HEADERS 數(shù)據(jù)
SPY_IO_PE_EXPORT
21
0x8000E054
返回 IMAGE_EXPORT_DirectorY 數(shù)據(jù)
SPY_IO_PE_SYMBOL
22
0x8000E058
返回導(dǎo)出的系統(tǒng)符號的地址
SPY_IO_CALL
23
0x8000E05C
調(diào)用已加載模塊中的一個函數(shù)
#define MUTEX_INITIALIZE(_mutex)
KeInitializeMutex
(&(_mutex), 0)
#define MUTEX_WAIT(_mutex)
KeWaitForMutexObject
(&(_mutex), Executive, KernelMode, FALSE, NULL)
#define MUTEX_RELEASE(_mutex)
KeReleaseMutex
(&(_mutex), FALSE)
列表 4-9. 管理 Kernel-Mutex 的宏
SpyDispatcher() 使 用一對幫助函數(shù)來讀取輸入?yún)?shù),以獲取被請求的數(shù)據(jù),并將產(chǎn)生的數(shù)據(jù)寫入調(diào)用者提供的輸出緩沖區(qū)中。就像前面提到的,內(nèi)核模式的驅(qū)動程序總是過分挑剔的對 待它接受到的來自用戶模式的參數(shù)。以驅(qū)動程序的觀點來看,所有用戶模式下的代碼都是有害的,它們除了讓系統(tǒng)崩潰就什么都不知道了。這種多少有些多疑癥的觀 點并不是荒謬的 ---- 僅有很小的比率會導(dǎo)致整個系統(tǒng)立即終止,同時出現(xiàn)藍(lán)屏。因此,如果一個客戶端程序說:“這是我的緩沖區(qū) ----- 它最多可容納 4,096 個字節(jié)”,驅(qū)動程序不會接受這個緩沖區(qū) ---- 即使該緩沖區(qū)指向有效的內(nèi)存,并且其大小也是正確的。在 IOCTL 的可緩沖的 I/O 模式( Buffered I/O )下(如果 IOCTL 編碼的模式部分為 METHOD_BUFFERED ),系統(tǒng)會很小心的檢查并分配一個足夠容納所有輸入 / 輸出數(shù)據(jù)的緩沖區(qū)。然而,其他的 I/O 傳輸模式,尤其是 METHOD_NETTHER ,驅(qū)動程序會接受原始的用戶模式的緩沖區(qū)指針。
