Cobalt Strike 3.14 is now available. This release benefits the OPSEC of Beacon’s post-exploitation jobs. To take a screenshot, log keystrokes, dump credentials, or scan for targets: Beacon often spawns a temporary process, injects the capability into it, and receives results over a pipe. While Cobalt Strike has a lot of flexibility around launching temporary processes, it has had too few options for the actions that come next. This release changes that.

Malleable Process Injection, pt. 2

It makes sense to start this discussion with process injection. This is a key part of Beacon’s post-exploitation attack chain. The process injection code-path in Beacon is some of the oldest code in the payload and it was originally designed to “just work” in the myriad of corner cases this offense technique requires. These corner cases are not trivial. x86 -> x86, x86 -> x64, x64 -> x86, and x64 -> x64 in both suspended and not suspended processes are cases that a process-injection implementation needs to account for. The above becomes more complicated as some options fail across desktop session boundaries and others are riskier on older Windows XP-era systems. You can say “I don’t care about Windows XP”, and to some extent I don’t either, but Cobalt Strike’s Beacon circa 2012 had to cope with this.

Cobalt Strike 3.12 made some progress on process injection flexibility. This release picks up where 3.12 left off. Here’s what it looks like:

 process-inject 
{ # set remote memory allocation technique 
set allocator "NtMapViewOfSection";
 
# shape the content and properties of what we will inject 
set min_alloc "16384"; 
set userwx "false"; 

transform-x86 { 
prepend "\x90";
} 

transform-x64 { 
prepend "\x90"; 
} 

# specify how we execute code in the remote process 
execute { 
CreateThread "ntdll!RtlUserThreadStart"; 
CreateThread; 
NtQueueApcThread-s; 
CreateRemoteThread; 
RtlCreateUserThread; 
} 
} 

Remote Memory Allocation

The process-inject -> allocator Malleable C2 option presents two paths to allocate memory within and copy data to a remote process.

The default VirtualAllocEx uses the venerable VirtualAllocEx -> WriteProcessMemory combination. The NtMapViewOfSection option has Beacon create a file mapping in the current process, copy the code to this local section, and map it into the remote process with NtMapViewOfSection. This option is limited to same-architecture target process (VirtualAllocEx is always the fallback).

Code Execution

3.14 also offers control over which techniques Beacon uses to execute code in a remote process and in which order it attempts them. This is done with the process-inject -> execute block. When executing code in a remote process: Beacon examines each option in the execute block, determines if the option is fair game for the current context, tries it if it’s relevant, and stops this process if code execution was successful.

Cobalt Strike’s options include:

  • CreateThread
  • CreateThread “module!Function+0x##”
  • CreateRemoteThread
  • CreateRemoteThread “module!Function+0x##”
  • NtQueueApcThread
  • NtQueueApcThread-s
  • RtlCreateUserThread
  • SetThreadContext

The CreateThread option is specific to self-injection.

The SetThreadContext and NtQueueApcThread-s options are specific to the temporary processes Beacon launches for its post-exploitation jobs. These functions take over the main thread of the suspended process and use it to execute the injected post-exploitation capability. The NtQueueApcThread-s option is Cobalt Strike’s implementation of the so-called Early Bird technique.

NtQueueApcThreadRtlCreateUserThread, and CreateRemoteThread are standard-issue options to inject code into a remote process. The RtlCreateUserThread option has an implementation variant for x86 -> x64 injection. CreateRemoteThread and RtlCreateUserThread both handle x64 -> x86 injection. All other options cover x86 -> x86 and x64 -> x64 injection.

CreateThread and CreateRemoteThread have variants that spawn a thread with the address fo another function, update the suspended thread to execute our code, and then resume the thread. This is useful to duck past techniques like Get-InjectedThread. Use [function] “module!function+0x##” to specify the start address to spoof. For remote processes, ntdll and kernel32 are the only recommended modules to pull from. The optional 0x## part is an offset added to the start address.

With the execute block, you may arrange these functions in an order of preference you’d like to use in your operations. It’s OK to omit or include options to tailor Beacon’s behavior to the adversary capability you’d like to emulate.

Revised Post-exploitation DLLs

Cobalt Strike 3.14 adds a post-ex block to Malleable C2. This block collects options to tweak the content and behavior of the post-exploitation jobs in Cobalt Strike.

 post-ex { 
# control the temporary process we spawn to 
set spawnto_x86 "%windir%\\syswow64\\WerFault.exe"; 
set spawnto_x64 "%windir%\\sysnative\\WerFault.exe"; 

# change the permissions and content of our post-ex DLLs 
set obfuscate "true"; 

# pass key function pointers from Beacon to its child jobs 
set smartinject "true"; 

# disable AMSI in powerpick, execute-assembly, and psinject 
set amsi_disable "true"; 
} 

Existing options such as spawnto_x86, spawnto_x64, and amsi_disable were moved to the post-ex block.

The obfuscate option scrambles the content of the post-ex DLLs and settles the post-ex capability into memory in a more OPSEC-safe way. It’s very similar to the obfuscate and userwx options available for Beacon via the stage block.

The smartinject option directs Beacon to embed key function pointers, like GetProcAddress and LoadLibrary, into its same-architecture post-ex DLLs. This allows post-ex DLLs to bootstrap themselves in a new process without shellcode-like behavior that is detected and mitigated by watching memory reads of the export address table in kernel32 and friends.

Blocking Vendor DLLs

Process injection does not exist in Windows solely to enable offense software. Some security products inject DLLs into user processes too. They do this to get deeper visibility into and veto power over the activities of the process. This is accomplished by hooking functions associated with common offense techniques.

Related: Google found that Chrome users were 15% more likely to experience a crash when injected code is present in the Chrome process space. Google decided to push back and block these DLLs from the Chrome process space. As an offense engineer, I thought it would benefit my processes if I could ALSO block these unwanted third-party DLLs from my processes. It turns out… this is possible.

Use blockdlls start and Cobalt Strike will launch child processes in a way that denies third-party DLLs access to the same process space. This is accomplished by running the process with a binary security policy attribute that restricts DLL loads to Microsoft-signed DLLs only. This is an option present in Windows 10 since late-2017.

Revised Process and File Browser Tabs

The 3.14 process browser now organizes the process information into a tree. Highlight a process in the tree and it will immediately highlight in the right-side detailed view of the processes. Your current process also shows in yellow. This is a lot easier than trying to follow a flat ps output and figure out which process is the parent of which process:

The 3.14 file browser is now (slightly) friendlier to Beacon’s asynchronous communication style. Each file browser now caches the folder/file listings it has seen. A tree on the left-hand side of the file browser shows which known folders are in the cache and which are not. Colored folders are in the cache. Grey folders are not. Click on an uncached folder in the tree and the file browser will ask Beacon to list that folder on its next check-in. In this way, you can revisit folders you’ve already seen, request multiple folders, and queue up multiple actions (e.g., download, execute, delete) in between Beacon checkins.

These changes should make post-exploitation a little more fun.

Check out the release notes to see a full list of what’s new in Cobalt Strike 3.14. Licensed users may use the update program to get the latest.