《Undocumented Windows 2000 Secrets》翻譯 --- 第三章(3)
第三章 編寫內(nèi)核模式驅(qū)動(dòng)程序
翻譯: Kendiv
更新: Thursday, February 10, 2005
設(shè)備 I/O 控制
就像在本章開頭的簡介中提到的,在本書中,我們不會(huì)構(gòu)建某一具體硬件的驅(qū)動(dòng)程序。替代的是,我們將利用功能強(qiáng)大的內(nèi)核驅(qū)動(dòng)程序來研究 Windows 2000 的秘密。從實(shí)際結(jié)果來看,驅(qū)動(dòng)程序的強(qiáng)大之處在于它們能在 CPU 的最高特權(quán)級別上運(yùn)行。這意味著內(nèi)核驅(qū)動(dòng)可以訪問所有的系統(tǒng)資源,可以讀取所有的內(nèi)存空間,而且也被允許執(zhí)行 CPU 的特權(quán)指令,如,讀取 CPU 控制寄存器的當(dāng)前值等。而處于用戶模式下的程序如果試圖從內(nèi)核空間中讀取一個(gè)字節(jié)或者試圖執(zhí)行像 MOV EAX,CR3 這樣的匯編指令都會(huì)被立即終止掉。不過,這種強(qiáng)大的底線是驅(qū)動(dòng)程序的一個(gè)很小的錯(cuò)誤就會(huì)讓整個(gè)系統(tǒng)崩潰。即使是非常小的錯(cuò)誤發(fā)生,也會(huì)讓系統(tǒng)藍(lán)屏,因此開發(fā)內(nèi)核程序的人員必須比 Win32 應(yīng)用程序或 DLL 的開發(fā)人員更加仔細(xì)的處理錯(cuò)誤。還記得我們在第一章里使用的導(dǎo)致系統(tǒng)藍(lán)屏的 Windows 2000 Killer device driver 嗎?它所作的一切只是觸及了虛擬內(nèi)存地址 0x00000000 ,然后就 ---Boom !!!你應(yīng)該意識(shí)到在開發(fā)內(nèi)核驅(qū)動(dòng)時(shí),你會(huì)比以往更頻繁的重啟你的機(jī)器。
在隨后章節(jié)中,我給出的驅(qū)動(dòng)程序代碼將采用稱為設(shè)備 I/O 控制( IOCTL )的技術(shù),以允許用戶模式下的代碼實(shí)現(xiàn)一定程序的“遠(yuǎn)程控制”。如果應(yīng)用程序需要訪問在用戶模式下無法觸及的系統(tǒng)資源,那內(nèi)核驅(qū)動(dòng)程序?qū)⒖珊芎玫耐瓿纱隧?xiàng)工作,而 IOCTL 則是聯(lián)系二者的橋梁。事實(shí)上, IOCTL 并不是 Windows 2000 采用的新技術(shù)。即使舊的操作系統(tǒng) ---Dos 2.11 也具有這種能力, 0x44 函數(shù)及其子函數(shù)構(gòu)成了 DOS 的 IOCTL 。基本上, IOCTL 是通過控制通路和設(shè)備通訊的一中手段,控制通路在邏輯上獨(dú)立于數(shù)據(jù)通路。想象一個(gè)硬盤設(shè)備通過其主數(shù)據(jù)通路傳遞磁盤扇區(qū)中的內(nèi)容。如果客戶想獲取當(dāng)前設(shè)備使用的媒體信息,它就必須使用另一個(gè)不同的通路。例如, DOS 函數(shù) 0x44 ,其子函數(shù) 0x0d 、 0x66 構(gòu)成了 DOS 的 IOCTL ,調(diào)用這些函數(shù)就可讀取磁盤的 32 位連續(xù)數(shù)據(jù)(參考 Brown and Kyle 1991 , 1993 )。
設(shè)備 I/O 控制根據(jù)要控制的設(shè)備,可以有多種實(shí)現(xiàn)方式。就其一般形式來說, IOCTL 有如下幾類:
l 客戶端通過一個(gè)特殊的進(jìn)入點(diǎn)來控制設(shè)備。在 DOS 中,這個(gè)進(jìn)入點(diǎn)為 INT 21h 、函數(shù)號(hào) 0x44 。在 Windows 2000 中,則通過 Kernel32.dll 導(dǎo)出的 Win32 函數(shù) DeviceIoControl() 。
l 客戶端通過提供設(shè)備的唯一標(biāo)識(shí)符、控制代碼以及一個(gè)存放輸入數(shù)據(jù)的緩沖區(qū)、一個(gè)存放輸出數(shù)據(jù)的緩沖區(qū)來調(diào)用 IOCTL 的進(jìn)入點(diǎn)。對于 Windows 2000 ,設(shè)備標(biāo)識(shí)符是成功打開的設(shè)備的句柄( HANDLE )。
l 控制代碼用于告訴目標(biāo)設(shè)備的 IOCTL 分派器( dispatcher ),客戶端請求的是哪一個(gè)控制函數(shù)。
l 輸入緩沖區(qū)中可包含任意地附加數(shù)據(jù),設(shè)備可能需要這些數(shù)據(jù)來完成客戶所請求的操作。
l 客戶所請求的操作產(chǎn)生的任何數(shù)據(jù),都會(huì)保存在客戶端提供的輸出緩沖區(qū)中。
l IOCTL 操作的整體結(jié)果通過返回給客戶端的狀態(tài)代碼來表示
很 顯然這是一種強(qiáng)大的通用機(jī)制,這種機(jī)制可以適用于很大范圍的控制請求。例如,應(yīng)用程序在訪問系統(tǒng)內(nèi)核所占用的內(nèi)存空間時(shí)會(huì)被禁止,這是因?yàn)楫?dāng)程序觸及該內(nèi) 存空間時(shí)會(huì)立即拋出一個(gè)異常,不過程序可以通過加載一個(gè)內(nèi)核驅(qū)動(dòng)程序來完成此項(xiàng)工作,這樣就可避免出現(xiàn)異常。交互的兩個(gè)模塊都需遵循 IOCTL 協(xié)議來管理數(shù)據(jù)的傳輸。例如,程序可能通過給驅(qū)動(dòng)程序發(fā)送控制代碼 0x80002000 來讀取內(nèi)存或發(fā)送 0x80002001 來向內(nèi)存中寫入數(shù)據(jù)。對于讀取請求, IOCTL 輸 入緩沖區(qū)或許要提供基地址和要讀取的字節(jié)數(shù)。內(nèi)核驅(qū)動(dòng)程序能獲取這些請求并通過控制代碼來判斷是讀取操作還是寫入操作。對于讀取請求,內(nèi)核驅(qū)動(dòng)程序會(huì)將請 求的內(nèi)存范圍內(nèi)的數(shù)據(jù)復(fù)制到調(diào)用者提供的輸出緩沖區(qū)中,如果輸出緩沖區(qū)足夠容納這些數(shù)據(jù),則返回成功代碼。對于寫入請求,驅(qū)動(dòng)程序會(huì)將輸入緩沖區(qū)中的數(shù)據(jù) 復(fù)制到指定的內(nèi)存中(該內(nèi)存的起始位置也由輸入緩沖區(qū)指定)。在第四章,我將提供一個(gè) Memory Spy 的示列代碼。
現(xiàn)在,可以看出 IOCTL 是 Win32 應(yīng)用程序的一種后門,通過 IOCTL ,程序可以執(zhí)行幾乎所有的操作,而在此之前,這些操作僅允許特權(quán)模塊執(zhí)行。當(dāng)然,這需要首先編寫一個(gè)特權(quán)級的模塊,但是,一旦你擁有一個(gè)運(yùn)行于系統(tǒng)中的 Spy 模塊,一切就變得很簡單了。本書的兩個(gè)目標(biāo)是:詳細(xì)展示如何編寫內(nèi)核模式的驅(qū)動(dòng)程序以及一個(gè)可以完成很多讓人驚異的事的驅(qū)動(dòng)程序的示例代碼。
Windows 2000 的 Killer Device
在開始更高級的驅(qū)動(dòng)程序工程之前,讓我們先看看一個(gè)非常簡單的驅(qū)動(dòng)程序。在第一章中,我介紹了 Windows 2000 的 Killer Device----w2k_kill.sys ,它被設(shè)計(jì)為引發(fā)一個(gè)良性的系統(tǒng)崩潰。這個(gè)驅(qū)動(dòng)程序并不需要 示例 3-3 中的大多數(shù)代碼,因?yàn)樗谟袡C(jī)會(huì)收到第一個(gè) I/O 請求包之前就會(huì)使系統(tǒng)崩潰。 示例 3-7 給出了它的實(shí)現(xiàn)代碼。這里沒有給出 w2k_kill.h 文件,因?yàn)樗徊话魏挝覀兏信d趣的代碼。
示列 3-7 中的代碼沒有在 DriverEntry() 中執(zhí)行初始化操作,因?yàn)橄到y(tǒng)會(huì)在 DriverEntry() 返回前就崩潰,所以沒有必要進(jìn)行這些額外的工作。
#define _W2K_KILL_SYS_
#include <ddkntddk.h>
#include 'w2k_kill.h'
// =================================================================
// DISCARDABLE FUNCTIONS
// =================================================================
NTSTATUS DriverEntry (PDRIVER_OBJECT pDriverObject,
PUNICODE_STRING pusRegistryPath);
#ifdef ALLOC_PRAGMA
#pragma alloc_text (INIT, DriverEntry)
#endif
// =================================================================
// DRIVER INITIALIZATION
// =================================================================
NTSTATUS DriverEntry (PDRIVER_OBJECT pDriverObject,
PUNICODE_STRING pusRegistryPath)
{
return *((NTSTATUS *) 0);
}
// =================================================================
// END OF PROGRAM
// =================================================================
示列 3-7. 一個(gè)小巧的系統(tǒng)崩潰者
加載 / 卸載驅(qū)動(dòng)程序
在完成一個(gè)內(nèi)核驅(qū)動(dòng)程序之后,你可能會(huì)想立即執(zhí)行它。怎么做呢?典型的做法是,在系統(tǒng)啟動(dòng)時(shí)加載驅(qū)動(dòng)程序并執(zhí)行之。但這是不是就意味著我們每次更新驅(qū)動(dòng)程序后,都必須重新啟動(dòng)系統(tǒng)呢?很幸運(yùn),這并不是必須的。 Windows 2000 的一個(gè)特色就是提供了一個(gè) Win32 接口以允許在運(yùn)行時(shí)加載或卸載驅(qū)動(dòng)程序。這是由服務(wù)控制管理器( Service Control Manager , SCM )完成的,下面的將詳細(xì)介紹它的用法。
服務(wù)控制管理器
“服務(wù)控制管理器”這個(gè)名字容易讓人誤解,因?yàn)樗凳驹摻M件僅用于服務(wù)的管理。服務(wù)( Service )是 Windows 2000 的一類非常強(qiáng)大的模塊,它們在后臺(tái)運(yùn)行配套的程序,并且不需要用戶交互(也就是說沒有常見的用戶界面或者控制臺(tái))。換句話說,一個(gè)服務(wù)就是一個(gè)始終運(yùn)行于系統(tǒng)中的 Win32 進(jìn)程,即使沒有用戶登陸進(jìn)來也如此。盡管開發(fā)服務(wù)是一個(gè)令人興奮的話題,但它并不屬于本書的范疇。想進(jìn)一步了解服務(wù)的開發(fā),請閱讀 Windows Developer's Journal ( WDJ )( Tomlinson 1996a )中 Paula Tomlinson 提供的非常不錯(cuò)的教程,以及隨后在她的 WDJ 專欄 ----Understanding NT 中發(fā)表的有關(guān)服務(wù)的論文。
SC 管理器(即服務(wù)控制管理器)可以控制服務(wù)和驅(qū)動(dòng)程序。為了簡單起見,我在這里使用“服務(wù)”一詞來代表 SC 管理器控制的所有對象,這包括嚴(yán)格意義上的服務(wù)和內(nèi)核驅(qū)動(dòng)程。 SC 的接口對于 Win32 程序是可用的,它由 Win32 子系統(tǒng)組件 ----advapi32.dll 提供,這個(gè) DLL 還提供了很多有趣的 API 函數(shù)。 表 3-3 給出了用于加載、控制和卸載服務(wù)的 API 函數(shù)的名稱,同時(shí)還給出了簡單的描述。在你可以加載或訪問任何服務(wù)之前,你必須獲取 SC 管理器的句柄(通過調(diào)用 OpenSCManager() ),在隨后的討論中,該句柄將被稱為:管理器句柄。 CreateService() 和 OpenService() 都需要此句柄,而這些函數(shù)返回的句柄將被稱為:服務(wù)句柄。這種類型的句柄可以傳遞給需要引用一個(gè)服務(wù)的函數(shù),如 ControlService() 、 DeleteService() 和 StartService() 。這兩種類型的 SC 句柄都通過 CloseServiceHandle() 函數(shù)來釋放。
名 稱
描 述
CloseServiceHandle
關(guān)閉來自 OpenSCManager() 、 CreateService() 或 OpenService() 的句柄
ControlService
停止、暫停、繼續(xù)、查詢或通知已加載的服務(wù) / 驅(qū)動(dòng)程序
CreateService
加載一個(gè)服務(wù) / 驅(qū)動(dòng)程序
DeleteService
卸載一個(gè)服務(wù) / 驅(qū)動(dòng)程序
OpenSCManager
獲取 SC 管理器的句柄
OpenService
獲取一個(gè)已加載的服務(wù) / 驅(qū)動(dòng)程序的句柄
QueryServiceStatus
查詢一個(gè)服務(wù) / 驅(qū)動(dòng)程序的屬性和當(dāng)前狀態(tài)
StartService
啟動(dòng)一個(gè)已加載的服務(wù) / 驅(qū)動(dòng)程序
表 3-3. 基本的服務(wù)控制函數(shù)
加載和運(yùn)行一個(gè)服務(wù)需要執(zhí)行的典型操作步驟:
1. 調(diào)用 OpenSCManager() 以獲取一個(gè)管理器句柄
2. 調(diào)用 CreateService() 來向系統(tǒng)中添加一個(gè)服務(wù)
3. 調(diào)用 StartService() 來運(yùn)行一個(gè)服務(wù)
4. 調(diào)用 CloseServiceHandle() 來釋放管理器或服務(wù)句柄
要確保當(dāng)一個(gè)錯(cuò)誤發(fā)生時(shí),要回滾到最后一個(gè)成功的調(diào)用,然后再開始。例如,你在調(diào)用 StartService() 時(shí) SC 管理器報(bào)告了一個(gè)錯(cuò)誤,你就需要調(diào)用 DeleteService() 。否則,服務(wù)將保持在一個(gè)非預(yù)期的狀態(tài)。另一個(gè)使用 SC 管理器 API 易犯的錯(cuò)誤是,必須為 CreateService() 函數(shù)提供可執(zhí)行文件的全路徑名,否則,如果該函數(shù)在當(dāng)前目錄中沒有找到可執(zhí)行文件的話,就會(huì)失敗。因此,你應(yīng)該使用 Win32 函數(shù) ---GetFullPathName() 來規(guī)格化傳遞給 CreateService() 的所有文件名,除非可以保證它們已經(jīng)是全路徑的。
高層的驅(qū)動(dòng)程序管理函數(shù)
為了更容易的和 SC 管理器進(jìn)行交互,本書附帶的 CD 提供了多個(gè)更高級的外包函數(shù),這些函數(shù)屏蔽了原有的一些不方便的特殊要求。這些函數(shù)是本書提供的龐大的 Windows 2000 工具庫(位于隨書 CD 中的 srcw2k_lib )中的一部分。 w2k_lib.dll 導(dǎo)出的所有函數(shù)都有一個(gè)全局的名字前綴 w2k ,服務(wù)和驅(qū)動(dòng)程序管理函數(shù)都使用 w2kService 前綴。 列表 3-8 給出了本書提供的工具庫中實(shí)現(xiàn)的加載、控制和卸載服務(wù) / 驅(qū)動(dòng)程序的函數(shù)的細(xì)節(jié)。
// =================================================================
// SERVICE/DRIVER MANAGEMENT
// =================================================================
SC_HANDLE WINAPI w2kServiceConnect (void)
{
return OpenSCManager (NULL, NULL, SC_MANAGER_ALL_ACCESS);
}
// -----------------------------------------------------------------
SC_HANDLE WINAPI w2kServiceDisconnect (SC_HANDLE hManager)
{
if (hManager != NULL) CloseServiceHandle (hManager);
return NULL;
}
// -----------------------------------------------------------------
SC_HANDLE WINAPI w2kServiceManager (SC_HANDLE hManager,
PSC_HANDLE phManager,
BOOL fOpen)
{
SC_HANDLE hManager1 = NULL;
if (phManager != NULL)
{
if (fOpen)
{
if (hManager == NULL)
{
*phManager = w2kServiceConnect ();
}
else
{
*phManager = hManager;
}
}
else
{
if (hManager == NULL)
{
*phManager = w2kServiceDisconnect (*phManager);
}
}
hManager1 = *phManager;
}
return hManager1;
}
// -----------------------------------------------------------------
SC_HANDLE WINAPI w2kServiceOpen (SC_HANDLE hManager,
PWord pwName)
{
SC_HANDLE hManager1;
SC_HANDLE hService = NULL;
w2kServiceManager (hManager, &hManager1, TRUE);
if ((hManager1 != NULL) && (pwName != NULL))
{
hService = OpenService (hManager1, pwName,
SERVICE_ALL_ACCESS);
}
w2kServiceManager (hManager, &hManager1, FALSE);
return hService;
}
// -----------------------------------------------------------------
BOOL WINAPI w2kServiceClose (SC_HANDLE hService)
{
return (hService != NULL) && CloseServiceHandle (hService);
}
// -----------------------------------------------------------------
BOOL WINAPI w2kServiceAdd (SC_HANDLE hManager,
PWORD pwName,
PWORD pwInfo,
PWORD pwPath)
{
SC_HANDLE hManager1, hService;
PWORD pwFile;
WORD awPath [MAX_PATH];
DWORD n;
BOOL fOk = FALSE;
w2kServiceManager (hManager, &hManager1, TRUE);
if ((hManager1 != NULL) && (pwName != NULL) &&
(pwInfo != NULL) && (pwPath != NULL) &&
(n = GetFullPathName (pwPath, MAX_PATH, awPath, &pwFile)) &&
(n < MAX_PATH))
{
if ((hService = CreateService (hManager1, pwName, pwInfo,
SERVICE_ALL_ACCESS,
SERVICE_KERNEL_DRIVER,
SERVICE_DEMAND_START,
SERVICE_ERROR_NORMAL,
awPath, NULL, NULL,
NULL, NULL, NULL))
!= NULL)
{
w2kServiceClose (hService);
fOk = TRUE;
}
else
{
fOk = (GetLastError () ==
ERROR_SERVICE_EXISTS);
}
}
w2kServiceManager (hManager, &hManager1, FALSE);
return fOk;
}
// -----------------------------------------------------------------
BOOL WINAPI w2kServiceRemove (SC_HANDLE hManager,
PWORD pwName)
{
SC_HANDLE hService;
BOOL fOk = FALSE;
if ((hService = w2kServiceOpen (hManager, pwName)) != NULL)
{
if (DeleteService (hService))
{
fOk = TRUE;
}
else
{
fOk = (GetLastError () ==
ERROR_SERVICE_MARKED_FOR_DELETE);
}
w2kServiceClose (hService);
}
return fOk;
}
// -----------------------------------------------------------------
BOOL WINAPI w2kServiceStart (SC_HANDLE hManager,
PWORD pwName)
{
SC_HANDLE hService;
BOOL fOk = FALSE;
if ((hService = w2kServiceOpen (hManager, pwName)) != NULL)
{
if (StartService (hService, 1, &pwName))
{
fOk = TRUE;
}
else
{
fOk = (GetLastError () ==
ERROR_SERVICE_ALREADY_RUNNING);
}
w2kServiceClose (hService);
}
return fOk;
}
// -----------------------------------------------------------------
BOOL WINAPI w2kServiceControl (SC_HANDLE hManager,
PWORD pwName,
DWORD dControl)
{
SC_HANDLE hService;
SERVICE_STATUS ServiceStatus;
BOOL fOk = FALSE;
if ((hService = w2kServiceOpen (hManager, pwName)) != NULL)
{
if (QueryServiceStatus (hService, &ServiceStatus))
{
switch (ServiceStatus.dwCurrentState)
{
case SERVICE_STOP_PENDING:
case SERVICE_STOPPED:
{
fOk = (dControl == SERVICE_CONTROL_STOP);
break;
}
case SERVICE_PAUSE_PENDING:
case SERVICE_PAUSED:
{
fOk = (dControl == SERVICE_CONTROL_PAUSE);
break;
}
case SERVICE_START_PENDING:
case SERVICE_CONTINUE_PENDING:
case SERVICE_RUNNING:
{
fOk = (dControl == SERVICE_CONTROL_CONTINUE);
break;
}
}
}
fOk = fOk ||
ControlService (hService, dControl, &ServiceStatus);
w2kServiceClose (hService);
}
return fOk;
}
// -----------------------------------------------------------------
BOOL WINAPI w2kServiceStop (SC_HANDLE hManager,
PWORD pwName)
{
return w2kServiceControl (hManager, pwName,
SERVICE_CONTROL_STOP);
}
// -----------------------------------------------------------------
BOOL WINAPI w2kServicePause (SC_HANDLE hManager,
PWORD pwName)
{
return w2kServiceControl (hManager, pwName,
SERVICE_CONTROL_PAUSE);
}
// -----------------------------------------------------------------
BOOL WINAPI w2kServiceContinue (SC_HANDLE hManager,
PWORD pwName)
{
return w2kServiceControl (hManager, pwName,
SERVICE_CONTROL_CONTINUE);
}
// -----------------------------------------------------------------
SC_HANDLE WINAPI w2kServiceLoad (PWORD pwName,
PWORD pwInfo,
PWORD pwPath,
BOOL fStart)
{
BOOL fOk;
SC_HANDLE hManager = NULL;
if ((hManager = w2kServiceConnect ()) != NULL)
{
fOk = w2kServiceAdd (hManager, pwName, pwInfo, pwPath);
if (fOk && fStart)
{
if (!(fOk = w2kServiceStart (hManager, pwName)))
{
w2kServiceRemove (hManager, pwName);
}
}
if (!fOk)
{
hManager = w2kServiceDisconnect (hManager);
}
}
return hManager;
}
// -----------------------------------------------------------------
SC_HANDLE WINAPI w2kServiceLoadEx (PWORD pwPath,
BOOL fStart)
{
PVS_VERSIONDATA pvvd;
PWORD pwPath1, pwInfo;
WORD awName [MAX_PATH];
DWORD dName, dExtension;
SC_HANDLE hManager = NULL;
if (pwPath != NULL)
{
dName = w2kPathName (pwPath, &dExtension);
lstrcpyn (awName, pwPath + dName,
min (MAX_PATH, dExtension - dName + 1));
pwPath1 = w2kPathEvaluate (pwPath, NULL);
pvvd = w2kVersionData (pwPath1, -1);
pwInfo = ((pvvd != NULL) && pvvd->awFileDescription [0]
? pvvd->awFileDescription
: awName);
hManager = w2kServiceLoad (awName, pwInfo, pwPath1, fStart);
w2kMemoryDestroy (pvvd);
w2kMemoryDestroy (pwPath1);
}
return hManager;
}
// -----------------------------------------------------------------
BOOL WINAPI w2kServiceUnload (PWORD pwName,
SC_HANDLE hManager)
{
SC_HANDLE hManager1 = hManager;
BOOL fOk = FALSE;
if (pwName != NULL)
{
if (hManager1 == NULL)
{
hManager1 = w2kServiceConnect ();
}
if (hManager1 != NULL)
{
w2kServiceStop (hManager1, pwName);
fOk = w2kServiceRemove (hManager1, pwName);
}
}
w2kServiceDisconnect (hManager1);
return fOk;
}
// -----------------------------------------------------------------
BOOL WINAPI w2kServiceUnloadEx (PWORD pwPath,
SC_HANDLE hManager)
{
DWORD dName, dExtension;
WORD awName [MAX_PATH];
PWORD pwName = NULL;
if (pwPath != NULL)
{
dName = w2kPathName (pwPath, &dExtension);
lstrcpyn (pwName = awName, pwPath + dName,
min (MAX_PATH, dExtension - dName + 1));
}
return w2kServiceUnload (pwName, hManager);
}
// -----------------------------------------------------------------
列表 3-8. 服務(wù)和驅(qū)動(dòng)管理庫函數(shù)
