編寫軟體動態載入NT式驅動

epluguo發表於2013-07-24

        NT式裝置驅動程式的動態載入主要是由服務控制管理程式(Service Control Manager,即SCM)系統元件來完成的。

        Windwos服務可以在系統啟動時載入,使用者也可以按需在服務控制平臺開啟或者關閉服務。程式設計師可以通過Windows提供的相關服務函式進行載入或者解除安裝該服務等。服務程式更是可以在使用者還沒有登入系統的時候,就載入系統並且被執行。


載入NT驅動一般分為4個步驟:

1.    呼叫OpenSCManager開啟SCM管理器;

2.    呼叫CreateService建立服務;如果存在則呼叫OpenService開啟服務(可根據GetLastError判斷);

3.    呼叫StartService開啟服務;

4.    關閉控制程式碼。

 

解除安裝NT驅動一般分為5個步驟:

1.    呼叫OpenSCManager開啟SCM管理器;

2.    呼叫OpenService開啟此項服務;

3.    呼叫ControlService傳遞SERVICE_CONTROL_STOP來停止服務

4.    呼叫DeleteService解除安裝此項服務;

5.    關閉控制程式碼。

注意:DeleteService只是標記一下該項服務需要刪除,只有停止了服務並且關閉了開啟服務的控制程式碼,改服務才會被正式解除安裝。

 

開啟SCM管理器函式


SC_HANDLE WINAPI OpenSCManager(
  __in          LPCTSTR lpMachineName,	//計算機名稱
  __in          LPCTSTR lpDatabaseName,	//SCM資料庫名稱
  __in          DWORD dwDesiredAccess	//使用許可權
);

說明

函式建立了一個連線到服務控制管理器,並開啟指定的資料庫。

引數

lpMachineName

指向零終止字串,命名為目標計算機。如果該指標為NULL ,或者如果它指向一個空字串,函式連線到服務控制管理器在本地計算機上。

lpDatabaseName

指向零終止字串,名稱的服務控制管理資料庫,以開放。此字串應指定ServicesActive 。如果該指標為NULL ,該ServicesActive資料庫預設情況下開啟。

dwDesiredAccess

指定服務的訪問控制管理。才准予進入的要求,系統會檢查訪問令牌的呼叫程式對任意訪問控制列表的安全描述符與服務控制管理器物件。訪問型別的SC_MANAGER_CONNECT是含蓄地指明呼叫這個函式。此外,任何或所有下列服務控制管理器物件的訪問型別可以指定:

SC_MANAGER_ALL_ACCESS

包括STANDARD_RIGHTS_REQUIRED ,除了所有型別的訪問此表中列出。

SC_MANAGER_CONNECT

可以連線到服務控制管理器。

SC_MANAGER_CREATE_SERVICE

使要求的CreateService函式建立一個服務物件,並將其新增到資料庫中。

SC_MANAGER_ENUMERATE_SERVICE

使要求的EnumServicesStatus功能清單的服務,這是在資料庫中。

SC_MANAGER_LOCK

使要求的LockServiceDatabase功能獲得鎖定資料庫。

SC_MANAGER_QUERY_LOCK_STATUS

使要求的QueryServiceLockStatus檢索功能鎖定狀態資訊的資料庫。

返回值

如果函式成功,返回值是一個控制程式碼指定的服務控制管理器資料庫。如果函式失敗,返回值為NULL 。要獲得擴充套件錯誤資訊,請使用GetLastError 獲得錯誤程式碼。

 

關閉服務控制程式碼


BOOL WINAPI CloseHandle(
  __in          HANDLE hObject		//要關閉的控制程式碼
);

hObjece

物件控制程式碼,即使用OpenSCManager或者CreateService、OpenService返回的控制程式碼。

 

建立服務

建立一個服務物件並且把它加入到服務管理資料庫中。

SC_HANDLE WINAPI CreateService(
  __in          SC_HANDLE hSCManager,		//SCM管理器的控制程式碼
  __in          LPCTSTR lpServiceName,		//服務名稱
  __in          LPCTSTR lpDisplayName,		//服務顯示名稱
  __in          DWORD dwDesiredAccess,		//訪問許可權
  __in          DWORD dwServiceType,		//服務型別
  __in          DWORD dwStartType,		//啟動型別
  __in          DWORD dwErrorControl,		//關於錯誤處理的程式碼
  __in          LPCTSTR lpBinaryPathName,	//二進位制檔案的程式碼
  __in          LPCTSTR lpLoadOrderGroup,	//在載入順序此服務所屬的組的名稱
  __out         LPDWORD lpdwTagId,		//輸出驗證標籤
  __in          LPCTSTR lpDependencies,	//所依賴的服務名稱
  __in          LPCTSTR lpServiceStartName,	//使用者賬號名稱
  __in          LPCTSTR lpPassword		//使用者口令
);

引數

hSCManager

服務控制管理器資料庫的控制程式碼。 此控制程式碼由OpenSCManager函式返回,並且必須具有SC_MANAGER_CREATE_SERVICE 的訪問許可權。

lpServiceName

要安裝該服務的名稱。 最大字串長度為 256 個字元。 服務控制管理器資料庫將保留字元的大小寫,但是服務名稱比較總是區分大小寫。 正斜槓和一個反斜線不是有效的服務名稱字元。

lpDisplayName

對被使用者介面程式用來識別服務的顯示名稱。 此字串具有最大長度為 256 個字元。 服務控制管理器中的情況下保留名稱。 顯示名稱比較總是不區分大小寫。

dwDesiredAccess

對服務的訪問許可權。請求的訪問之前,系統將檢查呼叫程式的訪問令牌。如果沒有特殊要求,一般設定為SERVICE_ALL_ACCESS (0xF01FF)

 dwServiceType服務型別。一般選擇以下兩種:

Value

Meaning

SERVICE_FILE_SYSTEM_DRIVER
0x00000002

檔案系統的驅動

SERVICE_KERNEL_DRIVER
0x00000001

普通程式的驅動,一般使用此項。

dwStartType

服務啟動選項,亦即開啟服務的時間,有以下幾種選擇:

Value

Meaning

SERVICE_AUTO_START
0x00000002

系統啟動時由服務控制管理器自動啟動該服務程式。

SERVICE_BOOT_START
0x00000000

用於由系統載入器建立的裝置驅動程式。

只能用於驅動服務程式。

SERVICE_DEMAND_START
0x00000003

由服務控制管理器(SCM)啟動的服務,即手動啟動。

SERVICE_DISABLED
0x00000004

表示該服務不可啟動。

SERVICE_SYSTEM_START
0x00000001

用於由IoInitSystem函式建立的裝置驅動程式。

 

dwErrorControl當該啟動服務失敗時產生錯誤的嚴重程度以及採取的保護措施。此引數可以是下列值之一:

Value

Meaning

SERVICE_ERROR_CRITICAL
0x00000003

服務啟動程式將把該錯誤記錄到事件日誌中

SERVICE_ERROR_IGNORE
0x00000000

服務啟動程式將忽略該錯誤並返回繼續執行

SERVICE_ERROR_NORMAL
0x00000001

服務啟動程式將把該錯誤記錄到事件日誌中並返回繼續執行

SERVICE_ERROR_SEVERE
0x00000002

服務啟動程式將把該錯誤記錄到事件日誌中。

否則將返回繼續執行。

lpBinaryPathName

服務所用的二進位制檔案,也就是編譯後的驅動程式。 如果路徑中包含空格它必須被引用,以便它正確的解析。

 例如"d:\myshare\myservice.exe"應指定為""d:\myshare\myservice.exe""。該路徑也可以包含一個自動啟動服務的引數。

例如"d:\myshare\myservice.exearg1 arg2"。 這些引數被傳遞給服務的入口點通常主要作用。

 

開啟服務

此函式針對已經建立過的服務,再次開啟此項服務。

SC_HANDLE WINAPI OpenService(
  __in          SC_HANDLE hSCManager,		//SCM管理器的控制程式碼
  __in          LPCTSTR lpServiceName,		//服務名稱
  __in          DWORD dwDesiredAccess		//訪問許可權

hSCManager

SCM管理器的控制程式碼,即OpenSCManager開啟的控制程式碼。

lpSeviceName

已經建立的服務名稱。

dwDesiredAccess

訪問許可權。如果沒有特殊情況,一般使用SERVICE_ALL_ACCESS(0xF01FF)

 

控制服務

傳送一個控制碼去指定的服務,根據不同的控制碼操作服務。

BOOL WINAPI ControlService(
  __in          SC_HANDLE hService, 	//服務的控制程式碼
  __in          DWORD dwControl, 	//傳送的控制碼
  __out         LPSERVICE_STATUS lpServiceStatus 	//接收之前的服務狀態資訊
);

hService

服務的控制程式碼,即用CreateService建立或者使用OpenService開啟的控制程式碼。

dwControl

傳送給服務的控制碼,常用的有以下幾種:

Control code

Meaning

SERVICE_CONTROL_CONTINUE
0x00000003

針對暫停的服務發出繼續執行的指令。

SERVICE_CONTROL_PAUSE
0x00000002

暫停正在執行中的服務。

SERVICE_CONTROL_STOP
0x00000001

停止正在執行的服務。

lpServiceStatus

用於接收之前的服務狀態資訊。

 

刪除服務

標記刪除一個指定的服務。

BOOL WINAPI DeleteService(
  __in          SC_HANDLE hService	//服務控制程式碼
);

注意:必須停止服務並且關閉服務控制程式碼後,服務才會被刪除。



完整程式碼


// LoadNtDriver.cpp : 定義控制檯應用程式的入口點。
//

#include "stdafx.h"
#include <Windows.h>
#include "string.h"
#include "locale.h"

BOOL LoadNTDriver(TCHAR * lpszDriverName,TCHAR * lpszDriverPath)
{
	TCHAR szDriverPath[256] = {0};

	_tprintf(_T("載入驅動...\n"));

	//獲取完整的驅動路徑
	GetFullPathName(lpszDriverPath,sizeof(szDriverPath)/sizeof(szDriverPath[0]),szDriverPath,NULL);
//	_tprintf(szDriverPath);
	SC_HANDLE hSCM = NULL;
	SC_HANDLE hServie = NULL;

	hSCM = OpenSCManager(NULL,NULL,SC_MANAGER_ALL_ACCESS);
	if (!hSCM)
	{
		_tprintf(_T("OpenSCManger 失敗!,錯誤程式碼:%d\n"),GetLastError());
		return FALSE;
	}
	else
	{
		_tprintf(_T("OpenSCManger 成功!\n"));

		hServie = CreateService(hSCM,	
			lpszDriverName,				//服務的名稱
			lpszDriverName,				//顯示的名稱DisplayName
			SERVICE_ALL_ACCESS,			//訪問所有權
			SERVICE_KERNEL_DRIVER,		//表示載入的服務是驅動程式
			SERVICE_DEMAND_START,		//啟動型別為手動啟動
			SERVICE_ERROR_IGNORE,		//忽略錯誤
			szDriverPath,				//驅動檔名(保護路徑),登錄檔中的ImagePath值
			NULL,
			NULL,
			NULL,
			NULL,
			NULL);
		
		if (!hServie)
		{
			_tprintf(_T("CreateService 失敗!,錯誤程式碼:%d,嘗試使用OpenService\n"),GetLastError());
			hServie = OpenService(hSCM,lpszDriverName,SERVICE_ALL_ACCESS);
			if (!hServie)
			{	
				_tprintf(_T("OpenService 失敗!,錯誤程式碼:%d\n"),GetLastError());
				//清理
				CloseServiceHandle(hSCM);
				return FALSE;
			}
			else
			{
				_tprintf(_T("OpenService 成功!\n"));
			}
		}
		else
			_tprintf(_T("CreateService 成功!\n"));

		//開啟或建立服務成功後,開啟服務
		BOOL bRet = StartService(hServie,NULL,NULL);

		if (!bRet)
		{
			_tprintf(_T("StartService 失敗!,錯誤程式碼:%d\n"),GetLastError());
			bRet = FALSE;
		}
		else
		{
			bRet = TRUE;
			_tprintf(_T("載入驅動...成功!\n"));

		}
		//先關掉服務控制程式碼,再關掉服務管理器控制程式碼
		if (hServie)
		{
			CloseServiceHandle(hServie);
		}
		if (hSCM)
		{
			CloseServiceHandle(hSCM);
		}

		return bRet;

	} // hSCM

}

BOOL UnloadNTDriver(TCHAR * szSvrName)
{
	SC_HANDLE hSCM = NULL;		//SCManger
	SC_HANDLE hService = NULL;
	
	_tprintf(_T("解除安裝驅動...\n"));
	hSCM = OpenSCManager(NULL,NULL,SC_MANAGER_ALL_ACCESS);
	if (!hSCM) //開啟失敗
	{
		_tprintf(_T("OpenSCManger 失敗!,錯誤程式碼:%d\n"),GetLastError());
		return FALSE;
	}
	else
	{
		_tprintf(_T("OpenSCManager 成功!\n"));
		hService = OpenService(hSCM,szSvrName,SERVICE_ALL_ACCESS);
		
		if (!hService) //開啟服務失敗
		{
			_tprintf(_T("OpenService 失敗,錯誤程式碼:%d\n"),GetLastError());
			CloseServiceHandle(hSCM);
			return FALSE;
		}
		else
		{
			_tprintf(_T("OpenService 成功!\n"));

			SERVICE_STATUS SvrSta = {0};
			//停止服務。停止服務後,服務才能完成解除安裝。
			if (!ControlService(hService,SERVICE_CONTROL_STOP,&SvrSta))
			{
				_tprintf(_T("停止服務 失敗,錯誤程式碼:%d\n"),GetLastError());
			}
			else
			{
				_tprintf(_T("停止服務 成功!\n"));
			}

			BOOL bRet = FALSE;
			//動態解除安裝服務
			bRet = DeleteService(hService);
			if (!bRet)
			{
				//解除安裝失敗
				_tprintf(_T("DeleteService 失敗,錯誤程式碼:%d\n"),GetLastError());
			}
			else
			{
				//解除安裝成功
				_tprintf(_T("DeleteService 成功!\n"));

				_tprintf(_T("解除安裝驅動...成功!\n"));
			}
			//清理
			if (hService)
			{
				CloseServiceHandle(hService);
			}
			if (hSCM)
			{
				CloseServiceHandle(hSCM);
			}
			return bRet;
		} // hService
		
	}
}



int _tmain(int argc, _TCHAR* argv[])
{
	TCHAR szDriverPath[256];
	TCHAR szDriverName[25];

	setlocale(LC_ALL, "chs");//需要實現本地化,以實現中文正常輸出

	_tcscpy_s(szDriverPath,sizeof(szDriverPath)/sizeof(szDriverPath[0]),_T("Driver.sys"));
	_tcscpy_s(szDriverName,sizeof(szDriverPath)/sizeof(szDriverPath[0]),_T("TestDDK"));

	BOOL bRet = LoadNTDriver(szDriverName,szDriverPath);
	if (bRet)
	{
		printf("輸入任意鍵來解除安裝驅動程式.\n");
		getchar();
		UnloadNTDriver(szDriverName);
	}
	else
		_tprintf(_T("載入驅動...失敗!\n"));
	getchar();
	return 0;
}

【結果】

載入:




解除安裝:


相關文章