Detecting Malicious C2 Activity with EDR Telemetry

Dan Lussier
6 min readJan 2, 2021

--

Investigating post-compromise activity, be it simulated or real, with EDR logs can be extremely beneficial to building out advanced detection alerting. The detection rules in this article are written in YARA-L which utilizes regex, but they should be able to be converted to other formats (Sigma, Splunk, etc) as needed.

The first thing to be aware of is exactly how C2 platforms utilize additional tools for recon. Generally speaking, any time a particular malicious payload has commands issued to it from a threat actor it will need to spawn a sacrificial process to complete the task.

Sacrificial process spun up via current payload

That is not to say new tools won’t circumvent this behavior, for example things like Beacon Object Files for Cobalt Strike do not require a sacrificial process and try to load everything via the beacon payload. As more advanced threats begin to utilize this more often, EDR vendors will need to build additional detections for this behavior.

In the meantime, the majority of threat actors, including more advanced threats, will need to spawn a sacrificial process of some sort. Some C2 platforms hardcode this to things like scvhost.exe or rundll32.exe, while others allow for customization of the sacrificial process.

Having a C2 platform that allows for modular process spawn naming conventions is critical for good opsec from an advanced threat perspective, making it far more difficult to detect real from fake as a defender.

“commonProcess.exe” with no parameters

This first detection will be around the usage of common processes being used without parameters. The listed processes below have been seen in both real-world attacks and red team activities.

Let’s take a look at what this process tree may look like

MS Teams launching the sacrificial process chrome_proxy.exe with no parameters. chrome_proxy.exe usually has parameters like: chrome_proxy.exe — profile-directory=Default — app=”https://www.google.com"

Next up, let’s take a look at a rule to detect this behavior

rule c2_spawn_process {
meta:
author = "Dan L"
description = "Identify when a C2 framework is spawning a process without command line parameters."
Version = "3.0"
severity = "High"
mitre_TA = "TA0005 - Defense Evasion"
mitre_T1 = "T1055"
mitre_url = "https://attack.mitre.org/techniques/T1055/" reference_docs = "https://blog.cobaltstrike.com/2016/07/22/why-is-rundll32-exe-connecting-to-the-internet/"
events:$e1.metadata.event_type = "PROCESS_LAUNCH"
$e1.target.process.command_line = /.*\\windows\\(system32|syswow64|sysnative)\\rundll32.exe$/ nocase or
$e1.target.process.command_line = /.*\\windows\\(system32|syswow64|sysnative)\\searchprotocolhost.exe$/ nocase or
$e1.target.process.command_line = /.*\\windows\\(system32|syswow64|sysnative)\\searchindexer.exe$/ nocase or
$e1.target.process.command_line = /.*\\windows\\(system32|syswow64|sysnative)\\svchost.exe$/ nocase or
$e1.target.process.command_line = /.*\\windows\\(system32|syswow64|sysnative)\\dllhost.exe$/ nocase or
$e1.target.process.command_line = /.*\\windows\\(system32|syswow64|sysnative)\\notepad.exe$/ nocase or
$e1.target.process.command_line = /.*\\windows\\(system32|syswow64|sysnative)\\regsvr32.exe$/ nocase or
$e1.target.process.command_line = /.*\\windows\\(system32|syswow64|sysnative)\\mstsc.exe$/ nocase or
$e1.target.process.command_line = /.*\\windows\\(system32|syswow64|sysnative)\\w32tm.exe$/ nocase or
$e1.target.process.command_line = /.*\\windows\\(system32|syswow64|sysnative)\\werfault.exe$/ nocase or
$e1.target.process.command_line = /.*\\windows\\(system32|syswow64|sysnative)\\runonce.exe$/ nocase or
$e1.target.process.command_line = /.*\\windows\\(system32|syswow64|sysnative)\\wuauclt.exe$/ nocase or
$e1.target.process.command_line = /.*\\windows\\(system32|syswow64|sysnative)\\typeperf.exe$/ nocase or
$e1.target.process.command_line = /.*\\windows\\(system32|syswow64|sysnative)\\gpresult.exe$/ nocase or
$e1.target.process.command_line = /.*\\windows\\(system32|syswow64|sysnative)\\gpupdate.exe$/ nocase or
$e1.target.process.command_line = /.*\\windows\\(system32|syswow64|sysnative)\\wusa.exe$/ nocase or
$e1.target.process.command_line = /.*\\windows\\(system32|syswow64|sysnative)\\upnpcont.exe$/ nocase or
$e1.target.process.command_line = /.*\\windows\\(system32|syswow64|sysnative)\\mavinject.exe$/ nocase or
$e1.target.process.command_line = /.*\\windows\\(system32|syswow64|sysnative)\\wecutil.exe$/ nocase or
$e1.target.process.command_line = /.*\\windows\\(system32|syswow64|sysnative)\\msiexec.exe$/ nocase or
$e1.target.process.command_line = /.*\\windows\\(system32|syswow64|sysnative)\\net.exe$/ nocase or
$e1.target.process.command_line = /.*\\windows\\(system32|syswow64|sysnative)\\wudfhost.exe$/ nocase or
$e1.target.process.command_line = /.*\\windows\\(system32|syswow64|sysnative)\\eventvwr.exe$/ nocase or
$e1.target.process.command_line = /.*\\windows\\(system32|syswow64|sysnative)\\arp.exe$/ nocase or
$e1.target.process.command_line = /.*\\windows\\(system32|syswow64|sysnative)\\ping.exe$/ nocase or
$e1.target.process.command_line = /.*\\program.files\\google\\chrome\\application\\chrome_proxy.exe$/ nocase
$e1.principal.hostname = $hostname
match:
// Look for activity within a 1 minute
$hostname over 1m
condition:
$e1
}

The key factors to this rule are to look for a Process Launch (or New Process) event AND one of the many conditions below it such as rundll32.exe, searchprotocolhost.exe or chrome_proxy.exe as seen in the example above. Always make sure to have a nocase statement as well, upper/lower should not matter as threat actors may adjust this.

The next part of the rule is looking for this activity over a 1 minute window and reporting on that single condition if it matches.

Although this rule is very good at catching threat actors post-compromise, more advanced threats will not be utilizing these common process names, knowing the opsec advantage that defenders may have.

Identifying advanced threats via module load events

The next detection is based on the .NET framework triggering module load events while Sharp tools are being spawned and run from a sacrificial process. There are many Sharp tools utilized by threat actors and red teams alike, a quick overview of many of them can be found at SharpCollection.

Let’s quickly walk through what happens when Sharp tools are loaded into memory via various C2 platforms. In this scenario a threat actor already has a beacon on an asset and is now starting to run additional tools for recon and lateral movement.

Many C2 infrastructures have something called “execute-assembly” or something similar, this allows for .NET tools to run in memory without dropping any files to disk. When execute-assembly is run it loads a bunch of modules depending on what the Sharp tool is, but no matter what these default execute-assembly functions will always load more than 1 DLL from C:\Windows\Microsoft.NET\Framework*\v*\*.dll.

NOTE: There are some exceptions to this, such as utilizing a platform like ExecuteAssembly which has tooling to stomp out memory loads, and mask how assemblies are run. Additional analysis needs to be run on this tool to identify detection points via EDR logs, one of the potential detections would be identifying what the assembly is being spawned to like our first detection rule discussed and alert on common processes the tool uses, many threat actors utilize defaults.

Moving on from the exceptions, the following rule will detect the majority of threat actors utilizing Sharp tools.

Let’s take a look at what this process tree may look like

Notice the multiple module loads for files in the .NET framework directory

Now a rule to detect this behavior (Updated 2/24/21 for a more precise $e3)

rule detect_dotNET_into_sacrificial_process {
meta:
author = "Dan L"
description = "This will identify any/all .NET exec's being launched via a C2 framework and utilizing a sacrificial process"
severity = "High"
mitre_TA = "TA0005 - Defense Evasion"
mitre_T1 = "T1055"
mitre_url = "https://attack.mitre.org/techniques/T1055/"
requirements = "EDR must be logging ProcessInjection and Module Load events"
events:// Look for Process Injection
$e1.metadata.product_event_type = "ProcessInjection"
$e1.principal.hostname = $hostname
// Look for the launch of a process
$e2.metadata.event_type = "PROCESS_LAUNCH"
$e2.target.process.file.full_path = /.*exe/ nocase
$e2.principal.hostname = $hostname
// Look for .NET modules being loaded, in particular mscoreei.dll, clrjit.dll or clr.dll
$e3.metadata.event_type = "PROCESS_MODULE_LOAD"
$e3.target.file.full_path = /.*microsoft.net\\.*.(mscoreei|clrjit|clr).dll/ nocase
$e3.principal.hostname = $hostname
match:

$hostname over 1m
condition:
$e1 and $e2 and $e3
}

There are three events we need to correlate here to generate an alert, the first being Process Injection (this is the sacrificial process being created), followed by a .EXE process launching (rundll32.exe, svchost.exe, anything.exe) and finally module load events from the .NET framework directory mentioned above.

Then we’ll check for this activity over a 1 minute window, while looking for the module load event to happen more than once in that 1 minute window. With this you should detect almost all execute-assembly type injection tooling.

There will be a second part to this series where we’ll dive into detecting SMB lateral movement, and detecting the Spawn As command from Cobalt Strike.

Refs

Defensive > Chronicle, LimaCharlie (Robust EDR platform)
Offensive > Cobalt Strike, shad0w, Mythic, SharpC2

--

--

Dan Lussier
Dan Lussier

No responses yet