计划任务
在看银狐(Silver Fox / ValleyRAT / Winos)样本时,很多人会注意到一条很显眼的进程命令行:
C:\Windows\System32\svchost.exe -k netsvcs -p -s Schedule
这条命令很容易让人误解成:
银狐把自己的 EXE 直接塞进了 svchost 的参数里执行
但从 Windows 机制上讲,更准确的理解应该是:
这条命令表示“任务计划服务(Schedule)正在启动并运行”
而不是“svchost.exe 直接把某个恶意 EXE 当命令行参数执行”。
真正发生的事情通常是:
攻击者想办法把恶意载荷注册成一个会被任务计划器触发的对象
↓
Task Scheduler 服务负责读取任务定义
↓
到达触发条件后,由 Schedule 服务代表系统调度执行相应动作
↓
恶意程序被拉起,或者由中间加载器再去拉起后续模块
所以在银狐语境里,看到 svchost.exe -k netsvcs -p -s Schedule,更应该联想到:
恶意样本正在滥用 Windows 任务计划机制做持久化或恢复执行
而不是把注意力只放在 svchost.exe 这个文件名上。
机制
Windows 的很多系统服务都会跑在 svchost.exe 里,命令行中的:
-k netsvcs -p -s Schedule
本质是在说明:
- 这个
svchost.exe承载的是netsvcs这一组服务。 -s Schedule指明当前实例关注的是Task Scheduler服务。- 之后由任务计划子系统决定该在什么时机启动什么动作。
因此如果分析结论写成:
svchost.exe -k netsvcs -p -s Schedule 执行了银狐
更准确的展开应该是:
银狐通过任务计划相关机制完成注册或留存,随后由 Schedule 服务调度出恶意任务动作
这个区别很重要,因为它决定了检测和溯源时到底该去看哪里。
路径
这里不写可直接复现的恶意配置步骤,只从检测和分析角度说明常见路径。
先区分两个容易混淆的概念:
创建入口
=
调用方以什么方式向任务计划服务提出“注册任务”的请求
持久化落点
=
任务注册完成后,系统中留下的任务文件、TaskCache 映射和事件日志
schtasks、COM、PowerShell 和直接 RPC 的差别,主要在前者;它们成功注册后,最终通常都会收敛到同一套任务定义与调度服务。反过来,直接改任务文件或注册表属于绕过正常登记流程的篡改方式,不能把它和受支持的创建接口混为一谈。
schtasks
schtasks.exe 是 Windows 自带的任务计划命令行客户端,也是最直观的一种创建入口。它接收命令行参数,再把任务的名称、触发条件、运行身份和动作提交给 Task Scheduler 服务。
从行为链看,它更接近:
调用进程
↓
schtasks.exe
↓
Task Scheduler 接口
↓
Schedule 服务
↓
任务文件 + TaskCache + 调度信息
这条路径的特点是可见性较高:
- 进程创建遥测通常能直接看到
schtasks.exe、父进程和完整命令行。 - 防守方容易还原任务名称、动作和触发器;远程管理场景还可能出现网络认证与远程 RPC 线索。
- 它既可能是正常的软件部署、运维脚本,也可能是入侵后的持久化,因此不能只凭工具名定性。
对木马而言,使用它的门槛低;对检测而言,它也是最容易被传统规则覆盖的一条路径。
COM 注册
Task Scheduler 2.0 暴露了正式的 COM 自动化接口。程序可通过诸如 ITaskService、ITaskFolder、ITaskDefinition 和 IRegisteredTask 一类对象,构造任务定义并请求服务注册或更新任务。
从防守视角,可以把它理解成:
不是“命令行创建任务”
而是“程序化调用任务计划 API / COM 完成同样的事”
典型的内部关系是:
调用进程
↓
Task Scheduler COM 对象
↓
COM 代理 / RPC 运行时
↓
Schedule 服务
这样做的好处通常在于:
- 进程命令行上不一定出现明显的
schtasks.exe痕迹。 - 整体行为更像“软件自己在调用系统组件”。
- 可以更细粒度地设置触发器、运行账户、隐藏属性和动作内容。
但它并不会凭空消失,仍然会留下:
- 任务注册后的文件与注册表落点。
- COM 接口调用前后的任务计划服务交互痕迹。
- 任务创建和更新事件。
- 随后由计划任务触发出的进程树。
因此,COM 路径改变的是“注册方式”,不是“落地结果”。
PowerShell 与管理封装
有些样本、安装器或运维脚本会通过 PowerShell 的 ScheduledTasks 模块、.NET 封装或管理框架操作任务计划。它们常把“定义触发器、动作、运行条件”和“提交注册”拆成多个对象或命令,因此从脚本表面看,不一定出现 schtasks.exe。
它的本质仍然是对任务计划组件的上层封装:
PowerShell / 管理脚本
↓
模块、.NET 或管理提供程序
↓
任务计划 COM / 服务接口
↓
Schedule 服务
这类路径的排查重点应放在脚本来源、脚本块日志(若已启用)、PowerShell 启动参数,以及任务注册前后的落地结果。不要因为没有 schtasks.exe 就排除计划任务持久化。
直接 RPC 调用
任务计划服务的 RPC 协议由 Microsoft 的 [MS-TSCH] 规范描述,其中的服务接口称为 ITaskSchedulerService。任务计划 GUI、schtasks.exe 和跨进程 COM 在与 Schedule 服务交互时也可能使用 RPC;部分样本则跳过 schtasks.exe 和 COM Automation,直接构造该协议的 RPC 请求。
其调用链可以简化为:
调用进程
↓
RPC 运行时
↓
Task Scheduler RPC 接口
↓
Schedule 服务执行权限检查、XML 校验和登记
这里有三个关键点:
- RPC 是 Windows 服务间的正常通信机制,不是漏洞;服务仍会依据调用者令牌检查权限。
SchRpcRegisterTask、SchRpcRun、SchRpcDelete等是[MS-TSCH]中定义的协议操作。微软虽公开了协议规范,但常规软件开发一般仍使用 Task Scheduler COM API 或官方管理工具,而非自行实现 RPC 客户端。- 直接 RPC 往往不会产生
schtasks.exe或典型 COM 激活痕迹,所以只依赖进程名和命令行的规则容易漏报。
木马执行链
银狐等样本走直接 RPC 时,可以从“任务怎样被系统真正创建、运行和清理”来理解,而不是只把它看成少了一个 schtasks.exe。完整链路通常如下:
木马进程
↓
建立到 Task Scheduler RPC 服务的本地绑定
↓
提交任务路径与 XML 定义
↓
SchRpcRegisterTask
↓
Schedule 服务校验并登记任务
↓
可选:SchRpcRun 立即运行
↓
可选:SchRpcDelete 删除一次性任务
因此,恶意进程通常是 RPC 客户端;真正执行任务登记、维护任务状态、在触发时派生动作的主体是 Schedule 服务。这个区别会直接影响进程树、文件写入归因和检测规则的设计。
连接服务
样本会先利用 RPC 运行时创建指向任务计划服务的绑定,设置所需的认证与调用参数,然后向 ITaskSchedulerService 发起请求。实现细节在不同样本、系统版本和逆向工具中会有所不同,但防守视角不必执着于某个包装函数名,关键是:
不常见的客户端进程
↓
本地 RPC
↓
Task Scheduler 服务
这一步通常不会生成 schtasks.exe 子进程,也未必出现由 COM 激活带来的显眼进程或命令行线索;但它不意味着绕过权限控制。服务会在接收请求时基于调用者令牌进行授权判断。
交付任务定义
创建请求的核心内容是任务路径和任务 XML。XML 中描述了触发器、动作、运行账户、运行条件与其他设置;从语义上说,它和导入 XML、或由 schtasks / COM 生成的任务定义属于同一类格式。
任务路径
例如:\Microsoft\Windows\<伪装名称>
任务 XML
├── Triggers:何时触发
├── Actions:触发后做什么
├── Principals:以哪个主体运行
└── Settings:运行条件与行为
对检测来说,最重要的通常不是 RPC 调用本身,而是 XML 最终指定的动作:它是直接启动载荷,还是先经过脚本解释器、加载器、DLL 启动器或其他中转层。
注册与落地
随后,样本通过 SchRpcRegisterTask 请求注册任务。请求中可带有任务路径、XML、创建或更新语义、登录类型与安全描述符等信息;服务端再判断当前调用者是否有权完成相应操作。
这里的“注册”需要按结果区分:
- 对一个新任务使用创建语义并成功提交时,通常会新增一套标准任务状态。
- 对同一路径使用更新语义时,原有任务文件和
TaskCache会被修改,而不是再出现一个新任务。 - 若请求只是验证、被权限或 XML 校验拒绝,或在写入前失败,则不会得到完整的持久化落点。
当请求成功后,Schedule 服务负责维护任务状态,通常会:
- 创建或更新
%SystemRoot%\System32\Tasks\下的任务定义文件。 - 更新
HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache中任务路径与 GUID 等映射。常见的Tree子项将任务路径关联到 GUID,Tasks\{GUID}则保存路径、触发器、动态信息和安全相关数据等任务状态。 - 校验并登记触发器,使任务在登录、开机、定时或其他条件满足时运行。
所以文件、注册表与事件日志上的结果,通常应归因于任务计划服务代表调用者完成登记,而不是简单理解成木马进程自行写入了所有落点。
立即运行与删除
任务注册不一定等到触发器发生。部分样本会紧接着调用 SchRpcRun,要求服务立即运行指定任务;如果任务只用于一次性拉起、权限边界转换或中转执行,随后还可能调用 SchRpcDelete 删除它。
SchRpcRegisterTask
↓
SchRpcRun
↓
恶意动作被 Schedule 服务调度
↓
SchRpcDelete(可选)
这种“创建 → 运行 → 删除”的短生命周期任务,未必是持久化:它也可以是一次性执行或规避痕迹的手段。因此排查时必须把短时间窗口内的 RPC 调用、任务文件变化、任务计划事件和后续进程串联起来,不能只依赖一次静态任务盘点。
逆向命名
逆向报告中出现的 s_TaskSchedulerCreateSchedule、s_TaskSchedulerExecuteSchedule 等名称,通常是分析者或样本作者为封装函数赋予的可读标签,不是 Windows SDK 中的官方 API 名。它们往往分别包装了注册和运行相关 RPC 调用。
因此应区分:
s_TaskSchedulerCreateSchedule
=
逆向语境中的包装函数名
SchRpcRegisterTask
=
[MS-TSCH] 定义的任务注册 RPC 操作
从检测角度,重点不是“看到 RPC 就告警”,而是确认异常进程是否在同一时间窗内请求任务计划服务、注册了什么 XML,以及该任务随后执行了什么。有关 RPC 本身的分层关系可参阅 RPC。
WP TaskScheduler 私有 RPC(ShadowScheduler)
除了 [MS-TSCH] 的标准任务计划接口,还存在由 Windows 内部组件使用、但未作为常规开发接口文档化的 WP TaskScheduler 子系统。开源项目 miunasu/ShadowScheduler 演示了通过该私有 RPC 接口建立登录触发任务的 PoC;项目将底层创建操作命名为 WptsCreateSchedule。
它不是前文 SchRpcRegisterTask 的另一种封装,而是不同的 RPC 接口和不同的存储路径。根据该项目的逆向结果:
标准 MS-TSCH 路径
客户端
↓
ITaskSchedulerService / SchRpcRegisterTask
↓
Schedule 服务
↓
Tasks 文件 + TaskCache
ShadowScheduler 演示路径
客户端
↓
WP TaskScheduler 私有 RPC(ncalrpc)
↓
Schedule 服务内的 WPTaskScheduler.dll
↓
Schedule\WP\TaskScheduler\Schedules
该项目给出的接口标识为 33D84484-3626-47EE-8C6F-E7E98B113BE1,版本 2.0,并使用本地 ncalrpc 传输。它直接传递逆向得到的结构化触发器与动作参数,而非 [MS-TSCH] 中 SchRpcRegisterTask 所接收的标准任务 XML;因此不能把它归类为“标准任务 XML 换了一条 RPC 通道”。
这是根据第三方 PoC 的逆向结论整理的未文档化实现,不是 Microsoft 承诺的稳定接口或持久化行为。接口、存储位置、权限要求和遥测表现都可能随 Windows 版本、补丁与安全产品发生变化;应在隔离测试环境先验证,不能仅凭该项目 README 推断所有主机都完全相同。
与标准 RPC 的落点差异
标准 [MS-TSCH] 注册成功后,通常会生成任务 XML 并更新标准 TaskCache。ShadowScheduler 项目则声称任务状态只位于:
HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\WP\TaskScheduler\Schedules\
相应地,项目在其测试环境中观察到:
%SystemRoot%\System32\Tasks\下没有对应任务 XML。TaskCache\Tree、TaskCache\Tasks及其常见分类中没有对应的标准任务映射。schtasks /query、Get-ScheduledTask和 Task Scheduler MMC 无法通过标准枚举路径发现该任务。
这并不表示“没有注册表痕迹”,而是痕迹从常规 TaskCache 转移到了 WP 专用注册表子树。因此,依赖 TaskCache、任务 XML 或标准任务枚举结果的资产盘点,对这类实现可能存在盲区。
与标准 RPC 的对比
| 维度 | 标准 [MS-TSCH] 直接 RPC | WP TaskScheduler 私有 RPC(ShadowScheduler PoC) |
|---|---|---|
| 面向接口 | Microsoft 公开协议中的 ITaskSchedulerService | 逆向得到的未文档化 WP TaskScheduler 接口 |
| 创建操作 | SchRpcRegisterTask | 项目称为 WptsCreateSchedule 的私有调用 |
| 参数形态 | 任务路径、任务 XML、创建标志、登录类型、安全描述符等 | 逆向结构体中的任务名、触发器、动作等字段,不是标准任务 XML |
| RPC 传输 | 由任务计划服务端点承载,可被 COM / 工具间接使用 | 项目使用本地 ncalrpc 绑定 |
| 任务文件 | 通常写入 %SystemRoot%\System32\Tasks\ | 项目测试中无对应 XML 文件 |
| 标准注册表 | 通常更新 ...\Schedule\TaskCache\Tree、Tasks 等 | 项目测试中不写标准 TaskCache |
| 专用注册表 | 不适用 | 项目称写入 ...\Schedule\WP\TaskScheduler\Schedules |
| 标准管理工具 | schtasks、PowerShell、MMC 可枚举 | 项目测试中不可通过这些标准路径枚举 |
| 常规持久化排查 | 检查 XML、TaskCache、任务计划日志与进程链 | 额外检查 WP 子树、私有 RPC 接口活动及实际触发的动作 |
ETW、事件日志与审计
“不出现在任务计划 UI 或 TaskCache 中”不等于“ETW 完全没有记录”。ShadowScheduler 项目的 README 和源码没有给出 Task Scheduler Operational 或 ETW 抓包结果,因此仅凭该项目无法证明私有 RPC 创建一定不会产生任何事件。
更准确的判断应是:
| 证据面 | 标准 [MS-TSCH] RPC | WP TaskScheduler 私有 RPC |
|---|---|---|
| Task Scheduler Operational | 标准注册、更新、运行、删除流程通常是优先检查对象 | 是否产生等价的标准注册事件不能仅据 PoC 断言;需按具体 Windows 版本实测 |
| RPC ETW / EDR RPC 遥测 | 若产品采集接口与方法,可能关联到 ITaskSchedulerService 与 SchRpc* | 若采集本地 RPC 接口 UUID,可关注 33D84484-3626-47EE-8C6F-E7E98B113BE1、ncalrpc 和异常客户端;能否解析私有方法取决于产品 |
| 注册表 ETW / Sysmon / EDR | 可看到服务侧更新 TaskCache,前提是采集相应注册表操作 | 应将 ...\Schedule\WP\TaskScheduler\Schedules 纳入监控;写入者预期仍是任务计划服务侧,而非客户端直接写入 |
| 文件遥测 | 通常有 System32\Tasks 文件创建或修改 | 项目测试中没有对应文件落点,因此不能依赖文件创建发现 |
| 进程创建遥测 | 任务触发后可关联任务动作与后续载荷 | 同样适用;最终动作执行、网络连接、注入等后续行为仍是重要证据 |
也就是说,标准 Operational 事件是否缺失只是一个待验证的“可能盲点”,不能当作已证实的规避效果;而私有 RPC、WP 注册表子树和最终载荷执行,即使在标准任务枚举失效时,仍可能为 EDR、ETW 或注册表审计提供检测入口。
检测思路
对这类私有接口,更合适的检测链是:
不可信进程 / 被注入宿主
↓
本地 RPC(ncalrpc)访问 WP TaskScheduler 接口 UUID
↓
Schedule 服务侧修改 ...\Schedule\WP\TaskScheduler\Schedules\
↓
登录或其他触发条件满足
↓
非预期的任务动作、载荷启动、联网或注入
防守侧应额外基线化该 WP 注册表子树:正常系统是否存在条目、哪些 Windows 组件会写入、写入时间与用户登录或软件安装是否一致。只有在确认正常基线后,才能把来自 Office、下载目录、用户可写目录程序或异常宿主的私有 RPC 调用提升为高置信度告警。
任务文件与注册表篡改
注册成功后,任务计划通常会在两个位置维护状态:
%SystemRoot%\System32\Tasks\:任务定义文件,内容为 XML,但通常没有.xml扩展名。HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache:任务路径、GUID、树结构等注册表映射与缓存信息。
因此,具有足够权限的攻击者也可能尝试直接写任务文件、篡改已有任务文件,或修改 TaskCache 项,以伪造、隐藏或破坏任务状态。它和前面的“通过服务注册”不同:
受支持的接口
↓
Schedule 服务统一写入并保持文件、注册表和内部状态一致
直接篡改
↓
绕开部分服务逻辑,试图改动持久化落点
这类方式并不可靠,也不能把“写一个注册表值”理解成能够稳定创建计划任务:任务文件、TaskCache 和服务的内部状态需要一致;系统 ACL、XML 校验和后续服务刷新都可能使不完整的篡改失效或暴露异常。
不过,对防守方而言,它仍是高价值排查面:
- 任务文件或
TaskCache变化,却没有合理的schtasks、COM、PowerShell 或安装器来源。 - 文件时间、注册表最后写入时间与任务计划事件彼此对不上。
- 已有任务名称正常,但动作、主体、触发器或安全描述符突然改变。
新建、更新与劫持
无论入口是 schtasks、COM、PowerShell 还是 RPC,攻击者最后一般会落在以下三种结果之一:
新建任务
↓
留下新的任务名和新的执行链
更新任务
↓
保留任务名,但替换动作、触发器或运行条件
劫持任务
↓
利用已有任务或其依赖文件,使下一次触发落到恶意内容
新建任务是最常见、也最容易理解的一类。高层逻辑通常是:
落地或注入后的木马
↓
向任务计划器注册一个新任务
↓
设置触发条件(开机、登录、定时、空闲等)
↓
把动作指向恶意载荷、加载器或中转脚本
↓
后续由 Schedule 服务在合适时机拉起
在主机上,这类行为常见会留下几类痕迹:
%SystemRoot%\System32\Tasks\下出现新的任务文件。- 注册表
TaskCache相关项发生新增或修改。 - 任务计划日志出现注册、更新、启用、触发等事件。
- 很短时间后出现由
taskeng.exe、taskhostw.exe、svchost.exe -s Schedule关联出的子进程。
对于银狐这类样本来说,任务动作未必直接指向最终后门本体,也可能先指向一个安装器、加载器、旁加载链路或脚本中转层。
有些攻击者不愿意留下一个全新的陌生命名任务,因为那在资产盘点和规则扫描里比较显眼。
于是更隐蔽的一种思路是:
找到系统里已有的任务
↓
修改它的动作、触发器、运行条件或关联文件
↓
让它在下次触发时带出恶意载荷
这种方式的风险在于,它可能让“任务名看起来正常”,但内容已经变了。
对防守方来说,排查重点不应只盯:
有没有新增任务
还要看:
- 原本正常的任务 XML 是否被改过。
- 任务动作路径是否从系统目录变成了用户目录、临时目录或伪装路径。
- 任务运行账户、触发器和安全描述符是否发生异常变化。
- 同名任务的最后修改时间是否与可疑入侵时段重合。
恢复执行
银狐这类家族经常不是只靠单一计划任务长期存活。
更常见的理解方式是:
计划任务
=
持久化链路中的一个恢复节点
例如它可能与下列机制组合出现:
- 守护逻辑(watchdog)负责短周期恢复。
- 计划任务负责开机、登录或定时兜底恢复。
- 注入到常见宿主进程的模块负责继续通信与二次拉起。
这样即便某个进程被手工结束,只要任务仍在,系统后续仍可能把它带回来。
链路转接
你提到的“劫持已有服务”更适合从高层机制理解,而不是把它当作单一步骤。
从防守角度,攻击者的目标往往不是“改成某个服务直接运行恶意 EXE”,而是想办法让一条可信的系统执行链最终回到恶意载荷。
在这个思路下,常见现象可能包括:
- 先通过服务、注入或高权限宿主拿到足够权限。
- 再由高权限上下文去注册、修改或触发计划任务。
- 或者篡改与任务动作相关的文件、脚本、DLL、工作目录,使原本正常任务在触发时跑出恶意内容。
所以“服务劫持”和“计划任务持久化”在真实样本里经常不是互斥关系,而是前后衔接关系。
对比
下面的对比针对的是“任务如何被登记”,不是对任务本身恶意性的判断。无论走哪条受支持路径,真正需要分析的仍然是调用主体、任务定义和后续执行链。
| 手法 | 本质 | 常见可见性 | 与正常任务服务的关系 | 排查重点 |
|---|---|---|---|---|
schtasks.exe | 命令行客户端提交任务 | 高:进程创建与命令行通常明确 | 通过任务计划接口提交 | 父进程、命令行、任务动作与触发器 |
| COM | 通过 Task Scheduler COM 对象登记 | 中:未必有明显命令行工具 | COM 跨进程调用通常再由 RPC 到服务 | 调用进程、COM/服务交互、落地任务 |
| PowerShell / 管理封装 | 用模块、.NET 或脚本包装任务操作 | 中:可从脚本、模块和启动参数追溯 | 最终调用 COM 或任务计划服务接口 | 脚本来源、日志、任务创建前后文件与注册表变化 |
| 直接 RPC | 直接请求任务计划 RPC 方法 | 较低:没有 schtasks,可能也无典型 COM 激活 | 直接与 Schedule 服务通信,仍受权限检查 | RPC 上下文、请求内容、任务 XML 与执行结果 |
| WP TaskScheduler 私有 RPC | 通过未文档化 WP 调度接口登记任务 | 较低:标准任务枚举、XML 与 TaskCache 在项目测试中均不可见 | 同样在任务计划服务侧处理,但走不同接口与 WP 专用状态 | 私有 RPC UUID、WP 专用注册表子树、最终动作与版本化基线 |
直接写任务文件 / TaskCache | 篡改任务定义或缓存映射 | 取决于文件、注册表与审计覆盖 | 绕开标准登记路径,可能造成状态不一致 | 文件—注册表—事件日志是否一致、是否劫持既有任务 |
可以把它压缩成一句话:
schtasks、COM、PowerShell、标准 RPC 是同一类任务计划的不同“提交入口”;
WP 私有 RPC 则可能使用另一套服务内部状态;
任务文件与 TaskCache 是标准任务最常见的“状态落点”。
前四者改变的是创建时的可见性,
最后一种更多是在篡改落点,风险更高且可靠性更差。
对于成功新建的标准 [MS-TSCH] 任务,可以把最核心的服务侧落点概括为:
...\Schedule\TaskCache
+
%SystemRoot%\System32\Tasks\<任务路径>
+
TaskScheduler Operational 生命周期事件(日志启用且未被清理时)
因此,检测策略不应只写成:
发现 schtasks.exe 就告警
更稳妥的思路是同时覆盖:
- 谁请求或改动了任务计划服务。
- 任务文件、
TaskCache和任务计划事件是否新增、更新或不一致。 - 任务动作是否指向可疑路径、解释器或异常账户。
- 该任务触发后是否把可疑载荷带入后续注入、通信或自恢复链。
价值
因为对长期驻留木马来说,任务计划机制具备几个很现实的优势:
- 它是 Windows 原生机制,出现本身不反常。
- 可以选择多种触发方式,不只限于开机启动。
- 能以较稳定的方式在系统重启、用户登录、时间到达后再次拉起。
- 在进程树中容易和正常系统调度行为混在一起。
如果银狐前面已经拿到管理员甚至 SYSTEM,上任务计划就会让它的驻留能力更稳定。
检测
EDR 真正有效的地方,不是只看某一个 API,而是把“注册任务”和“任务实际执行”串成一条行为链。
任务变更
这类检测关注的是持久化动作本身。
常见信号包括:
- 任务计划相关注册表路径新增或修改,尤其是
TaskCache\Tree、TaskCache\Tasks。 %SystemRoot%\System32\Tasks\下任务 XML 新建、覆盖或属性异常变化。Microsoft-Windows-TaskScheduler/Operational日志中出现任务注册、更新、启用、运行事件。- 与任务管理相关的系统调用或 COM 调用,出现在不常见的进程上下文中。
这类规则往往适合做:
“谁在改任务”
的告警。
RPC 行为链
针对直接 RPC 的样本,单看 schtasks.exe 已经不够。若遥测能够识别 Task Scheduler RPC 活动,应优先关联下面这条链:
未知、未签名或来源异常的进程
↓
请求 ITaskSchedulerService
↓
SchRpcRegisterTask
↓
Schedule 服务创建或更新任务
↓
SchRpcRun(短时间内,可选)
↓
SchRpcDelete(短时间内,可选)
其中“创建后迅速运行、删除”的组合尤其值得关注:它可能表示一次性执行,而不是传统意义上的长期持久化。应继续检查任务动作是否指向 ProgramData、Users\Public、AppData 等用户可写目录,以及被调度进程后续是否出现联网、注入或自恢复行为。
RPC 调用留下的痕迹
直接 RPC 不等于无痕。它改变的主要是注册动作的调用链和写入主体:恶意进程请求服务,而 Schedule 服务代表调用者完成任务的校验、登记与后续调度。
传统命令行路径
可疑进程
↓
schtasks.exe
↓
任务计划服务
直接 RPC 路径
可疑进程
↓
本地 RPC
↓
svchost.exe(Schedule)
↓
写入任务状态 / 调度任务动作
因此,它绕过的是“schtasks.exe 作为明显中间进程”的检测习惯,不是把计划任务产生的状态和执行结果一起消掉。
不会出现的痕迹
任务计划 RPC 使用的是已存在的 Schedule 服务。注册任务本身通常不会新建 Windows 服务,也不会在服务注册表树下新增一个服务项。
不会因为注册任务而出现
CreateService / OpenSCManager
sc.exe create
HKLM\SYSTEM\CurrentControlSet\Services\<新服务>
同样,SchRpcRegisterTask 的职责是提交和登记任务定义,而不是立刻启动任务动作。若样本仅完成注册、等待登录或定时触发,那么注册阶段通常也没有新的载荷进程;不能把“发现任务文件创建”直接等同于“载荷已经执行”。
服务侧的任务状态
一旦 Schedule 服务接受注册请求,典型可见结果包括:
%SystemRoot%\System32\Tasks\下新增或更新任务定义文件。HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache\Tasks与TaskCache\Tree中的映射或缓存变化。- 任务计划 Operational 日志中的注册、更新、启用或删除记录。
这些文件和注册表项通常由 svchost.exe -s Schedule 所承载的服务写入,而不是 RPC 客户端进程直接写入。因此,若规则只寻找“可疑 EXE 直接创建 Tasks 文件”或“可疑 EXE 直接改 TaskCache”,直接 RPC 路径会遗漏;规则应改为关联服务侧写入前的异常 RPC 客户端。
对一次成功的新建标准任务而言,TaskCache 和任务定义文件是最稳定的两类落点;但“必然新建”应限定为新任务创建成功。若是更新已有任务,应观察到对应对象的修改;若 RPC 请求失败或仅做验证,则未必有任何持久化对象变化。
异常 RPC 客户端
↓
Schedule 服务
├── 创建 / 更新 Tasks 文件
├── 更新 TaskCache
└── 写入任务计划事件
运行阶段的进程痕迹
只有任务真正被触发,或样本另行调用 SchRpcRun 请求立即运行时,才会进入动作执行阶段。此时任务计划器会启动任务 XML 定义的动作;在不同 Windows 版本、任务类型、运行账户和会话条件下,遥测中可能看到 svchost.exe (Schedule)、taskeng.exe 或 taskhostw.exe 等任务计划相关组件参与调度。
更稳妥的判断方式不是写死某一个父进程名,而是关联:
任务注册或立即运行事件
↓
任务计划相关服务 / 宿主
↓
任务 XML 中定义的动作
↓
payload.exe、脚本解释器或加载器
如果随后调用 SchRpcDelete,任务文件、TaskCache 项和删除事件可能很快消失或被覆盖;但时间线中的文件、注册表、任务计划日志和进程创建遥测仍可能保留证据,保留时长取决于审计配置与日志轮转。
日志与遥测边界
可用日志会因系统审计和安全产品配置而不同,不能假定每台主机都能直接看到 RPC 方法名。常见证据源可按优先级理解为:
| 证据源 | 通常可回答的问题 | 局限 |
|---|---|---|
| EDR / ETW 的 RPC 遥测 | 哪个进程请求了 Task Scheduler RPC 接口,部分产品还能看到方法 | 是否采集接口 UUID、方法或请求内容取决于产品配置 |
Microsoft-Windows-TaskScheduler/Operational | 任务何时注册、启动、完成、删除 | 不一定直接给出原始 RPC 客户端或完整 XML |
| 文件与注册表审计 / EDR | 哪个服务进程写入任务文件和 TaskCache | 通常看到的是 Schedule 服务,而不是最初的 RPC 客户端 |
| Security 审计(如已启用相应审核) | 与计划任务创建、修改、删除、运行相关的安全事件 | 依赖高级审核策略,主机间覆盖并不一致 |
| 进程创建遥测 | 任务最终启动了什么、后续做了什么 | 如果任务只注册未运行,或日志已轮转,就无法独立证明创建来源 |
| NTFS USN Journal(如可获取) | 任务文件的创建、重命名、删除等文件系统变更时间线 | 只说明卷上的文件变化,不能单独还原 RPC 客户端;旧记录可能被覆盖 |
所以最有价值的证据不是孤立的一条“RPC 事件”或一条文件写入,而是按时间把客户端、服务侧落点、任务定义和执行结果串起来。
对标准任务计划的 Operational 日志,常见可关注的事件包括:
| Event ID | 常见含义 |
|---|---|
| 106 | 任务已注册 |
| 140 | 任务已更新 |
| 141 | 任务已删除 |
| 100 | 任务已启动 |
| 102 | 任务已完成 |
| 200 / 201 | 任务动作启动 / 完成 |
这些事件是否存在及字段细节仍取决于启用状态、Windows 版本和日志保留策略;它们通常能证明任务生命周期,却不一定直接给出最初 RPC 客户端或完整的原始 XML。
来源上下文
不是所有创建任务的行为都可疑,但父子进程和来源上下文经常很有价值。
例如下面这些组合通常值得提高优先级:
- Office、压缩包解压目录、下载目录中的可执行文件随后创建或修改任务。
- 被注入宿主进程、脚本解释器、临时目录程序去碰任务计划接口。
- 没有运维背景的普通用户程序突然大量访问任务计划组件。
如果 EDR 能关联:
钓鱼附件 / Loader
↓
提权或注入
↓
任务注册
↓
计划任务触发恶意进程
那条链的置信度通常会很高。
任务内容
很多高价值检测并不在“有没有任务”,而在“任务执行什么”。
EDR 或检测规则通常会重点关注:
- 任务动作指向用户目录、临时目录、
ProgramData、回收站伪装路径等非常见位置。 - 动作调用
cmd.exe、powershell.exe、wscript.exe、mshta.exe、rundll32.exe等中转解释器。 - 任务动作路径与文件签名、文件创建时间、父进程来源明显不匹配。
- 任务名伪装成系统组件,但落地路径和作者信息不可信。
- 任务 XML 中隐藏属性、异常运行账户、异常触发频率等特征。
这类检测的核心是:
任务看起来像系统任务,但执行内容不像系统任务
执行结果
因为很多恶意样本最终都会体现在执行链上,所以 EDR 很常做下面这类关联:
svchost.exe -k netsvcs -p -s Schedule之后触发出异常子进程。taskeng.exe、taskhostw.exe、svchost.exe触发的进程路径落在用户可写目录。- 被拉起进程随后又表现出联网、注入、释放文件、禁用安全软件等高危动作。
- 同一任务被反复短周期触发,像“自恢复”而不是正常计划作业。
这类检测关注的是:
“任务计划服务帮谁把谁拉起来了”
而不是孤立地看 svchost.exe 本身。
关联分析
银狐样本的危险之处,往往不是“它会创建计划任务”这么简单,而是它会把多种能力接起来。
因此更成熟的 EDR 会做跨阶段关联,例如:
- 先出现 UAC 绕过或管理员权限获取。
- 紧接着启用
SeDebugPrivilege或访问高权限进程。 - 随后由高权限上下文创建计划任务。
- 后续计划任务拉起的进程又进行注入、通信或对抗安全产品。
当这些动作出现在同一主机、同一时间窗、同一进程家族里时,单点噪音就会变成一条很强的入侵链证据。
排查
如果你已经在某台机器上看到了可疑的 Schedule 相关行为,实战排查通常可以先看:
C:\Windows\System32\Tasks\中最近新增或修改的任务文件。HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache\TreeHKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache\TasksMicrosoft-Windows-TaskScheduler/Operational日志。- 由
taskeng.exe、taskhostw.exe、svchost.exe -s Schedule触发的后续进程树。 - 任务动作指向的 EXE、DLL、脚本、快捷方式、中转命令是否近期落地。
如果排查对象是银狐,还要特别结合:
- 是否存在注入到
explorer.exe、svchost.exe、VSSVC.exe一类宿主的迹象。 - 是否同时存在 watchdog、提权、
SeDebugPrivilege、网络回连等链路证据。 - 计划任务是不是只是“恢复执行”的一环,而非唯一落点。
案例
你给的奇安信文章《【天穹】新瓶旧酒——银狐钓鱼再现江湖》很适合作为这一点的案例。
从文中公开分析可以提炼出下面这条链:
初始样本运行
↓
内存加载 PE 模块
↓
检查权限并尝试提升权限
↓
启动并注入 VSS 服务进程(失败时回退 explorer.exe)
↓
释放额外文件
↓
通过任务计划相关 COM 接口写入计划任务
↓
在后续阶段运行 Schedule 服务对应链路
↓
创建 / 拉起新的 svchost 宿主并注入后门模块
↓
后门常驻并连接远端 C2
这里有两个很值得记住的点。
第一,文章里的重点并不是:
svchost 直接带参数执行了银狐 EXE
而是:
银狐先通过高权限进程和任务计划机制建立起可持续调度链路
第二,文章说明了任务计划并不是孤立动作,而是夹在:
提权 / 注入
和
后门常驻 / 通信
之间的中间环节。
这也是为什么在主机取证时,不能只看到一个任务就结束分析,而要继续往前追它是谁创建的,往后追它又拉起了什么。
简化理解
如果想把这篇笔记压缩成一句话,可以记成:
银狐不是“把自己写进 svchost 参数里”,而是“把自己挂到任务计划这条系统调度链上”,再借 Schedule 服务把恶意动作稳定带出来。
参考链接
- 奇安信天穹沙箱案例:《【天穹】新瓶旧酒——银狐钓鱼再现江湖》: tianwen.qianxin.com
- Microsoft Learn: Task Scheduler start page: learn.microsoft.com
- Microsoft Learn: Task Scheduler schema: learn.microsoft.com
- Microsoft Open Specifications: [MS-TSCH] Task Scheduler Service Remoting Protocol
- ESET Research: RomCom exploits Firefox and Windows zero days in the wild
- ShadowScheduler(第三方 PoC,未文档化 WP TaskScheduler 私有 RPC):miunasu/ShadowScheduler