用匯編寫系統服務程序 |
本想寫一篇關于服務的文章,結果搜了一遍以后,發現發表于天極網上的一篇文章《Win32程序設計之服務》把要說的大部分東西都說了,為了不做重復勞動,所以在這里首先引用這篇文章來說明服務程序的原理,在后面的補充一節中再補上一些內容和一個用匯編實現服務的源代碼。 ====================================================================== 第一部分:轉載《Win32程序設計之服務》 發表于:yesky 編譯:QQ新人類 原始位置:http://www.chinabyte.com/20010528/181751.sHtml ====================================================================== 每個操作系統都需要有在后臺執行任務的方法,無論是誰正在使用這部機器,這些任務都可以繼續運行,后臺任務可以處理各種重要的服務,包括系統的或者用戶的。例如,一個信使服務可以監控網絡,并且在接收到另一臺機子的信息時,可以顯示一個對話框。一個發送和接收傳真的應用需要在啟動的時候運行,并且不斷地監控負責傳真的modem,看有沒有傳真進來。一個家庭的或者辦公室的安全程序,用來控制一件檢測設備時,它需要不時地查詢傳感器,并且在適當的時候響應它。所有這些任務都需要CPU時間來執行它們,不過由于它們需要的CPU時間很少,因此可以放在后臺而不影響用戶使用系統。 在MS-DOS中,后臺的任務是通過TSR(Terminate and Stay Resident)程序來處理的。這些程序經由autoexec.bat文件開始。在UNIX中,后臺任務是通過Daemons來處理的。在每次啟動UNIX的過程中,你都可以看到操作系統啟動一些任務,例如定時的程序(Cron)和Finger的daemons,然后才可以讓首個用戶登錄。在Windows NT中,后臺的任務被稱為服務。服務可在每次NT啟動的時候運行,并且不管是誰登陸,都會一直運行下去。 Windows NT的服務都是通過一般的可執行程序實現的,不同的是,它遵循內部的一個特定協議來設計,以便它們能夠與服務控制治理器(SCM,Service Control Manager)進行正確的交互。在這篇文章中,你將學習到如何在Windows NT中創建和安裝簡單的Win32服務。一旦你懂得了這個簡單的服務,你要建立自己的服務也不難了,因為所有的服務,不論是如何地復雜,都必須包含有同樣基本的SCM接口代碼。只要符合SCM的要求,其實為服務設計的可執行文件和一般的程序并沒有多少的區別。 無論是對于編程者或者系統治理員,了解NT的服務如何工作都是很重要的。編程者就不必說了,因為他們要創建自己的服務,而對于系統治理員,也是同樣重要的。因為后臺的任務可以是很危險的。MS-DOS和Macintosh系統都是一個病毒的溫床,因為它們在安全性方面先天不足,它們都可以答應任何人或者程序在任何時間創建后臺的任務。Windows NT和UNIX系統是較安全的,因為只有系統治理員才可以為系統增加后臺的任務,不過,假如系統治理員加入了一個破壞性的后臺程序,就它就可以為所欲為了。因此系統治理員要了解Windows NT服務的技巧和權限設置,就可以避免加入有潛在危險的后臺任務。 基本的概念 服務有兩種不同的形式。驅動器服務使用驅動器協議,讓NT可以與特定的硬件進行通信。另一個是Win32服務,通過一般的Win32 API來實現后臺任務。這篇文章的重點是談Win32服務,因為它們更為常見,而且創建起來也很輕易。任何的NT編程者通過使用一般的NT SDK(或者Visual C++),并且可以用治理員的身份訪問一臺NT機器,都可以實現和安裝自己的Win32服務。假如你想創建一些在Windows NT啟動時就運行的程序,并且要求它會在系統中一直運行,你就要使用Win32的服務。 在NT中,服務通過控制面板進行治理。在控制面板中,你會發現有一個服務的圖標,打開它你會看到所有Win32服務的清單。在那里你可以開始、停止、暫停和繼續某個服務。你按下其中的啟動按鈕后,就會出現一個對話框,你可以修改啟動操作以及服務使用的默認帳號。一個服務可以在系統啟動的時候自動運行,也可以被完全禁止。或者設置為手動執行。在手動的時候,用戶還可以設置啟動的參數。要對服務中的項目作修改的話,你需要以一個治理員或者超級用戶的身份登錄。 Windows NT自帶有一些預裝的任務,用來處理諸如網絡信使服務的操作或者使用“at”命令定時執行的操作,以及分布的RPC命名。在你創建自己的服務時,你必須執行一個獨立的安裝步驟,以將服務的信息插入到服務治理工具的列表中,這些信息包括有新服務的名字、執行文件的名字和啟動的類型等,都會寫入到注冊表中,這樣在機器下次啟動的時候,SCM就會得到新服務的相關信息。 創建一個新的服務 執行服務的程序也是一個EXE文件,不過它必須符合某些特定的規范,以便可以與SCM進行正確的交互。微軟很細致地設計了函數調用的流程,你必須遵循這些流程,否則你的服務就不能工作。具體的規定如下所列。你可以在Win32編程者的參考指南中找到以下涉及的函數,這些資料在SDK的Win32在線幫助或者Visual C++都有: .服務的代碼必須要有一個一般的main或者WinMain函數。這個函數應該會馬上調用StartServiceCrtlDispatcher函數。通過調用這個函數,你可以讓SCM得到ServiceMain函數的指針,這樣在SCM要啟動該服務時,就可以調用它 .在SCM要啟動服務的時候,就會調用ServiceMain函數。例如,假如治理員在服務治理器中按下啟動的按鈕,SCM就會在一個獨立的線程中執行ServiceMain函數。ServiceMain應該調用RegisterServiceCtrlHandler函數,這樣可以注冊一個Handler函數,以便SCM對服務進行控制。Handler函數的名字可以是任意的,不過它會在Handler下的文檔中列出來。RegisterServiceCtrlHandler函數會返回一個句柄,在服務需要發送狀態信息給SCM時,可以通過該句柄進行。 .ServiceMain函數也必須啟動做該服務實際工作的線程。在服務停止前,ServiceMain函數是不應該有返回的。當它返回的時候,服務已經停止了。 .Handler函數包含了一個switch語句,用來分析由SCM傳送過來的請求。默認的情況,SCM可以發送以下任何的的控制常數: SERVICE_CONTROL_STOP - 要服務停止 SERVICE_CONTROL_PAUSE - 要服務暫停 SERVICE_CONTROL_CONTINUE - 要服務繼續 SERVICE_CONTROL_INTERROGATE - 要服務馬上報告它的狀態 SERVICE_CONTROL_SHUTDOWN - 告訴服務即將關機 也可以創建自定義的常數(值在128到255之間),并且通過SCM發送給服務。 假如你創建的EXE包括有以上提到的main、ServiceMain和Handler函數,以及執行服務自身任務的線程函數,那么你的服務程序設計就完成了。以下的圖總結了這些不同的函數和SCM之間的交互: 在本文的最后還有幾段程序的列表,其中列表一為我們展示了一個可能是最簡單的服務。該服務只發出"嘟"的響聲。默認的狀態下,它每兩秒響一次。你可以通過啟動的參數來修改發聲的間隔。這個服務挺完整,它可以正確響應SCM傳來的每個控制信號。因此,這個程序可作為你創建自己服務的一個很好的模板。 main函數通過調用StartServiceCtrlDispatcher來注冊ServiceMain函數。注冊的操作使用了一個SERVICE_TABLE_ENTRY結構的數組。在這個例子中,該程序只包含了一個服務,因此在表中只會有一個項目。不過,對于一個EXE文件,可以創建幾個任務,這樣在表中就會有幾項,以識別不同的ServiceMain函數。在調用StartServiceCtrlDispatcher之前,可在main函數中放入初始化的代碼,不過這些代碼必須在少于30秒內完成,否則,SCM會認為某些地方出錯而終止服務。 在自動或者手動啟動服務時,將會調用ServiceMain函數。ServiceMain函數將包含有以下的步驟: 1.它馬上調用RegisterServiceCtrlHandler來注冊Handler函數,作為SCM控制該服務的Handler函數 2.然后它將調用SendStatusToSCM函數,將當前的進程通報給SCM。第四個參數是一個“click count”值,在程序每次更新狀態時,它的值就會增加。SCM和其它的程序可以根據click count的值來知道初始化期間進行的處理。最后的參數是"wait hint",是用來告訴SCM在click count下次更新前,它需要等待的時間(以毫秒計算)。 3.ServiceMain接著會創建一個事情,該事件在函數的底部使用,可讓它一直運行,直到SCM發出一個STOP的請求。 4.接著,ServiceMain檢查啟動的參數。參數可在用戶手動啟動服務時,通過服務治理工具中的啟動參數傳送過來。這些參數以一個argv形式的數組進入ServiceMain函數。 5.假如你的服務需要處理其它初始化的任務,它們應該放在這一步,在調用InitService之前。 6.ServiceMain函數接著調用InitService函數,這將啟動線程并做服務的真正工作。假如該調用成功,SverviceMain將會通知SCM服務已經成功啟動。 7.ServiceMain將調用WaitForSingleObject,用來等待terminateEvent事件對象被設置。這個對象通過Handler函數設置,一旦它被設置,ServiceMain將調用終止的函數來做清除的工作,然后就返回并停止服務。 你從上面可以看到,在這個函數中并沒有多少靈活的地方。除了第5步外,你必須按步執行以上提到的任務,否則服務將不可以正確啟動。 終止函數清除所有打開的句柄,并且發送一個狀態的信息給SCM,告訴它服務現已停止。 在SCM要暫停、繼續、詢問或者停止服務時,它就會調用Handler函數。要停止服務,Handler設置terminateEvent,這樣做會導致ServiceMain(它會以一個獨立線程的形式執行)終止并且返回。一旦ServiceMain返回,服務就停止了。 SendStatusToSCM 函數負責發送服務的當前狀態給SCM。 在需要啟動服務線程時,ServiceMain就會調用InitService函數。該函數調用CreateThread來為服務創建一個新的線程。 ServiceThread函數包含有該服務真正要做的工作。在這個例子中,該線程包含有一個無限的循環,以一個預定義的時間間隔發出響聲。在你創建自己的服務時,你可以在該線程中放入任何的代碼,調用Win32函數或者你自己的函數。 安裝和移除服務 為了使用上面提到的發響聲服務,你必須安裝它。安裝可讓SCM知道這個服務,并且讓SCM將它加入到控制面板中的服務列表中。表單二的代碼展示了如何安裝一個服務。 表單2先通過OpenSCManager函數打開一個到SCM的連接。在OpenSCManager的調用中,你必須指定你要做的東西,以便SCM可以驗證這個行為。假如你登錄的帳號沒有足夠的權限,該調用將返回NULL。 CreateService的調用是真正用來裝入新服務的。它使用OpenSCManager返回至SCM的指針、名字、標簽和命令行中的EXE文件,還有一些標準的參數值。使用SERVICE_WIN32_OWN_PROCESS表明該服務的EXE文件只包含有一個服務,而SERVICE_DEMAND_START則表明該服務是手動而不是自動啟動的。使用命令行安裝程序的一個典型格式如下: install BeepService "Beeper" c:/winnt/beep.exe 第一個參數是SCM內部使用的服務名字。這個名字也用在以后移除服務。第二個參數是一個標識符,即服務治理中顯示的名字。第三個參數指出該服務的執行文件的路徑。在你安裝服務后,可在控制面板的服務治理中啟動它。假如有錯,可在Win32 API的在線文檔中查出錯誤代碼的含義。 要移除服務,你要按列表3的步驟進行。它首先打開一個到SCM的連接,然后使用OpenService函數打開一個與服務的連接。列表3中接著查詢服務來看它是否現已停止了。假如不是的話,就會停止它。DeleteService 用來從控制面板中移除服務,移除操作的典型格式如下: remove BeepService 需要的話,你也可以在馬上重新安裝服務。 結論 服務對于Windows NT是很重要的一部分,因為它可讓你對操作系統進行擴展。使用列表1的代碼作為一個模板,你將會發現要建立一個自己的新服務是非常簡單的。 列表1 實現可能是最簡單NT服務的代碼 //*************************************************************** // From the book "Win32 System Services: The Heart of Windows NT" // by Marshall Brain // Published by Prentice Hall file:// // This code implements the simplest possible service. // It beeps every 2 seconds, or at a user specified interval. file://*************************************************************** // beepserv.cpp #include #include #include #include #define DEFAULT_BEEP_DELAY 2000 // Global variables // The name of the service char *SERVICE_NAME = "BeepService"; // Event used to hold ServiceMain from completing HANDLE terminateEvent = NULL; // Handle used to communicate status info with // the SCM. Created by RegisterServiceCtrlHandler SERVICE_STATUS_HANDLE serviceStatusHandle; // The beep interval in ms. int beepDelay = DEFAULT_BEEP_DELAY; // Flags holding current state of service BOOL pauseService = FALSE; BOOL runningService = FALSE; // Thread for the actual work HANDLE threadHandle = 0; void ErrorHandler(char *s, DWord err) { cout << s << endl; cout << "Error number: " << err << endl; ExitProcess(err); } // This function consolidates the activities of // updating the service status with // SetServiceStatus BOOL SendStatusToSCM (DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwServiceSpecificExitCode, DWORD dwCheckPoint, DWORD dwWaitHint) { BOOL sUCcess; SERVICE_STATUS serviceStatus; // Fill in all of the SERVICE_STATUS fields serviceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; serviceStatus.dwCurrentState = dwCurrentState; // If in the process of doing something, then accept // no control events, else accept anything if (dwCurrentState == SERVICE_START_PENDING) serviceStatus.dwControlsAccepted = 0; else serviceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE | SERVICE_ACCEPT_SHUTDOWN; // if a specific exit code is defined, set up // the win32 exit code properly if (dwServiceSpecificExitCode == 0) serviceStatus.dwWin32ExitCode = dwWin32ExitCode; else serviceStatus.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR; serviceStatus.dwServiceSpecificExitCode =dwServiceSpecificExitCode; serviceStatus.dwCheckPoint = dwCheckPoint; serviceStatus.dwWaitHint = dwWaitHint; // Pass the status record to the SCM success = SetServiceStatus (serviceStatusHandle, &serviceStatus); return success; } DWORD ServiceThread(LPDWORD param) { while (1) { Beep(200,200); Sleep(beepDelay); } return 0; } // Initializes the service by starting its thread BOOL InitService() { DWORD id; // Start the service's thread threadHandle = CreateThread(0, 0,(LPTHREAD_START_ROUTINE) ServiceThread,0, 0, &id); if (threadHandle==0) return FALSE; else { runningService = TRUE; return TRUE; } } // Dispatches events received from the SCM VOID Handler (DWORD controlCode) { DWORD currentState = 0; BOOL success; switch(controlCode) { // There is no START option because // ServiceMain gets called on a start // Stop the service case SERVICE_CONTROL_STOP: // Tell the SCM what's happening success = SendStatusToSCM(SERVICE_STOP_PENDING,NO_ERROR, 0, 1, 5000); runningService=FALSE; // Set the event that is holding ServiceMain // so that ServiceMain can return SetEvent(terminateEvent); return; // Pause the service case SERVICE_CONTROL_PAUSE: if (runningService && !pauseService) { // Tell the SCM what's happening success = SendStatusToSCM(SERVICE_PAUSE_PENDING, NO_ERROR, 0, 1, 1000); pauseService = TRUE; SuspendThread(threadHandle); currentState = SERVICE_PAUSED; } break; // Resume from a pause case SERVICE_CONTROL_CONTINUE: if (runningService && pauseService) { // Tell the SCM what's happening success = SendStatusToSCM(SERVICE_CONTINUE_PENDING, NO_ERROR, 0, 1, 1000); pauseService=FALSE; ResumeThread(threadHandle); currentState = SERVICE_RUNNING; } break; // Update current status case SERVICE_CONTROL_INTERROGATE: // it will fall to bottom and send status break; // Do nothing in a shutdown. Could do cleanup // here but it must be very quick. case SERVICE_CONTROL_SHUTDOWN: return; default: break; } SendStatusToSCM(currentState, NO_ERROR, 0, 0, 0); } // Handle an error from ServiceMain by cleaning up // and telling SCM that the service didn't start. VOID terminate(DWORD error) { // if terminateEvent has been created, close it. if (terminateEvent) CloseHandle(terminateEvent); // Send a message to the scm to tell about stopage if (serviceStatusHandle) SendStatusToSCM(SERVICE_STOPPED, error,0, 0, 0); // If the thread has started, kill it off if (threadHandle) CloseHandle(threadHandle); // Do not need to close serviceStatusHandle } // ServiceMain is called when the SCM wants to // start the service. When it returns, the service // has stopped. It therefore waits on an event // just before the end of the function, and // that event gets set when it is time to stop. // It also returns on any error because the // service cannot start if there is an eror. VOID ServiceMain(DWORD argc, LPTSTR *argv) { BOOL success; // immediately call Registration function serviceStatusHandle = RegisterServiceCtrlHandler( SERVICE_NAME, (LPHANDLER_FUNCTION)Handler); if (!serviceStatusHandle) {terminate(GetLastError()); return;} // Notify SCM of progress success = SendStatusToSCM(SERVICE_START_PENDING, NO_ERROR, 0, 1, 5000); if (!success) {terminate(GetLastError()); return;} // create the termination event terminateEvent = CreateEvent (0, TRUE, FALSE, 0); if (!terminateEvent) {terminate(GetLastError()); return;} // Notify SCM of progress success = SendStatusToSCM(SERVICE_START_PENDING, NO_ERROR, 0, 2, 1000); if (!success) {terminate(GetLastError()); return;} // Check for startup params if (argc == 2) { int temp = atoi(argv[1]); if (temp < 1000) beepDelay = DEFAULT_BEEP_DELAY; else beepDelay = temp; } // Notify SCM of progress success = SendStatusToSCM(SERVICE_START_PENDING, NO_ERROR, 0, 3, 5000); if (!success) {terminate(GetLastError()); return;} // Start the service itself success = InitService(); if (!success) {terminate(GetLastError()); return;} // The service is now running. // Notify SCM of progress success = SendStatusToSCM(SERVICE_RUNNING, NO_ERROR, 0, 0, 0); if (!success) {terminate(GetLastError()); return;} // Wait for stop signal, and then terminate WaitForSingleObject (terminateEvent, INFINITE); terminate(0); } VOID main(VOID) { SERVICE_TABLE_ENTRY serviceTable[] = { { SERVICE_NAME, (LPSERVICE_MAIN_FUNCTION) ServiceMain}, { NULL, NULL } }; BOOL success; // Register with the SCM success = StartServiceCtrlDispatcher(serviceTable); if (!success) ErrorHandler("In StartServiceCtrlDispatcher", GetLastError()); } 列表2 安裝NT服務的代碼 file://*************************************************************** // From the book "Win32 System Services: The Heart of Windows NT" // by Marshall Brain // Published by Prentice Hall file:// // This code installs a service. file://*************************************************************** // install.cpp #include #include void ErrorHandler(char *s, DWORD err) { cout << s << endl; cout << "Error number: " << err << endl; ExitProcess(err); } void main(int argc, char *argv[]) { SC_HANDLE newService, scm; if (argc != 4) { cout << "Usage:/n"; cout << " install service_name / service_label executable/n"; cout << " service_name is the / name used internally by the SCM/n"; cout << " service_label is the / name that appears in the Services applet/n"; cout << " (for multiple / words, put them in double quotes)/n"; cout << " executable is the / full path to the EXE/n/n"; return; } // open a connection to the SCM scm = OpenSCManager(0, 0, SC_MANAGER_CREATE_SERVICE); if (!scm) ErrorHandler("In OpenScManager", GetLastError()); // Install the new service newService = CreateService( scm, argv[1], // eg "beep_srv" argv[2], // eg "Beep Service" SERVICE_ALL_access, SERVICE_WIN32_OWN_PROCESS, SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL, argv[3], // eg "c:/winnt/xxx.exe" 0, 0, 0, 0, 0); if (!newService) ErrorHandler("In CreateService", GetLastError()); else cout << "Service installed/n"; // clean up CloseServiceHandle(newService); CloseServiceHandle(scm); } 列表3 移除NT服務的代碼 file://*************************************************************** // From the book "Win32 System Services: The Heart of Windows NT" // by Marshall Brain // Published by Prentice Hall file:// // This code removes a service from the Services applet in the // Control Panel. file://*************************************************************** // remove.cpp #include #include void ErrorHandler(char *s, DWORD err) { cout << s << endl; cout << "Error number: " << err << endl; ExitProcess(err); } void main(int argc, char *argv[]) { SC_HANDLE service, scm; BOOL success; SERVICE_STATUS status; if (argc != 2) { cout << "Usage:/n remove service_name/n"; return; } // Open a connection to the SCM scm = OpenSCManager(0, 0, SC_MANAGER_CREATE_SERVICE); if (!scm) ErrorHandler("In OpenScManager", GetLastError()); // Get the service's handle service = OpenService(scm, argv[1], SERVICE_ALL_ACCESS | DELETE); if (!service) ErrorHandler("In OpenService", GetLastError()); // Stop the service if necessary success = QueryServiceStatus(service, &status); if (!success) ErrorHandler("In QueryServiceStatus", GetLastError()); if (status.dwCurrentState != SERVICE_STOPPED) { cout << "Stopping service.../n"; success = ControlService(service, SERVICE_CONTROL_STOP, &status); if (!success) ErrorHandler("In ControlService", GetLastError()); } // Remove the service success = DeleteService(service); if (success) cout << "Service removed/n"; else ErrorHandler("In DeleteService", GetLastError()); // Clean up CloseServiceHandle(service); CloseServiceHandle(scm); } ====================================================================== 第二部分:用Win32匯編實現系統服務 by:羅云彬 好了,經過了上面這篇文章,大家對服務應該有個初步的了解了,但是要注重分辨文章中提及的 API 名稱和程序自己的模塊名稱,比如CreateService是一個 API,而 ServiceMain 是程序中一個子程序的名稱,千萬不要把子程序名當系統的 API 去使用了 :) 在這部分的補充中,我再歸納一下實現各種功能所使用的 API 函數名和使用的步驟,并提供一個匯編源程序例子來供大家參考,大家也可以下載整個源代碼包并修改使用。 一般來說,整個服務程序系統由兩個單獨的程序組成:服務程序和服務控制程序,經過了上面的介紹,大家一定明白這兩個程序應該由哪些部分組成了: 服務程序的結構 服務程序是被用來實現服務功能的部分,它由相對獨立的3個部分組成: 1. 程序入口 -> 調用StartServiceCtrlDispatcher(注冊服務主程序) 2. 服務主程序(被SCM調用)-> 調用RegisterServiceCtrlHandler(注冊控制handler)-> 調用 SetServiceStatus 設置服務的正確狀態 -> 執行服務的功能代碼 -> 需要結束服務的時候則返回。 3. 控制Handler -> 設置內部標志以控制第2部分服務主程序中的代碼走向,并調用 SetServiceStatus 更新服務的狀態。 可見,服務程序必須用到的 API 只有 StartServiceCtrlDispatcher、RegisterServiceCtrlHandler和SetServiceStatus ,其余的大部分代碼是執行本身的功能模塊。這里有一個服務程序的簡單例子,實現的是每秒種讓喇叭發聲一次的功能。 下面的源代碼是 Service.asm 程序: ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; Windows NT 服務程序模板 ; by 羅云彬, http://asm.yeah.net ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 服務端程序 Ver 1.0 ; ; 2002.06.20 ----- 第1版 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> .386 .model flat, stdcall option casemap :none ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; Include 文件定義 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> include windows.inc include user32.inc includelib user32.lib include kernel32.inc includelib kernel32.lib include AdvApi32.inc includelib AdvApi32.lib ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 數據段 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> .data? stSS SERVICE_STATUS <> ;服務的狀態 hSS dd ? ;服務的狀態句柄 dwOption dd ? F_STOP equ 0001h ;停止服務 include ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 代碼段 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> .code ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 服務控制程序 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> _ProcHandler proc _dwControl pushad mov eax,_dwControl .if eax == SERVICE_CONTROL_STOP or dwOption,F_STOP mov stSS.dwCurrentState,SERVICE_STOPPED invoke SetServiceStatus,hSS,addr stSS .elseif eax == SERVICE_CONTROL_INTERROGATE invoke SetServiceStatus,hSS,addr stSS .endif popad ret _ProcHandler endp ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 服務主程序 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> _ServiceMain proc _dwArgc,_lpszArgv pushad invoke RegisterServiceCtrlHandler,addr szServiceName,offset _ProcHandler mov hSS,eax mov stSS.dwServiceType,SERVICE_WIN32_OWN_PROCESS or SERVICE_INTERACTIVE_PROCESS mov stSS.dwCurrentState,SERVICE_START_PENDING mov stSS.dwControlsAccepted,SERVICE_ACCEPT_STOP mov stSS.dwWin32ExitCode,NO_ERROR invoke SetServiceStatus,hSS,addr stSS ;******************************************************************** ; 假如初始化代碼比較多,那么需要首先把狀態設置為 pending,等完成以后 ; 再設置為 Running。(在這里加入初始化代碼) ;******************************************************************** mov stSS.dwCurrentState,SERVICE_RUNNING invoke SetServiceStatus,hSS,addr stSS ;******************************************************************** ; 服務的具體執行代碼 ; 在這里是每隔1秒種讓喇叭發聲 ;******************************************************************** .repeat invoke MessageBeep,-1 invoke Sleep,1000 .until dwOption & F_STOP popad ret _ServiceMain endp ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 主程序 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> _WinMain proc local @stSTE[2]:SERVICE_TABLE_ENTRY invoke RtlZeroMemory,addr @stSTE,sizeof @stSTE mov @stSTE[0].lpServiceName,offset szServiceName mov @stSTE[0].lpServiceProc,offset _ServiceMain invoke StartServiceCtrlDispatcher,addr @stSTE ret _WinMain endp ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> start: invoke _WinMain invoke ExitProcess,NULL ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> end start 這里是服務名稱的定義文件 Define.inc,這個文件已經在前面用include語句包含進來: ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 公用文本信息 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> .const szServiceEXE db 'Service.exe',0 ;在這里定義運行服務的 exe 文件名 szServiceName db 'ServiceTemplate',0 ;在這里定義服務的名稱 szDisplayName db 'Service Template for test',0 ;在這里定義服務顯示的名稱 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 要注重的是,服務程序部分的代碼并沒有什么靈活性可言,它必須按照這樣的步驟來實現,否則Windows就不認為它是一個服務程序了,另外,需要補充說明的是: 1. StartServiceCtrlDispatcher 函數被調用后并不馬上返回,這時系統會去調用服務主程序(上面的_ServiceMain子程序),只有當服務主程序返回的時候,函數才會返回,這時主程序繼續執行并執行 ExitProcess 終止執行。 2. 從第1點可以發現:在服務程序中,服務主程序的退出就意味著服務自行終止了,所以假如程序創建了多個線程來執行功能代碼的話,在線程被創建以后,主程序還需要在這里等待,否則子程序返回以后服務進程就結束了,這些線程又怎么能繼續執行呢? 服務控制程序的結構 好了,有了服務程序以后,還需要把服務正確安裝才能讓它運行,這個工作就是服務控制程序的事情了,一般來說,服務控制程序的結構并沒有什么非凡的要求,它只是一個普通的Win32可執行文件而已,在服務控制程序中可以使用下面的 API 來進行各種控制,這些 API的具體語法請查看 Win32 API 手冊: 1. 在進行和服務相關的各種操作之前,必須使用OpenSCManager來聯系服務治理器,并從這個 API 得到一個 服務治理器的句柄 hSCM。 2. 操作某個具體的服務前,需要使用OpenService來打開服務,并得到服務的句柄hService,接下來就可以使用這個句柄來控制服務(當然,安裝服務的時候并不需要此步驟,這時服務還不存在呢,又如何打開?) 3. 安裝服務:使用 CreateService 函數,這個函數將服務程序的可執行文件在SCM中注冊,注重:“注冊”并不意味著執行,它只是在SCM的數據庫中登記一條包含可執行文件名、啟動方式、服務名稱等信息的數據而已,執行這個函數以后,在控制面板的服務一欄中就可以看到服務列表中已經出現這個服務了,由于這僅僅是“注冊”,所以系統并不檢查可執行文件的合法性,也就是說,即使注冊一個不存在的可執行文件,注冊操作仍然會成功,但這樣以后要啟動服務的時候就會失敗。注冊過一次以后,SCM數據庫中的信息會被自動保存,所以“注冊”操作是一次性的過程,并不需要在每次啟動服務的時候都去注冊一遍。 4. 啟動服務:當安裝了服務以后,可以用StartService函數來啟動服務,這時系統才真正根據注冊的文件名去執行服務程序。 5. 刪除服務:刪除服務是安裝服務的逆過程,也就是在SCM的數據庫中將服務程序的信息刪除,刪除服務使用DeleteService函數。 6. 服務的控制:服務的暫停、停止等工作都可以由服務控制函數ControlService來完成,需要的僅僅是指定不同的參數而已。 7. 服務狀態的查詢可以由QueryServiceStatus函數來完成。 8. 句柄的關閉:和上面的操作相關的句柄(包括SCM句柄和服務句柄)在不再使用以后需要用CloseServiceHandle函數關閉。 下面的幾個子程序舉例說明了安裝服務、刪除服務、啟動服務和停止服務的操作方法,注重子程序中用到的hSCM已經在其他地方首先調用OpenSCManager函數獲取了。更具體的源代碼請查看文章附帶的源代碼包中的Control.asm文件。 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 安裝服務 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> _InstallService proc local @hService,@dwReturn mov @dwReturn,FALSE ;******************************************************************** ; 創建服務 ;******************************************************************** invoke CreateService,hSCM,addr szServiceName,addr szDisplayName,/ SERVICE_ALL_ACCESS,SERVICE_WIN32_OWN_PROCESS or SERVICE_INTERACTIVE_PROCESS,/ SERVICE_AUTO_START,SERVICE_ERROR_NORMAL,addr szServiceFile,/ NULL,NULL,NULL,NULL,NULL .if ! eax invoke GetLastError .if eax == ERROR_DUP_NAME || eax == ERROR_SERVICE_EXISTS mov @dwReturn,TRUE .else mov @dwReturn,FALSE .endif jmp @F .endif mov @hService,eax mov @dwReturn,TRUE ;******************************************************************** invoke CloseServiceHandle,@hService @@: mov eax,@dwReturn ret _InstallService endp ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 刪除服務 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> _DeleteService proc local @hService,@dwReturn local @stStatus:SERVICE_STATUS mov @dwReturn,FALSE ;******************************************************************** ; 打開服務 ;******************************************************************** invoke OpenService,hSCM,addr szServiceName,SERVICE_ALL_ACCESS .if ! eax jmp @F .endif mov @hService,eax ;******************************************************************** ; 停止服務并刪除服務 ;******************************************************************** call _StopService invoke DeleteService,@hService .if eax mov @dwReturn,TRUE .endif invoke CloseServiceHandle,@hService @@: mov eax,@dwReturn ret _DeleteService endp ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 啟動服務 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> _StartService proc local @hService invoke OpenService,hSCM,addr szServiceName,SERVICE_START .if eax mov @hService,eax invoke StartService,@hService,0,NULL .if eax mov eax,TRUE .else mov eax,FALSE .endif push eax invoke CloseServiceHandle,@hService pop eax .else mov eax,FALSE .endif ret _StartService endp ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 停止服務 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> _StopService proc local @hService local @stStatus:SERVICE_STATUS invoke OpenService,hSCM,addr szServiceName,SERVICE_ALL_ACCESS .if eax mov @hService,eax invoke QueryServiceStatus,@hService,addr @stStatus invoke ControlService,@hService,SERVICE_CONTROL_STOP,addr @stStatus .if eax mov eax,TRUE .else mov eax,FALSE .endif push eax invoke CloseServiceHandle,@hService pop eax .else mov eax,FALSE .endif ret _StopService endp ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 合并服務程序和服務控制程序 在上面的例子中,將服務程序和服務控制程序分開是為了更好地從概念上說明兩者的區別,但在實際應用中,還是有很多的程序將兩個部分合在同一個可執行文件中實現,這時兩部分僅僅是在物理上被放在同一個exe文件中,在結構上還是無不相干,完全獨立的。 雖然兩個部分的實現方式是完全不同的,但只要在程序的入口處就按照不同的情況去執行不同部分的代碼,將它們合在一個文件中還是可行的,其要害就是分辨程序被執行的時候是作為服務程序執行還是被作為服務控制程序執行的,常見的方法有: 1. 用命令行參數來分辨,使用CreateService函數安裝服務的時候遞交文件名字符串時可以在文件名后面跟參數,這樣程序入口處用GetCommandLine函數獲取命令行參數,檢測到特定參數則按照服務方式執行,否則按照服務控制程序方式運行。 2. 用運行路徑分辨,一些病毒程序初始化的時候將自己的副本拷貝到Windows目錄下并將這個副本注冊為服務,所以到它檢測到當前路徑是Windows目錄的時候,就按照服務方式運行。 |
新聞熱點
疑難解答
圖片精選