Cobalt Strike Remote Threads detection

Update Nov 30 2018:> Found a way to change this behavior on Cobalt Strike, added at the bottom

Update Dec 6 2017:> The splunk app is available now here

I was playing around a bit with a cool new C# tool one of my colleagues created, NoPowerShell. This allows an attacker to execute certain PowerShell commands from Cobalt Strike without having to use PowerShell itself.

As a blue teamer I obviously want to be able to detect this, so I set to work in the lab and started looking at where Sysmon could help me out. Since NoPowerShell relies on Process Injection to do its business I started looking at the “Create Remote Thread” events and soon I noticed something interesting.

Every process injected bij Cobalt Strike is injected into a memory address which is starting from the same last 4 bytes on every thread.

Every injected thread ends with 0B80

I’ve tried this on several hosts and changing the malleable profiles to use different target processes but this behavior seems to be consistent.

Detection

Creating a use case is as simple as the above example. You want to search your Sysmon data for;

  • EventCode / event_id 8
  • StartAddress / target_process_address ending with 0B80

In this screenshot I’ve been using a PowerShell beacon, this can be anything so using this as an indicator is pointless, as with the target process.

I’ve incorporated it into my ThreatHunting app, which will be released at BlackHat EU next week on Dec 5th. A detection of the event will look like this:

Drilling deeper into that event will show;

  • a visual representation of the injection,
  • all subprocesses spawned by powershell.exe
  • the originating process, launching the beacon,
  • the beaconing traffic
  • and the remote thread events.

I’ll keep working on ways for Cobalt Strike to not cause this issue as well as digging deeper into the in-memory part of the processes to be able to detect them.

Evasion

It seems there is a way to change this default behavior by using the following code in a malleable profile;

{ stage
transform-x86 { # transform the x86 rDLL stage
    prepend "\x90\x90\x90\x90\x90\x90\x90\x90\x90"; # prepend 9 nops
}
    transform-x64 { # transform the x64 rDLL stage
    prepend "\x90\x90\x90\x90\x90\x90\x90\x90\x90"; # prepend 9 nops
}
}

Obviously there are variations possible here. The point is you, as a red teamer, want to be invisible. By adding 9 nops only the last character of the Start Address / target_process_address of the injected thread will change. There might be a point by when adding too many null bytes can cause instability.

Detection strategy

Doing this will bypass detection of the rule mentioned above, this obviously can be changed or widened. This probably will introduce some more false positives. I believe going for the “0B80" still remains a valid detection, most red teams/adversaries won’t know about this thus won’t change the default.

On top of this baseline injection behavior in your environment, this is not that common that you get swamped by data anyway. Create an alert on outlier processes receiving injects by uncommon sources. Also be aware or lateral movement before discounting multiple hosts with similar behavior.

Using a Cobalt Strike Malleable profile will be a global setting so again the Start Address / target_process_address of the injected thread will be identical across all systems targeted by this method.

Thanks to c_apt_ure, Scouby and mika for validating detection in their environment. Thanks to @_vivami for providing a mallable profile to evade this.