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
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 and http-post.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
dns_idle IP address used to indicate no tasks are available to DNS Beacon
dns_sleep 0 Force a sleep prior to each individual DNS request. (in milliseconds)
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.
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
uri http-get,
[required option] Transaction 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 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 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

Payload 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 gives you a few opportunities to customize the staging process though. The Beacon stager will use the global useragent option when it downloads the Beacon payload.

set useragent "Mozilla/5.0 (Windows NT 5.1; rv:31.0) Gecko/20100101 Firefox/31.0";

You may specify arbitrary headers for the server side of the staging process too. This is useful if you need to change the Content-Type header to get past mime-type filtering.

http-stager {
	server {
		header "Content-Type" "image/gif";

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.

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 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 can not 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.