《Undocumented Windows 2000 Secrets》翻譯 --- 第三章(4)
第三章 編寫內(nèi)核模式驅(qū)動程序
翻譯: Kendiv
更新: Thursday, February 10, 2005
表 3-4 列出了定義于 列表 3-8 中的函數(shù),同時(shí)還給出了簡短的介紹。其中的一些函數(shù)的名字,如 w2kServiceStart() 和 w2kServiceControl() 和 SC 管理器的原生 API 函數(shù) ---StartService() 和 ControlService() 比較類似。這沒有什么不一致,在這些外包函數(shù)的核心位置都能找到對這些原生函數(shù)的調(diào)用。外包函數(shù)和原生函數(shù)的主要區(qū)別在于: StartService() 和 ControlService() 的操作對象是服務(wù)句柄,而 w2kServiceOpen() 和 w2kServiceClose() 則是服務(wù)的名稱。這些名字會在內(nèi)部調(diào)用 w2kServiceOpen() 和 w2kServiceClose() 轉(zhuǎn)化為對應(yīng)的句柄, w2kServiceOpen() 和 w2kServiceClose() 會依次調(diào)用 OpenService() 和 CloseServiceHandle() 。
名 稱
描 述
w2kServiceAdd
向系統(tǒng)中增加一個(gè)服務(wù) / 驅(qū)動程序
w2kServiceClose
關(guān)閉一個(gè)服務(wù)句柄
w2kServiceConnect
連接到服務(wù)控制管理器
w2kServiceContinue
繼續(xù)執(zhí)行暫停的服務(wù) / 驅(qū)動程序
w2kServiceControl
停止、暫停、繼續(xù)、查詢或通知一個(gè)已加載的服務(wù) / 驅(qū)動程序
w2kServiceDisconnect
斷開和服務(wù)控制管理器的連接
w2kServiceLoad
加載和啟動(可選的)一個(gè)服務(wù) / 驅(qū)動程序
w2kServiceLoadEx
加載和啟動(可選的)一個(gè)服務(wù) / 驅(qū)動程序(自動生成名稱)
w2kServiceManager
打開 / 關(guān)閉一個(gè)臨時(shí)的服務(wù)控制管理器句柄
w2kServiceOpen
獲取一個(gè)已加載的服務(wù) / 驅(qū)動程序的句柄
w2kServicePause
暫停一個(gè)正在運(yùn)行的服務(wù) / 驅(qū)動程序
w2kServiceRemove
從系統(tǒng)中移除一個(gè)服務(wù) / 驅(qū)動程序
w2kServiceStart
啟動一個(gè)已加載的服務(wù) / 驅(qū)動程序
w2kServiceStop
停止一個(gè)正在運(yùn)行的服務(wù) / 驅(qū)動程序
w2kServiceUnload
停止和卸載一個(gè)服務(wù) / 驅(qū)動程序
w2kServiceUnloadEx
停止和卸載一個(gè)服務(wù) / 驅(qū)動程序(自動生成名稱)
表 3-4. w2k_lib.dll 提供的 SC 管理器的外包函數(shù)
表 3-4 中函數(shù)的典型用法都需遵循如下的指導(dǎo)方針:
l 使用 w2kServiceLoad() 或 w2kServiceLoadEx() 來加載一個(gè)服務(wù)。后一個(gè)函數(shù)會根據(jù)可執(zhí)行文件的路徑和版本信息自動生成服務(wù)的顯示名稱。邏輯變量 fStart 用來確定是否在成功加載服務(wù)后自動執(zhí)行該服務(wù)。在成功的情況下,該函數(shù)會為后續(xù)的調(diào)用返回一個(gè)管理器句柄。如果服務(wù)已經(jīng)加載或服務(wù)已經(jīng)開始運(yùn)行而 fStart 為 TRUE ,調(diào)用該函數(shù)不會返回任何錯(cuò)誤。但如果發(fā)生錯(cuò)誤,如有必要,發(fā)生錯(cuò)誤的服務(wù)會被自動卸載。
l 使用 w2kServiceUnload() 和 w2kServiceUnloadEx() 來卸載一個(gè)服務(wù),這需要用到 w2kServiceLoad() 或 w2kServiceLoadEx() 返回的管理器句柄。 w2kServiceUnloadEx() 會根據(jù)可執(zhí)行文件的路徑自動生成服務(wù)名稱。如果你已經(jīng)關(guān)閉了管理器句柄,可使用 w2kServiceConnect() 來或取一個(gè)新的管理器句柄或者簡單的傳遞一個(gè) NULL (這表示使用臨時(shí)的管理器句柄)。管理器句柄會由 w2kServiceUnload() 自動關(guān)閉。如果服務(wù)已經(jīng)有刪除標(biāo)志,則不會返回任何錯(cuò)誤,但并不會立即刪除服務(wù),這是因?yàn)榇蜷_的設(shè)備句柄還存在著。
l 使用 w2kServiceStart() 、 w2kServiceStop() 、 w2kServicePause() 或 w2kServiceContinue() 來控制一個(gè)服務(wù)。這些函數(shù)也需要使用 w2kServiceLoad() 或 w2kServiceLoadEx() 返回的管理器句柄。如果你提供一個(gè)值為 NULL 的管理器句柄,則使用臨時(shí)管理器句柄。如果指定的服務(wù)已處于所要求的狀態(tài),則不會返回任何錯(cuò)誤。
l 調(diào)用 w2kServiceDisconnect() 來關(guān)閉一個(gè)管理器句柄。你可以在任何時(shí)候調(diào)用 w2kServiceConnect() 來獲取一個(gè)管理器句柄。
w2kServiceLoadEx() 是一個(gè)十分強(qiáng)大的函數(shù)。它會構(gòu)建自動加載一個(gè)服務(wù)時(shí)所需的全部參數(shù),但你要提供可執(zhí)行文件的路徑。 SC 管理器的 CreateService() 函數(shù)所需要的服務(wù)名稱將從可執(zhí)行文件名(會去掉文件的擴(kuò)展名)中派生出來。為了給新創(chuàng)建的服務(wù)構(gòu)建一個(gè)適當(dāng)?shù)挠糜陲@示名稱, w2kServiceLoadEx() 會嘗試從文件的版本信息中讀取 FileDescription 字符串。如果可執(zhí)行文件中不包含版本信息,或者 FileDescription 字符串不可用,則將使用缺省的服務(wù)名稱。
和 w2kServiceLoad() 不同, w2kServiceLoadEx() 支持路徑中的環(huán)境變量。換句話說,如果路徑字符串中包含如 %SystemRoot% 或 %TEMP% 這樣的子串,它們會被相應(yīng)系統(tǒng)變量的當(dāng)前值替換掉。 w2kServiceUnloadEx() 是 w2kServiceLoadEx() 的很好的搭檔,它會從提供的路徑中提取服務(wù)的名稱,與前面提及的展開過程類似,并將提取出來的服務(wù)名稱傳遞給 w2kServiceUnload() 。這兩個(gè)函數(shù)是需要加載 / 卸載第三方設(shè)備驅(qū)動的應(yīng)用程序的理想搭檔,只需提供這些驅(qū)動的全路徑即可。本書的光盤中包含一個(gè)這樣的示例程序。
控制臺模式的工具 -----w2k_load.exe 是一個(gè)通用的內(nèi)核驅(qū)動程序加載 / 卸載器,它為 w2kServiceLoadEx() 和 w2kServiceUnloadEx() 提供了簡單的命令行接口。其源代碼可以在隨書 CD 的 srcw2k_load 目錄下找到。 列表 3-9 給出了相關(guān)的代碼,該工具僅是一種示意性的實(shí)現(xiàn)。因?yàn)榇罅康墓ぷ鞫际怯?w2k_lib.dll 中的 w2kServiceLoadEx() 和 w2kServiceUnloadEx() 完成的。
// =================================================================
// GLOBAL STRINGS
// =================================================================
Word awUsage [] =
L'rn'
L'Usage: ' SW(MAIN_MODULE) L' <driver path>rn'
L' ' SW(MAIN_MODULE) L' <driver path> %srn'
L' ' SW(MAIN_MODULE) L' <driver name> %srn';
WORD awUnload [] = L'/unload';
WORD awOk [] = L'OKrn';
WORD awError [] = L'ERRORrn';
// =================================================================
// COMMAND HANDLERS
// =================================================================
BOOL WINAPI DriverLoad (PWORD pwPath)
{
SC_HANDLE hManager;
BOOL fOk = FALSE;
_printf (L'rnLoading '%s' ... ', pwPath);
if ((hManager = w2kServiceLoadEx (pwPath, TRUE)) != NULL)
{
w2kServiceDisconnect (hManager);
fOk = TRUE;
}
_printf (fOk ? awOk : awError);
return fOk;
}
// -----------------------------------------------------------------
BOOL WINAPI DriverUnload (PWORD pwPath)
{
BOOL fOk = FALSE;
_printf (L'rnUnloading '%s' ... ', pwPath);
fOk = w2kServiceUnloadEx (pwPath, NULL);
_printf (fOk ? awOk : awError);
return fOk;
}
// =================================================================
// MAIN PROGRAM
// =================================================================
DWORD Main (DWORD argc, PTBYTE *argv, PTBYTE *argp)
{
_printf (atAbout);
if (argc == 2)
{
DriverLoad (argv [1]);
}
else
{
if ((argc == 3) && (!lstrcmpi (argv [2], awUnload)))
{
DriverUnload (argv [1]);
}
else
{
_printf (awUsage, awUnload, awUnload);
}
}
return 0;
}
// =================================================================
// END OF PROGRAM
// =================================================================
列表 3-9. 加載 / 卸載設(shè)備驅(qū)動
表 3-4 中剩余的庫函數(shù)在更低一級的層面上工作,它們都在 w2k_lib.dll 內(nèi)部使用。當(dāng)然,如果你喜歡的話,你也可以從你的程序里調(diào)用它們。從 列表 3-8 給出的它們的源代碼中,可以很容易得出它們的使用方式。
枚舉服務(wù)和驅(qū)動
有時(shí)很有必要知道系統(tǒng)當(dāng)前加載了那個(gè)服務(wù)或驅(qū)動,以及它們現(xiàn)在處于什么狀態(tài)。為了實(shí)現(xiàn)這一目的, SC 管理器提供了另一個(gè)名為 EnumServiceStatus() 的強(qiáng)大函數(shù)。該函數(shù)需要一個(gè)管理器句柄和一個(gè)類型為 ENUM_SERVICE_STATUS 的數(shù)組,該數(shù)組中將包含有關(guān)當(dāng)前已加載的服務(wù)或驅(qū)動的信息。這個(gè)列表可以根據(jù)服務(wù) / 驅(qū)動的類型和狀態(tài)來過濾。如果調(diào)用者提供的緩沖區(qū)不能一次性的容納所有項(xiàng)目,可反復(fù)調(diào)用該函數(shù)直到獲取所有的項(xiàng)目。
不過很難預(yù)先計(jì)算出所需的緩沖區(qū)大小,這是因?yàn)榫彌_區(qū)必須為那些大小未知的字符串提供額外的空間,這些字符串由 ENUM_SERVICE_STATUS 的成員引用。幸運(yùn)的是, EnumServiceStatus() 會返回剩余的項(xiàng)目所需的字節(jié)數(shù),因此可以通過反復(fù)嘗試得出確定的緩沖區(qū)大小。 列表 3-10 給出了 SERVICE_STATUS 和 ENUM_SERVICE_STATUS 結(jié)構(gòu)的定義。這些聲明位于 Win32 頭文件 WinSvc.h 中。
typedef struct _SERVICE_STATUS
{
DWORD dwServiceType;
DWORD dwCurrentState;
DWORD dwControlAccepted;
DWORD dwWin32ExitCode;
DWORD dwServiceSpecificExitCode;
DWORD dwCheckPoint;
DWORD dwWaitHint;
} SERVICE_STATUS, *LPSERVICE_STATUS;
typedef struct _ENUM_SERVICE_STATUS
{
LPTSTR lpServiceName;
LPTSTR lpDisplayName;
SERVICE_STATUS ServiceStatus;
} ENUM_SERVICE_STATUS;
列表 3-10 SERVICE_STATUS 和 ENUM_SERVICE_STATUS 結(jié)構(gòu)的定義
列表 3-11 給出的 w2kServiceList() 函數(shù)是來自 w2k_lib.dll 工具庫的另一個(gè)好東東。它省略了前面提到的動作,并返回一個(gè)隨時(shí)可用的結(jié)構(gòu),該結(jié)構(gòu)中包含所有請求的數(shù)據(jù)以及一對擴(kuò)展結(jié)構(gòu)。該函數(shù)將返回一個(gè)指向 W2K_SERVICES 結(jié)構(gòu)的指針,該結(jié)構(gòu)定義于 w2k_lib.h ,在 列表 3-11 的頂部給出了其定義。隨 ENUM_SERVICE_STATUS 結(jié)構(gòu)數(shù)組 aess[] , W2K_SERVICES 結(jié)構(gòu)體還包含四個(gè)附加成員。 dEntrIEs 表示向狀態(tài)數(shù)組中復(fù)制了多少項(xiàng)目, dBytes 表示返回的 W2K_SERVICES 結(jié)構(gòu)的大小。 dDisplayName 和 dServiceName 被分別設(shè)置為 aess[] 中的 lpDisplayName 和 lpServiceName 字符串的最大長度。這些值將提供很大的方便,尤其是當(dāng)你編寫一個(gè)控制臺模式的程序,在屏幕上輸出服務(wù) / 驅(qū)動列表,并要求名稱列采用合適的對齊方式。
為了提供精確的系統(tǒng)快照, w2kServiceList() 試圖通過一次調(diào)用 EnumServiceStatus() 來獲取所有的項(xiàng)目。為此目的,該函數(shù)首先提供一個(gè)長度為 0 的緩沖區(qū),這通常會導(dǎo)致返回 ERROR_MORE_DATA 錯(cuò)誤代碼。在此種情況下, EnumServiceStatus() 將返回需要的緩沖區(qū)大小,然后按照此大小分配適當(dāng)?shù)木彌_區(qū),然后再次調(diào)用 EnumServiceStatus() 。此時(shí), EnumServiceStatus() 應(yīng)該返回成功。不過,這存在一個(gè)很小的概率事件 --- 在兩次調(diào)用 EnumServiceStatus() 之間另一個(gè)項(xiàng)目可能會被增加到列表中。因此,將會在一個(gè)循環(huán)中重復(fù)這一過程直到所有的一切都正確或者一個(gè)非 ERROR_MORE_DATA 的錯(cuò)誤返回。
// -----------------------------------------------------------------
typedef struct _W2K_SERVICES
{
DWORD dEntries; // number of entries in aess[]
DWORD dBytes; // overall number of bytes
DWORD dDisplayName; // maximum display name length
DWORD dServiceName; // maximum service name length
ENUM_SERVICE_STATUS aess []; // service/driver status array
}
W2K_SERVICES, *PW2K_SERVICES, **PPW2K_SERVICES;
#define W2K_SERVICES_ sizeof (W2K_SERVICES)
#define W2K_SERVICES__(_n)
(W2K_SERVICES_ + ((_n) * ENUM_SERVICE_STATUS_))
// -----------------------------------------------------------------
PW2K_SERVICES WINAPI w2kServiceList (BOOL fDriver,
BOOL fWin32,
BOOL fActive,
BOOL fInactive)
{
SC_HANDLE hManager;
DWORD dType, dState, dBytes, dResume, dName, i;
PW2K_SERVICES pws = NULL;
if ((pws = w2kMemoryCreate (W2K_SERVICES_)) != NULL)
{
pws->dEntries = 0;
pws->dBytes = 0;
pws->dDisplayName = 0;
pws->dServiceName = 0;
if ((fDriver || fWin32) && (fActive || fInactive))
{
if ((hManager = w2kServiceConnect ()) != NULL)
{
dType = (fDriver ? SERVICE_DRIVER : 0) |
(fWin32 ? SERVICE_WIN32 : 0);
dState = (fActive && fInactive
? SERVICE_STATE_ALL
: (fActive
? SERVICE_ACTIVE
: SERVICE_INACTIVE));
dBytes = pws->dBytes;
while (pws != NULL)
{
pws->dEntries = 0;
pws->dBytes = dBytes;
pws->dDisplayName = 0;
pws->dServiceName = 0;
dResume = 0;
if (EnumServicesStatus (hManager, dType, dState,
pws->aess, pws->dBytes,
&dBytes, &pws->dEntries,
&dResume))
break;
dBytes += pws->dBytes;
pws = w2kMemoryDestroy (pws);
if (GetLastError () != ERROR_MORE_DATA) break;
pws = w2kMemoryCreate (W2K_SERVICES_ + dBytes);
}
w2kServiceDisconnect (hManager);
}
else
{
pws = w2kMemoryDestroy (pws);
}
}
if (pws != NULL)
{
for (i = 0; i < pws->dEntries; i++)
{
dName = lstrlen (pws->aess [i].lpDisplayName);
pws->dDisplayName = max (pws->dDisplayName, dName);
dName = lstrlen (pws->aess [i].lpServiceName);
pws->dServiceName = max (pws->dServiceName, dName);
}
}
}
return pws;
}
列表 3-11. 枚舉服務(wù) / 驅(qū)動程序
w2kServiceList() 需要四個(gè)邏輯類型的參數(shù),以確定要返回的列表的內(nèi)容。通過 fDriver 和 fWin32 參數(shù),你可以分別選擇是否包含驅(qū)動程序或服務(wù)。如果這兩個(gè)參數(shù)都為 TRUE ,那么返回的列表將同時(shí)包含驅(qū)動和服務(wù)。 fActive 和 fInactive 標(biāo)志用于控制加于列表上的狀態(tài)過濾器。。 fInactive 參數(shù)選擇剩余的模塊,也就是說,這些模塊已經(jīng)加載但已經(jīng)停止運(yùn)行。如果所有的四個(gè)參數(shù)都為 FALSE ,函數(shù)返回的 W2K_SERVICES 結(jié)構(gòu)將包含一個(gè)空的狀態(tài)數(shù)組。光盤中的示例代碼包含一個(gè)簡單的服務(wù) / 驅(qū)動瀏覽器,它被設(shè)計(jì)為 Win32 控制臺模式,并依賴于 w2k_lib.dll 中的 w2kServiceList() 。它使用 W2K_SERVICES 結(jié)構(gòu)(參見 列表 3-11 )中的 dDisplayName 和 dServiceName 成員來為所有的名稱選擇合適的水平對齊方式。你可以在光盤的 srcw2k_svc 目錄下找到此工具的源代碼。其可執(zhí)行文件對應(yīng)光盤中的 binw2k_svc.exe 。 示列 3-4 列出了在我的機(jī)器上運(yùn)行該工具,列出的所有活動的內(nèi)核驅(qū)動程序(使用命令選項(xiàng) /drivers /active )。
在下一章中,我們將開始開發(fā)一個(gè)可實(shí)際工作的內(nèi)核驅(qū)動程序,它會偵測內(nèi)核使用的內(nèi)存,并且會 Crack 基本的內(nèi)存管理數(shù)據(jù)結(jié)構(gòu)。這個(gè)工程將伴隨你閱讀第 4 、 5 和 6 章,在每一章中,該驅(qū)動程序都會被加強(qiáng)。最后將得到一個(gè)通用的 Windows 2000 Kernel Spy 。
