荔园在线

荔园之美,在春之萌芽,在夏之绽放,在秋之收获,在冬之沉淀

[回到开始] [上一篇][下一篇]


发信人: hellsolaris (qq), 信区: Security
标  题: Svchost.exe的原理(转载)
发信站: 荔园晨风BBS站 (Sat Oct 25 18:10:36 2003), 站内信件

原理


1. 多个服务共享一个Svchost.exe进程利与弊

windows 系统服务分为独立进程和共享进程两种,在windows NT时只有服务器管理器SCM(
Services.exe)有多个共享服务,随着系统内置服务的增加,在windows 2000中ms又把很
多服务做成共享方式,由svchost.exe启动。windows 2000一般有2个svchost进程,一个是
RPCSS(Remote Procedure Call)服务进程,另外一个则是由很多服务共享的一个svchos
t.exe。而在windows XP中,则一般有4个以上的svchost.exe服务进程,windows 2003 se
rver中则更多,可以看出把更多的系统内置服务以共享进程方式由svchost启动是ms的一个
趋势。这样做在一定程度上减少了系统资源的消耗,不过也带来一定的不稳定因素,因为
任何一个共享进程的服务因为错误退出进程就会导致整个进程中的所有服务都退出。另外
就是有一点安全隐患,首先要介绍一下svchost.exe的实现机制。


2. Svchost原理

Svchost本身只是作为服务宿主,并不实现任何服务功能,需要Svchost启动的服务以动态
链接库形式实现,在安装这些服务时,把服务的可执行程序指向svchost,启动这些服务时
由svchost调用相应服务的动态链接库来启动服务。

那么svchost如何知道某一服务是由哪个动态链接库负责呢?这不是由服务的可执行程序路
径中的参数部分提供的,而是服务在注册表中的参数设置的,注册表中服务下边有一个Pa
rameters子键其中的ServiceDll表明该服务由哪个动态链接库负责。并且所有这些服务动
态链接库都必须要导出一个ServiceMain()函数,用来处理服务任务。

例如rpcss(Remote Procedure Call)在注册表中的位置是 HKEY_LOCAL_MACHINE\SYSTEM
\CurrentControlSet\Services\RpcSs,它的参数子键Parameters里有这样一项:
"ServiceDll"=REG_EXPAND_SZ:"%SystemRoot%\system32\rpcss.dll"
当启动rpcss服务时,svchost就会调用rpcss.dll,并且执行其ServiceMain()函数执行具
体服务。

既然这些服务是使用共享进程方式由svchost启动的,为什么系统中会有多个svchost进程
呢?ms把这些服务分为几组,同组服务共享一个svchost进程,不同组服务使用多个svcho
st进程,组的区别是由服务的可执行程序后边的参数决定的。

例如rpcss在注册表中 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\RpcSs
 有这样一项:
"ImagePath"=REG_EXPAND_SZ:"%SystemRoot%\system32\svchost -k rpcss"
因此rpcss就属于rpcss组,这在服务管理控制台也可以看到。

svchost的所有组和组内的所有服务都在注册表的如下位置: HKEY_LOCAL_MACHINE\SOFTW
ARE\Microsoft\Windows NT\CurrentVersion\Svchost,例如windows 2000共有4组rpcss、
netsvcs、wugroup、BITSgroup,其中最多的就是netsvcs=REG_MULTI_SZ:EventSystem.Ia
s.Iprip.Irmon.Netman.Nwsapagent.Rasauto.\
Rasman.Remoteaccess.SENS.Sharedaccess.Tapisrv.Ntmssvc.wzcsvc..

在启动一个svchost.exe负责的服务时,服务管理器如果遇到可执行程序内容ImagePath已
经存在于服务管理器的映象库中,就不在启动第2个进程svchost,而是直接启动服务。这
样就实现了多个服务共享一个svchost进程。


3. Svchost代码

现在我们基本清楚svchost的原理了,但是要自己写一个DLL形式的服务,由svchost来启动
,仅有上边的信息还有些问题不是很清楚。比如我们在导出的ServiceMain()函数中接收的
参数是ANSI还是Unicode?我们是否需要调用RegisterServiceCtrlHandler和StartServic
eCtrlDispatcher来注册服务控制及调度函数?

这些问题要通过查看svchost代码获得。下边的代码是windows 2000+ service pack 4 的
svchost反汇编片段,可以看出svchost程序还是很简单的。

主函数首先调用ProcCommandLine()对命令行进行分析,获得要启动的服务组,然后调用S
vcHostOptions()查询该服务组的选项和服务组的所有服务,并使用一个数据结构 svcTab
le 来保存这些服务及其服务的DLL,然后调用PrepareSvcTable() 函数创建SERVICE_TABL
E_ENTRY 结构,把所有处理函数SERVICE_MAIN_FUNCTION 指向自己的一个函数FuncServic
eMain(),最后调用API StartServiceCtrlDispatcher() 注册这些服务的调度函数。

; =============================== Main Funcion ===============================
============
.text:010010B8 public start
.text:010010B8 start proc near
.text:010010B8 push esi
.text:010010B9 push edi
.text:010010BA push offset sub_1001EBA ; lpTopLevelExceptionFilter
.text:010010BF xor edi, edi
.text:010010C1 call dsetUnhandledExceptionFilter
.text:010010C7 push 1 ; uMode
.text:010010C9 call dsetErrorMode
.text:010010CF call ds:GetProcessHeap
.text:010010D5 push eax
.text:010010D6 call sub_1001142
.text:010010DB mov eax, offset dword_1003018
.text:010010E0 push offset unk_1003000 ; lpCriticalSection
.text:010010E5 mov dword_100301C, eax
.text:010010EA mov dword_1003018, eax
.text:010010EF call ds:InitializeCriticalSection
.text:010010F5 call ds:GetCommandLineW
.text:010010FB push eax ; lpString
.text:010010FC call ProcCommandLine
.text:01001101 mov esi, eax
.text:01001103 test esi, esi
.text:01001105 jz short lab_doservice
.text:01001107 push esi
.text:01001108 call SvcHostOptions
.text:0100110D call PrepareSvcTable
.text:01001112 mov edi, eax ; SERVICE_TABLE_ENTRY returned
.text:01001114 test edi, edi
.text:01001116 jz short loc_1001128
.text:01001118 mov eax, [esi+10h]
.text:0100111B test eax, eax
.text:0100111D jz short loc_1001128
.text:0100111F push dword ptr [esi+14h] ; dwCapabilities
.text:01001122 push eax ; int
.text:01001123 call InitializeSecurity
.text:01001128
.text:01001128 loc_1001128: ; CODE XREF: start+5Ej
.text:01001128 ; start+65j
.text:01001128 push esi ; lpMem
.text:01001129 call HeapFreeMem
.text:0100112E
.text:0100112E lab_doservice: ; CODE XREF: start+4Dj
.text:0100112E test edi, edi
.text:01001130 jz ExitProgram
.text:01001136 push edi ; lpServiceStartTable
.text:01001137 call dstartServiceCtrlDispatcherW
.text:0100113D jmp ExitProgram
.text:0100113D start endp
; =============================== Main Funcion end ===========================
================

由于svchost已经调用了StartServiceCtrlDispatcher来服务调度函数,因此我们在实现D
LL实现时就不用了,这主要是因为一个进程只能调用一次StartServiceCtrlDispatcher A
PI。但是需要用 RegisterServiceCtrlHandler 来注册响应控制请求的函数。最后我们的
DLL接收的都是unicode字符串。

由于这种服务启动后由svchost加载,不增加新的进程,只是svchost的一个DLL,而且一般
进行审计时都不会去HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVers
ion\Svchost 检查服务组是否变化,就算去检查,也不一定能发现异常,因此如果添加一
个这样的DLL后门,伪装的好,是比较隐蔽的。


4. 安装服务与设置
要通过svchost调用来启动的服务,就一定要在HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft
\Windows NT\CurrentVersion\Svchost下有该服务名,这可以通过如下方式来实现:
1) 添加一个新的服务组,在组里添加服务名
2) 在现有组里添加服务名
3) 直接使用现有服务组里的一个服务名,但本机没有安装的服务
4) 修改现有服务组里的现有服务,把它的ServiceDll指向自己

其中前两种可以被正常服务使用,如使用第1种方式,启动其服务要创建新的svchost进程
;第2种方式如果该组服务已经运行,安装后不能立刻启动服务,因为svchost启动后已经
把该组信息保存在内存里,并调用API StartServiceCtrlDispatcher() 为该组所有服务注
册了调度处理函数,新增加的服务不能再注册调度处理函数,需要重起计算机或者该组的
svchost进程。而后两种可能被后门使用,尤其是最后一种,没有添加服务,只是改了注册
表里一项设置,从服务管理控制台又看不出来,如果作为后门还是很隐蔽的。比如EventS
ystem服务,缺省是指向es.dll,如果把ServiceDll改为EventSystem.dll就很难发现。


因此服务的安装除了调用CreateService()创建服务之外,还需要设置服务的ServiceDll,
如果使用前2种还要设置svchost的注册表选项,在卸载时也最好删除增加的部分。

具体代码参见后边的附例(使用的是方法3)。

注: ImagePath 和ServiceDll 是ExpandString不是普通字符串。因此如果使用.reg文件
安装时要注意。


5. DLL服务实现
DLL程序的编写比较简单,只要实现一个ServiceMain()函数和一个服务控制程序,在Serv
iceMain()函数里用RegisterServiceCtrlHandler()注册服务控制程序,并设置服务的运行
状态就可以了。

另外,因为此种服务的安装除了正常的CreateService()之外,还要进行其他设置,因此最
好实现安装和卸载函数。

为了方便安装,实现的代码提供了InstallService()函数进行安装,这个函数可以接收服
务名作为参数(如果不提供参数,就使用缺省的iprip),如果要安装的服务不在svchost
的netsvcs组里安装就会失败;如果要安装的服务已经存在,安装也会失败;安装成功后程
序会配置服务的ServiceDll为当前Dll。提供的UninstallService()函数,可以删除任何函
数而没有进行任何检查。

为了方便使用rundll32.exe进行安装,还提供了RundllInstallA()和RundllUninstallA()
分别调用InstallService()及UninstallService()。因为rundll32.exe使用的函数原型是

void CALLBACK FunctionName(
HWND hwnd, // handle to owner window
HINSTANCE hinst, // instance handle for the DLL
LPTSTR lpCmdLine, // string the DLL will parse
int nCmdShow // show state
);
对应的命令行是rundll32 DllName,FunctionName [Arguments]

DLL服务本身只是创建一个进程,该程序命令行就是启动服务时提供的第一个参数,如果未
指定就使用缺省的svchostdll.exe。启动服务时如果提供第二个参数,创建的进程就是和
桌面交互的。

具体代码参见后边的附例8,源代码和DLL文件请到http://www.binglesite.net下载。


//main service process function
void __stdcall ServiceMain( int argc, wchar_t* argv[] );
//report service stat to the service control manager
int TellSCM( DWORD dwState, DWORD dwExitCode, DWORD dwProgress );
//service control handler, call back by service control manager
void __stdcall ServiceHandler( DWORD dwCommand );
//RealService just create a process
int RealService(char *cmd, int bInteract);

//Install this dll as a Service host by svchost.exe, service name is given by
caller
int InstallService(char *name);
//unInstall a Service, be CARE FOR call this to delete a service
int UninstallService(char *name);
//Install this dll as a Service host by svchost.exe, used by RUNDLL32.EXE to c
all
void CALLBACK RundllInstallA(HWND hwnd, HINSTANCE hinst, char *param, int nCmd
Show);
//unInstall a Service used by RUNDLL32.EXE to call, be CARE FOR call this to d
elete a service
void CALLBACK RundllUninstallA(HWND hwnd, HINSTANCE hinst, char *param, int nC
mdShow);

//output the debug infor into log file(or stderr if a console program call me)
 & DbgPrint
void OutputString( char *lpFmt, ... );

BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
hDll = hModule;
#ifdef _DEBUG
AllocConsole();
OutputString("SvcHostDLL: DllMain called DLL_PROCESS_ATTACH");
break;

case DLL_THREAD_ATTACH:
OutputString("SvcHostDLL: DllMain called DLL_THREAD_ATTACH");
case DLL_THREAD_DETACH:
OutputString("SvcHostDLL: DllMain called DLL_THREAD_DETACH");
case DLL_PROCESS_DETACH:
TellSCM( SERVICE_STOP_PENDING, 0, 0 );
Sleep(1500);
TellSCM( SERVICE_STOPPED, 0, 0 );
OutputString("SvcHostDLL: DllMain called DLL_PROCESS_DETACH");
#endif
break;
}

return TRUE;
}


void __stdcall ServiceMain( int argc, wchar_t* argv[] )
{
// DebugBreak();
char svcname[256];
strncpy(svcname, (char*)argv[0], sizeof svcname); //it's should be unicode, bu
t if it's ansi we do it well
wcstombs(svcname, argv[0], sizeof svcname);
OutputString("SvcHostDLL: ServiceMain(%d, %s) called", argc, svcname);

hSrv = RegisterServiceCtrlHandler( svcname, (LPHANDLER_FUNCTION)ServiceHandler
 );
if( hSrv == NULL )
{
OutputString("SvcHostDLL: RegisterServiceCtrlHandler %S failed", argv[0]);
return;
}else FreeConsole();

TellSCM( SERVICE_START_PENDING, 0, 1 );
TellSCM( SERVICE_RUNNING, 0, 0 );

// call Real Service function noew
if(argc > 1)
strncpy(svcname, (char*)argv[1], sizeof svcname),
wcstombs(svcname, argv[1], sizeof svcname);
RealService(argc > 1 ? svcname : MY_EXECUTE_NAME, argc > 2 ? 1 : 0);

do{
Sleep(10);//not quit until receive stop command, otherwise the service will st
op
}while(dwCurrState != SERVICE_STOP_PENDING && dwCurrState != SERVICE_STOPPED);


OutputString("SvcHostDLL: ServiceMain done");
return;
}

int TellSCM( DWORD dwState, DWORD dwExitCode, DWORD dwProgress )
{
SERVICE_STATUS srvStatus;
srvStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
srvStatus.dwCurrentState = dwCurrState = dwState;
srvStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONT
INUE | SERVICE_ACCEPT_SHUTDOWN;
srvStatus.dwWin32ExitCode = dwExitCode;
srvStatus.dwServiceSpecificExitCode = 0;
srvStatus.dwCheckPoint = dwProgress;
srvStatus.dwWaitHint = 3000;
return SetServiceStatus( hSrv, &srvStatus );
}

void __stdcall ServiceHandler( DWORD dwCommand )
{
// not really necessary because the service stops quickly
switch( dwCommand )
{
case SERVICE_CONTROL_STOP:
TellSCM( SERVICE_STOP_PENDING, 0, 1 );
OutputString("SvcHostDLL: ServiceHandler called SERVICE_CONTROL_STOP");
Sleep(10);
TellSCM( SERVICE_STOPPED, 0, 0 );
break;
case SERVICE_CONTROL_PAUSE:
TellSCM( SERVICE_PAUSE_PENDING, 0, 1 );
OutputString("SvcHostDLL: ServiceHandler called SERVICE_CONTROL_PAUSE");
TellSCM( SERVICE_PAUSED, 0, 0 );
break;
case SERVICE_CONTROL_CONTINUE:
TellSCM( SERVICE_CONTINUE_PENDING, 0, 1 );
OutputString("SvcHostDLL: ServiceHandler called SERVICE_CONTROL_CONTINUE");

TellSCM( SERVICE_RUNNING, 0, 0 );
break;
case SERVICE_CONTROL_INTERROGATE:
OutputString("SvcHostDLL: ServiceHandler called SERVICE_CONTROL_INTERROGATE");

TellSCM( dwCurrState, 0, 0 );
break;
case SERVICE_CONTROL_SHUTDOWN:
OutputString("SvcHostDLL: ServiceHandler called SERVICE_CONTROL_SHUTDOWN");

TellSCM( SERVICE_STOPPED, 0, 0 );
break;
}
}

return 0;
}


int InstallService(char *name)
{
// Open a handle to the SC Manager database.
int rc = 0;
HKEY hkRoot = HKEY_LOCAL_MACHINE, hkParam = 0;
SC_HANDLE hscm = NULL, schService = NULL;

try{
char buff[500];
char *svcname = DEFAULT_SERVICE;
if(name && name[0]) svcname = name;

//query svchost setting
char *ptr, *pSvchost = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Svcho
st";
rc = RegOpenKeyEx(hkRoot, pSvchost, 0, KEY_QUERY_VALUE, &hkRoot);
if(ERROR_SUCCESS != rc)
{
OutputString("RegOpenKeyEx(%s) KEY_QUERY_VALUE error %d.", pSvchost, rc);
throw "";
}

DWORD type, size = sizeof buff;
rc = RegQueryValueEx(hkRoot, "netsvcs", 0, &type, (unsigned char*)buff, &size)
;
RegCloseKey(hkRoot);
SetLastError(rc);
if(ERROR_SUCCESS != rc)
throw "RegQueryValueEx(Svchost\\netsvcs)";

for(ptr = buff; *ptr; ptr = strchr(ptr, 0)+1)
if(stricmp(ptr, svcname) == 0) break;

if(*ptr == 0)
{
OutputString("you specify service name not in Svchost\\netsvcs, must be one of
 following:");
for(ptr = buff; *ptr; ptr = strchr(ptr, 0)+1)
OutputString(" - %s", ptr);
throw "";
}

//install service
hscm = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
if (hscm == NULL)
throw "OpenSCManager()";

char *bin = "%SystemRoot%\\System32\\svchost.exe -k netsvcs";

schService = CreateService(
hscm, // SCManager database
svcname, // name of service
NULL, // service name to display
SERVICE_ALL_ACCESS, // desired access
SERVICE_WIN32_SHARE_PROCESS, // service type
SERVICE_AUTO_START, // start type
SERVICE_ERROR_NORMAL, // error control type
bin, // service's binary
NULL, // no load ordering group
NULL, // no tag identifier
NULL, // no dependencies
NULL, // LocalSystem account
NULL); // no password

if (schService == NULL)
{
OutputString("CreateService(%s) error %d", svcname, rc = GetLastError());
throw "";
}

rc = RegCreateKey(hkRoot, "Parameters", &hkParam);
SetLastError(rc);
if(ERROR_SUCCESS != rc)
throw "RegCreateKey(Parameters)";

if(!GetModuleFileName(HMODULE(hDll), buff, sizeof buff))
throw "GetModuleFileName() get dll path";

rc = RegSetValueEx(hkParam, "ServiceDll", 0, REG_EXPAND_SZ, (unsigned char*)bu
ff, strlen(buff)+1);
SetLastError(rc);
if(ERROR_SUCCESS != rc)
throw "RegSetValueEx(ServiceDll)";

OutputString("Config service %s ok.", svcname);
}catch(char *str)
{
if(str && str[0])
{
rc = GetLastError();
OutputString("%s error %d", str, rc);
}
}

RegCloseKey(hkRoot);
RegCloseKey(hkParam);
CloseServiceHandle(schService);
CloseServiceHandle(hscm);

return rc;
}

/*
used to install by rundll32.exe
Platform SDK: Tools - Rundll32
The Run DLL utility (Rundll32.exe) included in Windows enables you to call fun
ctions exported from a 32-bit DLL. These functions must have the following syn
tax:
*/
void CALLBACK RundllInstallA(
HWND hwnd, // handle to owner window
HINSTANCE hinst, // instance handle for the DLL
char *param, // string the DLL will parse
int nCmdShow // show state
)
{
InstallService(param);
}


int UninstallService(char *name)
{
int rc = 0;
SC_HANDLE schService;
SC_HANDLE hscm;

__try{
hscm = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
if (hscm == NULL)
{
OutputString("OpenSCManager() error %d", rc = GetLastError() );
return rc;
}

char *svcname = DEFAULT_SERVICE;
if(name && name[0]) svcname = name;

schService = OpenService(hscm, svcname, DELETE);
if (schService == NULL)
{
OutputString("OpenService(%s) error %d", svcname, rc = GetLastError() );
return rc;
}

if (!DeleteService(schService) )
{
OutputString("OpenService(%s) error %d", svcname, rc = GetLastError() );
return rc;
}

OutputString("DeleteService(%s) SUCCESS.", svcname);
}__except(1)
{
OutputString("Exception Catched 0x%X", GetExceptionCode());
}

CloseServiceHandle(schService);
CloseServiceHandle(hscm);
return rc;
}

/*
used to uninstall by rundll32.exe
Platform SDK: Tools - Rundll32
The Run DLL utility (Rundll32.exe) included in Windows enables you to call fun
ctions exported from a 32-bit DLL. These functions must have the following syn
tax:
*/
void CALLBACK RundllUninstallA(
HWND hwnd, // handle to owner window
HINSTANCE hinst, // instance handle for the DLL
char *param, // string the DLL will parse
int nCmdShow // show state
)
{
UninstallService(param);
}

//output the debug infor into log file & DbgPrint
void OutputString( char *lpFmt, ... )
{
char buff[1024];
va_list arglist;
va_start( arglist, lpFmt );
_vsnprintf( buff, sizeof buff, lpFmt, arglist );
va_end( arglist );

DWORD len;
HANDLE herr = GetStdHandle(STD_OUTPUT_HANDLE);
if(herr != INVALID_HANDLE_VALUE)
{
WriteFile(herr, buff, strlen(buff), &len, NULL);
WriteFile(herr, "\r\n", 2, &len, NULL);
}else
{
FILE *fp = fopen("SvcHost.DLL.log", "a");
if(fp)
{
char date[20], time[20];
fprintf(fp, "%s %s - %s\n", _strdate(date), _strtime(time), buff);
if(!stderr) fclose(fp);
}
}

OutputDebugString(buff);
}
--
个人主页:http://psnow.7u7.net/
http://bbs.nju.edu.cn/file/ozn.jpg
http://bbs.nju.edu.cn/file/1037518954wbk7342.gif

--
※ 来源:·荔园晨风BBS站 bbs.szu.edu.cn·[FROM: 192.168.44.223]


[回到开始] [上一篇][下一篇]

荔园在线首页 友情链接:深圳大学 深大招生 荔园晨风BBS S-Term软件 网络书店