網絡安全編程:服務控制管理器實例
幾乎每一種操作系統都有一種在系統啟動時啟動的進程機制,這種機制不會依賴于用戶的交互。在Windows下,類似的機制被稱為服務或Windows服務。服務是一種程序類型,它在后臺運行,服務程序通常可以在本地和通過網絡為用戶提供一些功能,服務在操作系統啟動時就會隨之啟動的程序。服務程序可能是EXE程序,具有其單獨的進程,也有可能是DLL文件依附于某個進程(比如svchost.exe),更有可能是SYS文件而處于系統的內核之中。由于服務所處的核心地位、啟動方式等因素,它也是反病毒軟件與惡意軟件的“兵家必爭之地”。對于研究系統安全來說則非常重要。
01 如何查看系統服務
在Windows下,有很多服務是跟隨操作系統一起啟動的,具體有哪些服務是跟隨操作系統一起啟動的呢?如何查看呢?其實非常簡單。在“我的電腦”上單擊鼠標右鍵,然后在彈出的菜單上選擇“管理”,打開“計算機管理”工具,單擊左面樹形列表的“服務和應用程序”會打開子列表,選擇“服務”,則在右側出現服務項列表。較為簡單的方法是直接在“運行”窗口中輸入“services.msc”,打開服務管理器。服務管理器主要用于顯示系統中已經存在的應用程序服務,顯示對服務的描述,還可以控制服務的啟動狀態和啟動方式。服務管理器如圖1所示。
圖1 Windows下的服務管理程序
在圖1顯示的服務列表中,只能查看Win32應用程序的服務,無法查看關于驅動程序的服務。可以借助于其他一些工具來查看驅動程序級別的服務,圖2使用SREng來查看驅動程序相關的服務列表。
圖2 使用SREng查看驅動程序服務列表
接下來會編寫一個類似的程序,既可以查看應用程序服務列表,也可以查看驅動程序服務列表。編寫完成后的程序界面如圖3所示。
圖3 服務管理程序界面
編寫的服務管理程序既可以查看“Win32服務應用程序”,也可以查看“驅動服務程序”,并且可以對它們的運行狀態進行簡單的控制。這里開發的服務控制管理器使用MFC的對話框,其中用到了CListCtrl控件。現在就開始打造一個屬于自己的服務控制管理器。
02 服務控制管理器的實現
服務控制管理器的開發過程與注冊表啟動管理器的開發過程比較類似,主要也是枚舉服務并顯示到列表控件中。對服務狀態的控制,是通過服務相關的API函數來完成的。首先來編寫代碼,希望大家能夠掌握服務相關的API函數。在代碼的后面,會對開發服務管理器涉及的API進行相應的解釋。
學習API函數的使用,MSDN是最好的老師,詳細、透徹、權威。在編程的道路上,要不斷通過閱讀別人的代碼來提高自己的編程能力,就需要自己來掌握陌生的API函數,那時一定要想起查閱MSDN。
1. 服務的類型
服務控制管理器的界面都已經熟悉了,界面的布局可以按照自己的方式進行調整。在枚舉服務的時候,將“Win32應用程序服務”和“驅動程序服務”分開枚舉,這樣有助于對各種服務的了解。
枚舉這兩類服務的主要差別在于調用EnumServicesStatus()函數時為其傳遞的第二個參數。如果枚舉“Win32應用程序服務”,那么傳遞的參數為SERVICE_WIN32;如果枚舉“驅動程序服務”,那么傳遞的參數為SERVICE_DRIVER。這兩個參數其實是系統定義的宏,該宏定義在WinNt.h頭文件中,具體定義如下:
- #define SERVICE_DRIVER (SERVICE_KERNEL_DRIVER | \
- SERVICE_FILE_SYSTEM_DRIVER | \
- SERVICE_RECOGNIZER_DRIVER)
- #define SERVICE_WIN32 (SERVICE_WIN32_OWN_PROCESS | \
- SERVICE_WIN32_SHARE_PROCESS)
SERVICE_DRIVER 和 SERVICE_WIN32 是其他宏的組合,而那些宏又有具體的值。下面解釋一下其他宏的含義。
SERVICE_DRIVER 宏由 3 個宏組成,具體如下:
- #define SERVICE_KERNEL_DRIVER 0x00000001 // 設備驅動程序
- #define SERVICE_FILE_SYSTEM_DRIVER 0x00000002 // 內核模式文件系統驅動程序
- #define SERVICE_RECOGNIZER_DRIVER 0x00000008 // 文件系統識別器驅動程序
SERVICE_WIN32 宏由兩個宏組成,具體如下:
- #define SERVICE_WIN32_OWN_PROCESS 0x00000010 // 獨占一個進程的服務
- #define SERVICE_WIN32_SHARE_PROCESS 0x00000020 // 與其他服務共享一個進程的服務
如果想要枚舉全部類型的服務,那么使用SERVICE_TYPE_ALL宏即可,該宏的定義如下:
- #define SERVICE_TYPE_ALL (SERVICE_WIN32 | \
- SERVICE_ADAPTER | \
- SERVICE_DRIVER | \
- SERVICE_INTERACTIVE_PROCESS)
2. 服務的枚舉函數
服務的枚舉所使用的API函數是EnumServicesStatus(),該函數中需要指定枚舉的類型分別是SERVICE_DRIVER和SERVICE_WIN32。
具體來看服務枚舉的函數,代碼如下:
- VOID CManageServicesDlg::ShowServiceList(DWORD dwServiceType)
- {
- m_ServiceList.DeleteAllItems();
- // 打開服務管理器
- SC_HANDLE hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
- if ( NULL == hSCM )
- {
- AfxMessageBox("OpenSCManger Error!");
- return ;
- }
- DWORD ServiceCount = 0;
- DWORD dwSize = 0;
- LPENUM_SERVICE_STATUS lpInfo;
- // 第一次調用
- BOOL bRet = EnumServicesStatus(hSCM,
- dwServiceType, SERVICE_STATE_ALL,
- NULL, 0, &dwSize,
- &ServiceCount, NULL);
- // 由于沒有給定接收服務列表的緩沖區,這里必定會調用失敗
- // 失敗的原因是 ERROR_MORE_DATA
- // 說明需要更大的緩沖區來保存數據
- if ( !bRet && GetLastError() == ERROR_MORE_DATA )
- {
- // 分配緩沖區,保存服務列表
- lpInfo = (LPENUM_SERVICE_STATUS)(new BYTE[dwSize]);
- bRet = EnumServicesStatus(hSCM,
- dwServiceType, SERVICE_STATE_ALL,
- (LPENUM_SERVICE_STATUS)lpInfo,
- dwSize, &dwSize,
- &ServiceCount, NULL);
- if ( !bRet )
- {
- CloseServiceHandle(hSCM);
- return ;
- }
- //逐個獲取數據,添加至列表控件
- for ( DWORD i = 0; i < ServiceCount; i ++)
- {
- CString str;
- m_ServiceList.InsertItem(i, lpInfo[i].lpServiceName);
- m_ServiceList.SetItemText(i, 1, lpInfo[i].lpDisplayName);
- switch ( lpInfo[i].ServiceStatus.dwCurrentState )
- {
- case SERVICE_PAUSED:
- {
- m_ServiceList.SetItemText(i, 2, "暫停");
- break;
- }
- case SERVICE_STOPPED:
- {
- m_ServiceList.SetItemText(i, 2, "停止");
- break;
- }
- case SERVICE_RUNNING:
- {
- m_ServiceList.SetItemText(i, 2, "運行");
- break;
- }
- default:
- {
- m_ServiceList.SetItemText(i, 2, "其他");
- }
- }
- }
- // 釋放申請的空間
- delete lpInfo;
- }
- // 關閉服務管理器句柄
- CloseServiceHandle(hSCM);
- }
該函數有一個參數,用來指明枚舉類型是“Win32應用程序服務”,還是“驅動程序服務”。該函數的默認參數為“Win32應用程序服務”,該函數的定義如下:
- VOID ShowServiceList(DWORD dwServiceType = SERVICE_WIN32);
3. 枚舉服務相關API函數解釋
(1)打開和關閉服務管理器
打開服務管理器的函數定義如下:
- SC_HANDLE OpenSCManager(
- LPCTSTR lpMachineName, // computer name
- LPCTSTR lpDatabaseName, // SCM database name
- DWORD dwDesiredAccess // access type
- );
參數說明如下。
lpMachineName:指向欲打開服務控制管理器數據庫的目標主機名,本機則設置為 NULL。
lpDatabaseName:指向目標主機 SCM 數據庫名字的字符串。
dwDesiredAccess:指定對 SCM 數據庫的訪問權限。
該函數調用成功,返回一個 SCM 句柄,否則返回 NULL。
SCM是服務控制管理器的意思,它是系統服務的一個組成部分,跟開發的軟件不是一個概念。
關閉服務句柄的函數定義如下:
- BOOL CloseServiceHandle(
- SC_HANDLE hSCObject // handle to service or SCM object
- );
該函數用來關閉由OpenSCManager()和OpenService()打開的句柄。
(2)服務的枚舉函數
枚舉服務的函數定義如下:
- BOOL EnumServicesStatus(
- SC_HANDLE hSCManager, // handle to SCM database
- DWORD dwServiceType, // service type
- DWORD dwServiceState, // service state
- LPENUM_SERVICE_STATUS lpServices, // status buffer
- DWORD cbBufSize, // size of status buffer
- LPDWORD pcbBytesNeeded, // buffer size needed
- LPDWORD lpServicesReturned, // number of entries returned
- LPDWORD lpResumeHandle // next entry
- );
參數說明如下。
hSCManager:OpenSCManager()函數返回的句柄。
dwServiceType:指定枚舉的服務類型,也就是自定義函數的參數。
dwServiceState:枚舉指定狀態的服務。
lpServices:指向 ENUM_SERVICE_STATUS 類型的指針。
cbBufSize:指定緩沖區的大小。
pcbBytesNeeded:返回實際使用的內存空間大小。
lpServicesReturned:返回枚舉服務的個數。
lpResumeHandle:返回枚舉是否成功。
ENUM_SERVICE_STATUS 結構體的定義如下:
- typedef struct _ENUM_SERVICE_STATUS {
- LPTSTR lpServiceName;
- LPTSTR lpDisplayName;
- SERVICE_STATUS ServiceStatus;
- } ENUM_SERVICE_STATUS, *LPENUM_SERVICE_STATUS;
SERVICE_STATUS 結構體的定義如下:
- typedef struct _SERVICE_STATUS {
- DWORD dwServiceType;
- DWORD dwCurrentState;
- DWORD dwControlsAccepted;
- DWORD dwWin32ExitCode;
- DWORD dwServiceSpecificExitCode;
- DWORD dwCheckPoint;
- DWORD dwWaitHint;
- } SERVICE_STATUS, *LPSERVICE_STATUS;
代碼中兩次調用EnumServicesStatus()函數。第1次沒有傳遞第4個和第5個參數,使得函數返回FALSE,用GetLastError()得到錯誤的原因為ERROR_MORE_DATA。這時,第6個參數pcbBytesNeeded返回實際需要使用的內存大小,這樣可以通過new動態申請所需的堆空間。以這種方式來獲取實際所需緩沖區大小的情況是比較多的,請大家一定要理解。
4. 服務的啟動與停止
對服務的操作只介紹兩種,一種是啟動服務,另一種是停止服務,也就是改變服務的狀態。經常會用到停止服務的操作,因為系統中有很多用不到的服務,但是它仍然會隨著系統的啟動而啟動,這樣既會影響到系統的啟動速度,也會占用寶貴的系統資源。因此,沒有用的系統服務最好將其停止(其實真正停止服務是改變它的啟動狀態,而不是這里介紹的運行狀態)。
啟動服務的代碼如下:
- void CManageServicesDlg::OnBtnStart()
- {
- // TODO: Add your control notification handler code here
- // 選中服務的索引
- POSITION Pos = m_ServiceList.GetFirstSelectedItemPosition();
- int nSelect = -1;
- while ( Pos )
- {
- nSelect = m_ServiceList.GetNextSelectedItem(Pos);
- }
- if ( -1 == nSelect )
- {
- AfxMessageBox("請選擇要啟動的服務");
- return ;
- }
- // 獲取選中服務的服務名
- char szServiceName[MAXBYTE] = { 0 };
- m_ServiceList.GetItemText(nSelect, 0, szServiceName, MAXBYTE);
- SC_HANDLE hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
- if ( NULL == hSCM )
- {
- AfxMessageBox("OpenSCManager Error");
- return ;
- }
- // 打開指定的服務
- SC_HANDLE hSCService = OpenService(hSCM, szServiceName, SERVICE_ALL_ACCESS);
- // 啟動服務
- BOOL bRet = StartService(hSCService, 0, NULL);
- if ( bRet == TRUE )
- {
- m_ServiceList.SetItemText(nSelect, 2, "運行");
- }
- else
- {
- AfxMessageBox("啟動失敗!");
- }
- CloseServiceHandle(hSCService);
- CloseServiceHandle(hSCM);
- }
停止服務的代碼如下:
- void CManageServicesDlg::OnBtnStop()
- {
- // TODO: Add your control notification handler code here
- // 選中服務的索引
- // 此部分操作與啟動服務相同,為節省篇幅,此處省略
- // ……
- SC_HANDLE hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
- if ( NULL == hSCM )
- {
- AfxMessageBox("OpenSCManager Error");
- return ;
- }
- // 打開指定的服務
- SC_HANDLE hSCService = OpenService(hSCM, szServiceName, SERVICE_ALL_ACCESS);
- SERVICE_STATUS ServiceStatus;
- // 停止服務
- BOOL bRet = ControlService(hSCService, SERVICE_CONTROL_STOP, &ServiceStatus);
- if ( bRet == TRUE )
- {
- m_ServiceList.SetItemText(nSelect, 2, "停止");
- }
- else
- {
- AfxMessageBox("停止失敗!");
- }
- CloseServiceHandle(hSCService);
- CloseServiceHandle(hSCM);
- }
5. 啟動與停止服務相關API函數解釋
打開指定服務的函數定義如下:
- SC_HANDLE OpenService(
- SC_HANDLE hSCManager, // handle to SCM database
- LPCTSTR lpServiceName, // service name
- DWORD dwDesiredAccess // access
- );
參數說明如下。
hSCManager:指定由 OpenSCManager()函數打開的服務句柄。
lpServiceName:指定要打開的服務的名稱。
dwDesiredAccess:對要打開服務的訪問權限,這里為了方便,指定為 SC_MANAGER_ALL_ACCESS。
啟動服務的函數定義如下:
- BOOL StartService(
- SC_HANDLE hService, // handle to service
- DWORD dwNumServiceArgs, // number of arguments
- LPCTSTR *lpServiceArgVectors // array of arguments
- );
參數說明如下。
hService:指定要啟動服務的句柄,該句柄由 OpenService()返回。
dwNumServiceArgs:指向啟動服務所需的參數個數。
lpServiceArgVectors:指向啟動服務的參數。
停止服務的函數定義如下:
- BOOL ControlService(
- SC_HANDLE hService, // handle to service
- DWORD dwControl, // control code
- LPSERVICE_STATUS lpServiceStatus // status information
- );
參數說明如下。
hService:指定一個由 OpenService()打開的服務句柄。
dwControl:指定要發送的控制碼。
lpServiceStatus:返回服務的狀態。
ControlService()可以對服務進行多種控制操作,每種控制操作對應一種控制碼。當要停止服務時,使用的控制碼為 SERVICE_CONTROL_STOP。一定要注意,停止服務不要想當然的使用 StopService()這樣的函數,因為沒有這個 API 函數。