前言 :
在第一章曾經介紹過使用 DriverMonitor 載入 NT 式驅動. 這裡介紹另一種手動的載入方式. 其原理是一樣的, 都是在登入表中填寫對應的欄位. 如下圖所示. Windows 對 NT 式驅動程式的載入都是基於服務的方式載入的 :
接著說明如何透過 Register key 進行手動載入 NT 式驅動 :
1. 在 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services 中新增機碼項目, 名稱為這個驅動名稱 HelloDDK.
2. 新增以下子鍵 :
DisplayName (REG_SZ) : HelloDDK
ErrorControl (REG_DWORD) : 1
Group (REG_SZ) :
ImagePath (REG_EXPAND_SZ) : \??\C:\MyDriver\MyDriver_Check\HelloDDK.sys
Start (REG_DWORD) : 3
Type (RED_DWORD) : 1
DisplayName 的內容, 會在 裝置管理員中顯示裝置名字時顯示出來.
ImagePath 要以 \??\ 開頭, 這代表符號連結.
Start 為 3, 表明規定按需要載入此驅動程式.
Type 為 1, 表明此驅動在核心模式下載入.
3. 在命令列方式執行 net start 驅動名稱, 例如 net start HelloDDK. 這時驅動會被 I/O 管理器載入, 並且呼叫入口函式 DriverEntry. 停止驅動的方式很類似, 執行 net stop HelloDDK. 如果不採用命令列的方式, 可以在裝置管理員開始或停止. 底下是執行結果 :
編寫程式載入 NT 驅動 :
裝置驅動程式的動態載入主要是由服務控制管理程式 (Service Control Manager, SCM ) 系統元件完成. SCM 元件為 Windows 2000 中執行的元件提供許多服務. 例如 啟動, 停止和控制服務. 這裡服務的概念有點像是 Linux 的 daemon. 編寫載入驅動程式主要是透過操作 SCM 元件.
- SCM 元件和 Windows 服務
Windows 服務應用程式遵循服務控制管理器 (Service Control Manager). Windows 服務可以在系統啟動時載入, 使用者需在服務控制平台開啟或關閉服務. 程式員可以透過 Windows 提供的相關服務函式進行載入或者移除服務. 服務程式可以在使用者沒有登入系統時後就被載入系統並且執行. Driver Service 是 Windows 服務的一個特例, 它遵從 Windows 服務的協定. 載入和移除 NT 驅動分為四個步驟 :
1. 為 NT 驅動新建新的服務.
2. 開啟此項服務.
3. 關閉此項服務.
4. 刪除 NT 驅動所新建的服務.
以上四個步驟, 都是透過呼叫 SCM 元件的服務來實現的. 下面列出常用的 SCM 元件 API 相關函式.
1. 打開 SCM 管理函式
此函式的作用是打開 SCM 管理器, 用於 SCM 的初始化 :
- SC_HANDLE OpenSCManager (
- LPCTSTR lpManchineName,
- LPCTSTR lpDatabaseName,
- DWORD dwDesiredAddress
- );
* lpMachineName : 指定電腦名稱. 如果為 NULL 代表是本機.
* lpDatabaseName : 指定 SCM 資料名稱. 如果為 NULL 代表使用預設資料庫.
* dwDesiredAccess : 使用者權限, 一般設置為 SC_MANAGER_ALL_ACCESS.
* 返回值 : 如果成功, 返回 SCM 管理器的控制碼, 如果失敗返回 NULL.
2. 關閉服務控制碼
此函式的作用是關閉 SCM 管理器的控制碼, 用於 SCM 的清除工作 :
- BOOL CloseServiceHandle(
- SC_HANDLE hSCObject
- )
* hSCObject : 要關閉的控制碼.
3. 新建服務
此函式的作用是新建 SCM 管理器的控制碼, 後面介紹的操作都是基於這個控制碼進行的.
- SC_HANDLE CreateService(
- SC_HANDLE hSCManager,
- LPCTSTR lpServiceName,
- LPCTSTR lpDisplayName,
- DWORD dwDesiredAddress
- DWORD dwServiceType,
- DWORD dwStartType,
- DWORD dwErrorControl,
- LPCTSTR lpBinaryPathName,
- LPCTSTR lpLoadOrderGroup,
- LPDWORD lpdwTagId,
- LPCTSTR lpDependencies,
- LPCTSTR lpServiceStartName,
- LPCTSTR lpPassword,
- );
* hSCManager : SCM 管理器的控制碼, 也就是 OpenSCManager 打開的控制碼.
* dwServiceType : 服務類型, 有以下幾種選擇
- SERVICE_FILE_SYSTEM_DRIVER > 檔案系統驅動
- SERVICE_KERNEL_DRIVER > 普通程式的驅動, 一般使用此項.
* dwStartType : 打開服務的時間, 有以下幾種選擇.
- SERVICE_AUTO_START > 驅動自動載入
- SERVICE_BOOT_START > 被 system loader 載入, 即系統啟動前就被載入.
- SERVICE_DEMAND_START > 按照需求時啟動, 一般選擇此項.
* dwErrorControl : 遇到錯誤處理的程式碼, 有以下幾種選擇.
- SERVICE_ERROR_IGNORE > 遇到錯誤全部忽略掉
- SERVICE_ERROR_NORMAL > 遇到錯誤按照預設辦法處理.
- SERVICE_ERROR_CRITICAL > 增加對錯誤處理的校驗. 並提示出對話框且記錄錯誤訊息到 log 檔中.
* lpBinaryPathName : 服務用的二進位碼, 也就是編譯後的驅動程式.
4. 打開服務
此函式的作用是針對已經新建過的服務, 再次打開此服務.
- SC_HANDLE OpenService(
- SC_HANDLE hSCManager,
- LPCTSTR lpServiceName,
- DWORD dwDesiredAccess
- );
5. 控制服務
此函式作用是對服務發出控制碼, 根據不同控制碼操作服務.
- BOOL ControlService(
- SC_HANDLE hService,
- DWORD dwControl,
- LPSERVICE_STATUS lpServiceStatus
- );
* hService : 服務的控制碼, 也就是用 CreateService 新建的控制碼, 或者 OpenService 打開的控制碼.
* dwControl : 對服務的控制碼, 此處列出常用控制碼.
- SERVICE_CONTROL_CONTINUE > 針對服務暫停發出繼續執行命令
- SERVICE_CONTROL_PAUSE > 針對正在執行服務發出暫停命令
- SERVICE_CONTROL_STOP > 針對正在執行服務發出停止命令
- 載入 NT 驅動的程式碼
這裡將載入驅動行為封裝在 LoadNTDriver 函式裡. 函式的輸入參數為驅動程式的名稱與驅動映射檔的路徑名, 返回值代表載入是否成功. LoadNTDriver 的步驟如下 :
1. 呼叫 OpenSCManager, 打開 SCM 管理器. 如果返回 NULL 則失敗, 否則繼續.
2. 呼叫 CreateService, 新建服務. 新建成功則繼續.
3. 用 GetLastError 得到錯誤返回值.
4. 如果錯誤, 返回值為 ERROR_IO_PENDING, 說明服務已經新建過, 不需要新建, 用 OpenService 打開此服務.
5. 如果錯誤而返回值為其他值, 說明新建服務失敗, 返回失敗.
6. 呼叫 StartService 開啟服務.
7. 成功返回.
底下為 LoadNTDriver() 函式代碼 :
- main.cpp (LoadNTDriver 函式) :
-
- BOOL LoadNTDriver(char* lpszDriverName,char* lpszDriverPath)
- {
- char szDriverImagePath[256];
-
- GetFullPathName(lpszDriverPath, 256, szDriverImagePath, NULL);
-
- BOOL bRet = FALSE;
-
- SC_HANDLE hServiceMgr=NULL;
- SC_HANDLE hServiceDDK=NULL;
-
-
- hServiceMgr = OpenSCManager( NULL, NULL, SC_MANAGER_ALL_ACCESS );
-
- if( hServiceMgr == NULL )
- {
-
- printf( "OpenSCManager() Faild %d ! \n", GetLastError() );
- bRet = FALSE;
- goto BeforeLeave;
- }
- else
- {
-
- printf( "OpenSCManager() ok ! \n" );
- }
-
-
- hServiceDDK = CreateService( hServiceMgr,
- lpszDriverName,
- lpszDriverName,
- SERVICE_ALL_ACCESS,
- SERVICE_KERNEL_DRIVER,
- SERVICE_DEMAND_START,
- SERVICE_ERROR_IGNORE,
- szDriverImagePath,
- NULL,
- NULL,
- NULL,
- NULL,
- NULL);
-
- DWORD dwRtn;
-
- if( hServiceDDK == NULL )
- {
- dwRtn = GetLastError();
- if( dwRtn != ERROR_IO_PENDING && dwRtn != ERROR_SERVICE_EXISTS )
- {
-
- printf( "CrateService() Faild %d ! \n", dwRtn );
- bRet = FALSE;
- goto BeforeLeave;
- }
- else
- {
-
- printf( "CrateService() Faild Service is ERROR_IO_PENDING or ERROR_SERVICE_EXISTS! \n" );
- }
-
-
- hServiceDDK = OpenService( hServiceMgr, lpszDriverName, SERVICE_ALL_ACCESS );
- if( hServiceDDK == NULL )
- {
-
- dwRtn = GetLastError();
- printf( "OpenService() Faild %d ! \n", dwRtn );
- bRet = FALSE;
- goto BeforeLeave;
- }
- else
- {
- printf( "OpenService() ok ! \n" );
- }
- }
- else
- {
- printf( "CrateService() ok ! \n" );
- }
-
-
- bRet= StartService( hServiceDDK, NULL, NULL );
- if( !bRet )
- {
- DWORD dwRtn = GetLastError();
- if( dwRtn != ERROR_IO_PENDING && dwRtn != ERROR_SERVICE_ALREADY_RUNNING )
- {
- printf( "StartService() Faild %d ! \n", dwRtn );
- bRet = FALSE;
- goto BeforeLeave;
- }
- else
- {
- if( dwRtn == ERROR_IO_PENDING )
- {
-
- printf( "StartService() Faild ERROR_IO_PENDING ! \n");
- bRet = FALSE;
- goto BeforeLeave;
- }
- else
- {
-
- printf( "StartService() Faild ERROR_SERVICE_ALREADY_RUNNING ! \n");
- bRet = TRUE;
- goto BeforeLeave;
- }
- }
- }
- bRet = TRUE;
-
- BeforeLeave:
- if(hServiceDDK)
- {
- CloseServiceHandle(hServiceDDK);
- }
- if(hServiceMgr)
- {
- CloseServiceHandle(hServiceMgr);
- }
- return bRet;
- }
- 移除 NT 驅動的程式碼
UnloadNTDriver 函式作用是停止驅動並移除驅動. 函式的輸入參數為驅動程式名稱. 返回值代表移除驅動是否成功. UnloadNTDriver 的步驟如下 :
1. 呼叫 OpenSCManager, 打開 SCM 管理器. 如果返回 NULL, 則返回失敗, 否則繼續.
2. 呼叫 OpenService. 如果返回 NULL, 則返回失敗, 否則繼續.
3. 呼叫 DeleteService 移除此項服務.
4. 成功返回.
底下為 UnloadNTDriver 函式代碼 :
- main.cpp (UnloadNTDriver 函式) :
-
- BOOL UnloadNTDriver( char * szSvrName )
- {
- BOOL bRet = FALSE;
- SC_HANDLE hServiceMgr=NULL;
- SC_HANDLE hServiceDDK=NULL;
- SERVICE_STATUS SvrSta;
-
- hServiceMgr = OpenSCManager( NULL, NULL, SC_MANAGER_ALL_ACCESS );
- if( hServiceMgr == NULL )
- {
-
- printf( "OpenSCManager() Faild %d ! \n", GetLastError() );
- bRet = FALSE;
- goto BeforeLeave;
- }
- else
- {
-
- printf( "OpenSCManager() ok ! \n" );
- }
-
- hServiceDDK = OpenService( hServiceMgr, szSvrName, SERVICE_ALL_ACCESS );
-
- if( hServiceDDK == NULL )
- {
-
- printf( "OpenService() Faild %d ! \n", GetLastError() );
- bRet = FALSE;
- goto BeforeLeave;
- }
- else
- {
- printf( "OpenService() ok ! \n" );
- }
-
- if( !ControlService( hServiceDDK, SERVICE_CONTROL_STOP , &SvrSta ) )
- {
- printf( "ControlService() Faild %d !\n", GetLastError() );
- }
- else
- {
-
- printf( "ControlService() ok !\n" );
- }
-
- if( !DeleteService( hServiceDDK ) )
- {
-
- printf( "DeleteSrevice() Faild %d !\n", GetLastError() );
- }
- else
- {
-
- printf( "DelServer:eleteSrevice() ok !\n" );
- }
- bRet = TRUE;
- BeforeLeave:
-
- if(hServiceDDK)
- {
- CloseServiceHandle(hServiceDDK);
- }
- if(hServiceMgr)
- {
- CloseServiceHandle(hServiceMgr);
- }
- return bRet;
- }
- 實驗
為了實驗前面的載入步驟和移除函式的使用, 這裡給出一個測試用例, 用於載入 HelloDDK. 該測試分為以下三個步驟 :
1. 呼叫 LoaderNTDriver, 載入驅動. 如果呼叫成功, 則程式暫停住, 此時可以觀察載入狀況. 先觀察登錄表的前後變化, 再用 DebugView 查看驅動輸出的 log, 是否執行了 DriverEntry 函式. 同時再查看裝置管理員中, 裝置是否已經被載入.
2. 按任意鍵繼續, 執行編寫的 TestDriver 函式, 此函式對驅動程式進行了新建與關閉操作. 此時觀察 DebugView 是否有對應的輸出 log.
3. 再次按任意鍵繼續, 執行 UnloadNTDriver, 移除驅動程式. 此時再次觀察登錄表的變化, 會發現登錄表相關的項目已經不存在. 另外觀察裝置管理員, 裝置也不見了. 同時觀察 DebugView, 會發現驅動中的 HelloDDKUnload 輸出的 log 資訊.
底下是執行結果 :
沒有留言:
張貼留言