权限维持-计划任务
深入理解计划任务
计划任务的父进程
运行计划任务的相关服务是:Task Scheduler。该服务使用 svchost.exe 的 netsvcs 组进行托管。
通过进程树看到 svchost.exe 进程的命令行为
svchost.exe -k netsvcs -p -s Schedule
在 Windows 10.1703以下可能看不到 -s Schedule
参数。
taskeng.exe
在旧系统中计划任务的进程派生顺序是这样子的: svchost.exe -> taskeng.exe -> [SpecialTaskProcess]
taskeng.exe 的进程参数语法如下:
taskeng.exe {GUID} [User SID]:[Domain]\[User Name]:[Options]
某些情况下,可能只有 {GUID} 一个参数,Options 参数可能会标识一些其他信息,例如权限信息,如果一个任务运行在高权限模式下,Options 的内容会是:Interactive:Highest[1]
所以在旧的系统中可以查看进程树排查恶意进程,例如找到 taskeng.exe 的进程树,它的父进程为svchost.exe -k netsvcs
。 子进程就是运行中的任务对应的进程,通过排查子进程确定恶意进程。
svchost.exe
win7之后,计划任务托管程序从 taskeng.exe 慢慢迁移到 svchost.exe,这期间计划任务进程派生可能有两种顺序:
- wininit.exe -> services.exe -> svchost.exe -> [SpecialTaskProcess]
- wininit.exe -> services.exe -> svchost.exe -> taskeng.exe -> [SpecialTaskProcess]
从 Windows 10.1511 版本开始,不再有 taskeng.exe 了,新版本系统中找不到该程序,任务进程直接运行在托管 Task Scheduler 服务的 svchost.exe 进程下:
所以高版本系统中,我们可以通过
svchost.exe -k netsvcs
排查进程的子进程来确定恶意或可疑程序。
taskhostw.exe
在上面的图里,可以很明显注意到
svchost.exe -k netsvcs
下还有一个名为 taskhostw.exe 的进程。
在 Windows 7 上该进程名为:taskhost.exe。
在 Windows 8 上该进程名为:taskhostex.exe。
该进程的作用同 dllhost.exe 和 svchost.exe 相似,起到一个 DLL 托管的作用。
通过搜索 %SystemRoot%\System32\Tasks
文件夹,我们能发现一些任务对应的动作不是 Exec,而是 ComHandler。
我们找到一些能起到对比效果的任务 XML,隐藏了一些不必要的字段。
这是我们利用 schtasks.exe 命令创建的任务 XML:
<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
<Actions Context="Author">
<Exec>
<Command>"C:\Program Files (x86)\IObit\Advanced SystemCare\ASC.exe"</Command>
<Arguments>/SkipUac</Arguments>
</Exec>
</Actions>
</Task>
这个是系统 .NET Framework 的计划任务 XML 内容,如果安装了 .NET 框架可以在
%SystemRoot%\System32\Tasks\Microsoft\Windows\.NET Framework
目录下找到:
<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.6" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
<Actions Context="Author">
<ComHandler>
<ClassId>{84F0FAE1-C27B-4F6F-807B-28CF6F96287D}</ClassId>
<Data><![CDATA[/RuntimeWide]]></Data>
</ComHandler>
</Actions>
</Task>
手动触发 .NET Framework 任务,该任务调用 ngentasklauncher.dll ,通过 ProcExp.exe 监控进程观察到的进程树:
可以注意到 taskhostw.exe 的参数 /RuntimeWide
和 xml 中 \<Data\>
标签指定的一样。
在观察 taskhostw.exe 进程树的过程中,发现下面的命令行参数:
taskhosw.exe Install $(Arg0)
这个东西在 MSDN 中做了介绍
由此可知,$(Arg0)
这个参数是在通过 IRegisteredTask::Run[Ex]
接口运行任务时动态指定的。在一些 Windows 默认的任务中常见:
\Microsoft\Windows\Workplace Join\Automatic-Device-Join 任务:
<?xml version="1.0" encoding="UTF-16"?>
<Task xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
<Actions Context="LocalSystem">
<Exec>
<Command>%SystemRoot%\System32\dsregcmd.exe</Command>
<Arguments>$(Arg0) $(Arg1) $(Arg2)</Arguments>
</Exec>
</Actions>
</Task>
\Microsoft\Windows\Maps\MapsToastTask 任务:
<?xml version="1.0" encoding="UTF-16"?>
<Task xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
<Actions Context="Users">
<ComHandler>
<ClassId>{9885AEF2-BD9F-41E0-B15E-B3141395E803}</ClassId>
<Data><![CDATA[$(Arg0);$(Arg1);$(Arg2);$(Arg3);$(Arg4);$(Arg5);$(Arg6);$(Arg7)]]></Data>
</ComHandler>
</Actions>
</Task>
查阅 MSDN 可知,该参数可以通过以下 API 进行传递:
- ITaskHandler::Start
- IRegisteredTask::Run
- IRegisteredTask::RunEx
- IExecAction::put_Arguments
- ITask::SetParameters
计划任务相关注册表项
在一次应急排查中,只能从计划任务日志中看到恶意计划任务在周期性的执行,但却无法通过 taskschd.msc 或 schtasks 查询到恶意任务,并且通过排查 %SystemRoot%\System32\Tasks
目录后仍无法找到它。
在测试中发现,创建计划任务 test 后,无论是手动修改任务 xml 文件,还是删除任务 xml 文件,都无法影响该任务的运行。于是对注册表项进行监控,发现在创建任务后,下面的注册表项发生变化:
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Schedule
下面是从 [winreg-kb](https://github.com/libyal/winreg-kb/blob/master/documentation/Task Scheduler Keys.asciidoc) 项目中得到该注册表对应的信息:
在 XP 时,计划任务注册表路径为
HKEY_LOCAL_MACHINE\Software\Microsoft\SchedulingAgent
Win7 以后发生变化,变成
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Schedule
子项有:
名称 | 描述 |
---|---|
Aliases | 存储AtServiceAccount,默认NT AUTHORITY\System |
CompatibilityAdapter | |
Configuration | |
CredWom | |
Handlers | |
Handshake | |
TaskCache | 存储任务项信息 |
任务项信息除了在磁盘中的 %SystemRoot%\System32\Tasks
下之外,还在下面的注册表项中存在:
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache
Schedule\TaskCache :
名称 | 描述 |
---|---|
Boot | |
Logon | |
Plain | |
Plain | |
Tree |
TaskCache\Tree 子项以任务名称命名,每个任务下的 Value 结构如下:
名称 | 类型 | 描述 |
---|---|---|
Id | REG_SZ | {GUID},任务对应的guid编号 |
Index | REG_DWORD | 一般任务为3,其他值未知 |
SD | REG_BINARY | 该任务项的安全描述信息,二进制值,结构未知 |
每个 Schedule\TaskCache\Tasks%GUID% 对应一个任务,有这些 Value :
名称 | 类型 | 描述 |
---|---|---|
Actions | REG_BINARY | 二进制值,动作信息,中间包含 UNICODE 形式 COMMAND 信息 |
Date | REG_SZ | 任务创建日期? |
Description | REG_SZ | 任务描述 |
DynamicInfo | REG_BINARY | Win7 以下 28 位,Win8 以上 32 位 |
Hash | REG_BINARY | SHA-256 or CRC32, 疑似对应 xml 文件 Hash |
Path | REG_SZ | 在 TaskCache\Tree 中的任务路径 |
Schema | REG_DWORD | |
Triggers | REG_BINARY | 二进制,触发器信息 |
URI | REG_SZ | 任务路径 |
如果是 at 命令创建的计划任务,对应的注册表位置在
Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache\Tree\At1
计划任务的安全描述符(SD)
计划任务的 SD 配置在注册表中的位置:
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache\Tree\{TaskName}\SD
为人类不可读的二进制格式
在一篇关于隐藏windows服务的文章中,了解到通过修改对象的安全描述信息可以达到隐藏的目的。触类旁通一下,计划任务的隐藏应该也是可以通过改变安全描述信息(SD)实现。
Windows 自带的工具 schtasks.exe 并不支持 sd 的设置,需要通过 API 实现。通过查阅 MSDN 并未发现对于 TASK 的 SDDL 该怎么写,相关 API 列表: - IRegistrationInfo::put_SecurityDescriptor - ITaskFolder::CreateFolder - ITaskFolder::CreateFolder - ITaskFolder::RegisterTaskDefinition - ITaskFolder::SetSecurityDescriptor - IRegisteredTask::SetSecurityDescriptor
通过这些 API 可以实现设置 TASK 的 SD 信息。我尝试使用项目 TaskScheduler 进行任务的创建,它用起来会更方便一些:
using System;
using System.Security.Principal;
using System.Security.AccessControl;
using Microsoft.Win32.TaskScheduler;
namespace SchTaskOpt
{
class Program
{
static void Main(string[] args)
{
TaskDefinition td = TaskService.Instance.NewTask();
td.RegistrationInfo.Description = "do something";
td.Principal.RunLevel = TaskRunLevel.Highest;
td.Principal.LogonType = TaskLogonType.ServiceAccount;
td.Principal.UserId = "SYSTEM";
td.RegistrationInfo.SecurityDescriptorSddlForm = @"D:P(D;;DCLCWPDTSD;;;IU)(A;;CCLCSWLOCRRC;;;IU)(A;;CCLCSWLOCRRC;;;SU)(A;;CCLCSWRPWPDTLOCRRC;;;SY)";
DailyTrigger dt = new DailyTrigger();
dt.StartBoundary = DateTime.Now;
dt.DaysInterval = 1;
dt.Repetition.Interval = TimeSpan.FromMinutes(1);
//td.Triggers.Add(dt);
td.Actions.Add("notepad", null, null);
Task t = TaskService.Instance.RootFolder.RegisterTaskDefinition(path:"test2", definition:td, TaskCreation.CreateOrUpdate, null, null, TaskLogonType.ServiceAccount);
Console.WriteLine("success!!");
//TaskSecurity ts = new TaskSecurity(t);
//ts.AddAccessRule(new TaskAccessRule(new SecurityIdentifier(WellKnownSidType.AuthenticatedUserSid, null), TaskRights.Read | TaskRights.Write | TaskRights.ReadAttributes, AccessControlType.Deny));
//t.SetAccessControl(ts);
Console.WriteLine("success!!");
}
}
}
但不幸的是报错了,无论是通过管理员运行还是 SYSTEM,都无法另其正常运作:
PS C:\source\SchTaskOpt\bin\> .\SchTaskOpt.exe
未经处理的异常: System.Runtime.InteropServices.COMException: 异常来自 HRESULT:0xD0000061
在 Microsoft.Win32.TaskScheduler.V2Interop.ITaskFolder.RegisterTaskDefinition(String Path, ITaskDefinition pDefinition, Int32 flags, Object UserId, Object password, TaskLogonType LogonType, Object sddl)
在 Microsoft.Win32.TaskScheduler.TaskFolder.RegisterTaskDefinition(String path, TaskDefinition definition, TaskCreation createType, String userId, String password, TaskLogonType logonType, String sddl)
这个疑问先保留一下吧,需要清楚知道注册表中每个任务自动生成的 SD 格式才有头绪,这份类型结构清点也许会排上用场。
计划任务底层实现
官方文档:https://learn.microsoft.com/zh-cn/windows/win32/taskschd/task-scheduler-start-page
登录自启动代码:https://learn.microsoft.com/zh-cn/windows/win32/taskschd/logon-trigger-example–c—
已知:
我们知道计划任务可以通过UI或者命令行方式进行创建,其参数和选项大部分是对应的。
我们知道计划任务可以通过ITaskService
接口或是TaskSchedulerClass
类以及一系列对象进行操作。
我们知道计划任务可以导出一个XML,通过UI或是命令行均可再将其导入。
我们知道每一个计划任务文件都存放于%SystemRoot%\System32\Tasks
目录下,内容和导出的XML完全相同。
所以,从安全研究的角度,这里可以提出一个问题:计划任务的本质是什么?是那些类,还是XML?
如果是类的话,那么XML在其中充当着什么角色,是如何解析的?
如果是XML的话,那么类充当的又是什么角色?
MS-TSCH协议
以windows task scheduler rpc
为关键字搜索,我们可以找到[MS-TSCH](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-tsch/21e8e86e-ee5a-469d-917f-28a41f3c25a4)
协议,依文档所述,这是建立在RPC协议之上、用于远程对计划任务进行增删改查的接口,同时,我们也看到了熟悉的ITaskSchedulerService
:
微软通过MS-DCERPC
协议,在上层构建了MS-TSCH
协议,该协议通过XML作为参数,实现了对计划任务的管理。
MS-TSCH添加计划任务
https://mp.weixin.qq.com/s/vp-7l2kAihE12h58oTIIRQ
创建计划任务
https://mp.weixin.qq.com/s/vp-7l2kAihE12h58oTIIRQ
Windows的计划任务有多种添加方式,比如通过命令、PowerShell、C++调用COM组件等
C++调用COM组件添加自启动的方法:https://github.com/evilashz/PigScheduleTask
对于开启核晶的360已经失效。
rpc创建
使用的是ITaskSchedulerService
这个RPC接口,接口实现了使用XML来添加计划任务的方法。