自启动技术

第五章 自启动技术

注册表

实现开机自启动的途径和方式有很多种,其中修改注册表方式应用最为广泛。注册表相当是操作系统的数据库,记录着系统中方方面面的数据,其中也不乏直接或间接导致开机自启动的数据。本节介绍向Run注册表中添加程序路径的方式,以实现开机自启动。

实现原理

理解修改注册表实现开机自启动功能的一个重要前提就是,Windows提供了专门的开机自启动注册表。在每次开机完成后,它都会在这个注册表键下遍历键值,以获取键值中的程序路径,并创建进程启动程序。

本节介绍两种修改注册表的方式,它们的主要区别在于注册表键路径。本节介绍其中常见的两个路径,分别是:

HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run

HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run

在编程实现上,要修改HKEY_LOCAL_MACHINE主键的注册表,这要求程序要有管理员权限。而修改HKEY_CURRENT_USER主键
的注册表,只需要用户默认权限就可以实现。

如果程序运行在64位Windows系统上,则需要注意注册表重定位的问题。在64位Windows系统中,为了兼容32位程序的正常执行,64位的Windows系统采用重定向机制。系统为关键的文件夹和关键注册表创建了两个副本,使得32位程序在64位系统上不仅能操作关键文件夹和关键注册表,还可以避免与64位程序冲突。

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
BOOL RegCurrentUser(char *lpFileName, char *lpValueName)
{
// 默认权限
HKEY hKey;
// 打开注册表键
RegOpenKeyEx(HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Run", 0, KEY_WRITE, &hKey)
// 修改注册表值,实现开机自启
RegSetValueEx(hKey, lpszValueName, 0, REG_SZ, (BYTE *)lpszFileName, (1 + lstrlen(lpszFileName)))
// 关闭注册表键
RegCloseKey(hKey);
return TRUE;
}

// 需管理员权限的路径
BOOL RegLocalMachine(char *lpFileName, char *lpValueName)
{
// 管理员权限
HKEY hKey;
// 打开注册表键
RegOpenKeyEx(HKEY_LOCAL_MACHINE, "Software\\Microsoft\\Windows\\CurrentVersion\\Run", 0, KEY_WRITE, &hKey)
// 修改注册表值,实现开机自启
RegSetValueEx(hKey, lpszValueName, 0, REG_SZ, (BYTE *)lpszFileName, (1 + lstrlen(lpszFileName)))
// 关闭注册表键
RegCloseKey(hKey);
return TRUE;
}

快速启动目录

快速启动目录是一种实现起来最为简单的,不用修改任何系统数据的自启动方法。

实现原理

Windows系统有自带的快速启动的文件夹,它是最为简单的自启动方式,只要把程序放入到这个快速启动文件夹中,系统在启动时就会自动地加载并运行相应的程序,实现开机自启动功能。

快速启动目录并不是一个固定地目录,每台计算机都不相同,但是可以使用 SHGetSpecialFolderPath 函数获取Windows系统中快速启动目录的路径,快速启动目录的CSIDL标识值为CSIDL_STARTUP。

然后,使用CopyFile函数,将想要自启动的程序复制到快速启动目录下即可。当然,为程序创建快捷方式,并把快捷方式放入到快速启动自录中,也同样可以达到开机自启动的效果。

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
BOOL AutoRun_Startup(char *lpszSrcFilePath, char *lpszDestFileName)
{
BOOL bRet = FALSE;
char szStartupPath[MAX_PATH] = {0};
char szDestFilePath[MAX_PATH] = {0};
// 获取 快速启动目录 路径
bRet = SHGetSpecialFolderPath(NULL, szStartupPath, CSIDL_STARTUP, TRUE);
printf("szStartupPath=%s\n", szStartupPath);
if (FALSE == bRet)
{
return FALSE;
}
// 构造拷贝的 目的文件路径
wsprintf(szDestFilePath, "%s\\%s", szStartupPath, lpszDestFileName);
// 拷贝文件到快速启动目录下
bRet = CopyFile(lpszSrcFilePath, szDestFilePath, FALSE);
if (FALSE == bRet)
{
return FALSE;
}

return TRUE;
}

计划任务

Windows系统可以设置计划任务来执行一些定时任务。设置计划任务的触发条件为在用户登录时触发,执行启动指定路径程序的操作,从而实现开机自启动。

实现原理及代码

使用WindowsShell编程实现创建计划任务时,会涉及COM组件接口的调用。要注意的是,编程实现创建计划任务要求具有管理员权限

1.初始化操作

​ 首先,通过CoInitialize函数来初始化COM接口环境。
​ 其次,调用CoCrateInstance函数创建任务服务对象ITaskService,并将其连接到任务服务上。
​ 最后,从ITaskService对象中获取根任务Root Task Folder的指针对象ITaskFolder,这个指针指向的是新注册的任务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
CMyTaskSchedule::CMyTaskSchedule(void)
{
m_lpITS = NULL;
m_lpRootFolder = NULL;
// 初始化COM
HRESULT hr = ::CoInitialize(NULL);
if (FAILED(hr))
{
ShowError("CoInitialize", hr);
}

// 创建一个任务服务实例
hr = ::CoCreateInstance(CLSID_TaskScheduler, NULL, CLSCTX_INPROC_SERVER, IID_ITaskService, (LPVOID *)(&m_lpITS));
if (FAILED(hr))
{
ShowError("CoCreateInstance", hr);
}

// 连接到任务服务
hr = m_lpITS->Connect(_variant_t(), _variant_t(), _variant_t(), _variant_t());
if (FAILED(hr))
{
ShowError("ITaskService::Connect", hr);
}

// 获取Root Task Folder的指针,这个指针指向的是新注册的服务
hr = m_lpITS->GetFolder(_bstr_t("\\"), &m_lpRootFolder);
if (FAILED(hr))
{
ShowError("ITaskService::GetFolder", hr);
}
}
2.创建任务计划操作(重点)

​ 首先,从ITaskService对象中创建一个任务定义对象ITaskDefinition,它用来创建任务。
​ 然后,对任务定义对象ITaskDefinition进行设置:
​ 设置注册信息,包括设置作者信息。
​ 设置主体信息,包括登录类型、运行权限。
​ 设置配置信息,包括在使用电池运行时是否停止、是否允许使用电池运行、是否手动运行、是否设置多个实例等。
​ 设置操作信息,包括启动程序,并设置运行程序的路径和参数等。
​ 设置触发器信息,包括用户登录时触发。
​ 最后,使用ITaskFolder对象并利用任务定义对象ITaskDefinition的设置,注册任务计划。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
BOOL CMyTaskSchedule::NewTask(char *lpszTaskName, char* lpszProgramPath, char* lpszParameters, char *lpszAuthor)
{
if (NULL == m_lpRootFolder)
{
return FALSE;
}

// 如果存在相同的计划任务,则删除
Delete(lpszTaskName);

// 创建任务定义对象
ITaskDefinition *pTaskDefinition = NULL;
HRESULT hr = m_lpITS->NewTask(0, &pTaskDefinition);
if (FAILED(hr))
{
ShowError("ITaskService::NewTask", hr);
return FALSE;
}

// 设置注册信息
IRegistrationInfo *pRegInfo = NULL;
CComVariant variantAuthor(NULL);
variantAuthor = lpszAuthor;
hr = pTaskDefinition->get_RegistrationInfo(&pRegInfo);
if (FAILED(hr))
{
ShowError("ITaskDefinition::get_RegistrationInfo", hr);
return FALSE;
}

// 设置作者信息
hr = pRegInfo->put_Author(variantAuthor.bstrVal);
pRegInfo->Release();

// 设置登录类型和运行权限
IPrincipal *pPrincipal = NULL;
hr = pTaskDefinition->get_Principal(&pPrincipal);
if (FAILED(hr))
{
ShowError("ITaskDefinition::get_Principla", hr);
return FALSE;
}

// 设置登录类型
hr = pPrincipal->put_LogonType(TASK_LOGON_INTERACTIVE_TOKEN);
// 设置运行权限 - 最高权限
hr = pPrincipal->put_RunLevel(TASK_RUNLEVEL_HIGHEST);
pPrincipal->Release();

// 设置其他信息
ITaskSetting* pSetting = NULL;
hr = pTaskDefinition->get_Settings(&pSetting);
if (FAILED(hr))
{
ShowError("ITaskDefinition::get_Setttings", hr);
return FALSE;
}

// 设置其他信息
hr = pSetting->put_StopIfGoingOnBatteries(VARIANT_FALSE);
hr = pSetting->put_DisallowStartIfOnBatteries(VARIANT_FALSE);
hr = pSetting->put_AllowDemandStart(VARIANT_TRUE);
hr = pSetting->put_StartWhenAvailable(VARIANT_FLASE);
hr = pSetting->put_MultipleInstance(TASK_INSTANCES_PARALLEL);
pSetting->Release();

// 创建执行动作集
IActionCollection *pActionCollect = NULL;
hr = pTaskDefinition->get_Actions(&pActionCollect);
if (FAILED(hr))
{
ShowError("ITaskDefinition::get_Actions", hr);
return FALSE;
}

IAction* pAction = NULL;
// 创建执行操作
hr = pActionCollect->Create(TASK_ACTION_EXEC, &pAction);
pActionCollect->Release();

// 设置执行程序路径和参数
CComVariant variantProgramPath(NULL);
CComVariant variantParameters(NULL);
IExecAction *pExecAction = NULL;
hr = pAction->QueryInterface(IID_IExecAction, (PVOID *)(&pExecAction));
if (FAILED(hr))
{
pAction->Release();
ShowError("IAction::QueryInterface", hr);
return FALSE;
}
pAction->Release();

// 设置程序路径和参数
variantProgramPath = lpszProgramPath;
variantParameters = lpszParameters;
pExecAction->put_Path(variantProgramPath);
pExecAction->put_Arguments(variantPraameters.bstrVal);
pExecAction->Release();

// 创建触发器,实现用户登录自启动
ITriggerCollection *pTriggers = NULL;
hr = pTaskDefinition->get_Triggers(&pTriggers);
if (FAILED(hr))
{
ShowError("ITaskDefinition::get_Triggers", hr);
return FALSE;
}

// 创建触发器
ITrigger *pTrigger = NULL;
hr = pTriggers->Create(TASK_TRIGGER_LOGON, &pTrigger);
if (FAILED(hr))
{
ShowError("ITriggerCollection::Create", hr);
return FALSE;
}

// 注册任务计划
IRegisteredTask *pRegisteredTask = NULL;
CComVariant variantTaskName(NULL);
variantTaskName = lpszTaskName;
hr = m_lpRootFolder->RegisterTaskDefinition(variantTaskName.bstrVal, pTaskDefinition, TASK_CREATE_OR_UPDATE, _variant_t(), _variant_t(),
TASK_LOGON_INTERACTIVE_TOKEN, _variant_t(""), &pRegisterdTask);
if (FAILED(hr))
{
pTaskDefinition->Release();
ShowError("ITaskFolder::RegisterTaskDefinition", hr);
return FALSE;
}

pTaskDefiinition->Release();
pRegisteredTask->Release();

return TRUE;
}
3.删除计划任务操作

ITaskFolder对象存储着已经注册成功的任务计划信息,程序只需要调用DeleteTask接口函数,并将任务计划的名称传入其中,就可以删除指定名称的计划任务了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
BOOL CMyTaskSchedule::Delete(char* lpszTaskName)
{
if (NULL == lpszTaskName)
{
return FALSE;
}

CComVariant variantTaskName(NULL);
variantTaskName = lpszTaskName;
HRESULT hr = m_lpRootFolder->DeleteTask(variantTaskName.bstrVal, 0);
if (FAILED(hr))
{
return FALSE;
}

return TRUE;
}

系统服务

系统进程自启动是通过创建系统服务并设置服务启动类型为自动启动来实现的。

实现原理及代码

1.创建启动系统服务

​ 首先通过OpenSCManager函数打开服务控制管理器数据库并获取数据库的句柄。
​ 若要创建服务,则调用CreateService开始创建服务,指明创建的服务类型是SERVICE_WIN32_OWN_PROCESS系统服务,同时设置SERVICE_AUTO_START为开机自启动;若是其他操作,则调用OpenService打开服务,获取服务句柄。
​ 接着根据服务句柄考虑以下操作,若操作是启动,则使用StartService启动服务;若操作是停止,则使用ControlService停止服务;若操作是删除,则使用DeleteService删除服务。
​ 最后,关团服务柄和服务控制管理器数据库柄。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
// 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;
}
2.系统服务程序的编写

自启动服务程序并不是普通的程序,而是要求程序创建服务入口点函数,否则,不能创建系统服务。

调用系统函数StartServiceCtrlDispatcher将程序主线程 连接到 服务控制管理程序(其中定义了服务人口点函数是ServiceMain)。服务控制管理程序启动服务程序后,等待服务程序主函数调用StartServiceCtrlDispatcher函数。

服务程序main入口函数的代码如下所示。

1
2
3
4
5
6
7
8
int _tmain(int argc, _TCHAR* argv[])
{
// 注册服务入口函数
SERVICE_TABLE_ENTRY stDispatchTable[] = { { g_szServiceName, (LPSERVICE_MAIN_FUNCTION)ServiceMain }, { NULL, NULL } };
::StartServiceCtrlDispatcher(stDispatchTable);

return 0;
}

执行服务初始化任务(同时执行的多个服务有多个入口点函数),首先调用RegisterServiceCtrlHandler定义控制处理程序函数(本例中是ServiceCtrlHandle),初始化后通过SetServiceStatus设定运行状态,然后运行服务代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void __stdcall ServiceMain(DWORD dwArgc, char *lpszArgv)
{
g_ServiceStatusHandle = ::RegisterServiceCtrlHandler(g_szServiceName, ServiceCtrlHandle);

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

// 自己程序实现部分代码放在这里
// !!注意!! 此处一定要为死循环, 否则在关机再开机的情况(不是点击重启), 不能创建用户进程
while (TRUE)
{
Sleep(5000);
DoTask();
}
}

自启动技术
http://wangchenchina.github.io/2022/11/04/自启动技术/
作者
Demo
发布于
2022年11月4日
许可协议