不想看分析过程的,可以直接跳转到最后的汇总。
背景
通常我们要增加一个自启动,会在 SOFTWARE\Microsoft\Windows\CurrentVersion\Run
中添加一个项。如果需要禁用,很正常的思路是从次注册表中移除,然后保存到另外一个地方,待下次启动后,再把这一项添加回来(autoruns
就是这么干的)
但这样会有个问题,当被禁用(从注册表中移除)后,就相当于又被移除出去了,这样在任务管理器中就无法看到这个应用,而不是显示此应用被禁用。
看了下任务管理器是怎么做的,发现其并没有移除原注册表项,也实现了禁用的效果。
接下来就来研究下它是怎么实现的。
分析
开始上工具分析:
首先我们先用 Process Monitor 来看看禁用/启用自启动项的时候,任务管理器干了啥。
Process Monitor 初步分析
Process Monitor 可以在 https://live.sysinternals.com/ 中下载到。
打开 Process Monitor,并在过滤器中选择【Process Name】,并选择【taskmgr.exe】,如下图所示:
然后我们就可以在任务管理器上禁止一个应用,然后马上停止收集。这里以向日葵(SunloginClient.exe)为例。就可以看到如下的信息:
我们可以关注到HKLMSOFTWAREMicrosoftWindowsCurrentVersionExplorerStartupApprovedRunSunloginClient
这个注册表项。
再开启、禁用。发现此项确实有被修改。
注册表项值
从 Process Monitor 和 Regedit 中我们可以看到,这个项是一个二进制的值。这就有点麻烦了。
我们分别收集启用和禁用情况下的值:
启用时:
禁用时:
很明显,我们还是没法知道这些值都代表什么意思。
于是,用上我们的搜索大法:
发现一个相对比较有用的回答:
链接:
到这里,我们其实只了解了前两位的值有什么用。但后面的值,是没有信息的。
于是,我们就需要用逆向的手段来做进一步的分析了。
主要可以从静态分析和动态分析来入手。这里以静态分析为例:
IDA Pro
静态分析上,我们可以通过 ida pro 来进行分析。
首先我们需要找到突破口,根据之前的注册表,尝试搜索【StartupApproved
】,如下图:
于是就搜到了 ,跳转到那块区域:
从上图中,我们可以看到,其被引用在 StartupDB::StartupApproval::Initialize
,微软居然还提供了 pdb,太棒了。
于是我们就可以在函数界面,搜索下 StartupDB::StartupApproval
相关的方法了。
可以看到,里面包含 SetBlockedItemInfo
和 GetBlockedItemInfo
。点进去看看:
以 SetBlockedItemInfo
为例,可以看到,基本没啥逻辑,就是写了一下注册表。
于是,接下来找到调用它的人。通过 xref 可以看到,主要就是 WdcStartupMonitor::DisableItem
和 WdcStartupMonitor::EnableItem
。跳转到 WdcStartupMonitor::DisableItem
。
因为我们主要看看其逻辑,所以我们直接看伪代码即可。按 F5 转到 伪代码界面(当然,这里也是可以将汇编代码发给 Chatgpt,让它帮忙解释下是什么意思。也很好)
由于代码比较多,这里我们看看重点代码:
从代码中可以看到:
SystemTimeAsFileTime
主要分两部分:
SystemTimeAsFileTime[0]
:低 32 位就是上述提到的低两位(上述,02 为开启,03 为禁用)SystemTimeAsFileTime[1]
:高 64 位是 FileTime,也就是当前的系统时间
总共 12 位,也可以通过 SetBlockedItemInfo
的伪代码来验证。
于是,我们基本搞清楚了注册表中的信息是什么。
验证
参考代码如下:
Zeus::WinRegistry reg(item->rootKey, item->regPath, false);
TaskmgrAutorunsRegData data;
std::vector<char> val;
try
{
if (isEnable && !IsEnableType(item->type)) {
data.type = item->type - 1;
memset(&data.fileTime, 0, sizeof(FILETIME));
} else if (!isEnable && IsEnableType(item->type) ) {
data.type = item->type | 1;
GetSystemTimeAsFileTime(&data.fileTime);
}
else {
return true;
}
char* charData = reinterpret_cast<char*>(&data);
for (int i = 0; i < sizeof(TaskmgrAutorunsRegData); i++) {
val.push_back(charData[i]);
}
reg.SetBinary(item->name, val);
return true;
}
// TaskmgrAutorunsRegData 定义
struct TaskmgrAutorunsRegData {
// 01-禁用启动,具有管理员权限的用户可以启用
// 02-已启用启动,具有管理员权限的用户可以禁用
// 08-已启用启动,用户无法禁用(灰色显示)
// 09-禁用启动,用户无法启用(灰色显示)
int32_t type;
FILETIME fileTime;
};
修改 SunloginClient.exe 的自启动后,可以通过任务管理器查看到禁用时间。
重启后,也不会自己启动。
于是,我们就达成目的了。
汇总
获取自启动列表
主要在如下注册表中枚举:
"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\StartupApproved\Run”
"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\StartupApproved\Run32”
"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\StartupApproved\StartupFolder”
包含 HKEY_LOCAL_MACHINE
和 HKEY_CURRENT_USER
两个。
判断特定项是否开启自启动
获取 TaskmgrAutorunsRegData
的 type 是 2 或者是 8
设置特定项目的自启动状态
Zeus::WinRegistry reg(item->rootKey, item->regPath, false);
TaskmgrAutorunsRegData data;
std::vector<char> val;
try
{
if (isEnable && !IsEnableType(item->type)) {
data.type = item->type - 1;
memset(&data.fileTime, 0, sizeof(FILETIME));
} else if (!isEnable && IsEnableType(item->type) ) {
data.type = item->type | 1;
GetSystemTimeAsFileTime(&data.fileTime);
}
else {
return true;
}
char* charData = reinterpret_cast<char*>(&data);
for (int i = 0; i < sizeof(TaskmgrAutorunsRegData); i++) {
val.push_back(charData[i]);
}
reg.SetBinary(item->name, val);
return true;
}
// TaskmgrAutorunsRegData 定义
struct TaskmgrAutorunsRegData {
// 01-禁用启动,具有管理员权限的用户可以启用
// 02-已启用启动,具有管理员权限的用户可以禁用
// 08-已启用启动,用户无法禁用(灰色显示)
// 09-禁用启动,用户无法启用(灰色显示)
int32_t type;
FILETIME fileTime;
};
后续
后续研究还有几个方向:
- 这里只分析了注册表的自启动应用,还有 UWP 应用,怎么枚举,怎么设置?
- 这里只分析了,基于注册表有值的情况下的修改。系统是怎么判断什么时候是 2,什么时候是 8,以及是否还有其他类型?
0