Malleable Command and Control


Beacon's HTTP indicators are controlled by a Malleable C2 profile. A Malleable C2 profile is a simple program that specifies how to transform data and store it in a transaction. The same profile that transforms and stores data, interpreted backwards, also extracts and recovers data from a transaction.

To use a custom profile, you must start a Cobalt Strike team server and specify your profile file at that time.

./teamserver [external IP] [password] [/path/to/my.profile]

You may only load one profile per Cobalt Strike instance. If you need multiple profiles during an engagement, start multiple team servers [each with its own profile] and connect to them from one Cobalt Strike client.

Checking for Errors

Cobalt Strike's Linux package includes a c2lint program. This program will check the syntax of a communication profile, apply a few extra checks, and even unit test your profile with random data. It's highly recommended that you check your profiles with this tool before you load them into Cobalt Strike.

./c2lint [/path/to/my.profile]

Profile Language

The best way to create a profile is to modify an existing one. Take a look at the examples on Github.

When you open a profile, here is what you will see:

# this is a comment
set global_option "value";

protocol-transaction {
	set local_option "value";

	client {
		# customize client indicators

	server {
		# customize server indicators

Comments begin with a # and go until the end of the line. The set statement is a way to assign a value to an option. Profiles use { curly braces } to group statements and information together. Statements always end with a semi-colon.

To help all of this make sense, here's a partial profile:

http-get {
        set uri "/foobar";
        client {
                metadata {
                        prepend "user=";
                        header "Cookie";

This partial profile defines indicators for an HTTP GET transaction. The first statement, set uri, assigns the URI that the client and server will reference during this transaction. This set statement occurs outside of the client and server code blocks because it applies to both of them.

The client block defines indicators for the client that performs an HTTP GET. The client, in this case, is Cobalt Strike's Beacon.

When Cobalt Strike's Beacon "phones home" it sends metadata about itself to Cobalt Strike. In this profile, we have to define how this metadata is encoded and sent with our HTTP GET request.

The metadata keyword followed by a group of statements specifies how to transform and embed metadata into our HTTP GET request. The group of statements, following the metadata keyword, is called a data transform.

Step Action Data
0. Start metadata
1. base64 Base64 Encode bWV0YWRhdGE=
2. prepend "user=" Prepend String user=bWV0YWRhdGE=
3. header "Cookie" Store in Transaction

The first statement in our data transform states that we will base64 encode our metadata [1]. The second statement, prepend, takes our encoded metadata and prepends the string user= to it [2]. Now our transformed metadata is "user=" . base64(metadata). The third statement states we will store our transformed metadata into a client HTTP header called Cookie [3]. That's it.

Both Beacon and its server consume profiles. Here, we've read the profile from the perspective of the Beacon client. The Beacon server will take this same information and interpret it backwards. Let's say our Cobalt Strike web server receives a GET request to the URI /foobar. Now, it wants to extract metadata from the transaction.

Step Action Data
0. Start
1. header "Cookie" Recover from Transaction user=bWV0YWRhdGE=
2. prepend "user=" Remove first 5 characters bWV0YWRhdGE=
3. base64 Base64 Decode metadata

The header statement will tell our server where to recover our transformed metadata from [1]. The HTTP server takes care to parse headers from the HTTP client for us. Next, we need to deal with the prepend statement. To recover transformed data, we interpret prepend as remove the first X characters [2], where X is the length of the original string we prepended. Now, all that's left is to interpret the last statement, base64. We used a base64 encode function to transform the metadata before. Now, we use a base64 decode to recover the metadata [3].

We will have the original metadata once the profile interpreter finishes executing each of these inverse statements.

Data Transform Language

A data transform is a sequence of statements that transform and transmit data. The data transform statements are:

Statement Action Inverse
append "string" Append "string" Remove last LEN("string") characters
base64 Base64 Encode Base64 Decode
base64url URL-safe Base64 Encode URL-safe Base64 Decode
mask XOR mask w/ random key XOR mask w/ same random key
netbios NetBIOS Encode 'a' NetBIOS Decode 'a'
netbiosu NetBIOS Encode 'A' NetBIOS Decode 'A'
prepend "string" Prepend "string" Remove first LEN("string") characters

A data transform is a combination of any number of these statements, in any order. For example, you may choose to netbios encode the data to transmit, prepend some information, and then base64 encode the whole package.

A data transform always ends with a termination statement. You may only use one termination statement in a transform. This statement tells Beacon and its server where in the transaction to store the transformed data.

There are four termination statements.

Statement What
header "header" Store data in an HTTP header
parameter "key" Store data in a URI parameter
print Send data as transaction body
uri-append Append to URI

The header termination statement stores transformed data in an HTTP header. The parameter termination statement stores transformed data in an HTTP parameter. This parameter is always sent as part of URI. The print statement sends transformed data in the body of the transaction.

The print statement is the expected termination statement for the http-get.server.output, http-post.server.output, and http-stager.server.output blocks. You may use the header, parameter, print and uri-append termination statements for the other blocks.

If you use a header, parameter, or uri-append termination statement on http-post.client.output, Beacon will chunk its responses to a reasonable length to fit into this part of the transaction.

These blocks and the data they send are described in a later section.


Beacon's Profile Language allows you to use "strings" in several places. In general, strings are interpreted as-is. However, there are a few special values that you may use in a string:

Value Special Value
"\n" Newline character
"\r" Carriage Return
"\t" Tab character
"\u####" A unicode character
"\x##" A byte (e.g., \x41 = 'A')
"\\" \

Headers and Parameters

Data transforms are an important part of the indicator customization process. They allow you to dress up data that Beacon must send or receive with each transaction. You may add extraneous indicators to each transaction too.

In an HTTP GET or POST request, these extraneous indicators come in the form of headers or parameters. Use the parameter statement within the client block to add an arbitrary parameter to an HTTP GET or POST transaction.

This code will force Beacon to add ?bar=blah to the /foobar URI when it makes a request.

http-get {
	client {
		parameter "bar" "blah";

Use the header statement within the client or server blocks to add an arbitrary HTTP header to the client's request or server's response. This header statement adds an indicator to put network security monitoring teams at ease.

http-get {
	server {
		header "X-Not-Malware" "I promise!";

The Profile interpreter will interpret your header and parameter statements in order. That said, the WinINet library (client) and Cobalt Strike web server have the final say about where in the transaction these indicators will appear.


You may configure Beacon's defaults through the profile file. There are two types of options: global and local options. The global options change a global Beacon setting. Local options are transaction specific. You must set local options in the right context. Use the set statement to set an option.

set "sleeptime" "1000";

Here are the available options:

Option Context Default Value Changes
amsi_disable false (Attempt to) disable AMSI for execute-assembly, powerpick, and psinject
dns_idle IP address used to indicate no tasks are available to DNS Beacon; Mask for other DNS C2 values
dns_max_txt 252 Maximum length of DNS TXT responses for tasks
dns_sleep 0 Force a sleep prior to each individual DNS request. (in milliseconds)
dns_stager_prepend Prepend text to payload stage delivered to DNS TXT record stager
dns_stager_subhost .stage.123456. Subdomain used by DNS TXT record stager.
dns_ttl 1 TTL for DNS replies
host_stage true Host payload for staging over HTTP, HTTPS, or DNS. Required by stagers.
jitter 0 Default jitter factor (0-99%)
maxdns 255 Maximum length of hostname when uploading data over DNS (0-255)
pipename msagent_## Name of pipe to use for SMB Beacon's peer-to-peer communication. ## is replaced with a number unique to your team server.
pipename_stager status_## Name of pipe to use for SMB Beacon's named pipe stager. ## is replaced with a number.
sample_name My Profile The name of this profile (used in the Indicators of Compromise report)
sleeptime 60000 Default sleep time (in milliseconds)
spawnto_x86 %windir%\syswow64\rundll32.exe Default x86 program to open and inject shellcode into
spawnto_x64 %windir%\sysnative\rundll32.exe Default x64 program to open and inject shellcode into
tcp_port 4444 TCP Beacon listen port
uri http-get,
[required option] Transaction URI
uri_x86 http-stager x86 payload stage URI
uri_x64 http-stager x64 payload stage URI
useragent Internet Explorer (Random) Default User-Agent for HTTP comms.
verb http-get,
HTTP Verb to use for transaction

With the uri option, you may specify multiple URIs as a space separated string. Cobalt Strike's web server will bind all of these URIs and it will assign one of these URIs to each Beacon host when the Beacon stage is built.

Even though the useragent option exists; you may use the header statement to override this option.

A Beacon HTTP Transaction

To put all of this together, it helps to know what a Beacon transaction looks like and which data is sent with each request.

A transaction starts when a Beacon makes an HTTP GET request to Cobalt Strike's web server. At this time, Beacon must send metadata that contains information about the compromised system.

Tip: session metadata is an encrypted blob of data. Without encoding, it is not suitable for transport in a header or URI parameter. Always apply a base64, base64url, or netbios statement to encode your metadata.

Cobalt Strike's web server responds to this HTTP GET with tasks that the Beacon must execute. These tasks are, initially, sent as one encrypted binary blob. You may transform this information with the output keyword under the server context of http-get.

As Beacon executes its tasks, it accumulates output. After all tasks are complete, Beacon checks if there is output to send. If there is no output, Beacon goes to sleep. If there is output, Beacon initiates an HTTP POST request.

The HTTP POST request must contain a session id in a URI parameter or header. Cobalt Strike uses this information to associate the output with the right session. The posted content is, initially, an encrypted binary blob. You may transform this information with the output keyword under the client context of http-post.

Cobalt Strike's web server may respond to an HTTP POST with anything it likes. Beacon does not consume or use this information. You may specify the output of HTTP POST with the output keyword under the server context of http-post.

Note: while http-get uses GET by default and http-post uses POST by default, you’re not stuck with these options. Use the verb option to change these defaults. There's a lot of flexibility here.

This table summarizes these keywords and the data they send:

Request Component Block Data
http-getclientmetadataSession metadata
http-getserveroutputBeacon's tasks
http-postclientidSession ID
http-postclientoutputBeacon's responses
http-stagerserveroutputEncoded payload stage

HTTP Staging

Beacon is a staged payload. This means the payload is downloaded by a stager and injected into memory. Your http-get and http-post indicators will not take effect until Beacon is in memory on your target. Malleable C2's http-stager block customizes the HTTP staging process.

http-stager {
	set uri_x86 "/get32.gif";
	set uri_x64 "/get64.gif";

The uri_x86 option sets the URI to download the x86 payload stage. The uri_x64 option sets the URI to download the x64 payload stage.

	client {
		parameter "id" "1234";
		header "Cookie" "SomeValue";

The client keyword under the context of http-stager defines the client side of the HTTP transaction. Use the parameter keyword to add a parameter to the URI. Use the header keyword to add a header to the stager's HTTP GET request.

	server {
		header "Content-Type" "image/gif";
		output {
			prepend "GIF89a";

The server keyword under the context of http-stager defines the server side of the HTTP transaction. The header keyword adds a server header to the server's response. The output keyword under the server context of http-stager is a data transform to change the payload stage. This transform may only prepend and append strings to the stage. Use the print termination statement to close this output block.

HTTP Headers

The http-config block has influence over all HTTP responses served by Cobalt Strike’s web server. Here, you may specify additional HTTP headers and the HTTP header order.

http-config {
	set headers "Date, Server, Content-Length, Keep-Alive, Connection, Content-Type";
	header "Server" "Apache";
	header "Keep-Alive" "timeout=5, max=100";
	header "Connection" "Keep-Alive";

The header keyword adds a header value to each of Cobalt Strike's HTTP responses. If the header value is already defined in a response, this value is ignored.

The set headers option specifies the order these HTTP headers are delivered in an HTTP response. Any headers not in this list are added to the end.

Self-signed Certificates with SSL Beacon

The HTTPS Beacon uses the HTTP Beacon's indicators in its communication. Malleable C2 profiles may also specify parameters for the Beacon C2 server's self-signed SSL certificate. This is useful if you want to replicate an actor with unique indicators in their SSL certificate:

https-certificate {
	set CN       "";
	set O        "Bob's Malware";

The certificate parameters under your profile's control are:

Option Example Description
C US Country
CN Common Name; Your callback domain
L Washington Locality
O Strategic Cyber LLC Organization Name
OU Certificate Department Organizational Unit Name
ST DC State or Province
validity 365 Number of days certificate is valid for

Valid SSL Certificates with SSL Beacon

You have the option to use a Valid SSL certificate with Beacon. Use a Malleable C2 profile to specify a Java Keystore file and a password for the keystore. This keystore must contain your certificate's private key, the root certificate, any intermediate certificates, and the domain certificate provided by your SSL certificate vendor. Cobalt Strike expects to find the Java Keystore file in the same folder as your Malleable C2 profile.

https-certificate {
	set keystore "";
	set password "mypassword";

The parameters to use a valid SSL certificate are:

Option Example Description
keystore Java Keystore file with certificate information
password mypassword The password to your Java Keystore

Here are the steps to create a Valid SSL certificate for use with Cobalt Strike's Beacon:

1. Use the keytool program to create a Java Keystore file. This program will ask "What is your first and last name?" Make sure you answer with the fully qualified domain name to your Beacon server. Also, make sure you take note of the keystore password. You will need it later.

$ keytool -genkey -keyalg RSA -keysize 2048 -keystore

2. Use keytool to generate a Certificate Signing Request (CSR). You will submit this file to your SSL certificate vendor. They will verify that you are who you are and issue a certificate. Some vendors are easier and cheaper to deal with than others.

$ keytool -certreq -keyalg RSA -file domain.csr -keystore

3. Import the Root and any Intermediate Certificates that your SSL vendor provides.

$ keytool -import -trustcacerts -alias FILE -file FILE.crt -keystore

4. Finally, you must install your Domain Certificate.

$ keytool -import -trustcacerts -alias mykey -file domain.crt -keystore

And, that's it. You now have a Java Keystore file that's ready to use with Cobalt Strike's Beacon.

Code Signing Certificate

Attacks -> Packages -> Windows Executable and Windows Executable (S) give you the option to sign an executable or DLL file. To use this option, you must specify a Java Keystore file with your code signing certificate and private key. Cobalt Strike expects to find the Java Keystore file in the same folder as your Malleable C2 profile.

code-signer {
	set keystore "keystore.jks";
	set password "password";
	set alias    "server";

The code signing certificate settings are:

Option Example Description
alias server The keystore's alias for this certificate
digest_algorithm SHA256 The digest algorithm
keystore keystore.jks Java Keystore file with certificate information
password mypassword The password to your Java Keystore
timestamp false Timestamp the file using a third-party service
timestamp_url URL of the timestamp service

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 min_alloc "16384";
	set startrwx "true";
	set userwx "false";

	transform-x86 {
		prepend "\x90\x90";
	transform-x64 {
		# transform x64 injected content

	disable "CreateRemoteThread";

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 disable verb is a hint to avoid the use of certain APIs in Beacon’s process injection routines. You may disable: SetThreadContext, CreateRemoteThread, and RtlCreateUserThread. Be aware, when you disable these calls, you may introduce avoidable failures in Beacon’s process injection routines. The c2lint command warns about some of these.

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

Option Example Description
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.

Which is more dangerous, Malleable C2 or a swimming pool?

The answer? Both. Malleable C2 gives you a new level of control over your network and host indicators. With this power also comes responsibility. Malleable C2 is an opportunity to make a lot of mistakes too. Here are a few things to think about when you customize your profiles:

1. Each Cobalt Strike instance uses one profile at a time. If you change a profile or load a new profile, previously deployed Beacons cannot communicate with you.

2. Always stay aware of the state of your data and what a protocol will allow when you develop a data transform. For example, if you base64 encode metadata and store it in a URI parameter--it's not going to work. Why? Some base64 characters (+, =, and /) have special meaning in a URL. The c2lint tool and Profile Compiler will not detect these types of problems.

3. Always test your profiles, even after small changes. If Beacon can't communicate with you, it's probably an issue with your profile. Edit it and try again.

4. Trust the c2lint tool. This tool goes above and beyond the profile compiler. The checks are grounded in how this technology is implemented. If a c2lint check fails, it means there is a real problem with your profile.