基于Svchost的服务介绍

windows 系统服务分为独立进程和共享进程两种:

A、独立进程服务,一般都是一个EXE文件,里面有WinMain、ServiceMain和MessageHandler,用于安装、卸载、处理服务消息、提供服务等。
比如:
Windows Search
C:\Windows\system32\SearchIndexer.exe /Embedding
Windows Installer
C:\Windows\system32\msiexec.exe /V

B、共享进程服务,一般都是一个DLL文件,里面有DllMain、ServiceMain和MessageHandler,用于处理服务消息、提供服务。共享进程服务,一般用Svchost作为宿主进程,宿主进程只是一个框架,该进程通过启动参数+注册表定位到服务DLL,通过调用DLL中的函数,来实现业务逻辑。
比如:
Background Intelligent Transfer Service的启动参数为
C:\Windows\System32\svchost.exe -k netsvcs
Windows Firewall的启动参数为
C:\Windows\system32\svchost.exe -k LocalServiceNoNetwork

独立进程都会指定是哪个EXE,但svchost是如何知道启动netsvcs或LocalServiceNoNetwork要用哪个DLL呢?答案是在注册表中:
比如Background Intelligent Transfer Service的服务名为BITS,在下面路径中
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\BITS\Parameters\ServiceDll
指定了调用的dll为%SystemRoot%\System32\qmgr.dll
Windows Firewall的服务名为MpsSvc,在下面路径中
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\MpsSvc\Parameters\ServiceDll
指定了调用的dll为%SystemRoot%\system32\mpssvc.dll

既然这些服务是使用共享进程方式由svchost启动的,为什么系统中会有多个svchost进程呢?微软把这些服务进行了分组,同组服务共享一个svchost进程,不同组服务使用多个svchost进程,组的区别是由服务的可执行程序后边的参数决定的。
因此Background Intelligent Transfer Service的分组为netsvcs,Windows Firewall的分组为LocalServiceNoNetwork,这两个服务属于两个组,在两个不同的svchost进程中。

可以在下面的位置,找到svchost的所有组和组内的所有服务:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Svchost
在我的Win7系统中,有如下分组:
AxInstSVGroup, DcomLaunch, LocalService, LocalServiceAndNoImpersonation, LocalServiceNetworkRestricted, LocalServiceNoNetwork, LocalServicePeerNet, LocalSystemNetworkRestricted, NetworkService, NetworkServiceAndNoImpersonation, NetworkServiceNetworkRestricted, PeerDist, RPCSS, WbioSvcGroup, WerSvcGroup, apphost, bthsvcs, defragsvc, iissvcs, imgsvc, netsvcs, regsvc, sdrsvc, secsvcs, swprv, termsvcs, wcssvc
而在netsvcs分组下,有
AeLookupSvc, AppMgmt, AudioSrv, BITS, CertPropSvc, FastUserSwitchingCompatibility, Ias, Irmon, LogonHours, NWCWorkstation, Nla, Ntmssvc, Nwsapagent, PCAudit, Rasauto, Rasman, Remoteaccess, SCPolicySvc, SENS, SRService, SessionEnv, Sharedaccess, ShellHWDetection, Tapisrv, TermService, WmdmPmSp, Wmi, gpsvc, helpsvc, iphlpsvc, lanmanserver, msiscsi, schedule, uploadmgr, winmgmt, wuauserv

这样,服务启动时,ServiceManager会查看该分组的svchost的进程是否启动,如果已经启动,则不需要新开启svchost进程,只需要直接加载服务即可。

那如何增加一个新的分组,和一个新的服务呢?那就需要修改注册表啦:

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Svchost]
"NeoTest"=hex(7):53,00,68,00,54,00,73,00,74,00,00,00,00,00

重启后才会有效。
调试的时候,应该自己新建一个分类,否则有了问题,将其他系统服务搞挂就不好了。
自己新建分类,不爽的时候,可以直接杀掉改svchost进程。

PS:在XP时代的头几年,伪装Svchost进程的恶意软件还是很多的,但现在杀毒软件都会重点处理这块儿内容,也就慢慢淡出了。

svchost一般来说,运行的都是windows内部服务,不给第三方开放。
但如果我们要自己写一个这样的服务,那实现这样的服务需要什么呢?
1、Dll入口函数:DllMain,dll入口函数
2、服务入口函数:ServiceMain,Service入口函数
3、rundll32安装服务函数:RundllInstall,生成注册表信息
4、rundll32卸载服务函数:RundllUninstall,删除注册表信息
5、将自己添加为一个新的svchost服务

比如下面的例子,将自己注册为一个NeoTest的SvchostTest服务:
Win7+VS2012u4+Unicode+MDd+Use Standard Windows Libraries

svchosttest.h

#include <windows.h>

//服务主进程入口函数
void WINAPI ServiceMain(
	DWORD dwArgc, 
	LPTSTR* lpszArgv);

//服务消息处理函数
void WINAPI ServiceHandlerEx(
	DWORD dwControl,
	DWORD dwEventType,
	LPVOID lpEventData,
	LPVOID lpContext);

//更新服务状态
int UpdateSvcStatus(
	DWORD dwState, 
	DWORD dwExitCode, 
	DWORD dwProgress );

//实际业务数据处理
int StartProcessing(
	TCHAR *cmd, 
	int bInteract);

//安装服务
int InstallService(TCHAR *name);

//卸载服务
int UninstallService(TCHAR *name);

//Rundll接口
void CALLBACK RundllInstall(
	HWND hwnd, 
	HINSTANCE hinst, 
	TCHAR *param, 
	int nCmdShow);

//Rundll接口
void CALLBACK RundllUninstall(
	HWND hwnd, 
	HINSTANCE hinst, 
	TCHAR *param, 
	int nCmdShow);

//输出调试内容
void OutputString(TCHAR *lpFmt, ... );

svchosttest.cpp

#include <stdio.h>
#include <time.h>
#include <assert.h>
#include <tchar.h>
#include "svchosttest.h"

#define DEFAULT_SERVICE_NAME TEXT("SvchostTest")
#define DEFAULT_SERVICE_GROUP TEXT("NeoTest")
#define SERVICE_START_CMD TEXT("%SystemRoot%\\System32\\svchost.exe -k NeoTest")
#define SERVICE_DLL TEXT("ServiceDll")
#define MY_EXECUTE_NAME TEXT("Notepad.exe")
#define WAIT_INTERVAL (100)

//dll模块句柄
HANDLE hDll = NULL;
//描述服务状态的服务句柄和状态
SERVICE_STATUS_HANDLE hSrv;
DWORD dwCurrState;
//关键区用于同步服务状态
CRITICAL_SECTION criticalStatus;

//dll入口函数
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(TEXT("SvcHostDLL: DllMain called DLL_PROCESS_ATTACH"));
		break;

	case DLL_THREAD_ATTACH:
		OutputString(TEXT("SvcHostDLL: DllMain called DLL_THREAD_ATTACH"));
		break;

	case DLL_THREAD_DETACH:
		OutputString(TEXT("SvcHostDLL: DllMain called DLL_THREAD_DETACH"));
		break;

	case DLL_PROCESS_DETACH:
		UpdateSvcStatus( SERVICE_STOP_PENDING, 0, 0 );
		Sleep(WAIT_INTERVAL);
		UpdateSvcStatus( SERVICE_STOPPED, 0, 0 );
		OutputString(TEXT("SvcHostDLL: DllMain called DLL_PROCESS_DETACH"));
#endif
		break;
	}

	return TRUE;
}

//服务入口函数
void WINAPI ServiceMain(
	DWORD dwArgc, 
	LPTSTR *lpszArgv)
{
	//For Debug
	//直接DebugBreak会导致服务退出
	//DebugBreak();
	//下面也不好用
	//DWORD pId = GetCurrentProcessId();
	//DebugActiveProcess(pId);
	//手动附加到进程还可以的
#ifdef _DEBUG
	while(!IsDebuggerPresent())
	{
		Sleep(WAIT_INTERVAL);
	}
	DebugBreak();
#endif

	//使用ANSI编译时,字符集与Schost.exe传递过来的不同,
	//从而导致lpszArgv里面只有服务名称的第一个字母,而不是服务全名,需要自行处理
	//使用Unicode编译时,就不会有这个问题
	//TCHAR svcname[256];
	//_tcsncpy(svcname, (TCHAR*)lpszArgv[0], _countof(svcname));
	//OutputString(TEXT("SvcHostDLL: ServiceMain(%d, %s) called"), dwArgc, svcname);
	TCHAR svcname[256];
	_tcsncpy(svcname, DEFAULT_SERVICE_NAME, _countof(svcname));

	hSrv = RegisterServiceCtrlHandlerEx(svcname, (LPHANDLER_FUNCTION_EX)ServiceHandlerEx, NULL );
	if( hSrv == NULL )
	{
		OutputString(TEXT("SvcHostDLL: RegisterServiceCtrlHandler %S failed"), lpszArgv[0]);
#ifdef _DEBUG
		FreeConsole();
#endif
		return;
	}

	InitializeCriticalSection(&criticalStatus);
	UpdateSvcStatus( SERVICE_START_PENDING, 0, 0 );
	OutputString(TEXT("SvcHostDLL: ServiceMain() Start Pending"));
	Sleep(WAIT_INTERVAL);
	UpdateSvcStatus( SERVICE_RUNNING, 0, 0 );
	OutputString(TEXT("SvcHostDLL: ServiceMain() Running"));

	//开启服务
	TCHAR svccmd[256];
	if(dwArgc > 1)
	{
		_tcsncpy(svccmd, (TCHAR*)lpszArgv[1], _countof(svccmd));
	}
	else
	{
		_tcsncpy(svccmd, MY_EXECUTE_NAME, _countof(svccmd));
	}
	int bInteract = dwArgc > 2 ? 1 : 0;
	StartProcessing(svccmd, bInteract);

	do{
		//这里也可以用其它同步方式处理
		Sleep(WAIT_INTERVAL);
	}while(dwCurrState != SERVICE_STOP_PENDING && dwCurrState != SERVICE_STOPPED);

	OutputString(TEXT("SvcHostDLL: ServiceMain done"));

#ifdef _DEBUG
	FreeConsole();
#endif

	return;
}

//调用SetServiceStatus设置服务状态
int UpdateSvcStatus( DWORD dwState, DWORD dwExitCode, DWORD dwProgress )
{
	EnterCriticalSection(&criticalStatus);
	SERVICE_STATUS srvStatus;
	srvStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
	srvStatus.dwCurrentState = dwCurrState = dwState;
	srvStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE | SERVICE_ACCEPT_SHUTDOWN;
	srvStatus.dwWin32ExitCode = dwExitCode;
	srvStatus.dwServiceSpecificExitCode = NO_ERROR;
	srvStatus.dwCheckPoint = dwProgress;
	srvStatus.dwWaitHint = 3000;
	BOOL bRet = SetServiceStatus( hSrv, &srvStatus );
	LeaveCriticalSection(&criticalStatus);
	return bRet; 
}

//服务消息处理函数
void WINAPI ServiceHandlerEx(   
	DWORD dwControl,
	DWORD dwEventType,
	LPVOID lpEventData,
	LPVOID lpContext)
{
	switch( dwControl )
	{
	case SERVICE_CONTROL_STOP:
		UpdateSvcStatus( SERVICE_STOP_PENDING, 0, 1 );
		OutputString(TEXT("SvcHostDLL: ServiceHandler called SERVICE_CONTROL_STOP"));
		Sleep(WAIT_INTERVAL);
		UpdateSvcStatus( SERVICE_STOPPED, 0, 0 );
		break;
	case SERVICE_CONTROL_PAUSE:
		UpdateSvcStatus( SERVICE_PAUSE_PENDING, 0, 1 );
		OutputString(TEXT("SvcHostDLL: ServiceHandler called SERVICE_CONTROL_PAUSE"));
		UpdateSvcStatus( SERVICE_PAUSED, 0, 0 );
		break;
	case SERVICE_CONTROL_CONTINUE:
		UpdateSvcStatus( SERVICE_CONTINUE_PENDING, 0, 1 );
		OutputString(TEXT("SvcHostDLL: ServiceHandler called SERVICE_CONTROL_CONTINUE"));
		UpdateSvcStatus( SERVICE_RUNNING, 0, 0 );
		break;
	case SERVICE_CONTROL_INTERROGATE:
		OutputString(TEXT("SvcHostDLL: ServiceHandler called SERVICE_CONTROL_INTERROGATE"));
		UpdateSvcStatus( dwCurrState, 0, 0 );
		break;
	case SERVICE_CONTROL_SHUTDOWN:
		OutputString(TEXT("SvcHostDLL: ServiceHandler called SERVICE_CONTROL_SHUTDOWN"));
		UpdateSvcStatus( SERVICE_STOPPED, 0, 0 );
		break;
	}
}

//服务主进程
//这里只是启动了一个进程,就退出了
//如果要在这里实现业务逻辑的话,需要自己做好同步
int StartProcessing(TCHAR *cmd, int bInteract)
{
	OutputString(TEXT("SvcHostDLL: RealService called '%s' %s"), cmd, bInteract ? "Interact" : "");
	STARTUPINFO si = {0};
	si.cb = sizeof(si);
	//winlogon初始桌面:
	//应用程序桌面(\Windows\WinSta0\Default)
	//Winlogon桌面(\Windows\WinSta0\Winlogon)
	//屏幕保护桌面(\Windows\WinSta0\ScrenSaver)
	//相关API:
	//OpenWindowStation,SetProcessWindowStation
	//OpenDesktop,SetThreadDesktop,ConnectToDesktop,SwitchDesktop
	if(bInteract) si.lpDesktop = TEXT("WinSta0\\Default");
	PROCESS_INFORMATION pi;
	if(!CreateProcess(NULL, cmd, NULL, NULL, false, 0, NULL, NULL, &si, &pi))
	{
		OutputString(TEXT("SvcHostDLL: CreateProcess(%s) error:%d"), cmd, GetLastError());
	}
	else
	{
		OutputString(TEXT("SvcHostDLL: CreateProcess(%s) to %d"), cmd, pi.dwProcessId);
	}

	return 0;
}

//写注册表安装服务
//HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Svchost\\netsvcs
//HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\服务名称\\Parameters\\ServiceDll
//这里默认设置为自动启动,不与桌面交互,调整要修改注册表
int InstallService(TCHAR *newName)
{
	// 注册表中查找svchost配置信息
	// 调阅API注册服务
	// 配置服务所需的启动参数
	int rc = 0;
	HKEY hkRoot = HKEY_LOCAL_MACHINE, hkParam = 0;
	SC_HANDLE hscm = NULL, schService = NULL;

	try{
		TCHAR buff[500];
		TCHAR *svcname = DEFAULT_SERVICE_NAME;
		if(newName && newName[0]) svcname = newName;

		//新增服务
		hscm = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
		if (hscm == NULL)
		{
			throw "OpenSCManager()";
		}

		TCHAR *svhostStartCmd = SERVICE_START_CMD;

		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
			svhostStartCmd,				// 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(TEXT("CreateService(%s) error %d"), svcname, rc = GetLastError());
			throw "";
		}
		OutputString(TEXT("CreateService(%s) SUCCESS. Config it"), svcname);

		CloseServiceHandle(schService);
		CloseServiceHandle(hscm);

		//新增服务配置,设置服务dll路径
		hkRoot = HKEY_LOCAL_MACHINE;
		_tcsncpy(buff, TEXT("SYSTEM\\CurrentControlSet\\Services\\"), _countof(buff));
		_tcsnccat(buff, svcname, 100);
		rc = RegOpenKeyEx(hkRoot, buff, 0, KEY_ALL_ACCESS, &hkRoot);
		if(ERROR_SUCCESS != rc)
		{
			OutputString(TEXT("RegOpenKeyEx(%s) KEY_SET_VALUE error %d."), svcname, rc);
			throw "";
		}

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

		if(!GetModuleFileName(HMODULE(hDll), buff, _countof(buff)))
		{
			throw "GetModuleFileName() get dll path";
		}

		rc = RegSetValueEx(hkParam, TEXT("ServiceDll"), 0, REG_EXPAND_SZ, (BYTE*)buff, _tcsclen(buff)+1);
		SetLastError(rc);
		if(ERROR_SUCCESS != rc)
		{
			throw "RegSetValueEx(ServiceDll)";
		}

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

	if(hkRoot)
	{
		RegCloseKey(hkRoot);
	}

	if(hkParam)
	{
		RegCloseKey(hkParam);
	}
	
	if(schService)
	{
		CloseServiceHandle(schService);
	}

	if(hscm)
	{
		CloseServiceHandle(hscm);
	}

	return rc;
}

//用于rundll32.exe安装服务
//rundll32.exe SvchostTest.dll,RundllInstall
void CALLBACK RundllInstall(
	HWND hwnd,        // handle to owner window
	HINSTANCE hinst,  // instance handle for the DLL
	TCHAR *param,     // string the DLL will parse
	int nCmdShow      // show state
	)
{
	InstallService(param);
}

//写注册表卸载服务
int UninstallService(TCHAR *newName)
{
	int rc = 0;
	SC_HANDLE schService;
	SC_HANDLE hscm;

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

		TCHAR *svcname = DEFAULT_SERVICE_NAME;
		if(newName && newName[0]) svcname = newName;

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

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

		OutputString(TEXT("DeleteService(%s) SUCCESS."), svcname);
	}
	catch(TCHAR* ex)
	{
		rc = GetLastError();
		OutputString(TEXT("Exception Catched 0x%X"), rc);
	}

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

//用于rundll32.exe卸载服务
//rundll32.exe SvchostTest.dll,RundllUninstall
void CALLBACK RundllUninstall(
	HWND hwnd,        // handle to owner window
	HINSTANCE hinst,  // instance handle for the DLL
	TCHAR *param,     // string the DLL will parse
	int nCmdShow      // show state
	)
{
	UninstallService(param);
}

//输出日志到日志文件和DbgPrint
void OutputString( TCHAR *lpFmt, ... )
{
	TCHAR buff[1024];
	va_list    arglist;
	va_start( arglist, lpFmt );
	_vsntprintf( buff, _countof(buff), lpFmt, arglist );
	va_end( arglist );

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

	OutputDebugString(buff);

	return;
}

svchosttest.def

EXPORTS
ServiceMain
RundllUninstall
RundllInstall

参考(部分资料未能找到原文出处):
Writing a service that runs under svcho
Svchost.exe的原理

Comments are closed.