浅析windows计划任务

win计划任务的变迁及其非常规排查

研究背景

在server2012上使用schtasks创建计划任务时,我们意外的发现,当分别使用参数/mo和/ri时,计划任务创建的方式有所不同,具体如下图: 在这里插入图片描述

在这里插入图片描述我们在其参数说明中也未见对此现象的具体描述 在这里插入图片描述
且在计划任务管理器上发现其区别似乎只在于触发器的不同。
因此,抱着一探究竟的想法,我花了大致一周左右的时间研究并整理了此文,来为windows计划任务的相关问题提供些绵薄之力。

本文中所研究的计划任务均由schtasks.exe创建。

探索原因

为了一探究竟,在server2012上,我们分别对两种启动流程进行了追踪,在初步的了解之后,我们发现随着windows系统的变迁,计划任务的相关进程的启动和计划任务的创建有旧版和新版之分,为了更好的理解,结合研究的内容下述依次对新旧版进行大致的说明。

旧版的计划任务

进程启动

通过对计划任务的监控,我们发现,在server2012上,计划任务进程的启动,主要依赖于计划任务文件的读取和注册表项配置的访问。

当使用参数/ri时,计划任务进程创建的堆栈如下图所示:

在这里插入图片描述
我们发现此时其进程创建的关键模块为schedsvc,通过堆栈我们可以大致看出此类计划任务进程的创建由schedsvc管理,schedsvc会启动回调job,从队列中捕获到并启动计划任务的job,进而创建计划任务进程。
在schedsvc.dll中,我们可以清晰的看到,计划任务进程创建时,实际上创建的是taskeng.exe进程,这也解释了为什么我们看到此类计划任务的父进程是taskeng。
在这里插入图片描述
在这一参数创建的计划任务进程启动时,我们发现,schedsvc.dll与taskeng分工明确,schedsvc.dll主要负责注册表中对应计划任务的读取及更新,其中比较关键的行为是:schedsvc会负责从计划任务的job队列中启动计划任务,并且将计划任务进程的执行时间写入注册表项DynamicInfo中
写入注册表项DynamicInfo的过程如下
在这里插入图片描述在这里插入图片描述
在这里插入图片描述
写入注册表的值如下
在这里插入图片描述
写入后注册表中对应项的值如下
在这里插入图片描述
通过一定的分析,我们发现,在DynamicInfo注册表项中记录的二进制偏移+c处的内容正是其计划任务执行的UTC时间信息。
在这里插入图片描述
通过对计划任务进程行为的追踪,我们发现,每次计划任务进程被创建时,DynamicInfo注册表项均会被更新,也即是说,通过对注册表中的DynamicInfo的监控及其中时间数据的解析,我们可以得知某计划任务的进程在某时刻被执行,从而定位到对应的计划任务。
taskeng则主要负责\Windows\System32\Tasks目录下的计划任务文件的读取及启动对应的计划任务进程
Task文件读取如下
在这里插入图片描述
计划任务进程创建如下
在这里插入图片描述

当将参数更改为/mo时,我们发现此时的进程创建的堆栈完全改变了,如下图

在这里插入图片描述
我们可以看到,之前的schedsvc模块已经完全看不到了,取而代之的是UBPM和EventAggregation,关于UBPM,其全称是:统一后台进程管理器。它是自Windows 7和Windows Server 2008 R2引入的一种新的调度引擎,关于其更多的介绍,可以参考文末参考链接。而EventAggregation,其描述为“用户态的Event Aggregation库”(Event Aggregation User Mode Library)
在这里插入图片描述
从搜索引擎中,我们暂未找到关于其更详细的介绍,只知道其大致为事件聚合相关的用户态库,通过对计划任务进程行为的分析,我们可以看到,在计划任务进程启动的过程中,其主要扮演着对计划任务事件的处理、通知以及信号分发的角色。
结合相关堆栈,我们初步认为UBPM主要负责捕获被称之为Trigger的信号,当Trigger到达时,便会执行对应的TriggerActions启动计划任务进程,这一部分会在后文现代的计划任务中进行部分说明。
在winserver2012,根据UBPM的代码逻辑,计划任务进程启动时会在如下路径生成对应的计划任务ID的服务日志文件,并将计划任务的报告信息写入文件中。
在这里插入图片描述
其中记录的依然是计划任务进程执行的时间信息,依旧为UTC时间。
在这里插入图片描述
实际上,这也为我们提供了一个找到旧版UBPM调度引擎启动的对应计划任务的方法,我们可以直接在\Windows\System32\LogFiles\Scm文件夹,找到最新的文件,再根据文件名,在注册表中定位到计划任务的ID,从而定位到计划任务。

计划任务的创建

对于上述提到的/ri和/MO的这两个参数,计划任务创建的过程大致相同,在server2012上,负责计划任务创建的关键模块为schedsvc,在计划任务创建的过程中其会执行一系列操作,这里只简要对其中的关键行为进行说明,暂不做进一步挖掘。在后文中的现代的计划任务中也会做部分补充。
获取task文件夹的安全描述符进行权限检查
在这里插入图片描述
读取对应注册表项的中的关键项信息
在这里插入图片描述在这里插入图片描述在这里插入图片描述
创建计划任务文件
在这里插入图片描述
设置对应计划任务在注册表中各子项的值
在这里插入图片描述
在这里插入图片描述
即在计划任务创建的过程中,schedsvc主要负责获取相关计划任务的安全权限并对其进行检查,随后会对关键的注册表项TaskCache\Tasks\{ID}进行读取并创建计划任务文件,然后根据对关键注册表项的读取结果,再将注册表的各对应子项写入计划任务的相关内容。

现代的计划任务

然而,当我们把目光聚焦到较新的操作系统时,我们发现变化已然发生。

进程启动

在较新的windows版本中(此测试版本为win10 19042.685),我们发现在相关的schedsvc模块中旧版中的相关进程启动函数逻辑已经找不到了,之前的taskeng的执行逻辑也不复存在了。
在这里插入图片描述
取而代之的是,无论采用是 /MO还是/RI参数创建计划任务,计划任务的执行流程都统一由UBPM管理,其执行流程也和server2012上的略有不同,其堆栈情况如下 在这里插入图片描述
我们可以看到UBPM依然在Trigger到达后,会对其进程处理,但是实际上多了一层封装
在这里插入图片描述
经过分析,此处,handle函数的参数a3是一个UBPM_TRIGGER_CONSUMER_BLOCK 结构体,此结构体随后会被作为参数传递到UbpmpPerformTriggerActions函数。a3+0x18偏移处是一个UBPM_INPUT_ACTION_PARAMS结构体,此值会被传入UbpmpLaunchExeAction函数的第一个参数用来启动对应的计划任务action。最终UBPM_INPUT_ACTION_PARAMS结构体会在UbpmpLaunchExeAction函数中进行解析后作为参数传递给UbpmpLaunchOneTask用来启动计划任务进程。
在这里插入图片描述
在UBPM_INPUT_ACTION_PARAMS结构体中记录了计划任务名称,计划任务内容等相关信息如下:
在这里插入图片描述但是由于这些相关的结构体均未文档化,想要对其结构体进行进一步更详细的逆向分析需要耗费较长的时间,这也不是本文的目的,因此此处不做过多的展开。
通过对进程创建过程中的行为的跟踪发现,现代的计划任务进程启动的过程中,更多的依赖于注册表内容的读取而非计划任务文件的读取,单纯靠计划任务文件的检测已经很难有所效果。
此外,server2012中提到UBPM模式下的scm路径下的日志文件也不复存在,取而代之的是报告信息被写入到注册表DynamicInfo项中。与taskeng中的启动流程中的操作极为相似,其中依然包含计划任务进程的时间信息,在偏移+C处为其时间信息,依然采用的UTC时间。
在这里插入图片描述 在这里插入图片描述在这里插入图片描述
这也意味着,在现代的UBPM调度引擎启动的计划任务中,对计划任务的检测方向已经转移到了注册表中。我们需要对注册表DynamicInfo项中的数据进行解析,从而来确定某ID对应的计划任务进程在某一时刻曾被启动。

计划任务的创建

在现代的计划任务创建的流程中,我们发现对计划任务创建进行管理的关键模块依然是schedsvc.dll,但与server2012上的也有所不同,由于schedsvc模块功能多样且复杂,此处重点对SchRpcEnumTasks和SchRpcRegisterTask做一下补充说明。
在计划任务创建的过程中,schedsvc的SchRpcEnumTasks,SchRpcRegisterTask函数起到关键作用。SchRpcEnumTasks函数会对计划任务的关键注册表进行检索。
SchRpcEnumTasks主要负责对TaskCache\Tree注册表下各项计划任务的的SD(由于较新的系统上,注册表项中已引入SD子项,此处不再通过文件获取SD而是直接读取注册表下的读取),ID,Index等内容进行检索。
SchRpcEnumTasks实际调用的是RpcServer::EnumFolder函数,在EnumFolder函数中首先会调用RegTreeEntryOpen来打开目标计划任务在tree中的对应注册表。
在这里插入图片描述
其后,在RpcServer::EnumFolder函数中,其会调用JobStore::RegJobSecurityQuery对SD进行检索,调用JobStore::RegGetTreeInfo对ID和Index进行检索,同时还会做一些权限的获取行为,调用FolderEnumerator::FindNext进行循环遍历。
在这里插入图片描述
在这里插入图片描述
SchRpcRegisterTask负责管理具体计划任务的创建注册行为。
SchRpcRegisterTask实际调用的是RpcServer::RegisterTask函数,在其中经过一系列的判断后,会调用RegTaskEntryCreate函数进行计划任务注册表项的实际创建和值的设置或调用UpdateTaskEntry进行更新。
在这里插入图片描述
其关键函数调用及对应注册表项与函数的创建关系大致如下图,其中箭头代表API调用,圆圈代表设置的注册表项,从上到下为大致的执行流程逻辑,通过这些函数关系,我们可以进一步探索对应注册表项里数据的更多含义,由于关系较为复杂,限于篇幅和时间,此处仅将其重要流程图做以展示,不做更多的展开。在这里插入图片描述
当大部分注册表项相关值设置完毕后,schedsvc会调用JobStore::XmlSaveTaskFile函数将计划任务的内容写入到对应的tasks文件夹中的文件中,如\Windows\System32\Tasks\test。
在这里插入图片描述
在这里插入图片描述在这里插入图片描述
至此计划任务基本创建完毕。

计划任务的检测与排查

在前文中,我们断断续续的提到了部分关于计划任务检测与排查的思路,在这里,我们做下简单的总结,结合前文内容,我们可以将计划任务分为两大类。

计划任务ID的获取

对比server2012与win10中的计划任务的创建与启动情况,我们发现,在现代的计划任务中,微软对注册表的青睐显然更高一些,通过对计划任务进程创建的追踪我们发现,在传统的以taskeng进程启动的计划任务与现代的UBPM方式启动的计划任务的这一类方式中,在计划任务进程启动时,关于计划任务的执行时间信息均会被即时地记录在TaskCache\Tasks\{ID}\DynamicInfo注册表项中。
对于此类计划任务,我们可以通过sysmon这类工具进行主动检测。设置简单的规则对DynamicInfo注册表进行检测,随后通过sysmon的日志进行确认排查
当系统中有计划任务启动时,命中的sysmon规则效果如下图:
在这里插入图片描述
此外,结合上述分析,我们也可以自己写代码对对应注册表项进行遍历解析,获取计划任务的执行时间。核心代码如下:
在这里插入图片描述
代码获取到计划任务内容如下:
在这里插入图片描述
旧版的UBPM启动的这一类计划任务中,由于微软贴心地为我们提供了对应的计划任务日志文件,这使得我们可以直接通过文件的修改时间信息对计划任务进行定位,对应的文件路径通常为\Windows\System32\Tasks\{ID},如下:
在这里插入图片描述

定位注册表中的计划任务

通过前文的方式获取到计划任务的ID后,我们就可以直接通过ID在注册表中搜索或直接在\Schedule\TaskCache\Tasks{ID}注册表位置找到对应注册表项,如下:
在这里插入图片描述
在其中我们可以看到关于计划任务的具体信息,在一定的情况下,只借助注册表中的计划任务信息,计划任务也可以被正常启动。因此,当排查到恶意的计划任务项时,我们需要同时清理C:\Windows\System32\Tasks\目录下的计划任务文件和上图中的对应ID的计划任务注册表内容。

通过windows系统日志排查计划任务

此外,通过windows系统自带的事件查看器,我们也能对计划任务进行定位和排查启动的计划任务的相关事件,再通过对应的计划任务事件名在注册表Schedule\TaskCache\Tree{name}或C:\Windows\System32\Tasks\目录下找到对应计划任务获取到对应的计划任务ID
在这里插入图片描述在这里插入图片描述
获取到ID后,再按照前文所述内容进行进一步的定位操作即可。

小结

从传统的taskeng及旧版的UBPM的混合使用到现代的UBPM,我们可以看到,微软似乎更倾向于从普通文件的记录转换到注册表中的统一管理,实际上,在现代的计划任务中,单纯的通过检测计划任务的文件,对于计划任务的实时检测来说是收益甚微的,因此,这也在提醒我们在计划任务的检测中,我们的检测重心也应该向注册表倾斜。实际上,关于计划任务相应注册表的各项含义还可以做进一步的挖掘来对计划任务做更深的了解,但由于篇幅和时间所限,本次研究在一些地方也只是浅尝辄止。
纯的通过检测计划任务的文件,对于计划任务的实时检测来说是收益甚微的,因此,这也在提醒我们在计划任务的检测中,我们的检测重心也应该向注册表倾斜。实际上,关于计划任务相应注册表的各项含义还可以做进一步的挖掘来对计划任务做更深的了解,但由于篇幅和时间所限,本次研究在一些地方也只是浅尝辄止。
本文仅从schtasks创建的计划任务出发,对旧版的计划任务和现代的计划任务的相关过程和关键行为做了简单的分析,并结合分析情况对计划任务的检测提出了一些建议以作抛砖引玉之用,文中若有不恰之处也欢迎讨论指正。

  • 6
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值