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.

Viewing the Loaded Profile

To view the C2 profile that was loaded when the TeamServer was started select Help -> Malleable C2 Profile on the menu. This displays the profile for the currently selected TeamServer when multiple TeamServers are connected. The dialog is read-only.

To close the dialog use the 'x' in the upper right corner of the dialog.

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]

c2lint returns and logs the following result codes for the specified profile file:

  • A result of 0 is returned if c2lint completes with no errors
  • A result of 1 is returned if c2lint completes with only warnings
  • A result of 2 is returned if c2lint completes with only errors
  • A result of 3 is returned if c2lint completes with both errors and warnings.

The last lines of the c2lint output display a count of detected errors and warnings. No message is displayed if none are found. There can be more error messages displayed in the output than the count represents because a single error may produce more than 1 error message. This is the same possibility for warnings however less likely. For example:

  • [!] Detected 1 warning.
  • [-] Detected 3 errors.

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
data_jitter 0 Append random-length string (up to data_jitter value) to http-get and http-post server output.
headers_remove Comma-separated list of HTTP client headers to remove from Beacon C2.
host_stage true Host payload for staging over HTTP, HTTPS, or DNS. Required by stagers.
jitter 0 Default jitter factor (0-99%)
pipename msagent_## Name of pipe to use for SMB Beacon's peer-to-peer communication. Each # is replaced with a random hex value.
pipename_stager status_## Name of pipe to use for SMB Beacon's named pipe stager. Each # is replaced with a random hex value.
sample_name My Profile The name of this profile (used in the Indicators of Compromise report)
sleeptime 60000 Default sleep time (in milliseconds)
smb_frame_header Prepend header to SMB Beacon messages
ssh_banner Cobalt Strike 4.3 SSH client banner
ssh_pipename postex_ssh_#### Name of pipe for SSH sessions. Each # is replaced with a random hex value.
tcp_frame_header Prepend header to TCP Beacon messages
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.

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.

A Beacon HTTP Transaction Walk-through

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 Server Configuration

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";
	set trust_x_forwarded_for "false";
	set block_useragents "curl*,lynx*,wget*";

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.

The set trust_x_forwarded_for option decides if Cobalt Strike uses the X-Forwarded-For HTTP header to determine the remote address of a request. Use this option if your Cobalt Strike server is behind an HTTP redirector.

The block_useragents and allow_useragents options configure a list of user agents that are allowed or blocked with a 404 response. By default, requests from user agents that start with curl, lynx, or wget are all blocked. If both are specified, block_useragents will take precedence over allow_useragents. The option value supports a string of comma separated values. Values support simple generics:

Example Description
not specified Use the default value (curl*,lynx*,wget*). Blocks requests from user agents starting with curl, lynx, or wget.
blank (block_useragents) No user agents are blocked
blank (allow_useragents) All user agents are allowed
something Block/Allow requests with useragent equal 'something'
something* Block/Allow requests with useragent starting with 'something'
*something Block/Allow requests with useragent ending with 'something'
*something* Block/Allow requests with useragent containing 'something'

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.

Profile Variants

Malleable C2 profile files, by default, contain one profile. It's possible to pack variations of the current profile by specifying variant blocks.

Blocks that support variants:

  • http-get
  • http-post
  • http-stager
  • https-certificate
  • dns-beacon

A variant block is specified as [block name] "variant name" { ... }. Here's a variant http-get block named "My Variant":

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

A variant block creates a copy of the current profile with the specified variant blocks replacing the default blocks in the profile itself. Each unique variant name creates a new variant profile. You may populate a profile with as many variant names as you like.

Variants are selectable when configuring an HTTP or HTTPS Beacon listener. Variants allow each HTTP or HTTPS Beacon listener tied to a single team server to have network IOCs that differ from eachother.

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

DNS Beacons

You have the option to shape the DNS Beacon/Listener network traffic with Malleable C2.

dns-beacon {

    # Options moved into 'dns-beacon' group in 4.3:
    set dns_idle             "";
    set dns_max_txt          "199";
    set dns_sleep            "1";
    set dns_ttl              "5";
    set maxdns               "200";
    set dns_stager_prepend   "doc-stg-prepend";
    set dns_stager_subhost   "doc-stg-sh.";

    # DNS subhost override options added in 4.3:
    set beacon               "doc.bc.";
    set get_A                "doc.1a.";
    set get_AAAA             "doc.4a.";
    set get_TXT              "doc.tx.";
    set put_metadata         "";
    set put_output           "doc.po.";

    set ns_response          "zero";


Here are the available options:

Option Default Value Example Description
dns_idle IP address used to indicate no tasks are available to DNS Beacon; Mask for other DNS C2 values
dns_max_txt 252 199 Maximum length of DNS TXT responses for tasks
dns_sleep 0 1 Force a sleep prior to each individual DNS request. (in milliseconds)
dns_stager_prepend doc-stg-prepend Prepend text to payload stage delivered to DNS TXT record stager
dns_stager_subhost .stage.123456. doc-stg-sh. Subdomain used by DNS TXT record stager.
dns_ttl 1 5 TTL for DNS replies
maxdns 255 200 Maximum length of hostname when uploading data over DNS (0-255)
beacon doc.bc. DNS subhost prefix used for beaconing requests
get_A cdn. doc.1a. DNS subhost prefix used for A record requests
get_AAAA www6. doc.4a. DNS subhost prefix used for AAAA record requests
get_TXT api. doc.tx. DNS subhost prefix used for TXT record requests
put_metadata www. DNS subhost prefix used for metadata requests
put_output post. doc.po. DNS subhost prefix used for output requests
ns_response drop zero How to process NS Record requests. "drop" does not respond to the request (default), "idle" responds with A record for IP address from "dns_idle", "zero" responds with A record for

You can use "ns_response" when a DNS server is responding to a target with "Server failure" errors. A public DNS Resolver may be initiating NS record requests that the DNS Server in Cobalt Strike Team Server is dropping by default.

{target}       {DNS Resolver} Standard query 0x5e06 A
{DNS Resolver} {target}       Standard query response 0x5e06 Server failure A

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.

Malleable PE, Process Injection, and Post Exploitation?

This page covers the Malleable C2 features related to flexible network communications. Jump to Malleable PE, Process Injection, and Post Exploitation for information on Malleable C2's stage, process-inject, and post-ex blocks.