程式扎記: [ Windows DDP ] NT 式驅動載入介紹

標籤

2010年12月24日 星期五

[ Windows DDP ] NT 式驅動載入介紹

前言 : 
在第一章曾經介紹過使用 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 的初始化 : 
  1. SC_HANDLE OpenSCManager (  
  2.     LPCTSTR lpManchineName,    // 電腦名稱  
  3.     LPCTSTR lpDatabaseName,    // SCM 資料庫名稱  
  4.     DWORD  dwDesiredAddress   // 使用權限  
  5. );  
* lpMachineName : 指定電腦名稱. 如果為 NULL 代表是本機. 
* lpDatabaseName : 指定 SCM 資料名稱. 如果為 NULL 代表使用預設資料庫. 
* dwDesiredAccess : 使用者權限, 一般設置為 SC_MANAGER_ALL_ACCESS. 
* 返回值 : 如果成功, 返回 SCM 管理器的控制碼, 如果失敗返回 NULL. 

2. 關閉服務控制碼 
此函式的作用是關閉 SCM 管理器的控制碼, 用於 SCM 的清除工作 : 
  1. BOOL CloseServiceHandle(  
  2.     SC_HANDLE hSCObject    // 要關閉的 SCM 控制碼  
  3. )  
* hSCObject : 要關閉的控制碼. 

3. 新建服務 
此函式的作用是新建 SCM 管理器的控制碼, 後面介紹的操作都是基於這個控制碼進行的. 
  1. SC_HANDLE CreateService(  
  2.     SC_HANDLE  hSCManager,     // SCM 管理器  
  3.     LPCTSTR       lpServiceName,  // 服務名稱  
  4.     LPCTSTR       lpDisplayName,  // 服務顯示出來的名稱  
  5.     DWORD        dwDesiredAddress // 打開權限  
  6.     DWORD        dwServiceType,  // 服務類型  
  7.     DWORD        dwStartType,      // 打開服務時間  
  8.     DWORD        dwErrorControl,  // 關閉錯誤處理  
  9.     LPCTSTR       lpBinaryPathName, // 二進位檔案程式碼  
  10.     LPCTSTR       lpLoadOrderGroup, // 用何使用者群組開啟服務  
  11.     LPDWORD     lpdwTagId,         //  輸出驗證標籤  
  12.     LPCTSTR       lpDependencies, // 所依賴的服務名稱  
  13.     LPCTSTR       lpServiceStartName, // 使用者帳號名稱  
  14.     LPCTSTR       lpPassword,        // 使用者密碼  
  15. );  
* 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. 打開服務 
此函式的作用是針對已經新建過的服務, 再次打開此服務. 
  1. SC_HANDLE OpenService(  
  2.     SC_HANDLE hSCManager,    // SCM 資料庫控制碼  
  3.     LPCTSTR      lpServiceName,  // 服務名稱  
  4.     DWORD       dwDesiredAccess    // 存取權限  
  5. );  
5. 控制服務 
此函式作用是對服務發出控制碼, 根據不同控制碼操作服務. 
  1. BOOL ControlService(  
  2.     SC_HANDLE hService,    // 服務的控制碼  
  3.     DWORD       dwControl,  // 控制碼  
  4.     LPSERVICE_STATUS lpServiceStatus  // 返回狀態碼  
  5. );  
* 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 函式) :
  1. //裝載NT驅動程式  
  2. BOOL LoadNTDriver(char* lpszDriverName,char* lpszDriverPath)  
  3. {  
  4.     char szDriverImagePath[256];  
  5.     //得到完整的驅動路徑  
  6.     GetFullPathName(lpszDriverPath, 256, szDriverImagePath, NULL);  
  7.   
  8.     BOOL bRet = FALSE;  
  9.   
  10.     SC_HANDLE hServiceMgr=NULL;//SCM管理器的控制碼  
  11.     SC_HANDLE hServiceDDK=NULL;//NT驅動程式的服務控制碼  
  12.   
  13.     //打開服務控制管理器  
  14.     hServiceMgr = OpenSCManager( NULL, NULL, SC_MANAGER_ALL_ACCESS );  
  15.   
  16.     if( hServiceMgr == NULL )    
  17.     {  
  18.         //OpenSCManager失敗  
  19.         printf( "OpenSCManager() Faild %d ! \n", GetLastError() );  
  20.         bRet = FALSE;  
  21.         goto BeforeLeave;  
  22.     }  
  23.     else  
  24.     {  
  25.         ////OpenSCManager成功  
  26.         printf( "OpenSCManager() ok ! \n" );    
  27.     }  
  28.   
  29.     //新建驅動所對應的服務  
  30.     hServiceDDK = CreateService( hServiceMgr,  
  31.         lpszDriverName, //驅動程式的在登錄表中的名字   
  32.         lpszDriverName, // 登錄表驅動程式的 DisplayName 值   
  33.         SERVICE_ALL_ACCESS, // 載入驅動程式的存取權限  
  34.         SERVICE_KERNEL_DRIVER,// 表示載入的服務是驅動程式  
  35.         SERVICE_DEMAND_START, // 登錄表驅動程式的 Start 值  
  36.         SERVICE_ERROR_IGNORE, // 登錄表驅動程式的 ErrorControl 值  
  37.         szDriverImagePath, // 登錄表驅動程式的 ImagePath 值  
  38.         NULL,    
  39.         NULL,    
  40.         NULL,    
  41.         NULL,    
  42.         NULL);    
  43.   
  44.     DWORD dwRtn;  
  45.     //判斷服務是否失敗  
  46.     if( hServiceDDK == NULL )    
  47.     {    
  48.         dwRtn = GetLastError();  
  49.         if( dwRtn != ERROR_IO_PENDING && dwRtn != ERROR_SERVICE_EXISTS )    
  50.         {    
  51.             //由於其他原因新建服務失敗  
  52.             printf( "CrateService() Faild %d ! \n", dwRtn );    
  53.             bRet = FALSE;  
  54.             goto BeforeLeave;  
  55.         }    
  56.         else    
  57.         {  
  58.             //服務新建失敗,是由於服務已經創立過  
  59.             printf( "CrateService() Faild Service is ERROR_IO_PENDING or ERROR_SERVICE_EXISTS! \n" );    
  60.         }  
  61.   
  62.         // 驅動程式已經載入,只需要打開  
  63.         hServiceDDK = OpenService( hServiceMgr, lpszDriverName, SERVICE_ALL_ACCESS );    
  64.         if( hServiceDDK == NULL )    
  65.         {  
  66.             //如果打開服務也失敗,則意味錯誤  
  67.             dwRtn = GetLastError();    
  68.             printf( "OpenService() Faild %d ! \n", dwRtn );    
  69.             bRet = FALSE;  
  70.             goto BeforeLeave;  
  71.         }    
  72.         else   
  73.         {  
  74.             printf( "OpenService() ok ! \n" );  
  75.         }  
  76.     }    
  77.     else    
  78.     {  
  79.         printf( "CrateService() ok ! \n" );  
  80.     }  
  81.   
  82.     //開啟此項服務  
  83.     bRet= StartService( hServiceDDK, NULL, NULL );    
  84.     if( !bRet )    
  85.     {    
  86.         DWORD dwRtn = GetLastError();    
  87.         if( dwRtn != ERROR_IO_PENDING && dwRtn != ERROR_SERVICE_ALREADY_RUNNING )    
  88.         {    
  89.             printf( "StartService() Faild %d ! \n", dwRtn );    
  90.             bRet = FALSE;  
  91.             goto BeforeLeave;  
  92.         }    
  93.         else    
  94.         {    
  95.             if( dwRtn == ERROR_IO_PENDING )    
  96.             {    
  97.                 //裝置被掛住  
  98.                 printf( "StartService() Faild ERROR_IO_PENDING ! \n");  
  99.                 bRet = FALSE;  
  100.                 goto BeforeLeave;  
  101.             }    
  102.             else    
  103.             {    
  104.                 //服務已經開啟  
  105.                 printf( "StartService() Faild ERROR_SERVICE_ALREADY_RUNNING ! \n");  
  106.                 bRet = TRUE;  
  107.                 goto BeforeLeave;  
  108.             }    
  109.         }    
  110.     }  
  111.     bRet = TRUE;  
  112. //離開前關閉控制碼  
  113. BeforeLeave:  
  114.     if(hServiceDDK)  
  115.     {  
  116.         CloseServiceHandle(hServiceDDK);  
  117.     }  
  118.     if(hServiceMgr)  
  119.     {  
  120.         CloseServiceHandle(hServiceMgr);  
  121.     }  
  122.     return bRet;  
  123. }  

- 移除 NT 驅動的程式碼 
UnloadNTDriver 函式作用是停止驅動並移除驅動. 函式的輸入參數為驅動程式名稱. 返回值代表移除驅動是否成功. UnloadNTDriver 的步驟如下 : 
1. 呼叫 OpenSCManager, 打開 SCM 管理器. 如果返回 NULL, 則返回失敗, 否則繼續.
2. 呼叫 OpenService. 如果返回 NULL, 則返回失敗, 否則繼續.
3. 呼叫 DeleteService 移除此項服務.
4. 成功返回.

底下為 UnloadNTDriver 函式代碼 : 
- main.cpp (UnloadNTDriver 函式) :
  1. //卸載驅動程式  
  2. BOOL UnloadNTDriver( char * szSvrName )    
  3. {  
  4.     BOOL bRet = FALSE;  
  5.     SC_HANDLE hServiceMgr=NULL;//SCM管理器的控制碼  
  6.     SC_HANDLE hServiceDDK=NULL;//NT驅動程式的服務控制碼  
  7.     SERVICE_STATUS SvrSta;  
  8.     //打開SCM管理器  
  9.     hServiceMgr = OpenSCManager( NULL, NULL, SC_MANAGER_ALL_ACCESS );    
  10.     if( hServiceMgr == NULL )    
  11.     {  
  12.         //帶開SCM管理器失敗  
  13.         printf( "OpenSCManager() Faild %d ! \n", GetLastError() );    
  14.         bRet = FALSE;  
  15.         goto BeforeLeave;  
  16.     }    
  17.     else    
  18.     {  
  19.         //帶開SCM管理器失敗成功  
  20.         printf( "OpenSCManager() ok ! \n" );    
  21.     }  
  22.     //打開驅動所對應的服務  
  23.     hServiceDDK = OpenService( hServiceMgr, szSvrName, SERVICE_ALL_ACCESS );    
  24.   
  25.     if( hServiceDDK == NULL )    
  26.     {  
  27.         //打開驅動所對應的服務失敗  
  28.         printf( "OpenService() Faild %d ! \n", GetLastError() );    
  29.         bRet = FALSE;  
  30.         goto BeforeLeave;  
  31.     }    
  32.     else    
  33.     {    
  34.         printf( "OpenService() ok ! \n" );    
  35.     }    
  36.     //停止驅動程式,如果停止失敗,只有重新開機才能,再動態載入。    
  37.     if( !ControlService( hServiceDDK, SERVICE_CONTROL_STOP , &SvrSta ) )    
  38.     {    
  39.         printf( "ControlService() Faild %d !\n", GetLastError() );    
  40.     }    
  41.     else    
  42.     {  
  43.         //打開驅動所對應的失敗  
  44.         printf( "ControlService() ok !\n" );    
  45.     }    
  46.     //動態卸載驅動程式。    
  47.     if( !DeleteService( hServiceDDK ) )    
  48.     {  
  49.         //卸載失敗  
  50.         printf( "DeleteSrevice() Faild %d !\n", GetLastError() );    
  51.     }    
  52.     else    
  53.     {    
  54.         //卸載成功  
  55.         printf( "DelServer:eleteSrevice() ok !\n" );    
  56.     }    
  57.     bRet = TRUE;  
  58. BeforeLeave:  
  59. //離開前關閉打開的控制碼  
  60.     if(hServiceDDK)  
  61.     {  
  62.         CloseServiceHandle(hServiceDDK);  
  63.     }  
  64.     if(hServiceMgr)  
  65.     {  
  66.         CloseServiceHandle(hServiceMgr);  
  67.     }  
  68.     return bRet;      
  69. }   

- 實驗 
為了實驗前面的載入步驟和移除函式的使用, 這裡給出一個測試用例, 用於載入 HelloDDK. 該測試分為以下三個步驟 : 
1. 呼叫 LoaderNTDriver, 載入驅動. 如果呼叫成功, 則程式暫停住, 此時可以觀察載入狀況. 先觀察登錄表的前後變化, 再用 DebugView 查看驅動輸出的 log, 是否執行了 DriverEntry 函式. 同時再查看裝置管理員中, 裝置是否已經被載入.
2. 按任意鍵繼續, 執行編寫的 TestDriver 函式, 此函式對驅動程式進行了新建與關閉操作. 此時觀察 DebugView 是否有對應的輸出 log.
3. 再次按任意鍵繼續, 執行 UnloadNTDriver, 移除驅動程式. 此時再次觀察登錄表的變化, 會發現登錄表相關的項目已經不存在. 另外觀察裝置管理員, 裝置也不見了. 同時觀察 DebugView, 會發現驅動中的 HelloDDKUnload 輸出的 log 資訊.

底下是執行結果 : 

沒有留言:

張貼留言

網誌存檔

關於我自己

我的相片
Where there is a will, there is a way!