作者:REInject@73lab@青藤實驗室
原文鏈接:https://mp.weixin.qq.com/s/ktGug1VbSpmzh9CEGKbbdw
惡意軟件或攻擊者通常使用計劃任務作為他們的持久化機制。從風險發現的角度考慮,理解計劃任務的運行和創建方式以及和計劃任務相關聯的進程是有必要的。此外,本文還分享了一種未公開的計劃任務隱藏方式。
現在,讓我們開始了解計劃任務的一切。
計劃任務的創建方式
MSDN 里對計劃任務的細節信息描述得很詳細了,包括使用的API或工作機制。所以不再重復闡述,只引用一些必要的東西:

計劃任務調度器會根據任務的定義在指定的時間觸發任務,它包含以下組件:

- Triggers:任務觸發的條件
- Actions:任務運行的時候執行的動作
- Principals:指定運行任務的用戶或用戶組信息
- Settings:指定影響任務行為的其他設置
- Registration Information:包含任務創建時間、創建人等信息
- Data:執行任務時使用的額外的信息
更多信息可以在 MSDN 找到。
命令行創建計劃任務
命令行創建計劃任務包括 at.exe 和 schtasks.exe。
at.exe
使用 at 命令創建計劃任務的方式:
at 11:11 /every:Sunday,Monday,Tuesday "malware.exe"
以上命令將創建一個計劃任務,在每周日、周一、周二的 11:11 執行 malware.exe。
也可以通過 \\ComputerName 在指定計算機上運行,更多的參數信息參考 MSDN。
下面這些信息可能會幫助排查 at.exe 生成的計劃任務:
- 路徑:
%SystemRoot%\System32\at.exe - 權限: 必須是管理員組用戶
- 排查: 檢查創建任務時的命令行內容,看看可執行程序或者命令是否是惡意的
- 其他:
- at 創建的任務文件位置:
%SystemRoot%\Tasks,關注 At[x].job 文件,x 代表任務的 ID - 和任務相關的 XML 文件位置:
%SystemRoot%\System32\Tasks - 如果任務日志是啟用的,可以排查
應用程序和服務日志/Microsoft/Windows/TaskScheduler/Operational事件日志
schtasks.exe
at.exe 在 windows8 開始就棄用了,之后的系統都是使用 schtasks.exe 創建計劃任務,該命令參數信息如下:
SCHTASKS /Create [/S system [/U username [/P [password]]]]
[/RU username [/RP password]] /SC schedule [/MO modifier] [/D day]
[/M months] [/I idletime] /TN taskname /TR taskrun [/ST starttime]
[/RI interval] [ {/ET endtime | /DU duration} [/K] [/XML xmlfile] [/V1]]
[/SD startdate] [/ED enddate] [/IT | /NP] [/Z] [/F] [/HRESULT] [/?]
schtasks 比 at 更加強大,提供了很多自定義任務時需要的參數。在對計劃任務排查時,我們可能更關注的是執行的任務內容,和它相關的參數是 TR。一般情況創建惡意計劃任務的命令大概是這樣子:
"c:\Windows\System32\schtasks.exe" /Create /SC ONCE /TN KglN9I99 /TR "cmd /c \"start /min C:\ProgramData\KglN9I99.bat\"" /ST 20:21
這個命令中使用了 /TN 指定任務名稱為 KglN9I99,/TR 參數指定運行的惡意命令,/ST 指定了運行時間,/SC 指定運行周期,還可以通過 /ED 參數指定任務終止日期等。
更多關于 schtasks.exe 的用法,參考 MSDN
下面這些信息可能會幫助排查 schtasks.exe 生成的計劃任務:
- 路徑:
%SystemRoot%\System32\schtasks.exe - 權限: 普通用戶。如果要顯式指定高權用戶運行任務,需要該賬戶的賬戶名和密碼信息。
- 排查:
- 檢查調用 schtasks 的父進程信息,是否有權創建任務
- 檢查
/TR參數的值,可執行文件或命令是否是惡意的 - 其他:
- 和任務相關的 XML 文件位置:
%SystemRoot%\System32\Tasks - 如果任務日志是啟用的,可以排查
應用程序和服務日志/Microsoft/Windows/TaskScheduler/Operational事件日志
一旦任務創建,將會自動在目錄 %SystemRoot%\System32\Tasks 生成一個關于該任務的描述性 XML 文件,包含了所有的任務信息。
圖形界面創建計劃任務
win+r 啟動 taskschd.msc

mmc 程序啟動后會直接提權到管理員權限,所以普通用戶可以創建高權限的任務:

選中 Task Scheduler Library ,右鍵 Create Task...,在彈出界面,逐個配置即可:

需要注意的是,通過 taskschd.msc 創建的任務會直接從托管 Task Scheduler 服務的 svchost.exe 進程派生。
代碼創建計劃任務
代碼最終都是和 c:\windows\system32\taskschd.dll 提供的 COM 服務交互,它的 GUID 是 0F87369F-A4E5-4CFC-BD3E-73E6154572DD。
注冊表路徑:
HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{0f87369f-a4e5-4cfc-bd3e-73e6154572dd}

該 COM 組件不支持 Elevation,無法自動提權。
下面是2種創建計劃任務的代碼示例。
csharp
c# 實現的話,為了方便,引用 TaskScheduler 項目進行計劃任務的創建:
using System;
using System.Security.Principal;
using System.Security.AccessControl;
using Microsoft.Win32.TaskScheduler;
using System.Text.RegularExpressions;
namespace SchtaskHidden
{
class Program
{
static void Main(string[] args)
{
//TaskCollection tt = TaskService.Instance.RootFolder.GetTasks(new Regex("test"));
//foreach(Task ti in tt)
//{
// Console.WriteLine(ti.Name);
//}
//System.Environment.Exit(0);
TaskDefinition td = TaskService.Instance.NewTask();
td.RegistrationInfo.Description = "do something";
//td.Principal.RunLevel = TaskRunLevel.Highest;
//td.Principal.LogonType = TaskLogonType.ServiceAccount;
//td.Principal.UserId = "SYSTEM";
TimeTrigger dt = new TimeTrigger();
dt.StartBoundary = DateTime.Now;
dt.Repetition.Interval = TimeSpan.FromMinutes(1);
td.Triggers.Add(dt);
td.Actions.Add("cmd.exe", "/c \"calc.exe\"", null);
Task t = TaskService.Instance.RootFolder.RegisterTaskDefinition(path: "testxxx", definition: td, TaskCreation.CreateOrUpdate, null, null, 0);
Console.WriteLine("success!!");
//TaskSecurity ts = new TaskSecurity(t);
//ts.RemoveAccessRuleAll(new TaskAccessRule(new SecurityIdentifier(WellKnownSidType.ServiceSid, null), TaskRights.Read | TaskRights.Write | TaskRights.ReadAttributes, AccessControlType.Allow));
//t.SetAccessControl(ts);
//Console.WriteLine("success!!");
}
}
}
powershell
$TaskDescr = "test task"
$Author = "thin0"
$TaskName = "test"
$TaskStartTime = [datetime]::Now
$TaskCommand = "cmd.exe"
$TaskArg = "/c calc.exe"
$UserAcct = "$env:userdomain\$env:username"
# $UserAcct = "SYSTEM"
$ScheduleObject = new-object -ComObject("Schedule.Service")
# connect to the local machine.
$ScheduleObject.Connect("localhost")
$rootFolder = $ScheduleObject.GetFolder("\")
$TaskDefinition = $ScheduleObject.NewTask(0)
$TaskDefinition.RegistrationInfo.Description = "$TaskDescr"
$TaskDefinition.RegistrationInfo.Author = "$Author"
#$TaskDefinition.Principal.RunLevel = 1
$TaskDefinition.Settings.Enabled = $true
$TaskDefinition.Settings.AllowDemandStart = $true
$TaskDefinition.Settings.DisallowStartIfOnBatteries = $false
$TaskDefinition.Settings.ExecutionTimeLimit = "PT0S" # See Note Below
$triggers = $TaskDefinition.Triggers
$trigger = $triggers.Create(1) # Creates a "time-based" trigger, 8: system startup
$trigger.StartBoundary = $TaskStartTime.ToString("yyyy-MM-dd'T'HH:mm:ss")
$trigger.Repetition.Interval = 1
$trigger.Enabled = $true
$Action = $TaskDefinition.Actions.Create(0)
$action.Path = "$TaskCommand"
$action.Arguments = "$TaskArg"
$rootFolder.RegisterTaskDefinition($TaskName,$TaskDefinition,6,$UserAcct,$null,3)
powershell 可以不用這么麻煩,有自帶的 cmdlet:
$taskname = "test"
$cmd = "cmd.exe"
$cmdargs = "/c calc.exe"
$username = "$env:username"
#$username = "SYSTEM"
$taskdescription = "test task"
$action = New-ScheduledTaskAction -Execute $cmd -Argument $cmdargs
$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -minutes 1)
$settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Minutes 0) -RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 1)
Register-ScheduledTask -Action $action -Trigger $trigger -TaskName $taskname -Description $taskdescription -Settings $settings -User $username -RunLevel 1 # 1 for highest, 0 for low
刪除命令:Unregister-ScheduledTask -TaskName test -Confirm:$false
深入理解計劃任務
計劃任務的父進程
運行計劃任務的相關服務是: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 項目中得到該注冊表對應的信息:
在 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 格式才有頭緒,這份類型結構清點也許會排上用場。
計劃任務隱藏新姿勢
臆想一下,如果知道注冊表里的所有結構形式及其意義,就可以手動通過添加注冊表的方式進行創建計劃任務,實際對該注冊表項了解的并不是很清楚,所以只能做一些計劃任務隱藏刪除排查的工作。
我對計劃任務隱藏的手法進行深入探究,經過不斷測試,發現兩種隱藏任務的方式。
非完全隱藏
如果想要隱藏一個計劃任務,可以通過修改 Schedule\TaskCache\Tree 中對應任務的 Index 值,一般情況下都是 3,步驟如下:
- 啟動 SYSTEM 權限 cmd:
psexec64 -i -s cmd.exe - 執行 regedit 以 SYSTEM 權限啟動注冊表編輯器
- 修改
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache\Tree下對應任務的 Index 值為0 - 刪除
%SystemRoot%\System32\Tasks下任務對應的 XML 文件
優點: - 利用 taskschd.msc、schtasks 甚至系統API查詢出的所有任務中,都看不到該任務
缺點:
- 并非完全隱藏,如果知道該任務的名字,可以通過 schtasks /query /tn {TaskName} 查到
- 無論是低權的任務還是高權,都需要 SYSTEM 權限(在win10測試,低版本好像沒這個要求,待測試)
測試用例
PS C:\> schtasks.exe /create /tn test /tr "calc.exe" /sc minute /mo 1 /ru "administrator"
成功: 成功創建計劃任務 "test"。
PS C:\> schtasks.exe /query /tn test
文件夾: \
任務名 下次運行時間 模式
======================================== ====================== ===============
test 2021/1/11 14:54:00 就緒
PS C:\> schtasks.exe /query|findstr test
test 2021/1/11 14:55:00 ??
PS C:\> Set-ItemProperty -Path "HKLM:\Software\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache\Tree\test" -Name "Index" -Value 0
PS C:\> schtasks.exe /query|findstr test
PS C:\> schtasks.exe /delete /tn test /f
成功: 計劃的任務 "test" 被成功刪除。
PS C:\>
原理探究
Index 的含義未知,這個暫時不做探討。
完全隱藏
按道理,是可以通過配置任務 SD 實現。在一次測試過程中,偶然刪除了注冊表中的 SD 項,發現無論什么方式都查不到任務信息,達到完全隱藏的目的:
- 啟動 SYSTEM 權限 cmd:
psexec64 -i -s cmd.exe - 執行 regedit 以 SYSTEM 權限啟動注冊表編輯器
- 刪除
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache\Tree\{TaskName}\SD - 刪除
%SystemRoot%\System32\Tasks下任務對應的 XML 文件
優點: - 無論何種方式(除了注冊表),都查不到該任務,較為徹底
缺點: - 無論是低權的任務還是高權,都需要 SYSTEM 權限(在win10測試,低版本好像沒這個要求,待測試)
測試用例
下面是測試用的 powershell 代碼(不通用,里面涉及的二進制內容需要先dump一個正常創建的任務):
$taskname = "test"
$uuid = "{3EC79FBB-0533-4356-89B3-8CE2003F1CD8}"
$cmd = "calc.exe"
New-Item -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache\Tasks\" -Name $uuid
New-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache\Tasks\$uuid\" -Name "Path" -Value "\$taskname" -Type String -Force
New-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache\Tasks\$uuid\" -Name "URI" -Value "\$taskname" -Type String -Force
New-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache\Tasks\$uuid\" -Name "Schema" -Value 0x00010002 -Type DWORD -Force
$triggerstring = "FwAAAAAAAAABBwEAAAAIAAAI/NDz5dYBAAcBAAAACAD//////////zghQUNISEhI38PL80hISEgOAAAASEhISEEAdQB0AGgAbwByAAAASEgAAAAASEhISABISEhISEhIAEhISEhISEgBAAAASEhISBwAAABISEhIAQUAAAAAAAUVAAAAEXy5KUkCH0AHyc8n6AMAAEhISEgsAAAASEhISFQARQBDAEgATABJAFUAMQAwADUANwBcAHQAZQBjAGgAbABpAHUAAAAAAAAASEhISCwAAABISEhIWAIAABAOAACA9AMA/////wcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABISEhI3d0AAAAAAAABBwEAAAAIAAAI/NDz5dYBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8AAAAAAAAAP////8AAAAAAAAAAAAAAAAAAcVLAQAAAAAAAAB2oQAAAAAAAEhISEg="
$triggerbytes = [System.Convert]::FromBase64String($triggerstring)
$actionbytes = [byte[]](0x03,0x00,0x0c,0x00,0x00,0x00,0x41,0x00,0x75,0x00,0x74,0x00,0x68,0x00,0x6f,0x00,0x72,0x00,0x66,0x66,0x00,0x00,0x00,0x00,0x10,0x00,0x00,0x00) + [System.Text.Encoding]::Unicode.GetBytes($cmd) + [byte[]](0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00)
New-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache\Tasks\$uuid\" -Name "Triggers" -Value $triggerbytes -Type binary -Force
New-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache\Tasks\$uuid\" -Name "Actions" -Value $actionbytes -Type binary -Force
New-Item -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache\Tree" -Name $taskname -Force
New-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache\Tree\$taskname" -Name "Id" -Value "{3EC79FBB-0533-4356-89B3-8CE2003F1CD8}" -Type string -Force
New-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache\Tree\$taskname" -Name "Index" -Value 0x3 -Type DWORD -Force
#Remove-Item -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache\Tree\$taskname" -Force
#Remove-Item -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache\Tasks\$uuid" -Force
更通用一些的測試用例:
PS C:\> schtasks.exe /create /tn test /tr "calc.exe" /sc minute /mo 1 /ru "administrator"
成功: 成功創建計劃任務 "test"。
PS C:\> schtasks.exe /query /tn test
文件夾: \
任務名 下次運行時間 模式
======================================== ====================== ===============
test 2021/1/11 14:56:00 就緒
PS C:\> schtasks.exe /query|findstr test
test 2021/1/11 14:56:00 ??
PS C:\> Remove-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache\Tree\test" -Name "SD"
PS C:\> schtasks.exe /query /tn test
錯誤: 系統找不到指定的文件。
PS C:\> schtasks.exe /query|findstr test
PS C:\>
這種方式創建的計劃任務,刪除要相對麻煩一些:
$taskname = "test"
$uuid = (Get-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache\Tree\$taskname" -Name "Id").Id
Remove-Item -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache\Tasks\$uuid"
Remove-Item -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache\Tree\$taskname"
sc.exe stop schedule
sc.exe start schedule
原理探究
經過進程監控,發現在計劃任務信息查詢過程中的流程如下:
- 查詢
HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache\Tree\SD,查的到繼續,查不到則終止查詢 - 遍歷查詢
HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache\Tree\{TaskName}中的 Id、Index、SD等值 - (后面都是猜測)
- 查詢到的 SD 值,會對之后是否有權限查看該任務信息有影響,查不查的到和這個值息息相關
- 根據 SD 值,進行權限檢查
- 如果權限通過,格式化輸出
HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache\Tasks\{TaskId}中的任務詳細信息 - 權限不通過,則進行下一個任務的查詢
- 如果權限通過,格式化輸出
正常任務查詢:

刪除 SD 后的任務查詢:

可見,因為找不到任務的 SD 信息,無法確定用戶是否有權限查看該任務信息,導致系統直接判定無權限查看:

緩解計劃任務的惡意利用
- 配置計劃任務只允許運行在認證用戶上,不可以通過 SYSTEM 賬戶運行,可以在 GPO 中進行配置,參考 Allow server operators to schedule tasks
- 配置只允許 administrators 用戶組對進程的優先級進行修改,可以在 GPO 中配置,參考 Increase scheduling priority
參考
https://docs.microsoft.com/en-us/windows/win32/taskschd/task-scheduler-start-page
https://docs.microsoft.com/en-us/previous-versions/windows/desktop/automat/bstr
https://docs.microsoft.com/en-us/windows/application-management/svchost-service-refactoring
https://attack.mitre.org/techniques/T1053/002/
https://attack.mitre.org/techniques/T1053/005/
https://github.com/libyal/winreg-kb/blob/master/documentation/Task%20Scheduler%20Keys.asciidoc
https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-dtyp/
https://github.com/dahall/TaskScheduler
https://docs.microsoft.com/en-us/windows/win32/taskschd/task-scheduler-start-page
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1464/