《Undocumented Windows 2000 Secrets》翻譯 --- 第三章(4)
第三章 編寫內核模式驅動程序
翻譯: Kendiv
更新: Thursday, February 10, 2005
表 3-4 列出了定義于 列表 3-8 中的函數,同時還給出了簡短的介紹。其中的一些函數的名字,如 w2kServiceStart() 和 w2kServiceControl() 和 SC 管理器的原生 API 函數 ---StartService() 和 ControlService() 比較類似。這沒有什么不一致,在這些外包函數的核心位置都能找到對這些原生函數的調用。外包函數和原生函數的主要區別在于: StartService() 和 ControlService() 的操作對象是服務句柄,而 w2kServiceOpen() 和 w2kServiceClose() 則是服務的名稱。這些名字會在內部調用 w2kServiceOpen() 和 w2kServiceClose() 轉化為對應的句柄, w2kServiceOpen() 和 w2kServiceClose() 會依次調用 OpenService() 和 CloseServiceHandle() 。
名 稱
描 述
w2kServiceAdd
向系統中增加一個服務 / 驅動程序
w2kServiceClose
關閉一個服務句柄
w2kServiceConnect
連接到服務控制管理器
w2kServiceContinue
繼續執行暫停的服務 / 驅動程序
w2kServiceControl
停止、暫停、繼續、查詢或通知一個已加載的服務 / 驅動程序
w2kServiceDisconnect
斷開和服務控制管理器的連接
w2kServiceLoad
加載和啟動(可選的)一個服務 / 驅動程序
w2kServiceLoadEx
加載和啟動(可選的)一個服務 / 驅動程序(自動生成名稱)
w2kServiceManager
打開 / 關閉一個臨時的服務控制管理器句柄
w2kServiceOpen
獲取一個已加載的服務 / 驅動程序的句柄
w2kServicePause
暫停一個正在運行的服務 / 驅動程序
w2kServiceRemove
從系統中移除一個服務 / 驅動程序
w2kServiceStart
啟動一個已加載的服務 / 驅動程序
w2kServiceStop
停止一個正在運行的服務 / 驅動程序
w2kServiceUnload
停止和卸載一個服務 / 驅動程序
w2kServiceUnloadEx
停止和卸載一個服務 / 驅動程序(自動生成名稱)
表 3-4. w2k_lib.dll 提供的 SC 管理器的外包函數
表 3-4 中函數的典型用法都需遵循如下的指導方針:
l 使用 w2kServiceLoad() 或 w2kServiceLoadEx() 來加載一個服務。后一個函數會根據可執行文件的路徑和版本信息自動生成服務的顯示名稱。邏輯變量 fStart 用來確定是否在成功加載服務后自動執行該服務。在成功的情況下,該函數會為后續的調用返回一個管理器句柄。如果服務已經加載或服務已經開始運行而 fStart 為 TRUE ,調用該函數不會返回任何錯誤。但如果發生錯誤,如有必要,發生錯誤的服務會被自動卸載。
l 使用 w2kServiceUnload() 和 w2kServiceUnloadEx() 來卸載一個服務,這需要用到 w2kServiceLoad() 或 w2kServiceLoadEx() 返回的管理器句柄。 w2kServiceUnloadEx() 會根據可執行文件的路徑自動生成服務名稱。如果你已經關閉了管理器句柄,可使用 w2kServiceConnect() 來或取一個新的管理器句柄或者簡單的傳遞一個 NULL (這表示使用臨時的管理器句柄)。管理器句柄會由 w2kServiceUnload() 自動關閉。如果服務已經有刪除標志,則不會返回任何錯誤,但并不會立即刪除服務,這是因為打開的設備句柄還存在著。
l 使用 w2kServiceStart() 、 w2kServiceStop() 、 w2kServicePause() 或 w2kServiceContinue() 來控制一個服務。這些函數也需要使用 w2kServiceLoad() 或 w2kServiceLoadEx() 返回的管理器句柄。如果你提供一個值為 NULL 的管理器句柄,則使用臨時管理器句柄。如果指定的服務已處于所要求的狀態,則不會返回任何錯誤。
l 調用 w2kServiceDisconnect() 來關閉一個管理器句柄。你可以在任何時候調用 w2kServiceConnect() 來獲取一個管理器句柄。
w2kServiceLoadEx() 是一個十分強大的函數。它會構建自動加載一個服務時所需的全部參數,但你要提供可執行文件的路徑。 SC 管理器的 CreateService() 函數所需要的服務名稱將從可執行文件名(會去掉文件的擴展名)中派生出來。為了給新創建的服務構建一個適當的用于顯示名稱, w2kServiceLoadEx() 會嘗試從文件的版本信息中讀取 FileDescription 字符串。如果可執行文件中不包含版本信息,或者 FileDescription 字符串不可用,則將使用缺省的服務名稱。
和 w2kServiceLoad() 不同, w2kServiceLoadEx() 支持路徑中的環境變量。換句話說,如果路徑字符串中包含如 %SystemRoot% 或 %TEMP% 這樣的子串,它們會被相應系統變量的當前值替換掉。 w2kServiceUnloadEx() 是 w2kServiceLoadEx() 的很好的搭檔,它會從提供的路徑中提取服務的名稱,與前面提及的展開過程類似,并將提取出來的服務名稱傳遞給 w2kServiceUnload() 。這兩個函數是需要加載 / 卸載第三方設備驅動的應用程序的理想搭檔,只需提供這些驅動的全路徑即可。本書的光盤中包含一個這樣的示例程序。
控制臺模式的工具 -----w2k_load.exe 是一個通用的內核驅動程序加載 / 卸載器,它為 w2kServiceLoadEx() 和 w2kServiceUnloadEx() 提供了簡單的命令行接口。其源代碼可以在隨書 CD 的 srcw2k_load 目錄下找到。 列表 3-9 給出了相關的代碼,該工具僅是一種示意性的實現。因為大量的工作都是由 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. 加載 / 卸載設備驅動
表 3-4 中剩余的庫函數在更低一級的層面上工作,它們都在 w2k_lib.dll 內部使用。當然,如果你喜歡的話,你也可以從你的程序里調用它們。從 列表 3-8 給出的它們的源代碼中,可以很容易得出它們的使用方式。
枚舉服務和驅動
有時很有必要知道系統當前加載了那個服務或驅動,以及它們現在處于什么狀態。為了實現這一目的, SC 管理器提供了另一個名為 EnumServiceStatus() 的強大函數。該函數需要一個管理器句柄和一個類型為 ENUM_SERVICE_STATUS 的數組,該數組中將包含有關當前已加載的服務或驅動的信息。這個列表可以根據服務 / 驅動的類型和狀態來過濾。如果調用者提供的緩沖區不能一次性的容納所有項目,可反復調用該函數直到獲取所有的項目。
不過很難預先計算出所需的緩沖區大小,這是因為緩沖區必須為那些大小未知的字符串提供額外的空間,這些字符串由 ENUM_SERVICE_STATUS 的成員引用。幸運的是, EnumServiceStatus() 會返回剩余的項目所需的字節數,因此可以通過反復嘗試得出確定的緩沖區大小。 列表 3-10 給出了 SERVICE_STATUS 和 ENUM_SERVICE_STATUS 結構的定義。這些聲明位于 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 結構的定義
列表 3-11 給出的 w2kServiceList() 函數是來自 w2k_lib.dll 工具庫的另一個好東東。它省略了前面提到的動作,并返回一個隨時可用的結構,該結構中包含所有請求的數據以及一對擴展結構。該函數將返回一個指向 W2K_SERVICES 結構的指針,該結構定義于 w2k_lib.h ,在 列表 3-11 的頂部給出了其定義。隨 ENUM_SERVICE_STATUS 結構數組 aess[] , W2K_SERVICES 結構體還包含四個附加成員。 dEntrIEs 表示向狀態數組中復制了多少項目, dBytes 表示返回的 W2K_SERVICES 結構的大小。 dDisplayName 和 dServiceName 被分別設置為 aess[] 中的 lpDisplayName 和 lpServiceName 字符串的最大長度。這些值將提供很大的方便,尤其是當你編寫一個控制臺模式的程序,在屏幕上輸出服務 / 驅動列表,并要求名稱列采用合適的對齊方式。
為了提供精確的系統快照, w2kServiceList() 試圖通過一次調用 EnumServiceStatus() 來獲取所有的項目。為此目的,該函數首先提供一個長度為 0 的緩沖區,這通常會導致返回 ERROR_MORE_DATA 錯誤代碼。在此種情況下, EnumServiceStatus() 將返回需要的緩沖區大小,然后按照此大小分配適當的緩沖區,然后再次調用 EnumServiceStatus() 。此時, EnumServiceStatus() 應該返回成功。不過,這存在一個很小的概率事件 --- 在兩次調用 EnumServiceStatus() 之間另一個項目可能會被增加到列表中。因此,將會在一個循環中重復這一過程直到所有的一切都正確或者一個非 ERROR_MORE_DATA 的錯誤返回。
// -----------------------------------------------------------------
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. 枚舉服務 / 驅動程序
w2kServiceList() 需要四個邏輯類型的參數,以確定要返回的列表的內容。通過 fDriver 和 fWin32 參數,你可以分別選擇是否包含驅動程序或服務。如果這兩個參數都為 TRUE ,那么返回的列表將同時包含驅動和服務。 fActive 和 fInactive 標志用于控制加于列表上的狀態過濾器。。 fInactive 參數選擇剩余的模塊,也就是說,這些模塊已經加載但已經停止運行。如果所有的四個參數都為 FALSE ,函數返回的 W2K_SERVICES 結構將包含一個空的狀態數組。光盤中的示例代碼包含一個簡單的服務 / 驅動瀏覽器,它被設計為 Win32 控制臺模式,并依賴于 w2k_lib.dll 中的 w2kServiceList() 。它使用 W2K_SERVICES 結構(參見 列表 3-11 )中的 dDisplayName 和 dServiceName 成員來為所有的名稱選擇合適的水平對齊方式。你可以在光盤的 srcw2k_svc 目錄下找到此工具的源代碼。其可執行文件對應光盤中的 binw2k_svc.exe 。 示列 3-4 列出了在我的機器上運行該工具,列出的所有活動的內核驅動程序(使用命令選項 /drivers /active )。
在下一章中,我們將開始開發一個可實際工作的內核驅動程序,它會偵測內核使用的內存,并且會 Crack 基本的內存管理數據結構。這個工程將伴隨你閱讀第 4 、 5 和 6 章,在每一章中,該驅動程序都會被加強。最后將得到一個通用的 Windows 2000 Kernel Spy 。
