Malleable PE, Process Injection, and Post Exploitation

Malleable C2 profiles are more than communication indicators. Malleable C2 profiles control Beacon’s in-memory characteristics, determine how Beacon does process injection, and influence Cobalt Strike’s post-exploitation jobs too. This page documents these extensions to the Malleable C2 language.

PE and Memory Indicators

The stage block in Malleable C2 profiles controls how Beacon is loaded into memory and edit the content of the Beacon DLL.

stage {
	set userwx "false"; 
	set compile_time "14 Jul 2009 8:14:00";
	set image_size_x86 "512000";
	set image_size_x64 "512000";
	set obfuscate "true";

	transform-x86 {
		prepend "\x90\x90";
		strrep "ReflectiveLoader" "DoLegitStuff";
	transform-x64 {
		# transform the x64 rDLL stage

	stringw "I am not Beacon!";

The stage block accepts commands that add strings to the .rdata section of the Beacon DLL. The string command adds a zero-terminated string. The stringw command adds a wide (UTF-16LE encoded) string. The data command adds your string as-is.

The transform-x86 and transform-x64 blocks pad and transform Beacon's Reflective DLL stage. These blocks support three commands: prepend, append, and strrep.

The prepend command inserts a string before Beacon's Reflective DLL. The append command adds a string after the Beacon Reflective DLL. Make sure that prepended data is valid code for the stage's architecture (x86, x64). The c2lint program does not have a check for this. The strrep command replaces a string within Beacon's Reflective DLL.

The stage block accepts several options that control the Beacon DLL content and provide hints to change the behavior of Beacon's Reflective Loader:

Option Example Description
checksum 0 The CheckSum value in Beacon's PE header
cleanup false Ask Beacon to attempt to free memory associated with the Reflective DLL package that initialized it.
compile_time 14 July 2009 8:14:00 The build time in Beacon's PE header
entry_point 92145 The EntryPoint value in Beacon's PE header
image_size_x64 512000 SizeOfImage value in x64 Beacon's PE header
image_size_x86 512000 SizeOfImage value in x86 Beacon's PE header
module_x64 xpsservices.dll Same as module_x86; affects x64 loader
module_x86 xpsservices.dll Ask the x86 ReflectiveLoader to load the specified library and overwrite its space instead of allocating memory with VirtualAlloc.
name beacon.x64.dll The Exported name of the Beacon DLL
obfuscate false Obfuscate the Reflective DLL's import table, overwrite unused header content, and ask ReflectiveLoader to copy Beacon to new memory without its DLL headers.
rich_header Meta-information inserted by the compiler
sleep_mask false Obfuscate Beacon, in-memory, prior to sleeping
stomppe true Ask ReflectiveLoader to stomp MZ, PE, and e_lfanew values after it loads Beacon payload
userwx false Ask ReflectiveLoader to use or avoid RWX permissions for Beacon DLL in memory

Cloning PE Headers

Cobalt Strike's Linux package includes a tool, peclone, to extract headers from a DLL and present them as a ready-to-use stage block:

./peclone [/path/to/sample.dll]

In-memory Evasion and Obfuscation

Use the stage block's prepend command to defeat analysis that scans the first few bytes of a memory segment to look for signs of an injected DLL. If tool-specific strings are used to detect your agents, change them with the strrep command.

If strrep isn't enough, set sleep_mask to true. This directs Beacon to obfuscate itself in-memory before it goes to sleep. After sleeping, Beacon will de-obfuscate itself to request and process tasks. The SMB and TCP Beacons will obfuscate themselves while waiting for a new connection or waiting for data from their parent session.

Decide how much you want to look like a DLL in memory. If you want to allow easy detection, set stomppe to false. If you would like to lightly obfuscate your Beacon DLL in memory, set stomppe to true. If you'd like to up the challenge, set obfuscate to true. This option will take many steps to obfuscate your Beacon stage and the final state of the DLL in memory.

Set userwx to false to ask Beacon's loader to avoid RWX permissions. Memory segments with these permissions will attract extra attention from analysts and security products.

By default, Beacon's loader allocates memory with VirtualAlloc. Module stomping is an alternative to this. Set module_x86 to a DLL that is about twice as large as the Beacon payload itself. Beacon's x86 loader will load the specified DLL, find its location in memory, and overwrite it. This is a way to situate Beacon in memory that Windows associates with a file on disk. It's important that the DLL you choose is not needed by the applications you intend to reside in. The module_x64 option is the same story, but it affects the x64 Beacon.

If you're worried about the Beacon stage that initializes the Beacon DLL in memory, set cleanup to true. This option will free the memory associated with the Beacon stage when it's no longer needed.

Process Injection

The process-inject block in Malleable C2 profiles shapes injected content and controls process injection behavior.

process-inject {
	# set how memory is allocated in a remote process
	set allocator "VirtualAllocEx";
	# shape the memory characteristics and content
	set min_alloc "16384";
	set startrwx  "true";
	set userwx    "false";

	transform-x86 {
		prepend "\x90\x90";

	transform-x64 {
		# transform x64 injected content

	# determine how to execute the injected code
	execute {
		CreateThread "ntdll.dll!RtlUserThreadStart";

The process-inject block accepts several options that control the process injection process in Beacon:

Option Example Description
allocator VirtualAllocEx The preferred method to allocate memory in the remote process. Specify VirtualAllocEx or NtMapViewOfSection. The NtMapViewOfSection option is for same-architecture injection only. VirtualAllocEx is always used for cross-arch memory allocations.
min_alloc 4096 Minimum amount of memory to request for injected content
startrwx true Use RWX as initial permissions for injected content. Alternative is RW.
userwx false Use RWX as final permissions for injected content. Alternative is RX.

The transform-x86 and transform-x64 blocks pad content injected by Beacon. These blocks support two commands: prepend and append.

The prepend command inserts a string before the injected content. The append command adds a string after the injected content. Make sure that prepended data is valid code for the injected content's architecture (x86, x64). The c2lint program does not have a check for this.

The execute block controls the methods Beacon will use when it needs to inject code into a process. Beacon examines each option in the execute block, determines if the option is usable for the current context, tries the method when it is usable, and moves on to the next option if code execution did not happen. The execute options include:

Option x86 ‑> x64 x64 ‑> x86 Notes
CreateThread Current process only
CreateRemoteThread Yes No cross-session
NtQueueApcThread‑s This is the "Early Bird" injection technique. Suspended processes (e.g., post-ex jobs) only.
RtlCreateUserThread Yes Yes Risky on XP-era targets; uses RWX shellcode for x86 -> x64 injection.
SetThreadContext Yes Suspended processes (e.g., post-ex jobs) only.

The CreateThread and CreateRemoteThread options have variants that spawn a suspended thread with the address of another function, update the suspended thread to execute the injected code, and resume that thread. 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. These variants work x86 -> x86 and x64 -> x64 only.

The execute options you choose must cover a variety of corner cases. These corner cases include self injection, injection into suspended temporary processes, cross-session remote process injection, x86 -> x64 injection, x64 -> x86 injection, and injection with or without passing an argument. The c2lint tool will warn you about contexts that your execute block does not cover.

Post Exploitation Jobs

Larger Cobalt Strike post-exploitation features (e.g., screenshot, keylogger, hashdump, etc.) are implemented as Windows DLLs. To execute these features, Cobalt Strike spawns a temporary process, and injects the feature into it. The process-inject block controls the process injection step. The post-ex block controls the content and behaviors specific to Cobalt Strike’s post-exploitation features.

post-ex {
	# control the temporary process we spawn to
	set spawnto_x86 "%windir%\\syswow64\\rundll32.exe";
	set spawnto_x64 "%windir%\\sysnative\\rundll32.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";

The spawnto_x86 and spawnto_x64 options control the default temporary process Beacon will spawn for its post-exploitation features. Here are a few tips for these values:

  1. Always specify the full path to the program you want Beacon to spawn
  2. Environment variables (e.g., %windir%) are OK within these paths.
  3. Do not specify %windir%\system32 or c:\windows\system32 directly. Always use syswow64 (x86) and sysnative (x64). Beacon will adjust these values to system32 where it's necessary.
  4. For an x86 spawnto value, you must specify an x86 program. For an x64 spawnto value, you must specify an x64 program.
  5. The paths you specify (minus the automatic syswow64/sysnative adjustment) must exist from both an x64 (native) and x86 (wow64) view of the file system.

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 accesses to the PEB and kernel32.dll.

The amsi_disable option directs powerpick, execute-assembly, and psinject to patch the AmsiScanBuffer function before loading .NET or PowerShell code. This limits the Antimalware Scan Interface visibility into these capabilities.