背景想必实现程序开机自启动,是很常见的功能了。无论是恶意程序,还是正常的应用软件,都会提供这个功能,方便用户的使用。程序开机自启动,顾名思义,就是计算机开机后,不用人为地去运行程序,程序就可以自己运行起来。对于这个功能的,一直都是杀软重点监测的地方。因为,对于病毒来说,重要的不是如何被破坏,而是如何启动。
在过去写的大大小小的程序中,我也实现过程序自启动的功能。现在,我把这些自启动功能的实现方式进行下总结。常见的方式有:修改开机自启动注册表、开机自启动目录、创建开机自启计划任务、创建开机自启系统服务等方式。现在对这些技术一一进行分析,并形成文档分享给大家。本文介绍创建系统服务实现开机自启动的方式,其它的实现方式可以搜索我写的相关系列的文档。
函数介绍OpenSCManager 函数
建立了一个到服务控制管理器的连接,并打开指定的数据库。
函数声明
SC_HANDLE OpenSCManager( LPCTSTR lpMachineName, // computer name LPCTSTR lpDatabaseName, // SCM database name DWORD dwDesiredAccess // access type);
参数
lpMachineName指向零终止字符串,指定目标计算机的名称。如果该指针为NULL ,或者它指向一个空字符串,那么该函数连接到本地计算机上的服务控制管理器。
lpDatabaseName指向零终止字符串,指定将要打开的服务控制管理数据库的名称。此字符串应被置为 SERVICES_ACTIVE_DATABASE。如果该指针为NULL ,则打开默认的 SERVICES_ACTIVE_DATABASE数据库。
dwDesiredAccess指定服务访问控制管理器的权限。在授予要求的权限前,系统会检查调用进程的权限令牌,该令牌针对与服务控制管理器相关的安全描述符的权限控制列表。此外,该函数的调用将隐式地指定SC_MANAGER_CONNECT 的访问权限。此外,下列服务控制管理器对象的访问类型可以被指定:
VALUE
MEANING
SC_MANAGER_ALL_ACCESS
除了所有此表中列出的访问类型,还包括STANDARD_RIGHTS_REQUIRED
SC_MANAGER_CONNECT
可以连接到服务控制管理器
SC_MANAGER_CREATE_SERVICE
使要求的CreateService函数创建一个服务对象,并将其添加到数
SC_MANAGER_ENUMERATE_SERVICE
使要求的EnumServicesStatus功能清单的服务,这是在数据库
SC_MANAGER_LOCK
使要求的LockServiceDatabase功能获得锁定数据库
SC_MANAGER_QUERY_LOCK_STATUS
使要求的QueryServiceLockStatus检索功能锁定状态信息的数据
返回值
如果函数成功,返回值是一个指定的服务控制管理器数据库的句柄。如果函数失败,返回值为NULL 。要获得更详细的错误信息,可以使用GetLastError 获得错误代码。
CreateService 函数
创建一个服务对象,并将其添加到指定的服务控制管理器数据库的函数。
函数声明
SC_HANDLE CreateService( SC_HANDLE hSCManager, //服务控制管理程序的数据库句柄 LPCTSTR lpServiceName, //以NULL 结尾的服务名 LPCTSTR lpDisplayName, //以NULL 结尾的服务名 DWORD dwDesiredAccess, //指定服务返回类型 DWORD dwServiceType, //指定服务类型 DWORD dwStartType, //指定何时启动服务 DWORD dwErrorControl, //指定服务启动失败的严重程度 LPCTSTR lpBinaryPathName,//指定服务程序二进制文件的路径 LPCTSTR lpLoadOrderGroup,//指定顺序装入的服务组名 LPDWORD lpdwTagId, //忽略,NULL LPCTSTR lpDependencies, //指定启动该服务前必先启动的服务 LPCTSTR lpServiceStartName,//指定服务帐号 LPCTSTR lpPassword //指定对应的口令);
参数
hSCManager服务控制管理器数据库的句柄。 此句柄由OpenSCManager函数返回,并且必须 具有 SC_MANAGER_CREATE_SERVICE 的访问权限。 有关更多的信息请参阅Service安全和访问权限。
lpServiceName要安装该服务的名称。 最大字符串长度为 256 个字符。 服务控制管理器数据库将保留在的字符的大小写,但是服务名称比较总是区分大小写。 正斜杠和一个反斜线不是有效的服务名称字符。
lpDisplayName对被用户界面程序用来识别服务的显示名称。 此字符串具有最大长度为 256 个字符。 服务控制管理器中的情况下保留名称。 显示名称比较总是不区分大小写。
dwDesiredAccess对服务的访问。 请求的访问之前,系统将检查调用进程的访问令牌。 一个值列表请参阅服务安全和访问权限。
dwServiceType服务类型。 此参数可以是下列值之一:
VALUE
MEANING
SERVICE_ADAPTER
保留
SERVICE_FILE_SYSTEM_DRIVER
文件系统驱动服务程序
SERVICE_KERNEL_DRIVER
驱动服务程序
SERVICE_RECOGNIZER_DRIVER
保留
SERVICE_WIN32_OWN_PROCESS
运行于独立进程的服务程序
SERVICE_WIN32_SHARE_PROCESS
被多个进程共享的服务程序
若使用了SERVICE_WIN32_OWN_PROCESS 或 SERVICE_WIN32_SHARE_PROCESS且使用LocalSystem帐号来运行该服务程序,则还可以附加使用下面的值:
VALUE
MEANING
SERVICE_INTERACTIVE_PROCESS
该服务可以与桌面程序进行交互操作
dwStartType服务启动选项。此参数可以是下列值之一:
VALUE
MEANING
SERVICE_AUTO_START
系统启动时由服务控制管理器自动启动该服务程序
SERVICE_BOOT_START
用于由系统加载器创建的设备驱动程序, 只能用于驱动服务程序
SERVICE_DEMAND_START
由服务控制管理器(SCM)启动的服务
SERVICE_DISABLED
表示该服务不可启动
SERVICE_SYSTEM_START
用于由IoInitSystem函数创建的设备驱动程序
dwErrorControl当该启动服务失败时产生错误的严重程度以及采取的保护措施。
lpBinaryPathName服务程序二进制文件,完全限定路径。 如果路径中包含空格它必须被引用,以便它正确的解析。
lpLoadOrderGroup在加载顺序此服务所属的组的名称。
lpdwTagId指向接收 lpLoadOrderGroup 参数中指定的组中唯一的标记值的变量。
lpDependencies空分隔名称的服务或加载顺序组系统必须在这个服务开始之前的双空终止数组的指针。 如果服务没有任何依赖关系,请指定为 NULL 或空字符串。
lpServiceStartName该服务应在其下运行的帐户的名称。
lpPassword由lpServiceStartName参数指定的帐户名的密码。
返回值
如果函数成功,返回值将是该服务的句柄。如果函数失败,则返回值为 NULL。 若要扩展的错误了解调用GetLastError。
OpenService 函数
打开一个已经存在的服务。
函数声明
SC_HANDLE WINAPI OpenService( _In_ SC_HANDLE hSCManager, _In_ LPCTSTR lpServiceName, _In_ DWORD dwDesiredAccess);
参数
hSCManager
SCM数据库句柄。
lpServiceName
要打开服务的名字,这和CreateService形参lpServiceName一样,不是服务显示名称。
dwDesiredAccess
服务权限。
返回值
成功,返回服务句柄;失败返回NULL,可以通过GetLastError获取错误码。
StartService 函数
启动服务。
函数声明
BOOL WINAPI StartService( _In_ SC_HANDLE hService, _In_ DWORD dwNumServiceArgs, _In_opt_ LPCTSTR *lpServiceArgVectors);
参数
hService
OpenService 或CreateService function返回的服务句柄,需要有SERVICE_START权限。
dwNumServiceArgs
下一个形参lpServiceArgVectors的字符串个数,如果lpServiceArgVectors为空,那么该参数设为0。
lpServiceArgVectors
传递给服务ServiceMain的参数,如果没有,可以设为NULL;否则,第一个形参lpServiceArgVectors[0]为服务的名字,其他则为需要传入的参数。
注意
驱动不接受这些参数,即lpServiceArgVectors为空,dwNumServiceArgs为0。
返回值
成功,返回非0;失败,返回0,错误码使用GetLastError获得。
ControlService函数
给指定的服务发送一个控制码。
函数声明
BOOL WINAPI ControlService( _In_ SC_HANDLE hService, _In_ DWORD dwControl, _Out_ LPSERVICE_STATUS lpServiceStatus);
参数
hService
OpenService 或CreateService function返回的服务句柄;
dwControl
要发送的控制码,控制码可以是下列系统定义的控制码;也可以是用户自定义的控制码,范围是128-255,需要有SERVICE_USER_DEFINED_CONTROL权限。
lpServiceStatus
返回一个指向存储服务最新状态的结构体ServiceStatus,返回的服务信息来自SCM中最近的服务状态报告。
返回值
成功,返回非0;失败,返回0,错误码使用GetLastError获得。
DeleteService 函数
从服务控制管理器数据库中删除指定的服务。
函数声明
BOOL WINAPI DeleteService( _In_ SC_HANDLE hService);
参数
hService
[输入]服务的句柄,可以由OpenService 或者 CreateService 函数返回,而且该服务必须要有删除权限。
返回值
成功,返回非0;失败,返回0,错误码使用GetLastError获得。
StartServiceCtrlDispatcher 函数
将服务进程的主线程连接到服务控制管理器,该线程将线程作为调用过程的服务控制分派器线程。
函数声明
BOOL WINAPI StartServiceCtrlDispatcher( _In_ const SERVICE_TABLE_ENTRY *lpServiceTable);
参数
lpServiceTable
[输入]指向一系列SERVICE_TABLE_ENTRY结构的指针,其中包含可在调用进程中执行的每个服务的一个条目。 表中最后一个条目的成员必须具有NULL值来指定表的结尾。
返回值
成功,返回非0;失败,返回0,错误码使用GetLastError获得。
RegisterServiceCtrlHandler 函数实现原理创建自启动服务的原理是:
打开服务控制管理器数据库并获取数据库的句柄
若要创建服务的操作,则开始创建服务,设置SERVICE_AUTO_START开机自启动;若是其他操作,则打开服务,获取服务句柄
然后,根据服务句柄,若操作是启动,则使用StartService启动服务;若服务是停止,则使用ControlService停止服务;若操作是删除,则使用DeleteService删除服务
关闭服务句柄和服务控制管理器数据库句柄
其中,设置自启动服务启动的程序,并不是普通的程序,而是要求程序要创建了服务入口点函数,否则,不能创建系统服务。创建服务程序的原理是是:
服务程序主函数(main)调用系统函数StartServiceCtrlDispatcher连接程序主线程到服务控制管理程序(其中定义了服务入口点函数是ServiceMain)。服务控制管理程序启动服务程序后,等待服务程序主函数调用StartServiceCtrlDispatcher。如果没有调用该函数,设置服务入口点,则会报错。
执行服务初始化任务(同时执行多个服务的服务有多个入口点函数),首先调用RegisterServiceCtrlHandler定义控制处理程序函数(本例中是ServiceCtrlHandle),初始化后通过SetServiceStatus设定进行运行状态,然后运行服务代码。
控制处理程序(Handler)在服务收到控制请求时由控制分发线程引用(最少要有停止服务的能力)。
编码实现创建系统服务// 0 加载服务 1 启动服务 2 停止服务 3 删除服务BOOL SystemServiceOperate(char *lpszDriverPath, int iOperateType){ BOOL bRet = TRUE; char szName[MAX_PATH] = { 0 }; ::lstrcpy(szName, lpszDriverPath); // 过滤掉文件目录,获取文件名 ::PathStripPath(szName); SC_HANDLE shOSCM = NULL, shCS = NULL; SERVICE_STATUS ss; DWORD dwErrorCode = 0; BOOL bSuccess = FALSE; // 打开服务控制管理器数据库 shOSCM = ::OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); if (!shOSCM) { ShowError("OpenSCManager"); return FALSE; } if (0 != iOperateType) { // 打开一个已经存在的服务 shCS = OpenService(shOSCM, szName, SERVICE_ALL_ACCESS); if (!shCS) { ShowError("OpenService"); ::CloseServiceHandle(shOSCM); shOSCM = NULL; return FALSE; } } switch (iOperateType) { case 0: { // 创建服务 // SERVICE_AUTO_START 随系统自动启动 // SERVICE_DEMAND_START 手动启动 shCS = ::CreateService(shOSCM, szName, szName, SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS, SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, lpszDriverPath, NULL, NULL, NULL, NULL, NULL); if (!shCS) { ShowError("CreateService"); bRet = FALSE; } break; } case 1: { // 启动服务 if (!::StartService(shCS, 0, NULL)) { ShowError("StartService"); bRet = FALSE; } break; } case 2: { // 停止服务 if (!::ControlService(shCS, SERVICE_CONTROL_STOP, &ss)) { ShowError("ControlService"); bRet = FALSE; } break; } case 3: { // 删除服务 if (!::DeleteService(shCS)) { ShowError("DeleteService"); bRet = FALSE; } break; } default: break; } // 关闭句柄 if (shCS) { ::CloseServiceHandle(shCS); shCS = NULL; } if (shOSCM) { ::CloseServiceHandle(shOSCM); shOSCM = NULL; } return bRet;}
创建服务程序int _tmain(int argc, _TCHAR* argv[]){ // 注册服务入口函数 SERVICE_TABLE_ENTRY stDispatchTable[] = { { g_szServiceName, (LPSERVICE_MAIN_FUNCTION)ServiceMain }, { NULL, NULL } }; ::StartServiceCtrlDispatcher(stDispatchTable); return 0;}
void __stdcall ServiceMain(DWORD dwArgc, char *lpszArgv){ g_ServiceStatus.dwServiceType = SERVICE_WIN32; g_ServiceStatus.dwCurrentState = SERVICE_START_PENDING; g_ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP; g_ServiceStatus.dwWin32ExitCode = 0; g_ServiceStatus.dwServiceSpecificExitCode = 0; g_ServiceStatus.dwCheckPoint = 0; g_ServiceStatus.dwWaitHint = 0; g_ServiceStatusHandle = ::RegisterServiceCtrlHandler(g_szServiceName, ServiceCtrlHandle); g_ServiceStatus.dwCurrentState = SERVICE_RUNNING; g_ServiceStatus.dwCheckPoint = 0; g_ServiceStatus.dwWaitHint = 0; ::SetServiceStatus(g_ServiceStatusHandle, &g_ServiceStatus); // 自己程序实现部分代码放在这里 // !!注意!! 此处一定要为死循环, 否则在关机再开机的情况(不是点击重启), 不能创建用户进程 while (TRUE) { Sleep(5000); DoTask(); }}
void __stdcall ServiceCtrlHandle(DWORD dwOperateCode){ switch (dwOperateCode) { case SERVICE_CONTROL_PAUSE: { // 暂停 g_ServiceStatus.dwCurrentState = SERVICE_PAUSED; break; } case SERVICE_CONTROL_CONTINUE: { // 继续 g_ServiceStatus.dwCurrentState = SERVICE_RUNNING; break; } case SERVICE_CONTROL_STOP: { // 停止 g_ServiceStatus.dwWin32ExitCode = 0; g_ServiceStatus.dwCurrentState = SERVICE_STOPPED; g_ServiceStatus.dwCheckPoint = 0; g_ServiceStatus.dwWaitHint = 0; ::SetServiceStatus(g_ServiceStatusHandle, &g_ServiceStatus); break; } case SERVICE_CONTROL_INTERROGATE: { // 询问 break; } default: break; }}
程序测试在 main 函数中调用上述封装好的函数接口,进行测试。main 函数为:
int _tmain(int argc, _TCHAR* argv[]){ BOOL bRet = FALSE; char szFileName[MAX_PATH] = "C:\\Users\\DemonGan\\Desktop\\AutoRun_Service_Test\\Debug\\ServiceTest.exe"; // 创建并启动服务 bRet = SystemServiceOperate(szFileName, 0); if (FALSE == bRet) { printf("Create Error!\n"); } bRet = SystemServiceOperate(szFileName, 1); if (FALSE == bRet) { printf("Start Error!\n"); } printf("Create and Start OK.\n"); system("pause"); // 停止并删除服务 bRet = SystemServiceOperate(szFileName, 2); if (FALSE == bRet) { printf("Stop Error!\n"); } bRet = SystemServiceOperate(szFileName, 3); if (FALSE == bRet) { printf("Delete Error!\n"); } printf("Stop and Delete OK.\n"); system("pause"); return 0;}
测试结果:
“以管理员身份运行程序”打开程序,提示系统服务创建并启动成功。
查看任务管理器,发现“ServiceTest.exe”进程已经启动。
接着,打开服务管理器,查看服务,“ServiceTest.exe”服务成功创建。
查看服务属性,路径和启动类型正确。
执行服务停止删除,程序提示执行成功。检查任务管理器和服务管理器,均为发现“ServiceTest.exe”服务,所以,删除成功。
总结注意,创建系统服务,要求要有管理员权限。其中,创建系统服务启动的程序,它需要额外创建服务入口点函数ServiceMain,这样才能创建服务成功,否则会报错。
系统服务是运行在Session 0,系统服务的Session 0隔离,阻断了系统服务和用户桌面进程之间进行交互和通信的桥梁。各个Session之间是相互独立的。在不同Session中运行的实体,相互之间不能发送Windows消息、共享UI元素或者是在没有指定他们有权限访问全局名字空间(并且提供正确的访问控制设置)的情况下,共享核心对象。这样也就是为什么,在系统服务不能显示程序界面,也不能用常规的方式创建有界面的进程。要想创建带界面的进程,要使用另外的方式,我已经写成相应的文档分享给大家了,大家可以搜索我写的相关文档来参考。
参考参考自《Windows黑客编程技术详解》一书
1 回答
2018-12-20 12:16:21