Customizing C2Concealer - Part 2

If you haven't read Part I, we recommend starting there. If you're ready for further C2Concealer customization, then let's dive in.

The bulk of C2Concealer's operations are done in the subdirectory /components. Each file in that directory corresponds to a particular block within the c2 malleable profile. For example, there is a file for DNS Options configuration named /components/dnsoptions.py.

Each of these component files contains a Python class with two  functions - printify and randomizer. Printify() outputs a string of all our relevant configurations, which gets appended to the final c2 malleable profile. We can skip this function. Randomizer() is where we can make additional customizations. This function generates our configurations by grabbing data from the /data directory (discussed in Part I), randomly generating values (like integers) and using hardcoded data. Since we already reviewed the /data directory customizations, we'll look at making changes to the random() functions and hardcoded values in the various component files.

This post covers half of the files in the /components directory. Since there's a lot of options to customize and this post got quite long, we'll publish a third post (Part III), which will cover the rest.

dnsoptions.py

Description: This file creates our global DNS options.

Customizable Options:

ip_num = list(range(1,9)) + list(range(11,171)) + list(range(173,191)) + list(range(193,254))

##Customize any of the options below
self.dns_idle = ".".join(map(str, (random.choice(ip_num) for _ in range(4))))
self.maxdns = str(random.randint(240,255))
self.dns_sleep = str(random.randint(100,150))
self.dns_max_txt = str(252)
self.dns_ttl = str(3600)

You can customize any of the 5 DNS option values above:

  • dns_idle is the "IP address used to indicate no tasks are available to DNS Beacon". C2Concealer builds a list of most numbers between 1 and 254 (with the exception of any numbers that start a private IP range address, like 10, 192, etc). Then the tool randomly builds an IP address. You could change this, but it's sufficiently random, that we'd suggest leaving it alone.
  • maxdns is the "Maximum length of hostname when uploading data over DNS (0-255)". Right now the value is a random integer between 240 and 255. You could shift that range anyway you'd like as long as you're in between 0 and 255; however, larger values facilitate faster data exfiltration.
  • dns_sleep is the number of milliseconds between each individual DNS request. Presumably you'd make this a larger value, if you suspect the target network is doing a lot of DNS monitoring. C2Concealer grabs a random integer between 100 and 150 and uses that as the value. You could easily change this range, either closer to zero or farther away from zero. Totally up to you. We don't recommend making it too large of a value (say over 300 or 500), but it's up to you.
  • dns_max_txt is the "maximum length of DNS TXT responses to tasks". The max value here should be 255; however, you could make this any value between 0 and 255. We recommend a larger value, since it will facilitate faster communications. Consider generating a random integer for this value, somewhere between 200 and 255.
  • dns_ttl is the "TTL for DNS replies". A valid integer would do great here. We suggest removing the hardcoded value and creating a range, like 1 to 255.

getclient.py

Description: Builds configurations for the http-get client section of the profile.

Customizable Options:

You can get creative with this section and add in any headers that you'd like. As is, C2Concealer includes the following HTTP GET headers: 'Host', 'User-Agent', 'Connection','Accept', 'Accept_Encoding', 'Accept_Language'.

We recommend adding in new headers and values. To add in a new header, take the following steps:

  1. In the __init__ function, define a new variable for the header. The variable name should reflect how the header name should be sent in an HTTP request. Meaning it should be capitalized. And if there is a dash in the header name, since we can't add a dash here, add an underscore "_" instead. Later in the code, the underscores will be changed to a dash. For example, in the code below, we added an "If-Match" header by creating the self.If_Match variable.
  2. Add the new header name to the self.headerList Python list.
def __init__(self, name, host):

	##Existing headers
    self.Host = host
    self.Accept = None
    self.Accept_Encoding = None
    self.Accept_Language = None
    self.Connection = 'close'
    
    ##New headers
    self.Authorization = None
    self.If_Match = None

    self.headerList = ['Host', 'Connection','Accept', 'Accept_Encoding', 'Accept_Language', 'Authorization', 'If_Match']

3. Decide if you want the header to be there in every profile or only some of them. If you only want it in some of them, then add the header name to the self.optionalHeaders Python list and then add in logic near lines 62-68 for what value to give the header if it's chosen for inclusion in the profile. The way this works is every time a profile is generated, it will choose between 0 and 2 optional headers and add them into the profile. If the header is to be included in every profile, then define its value anywhere else in the randomizer function.

One other area to change is line 78. Since this is a HTTP GET request, we can add HTTP GET URL parameters. As is, these parameters are randomly generated from a list in a /data file and then defined as either true or false in line 78. You could make this value to anything you want. Just don't make it too long.

getserver.py

Description: Builds configurations for the http-get server section of the profile. This is where beacon tasking information is sent.

Customizable Options:

The main customization for this section is similar to the getclient.py header customizations. C2Concealer already adds the headers 'Status','Connection','Content_Type', and 'Server'. Consider adding another header or two. Follow steps 1-3 from the getclient.py section for adding a header (just note that there are no optional headers, so unless you add that logic into the randomizer function, you'll be adding the new headers in for all http-get server responses.)

globaloptions.py

Description: A selection of global malleable profile options.

Customizable Options:

All of these global options could be changed rather easily.

  • sample_name is just the name of this profile. Right now it's a random 8 character value built from a UUID. This has no impact on the profile, but you could easily change this to keep better track of your profiles.
  • sleeptime is just the default time the beacon sleeps. The value is in milliseconds. C2Concealer sets this value to a random integer between 55000ms and 65000ms. You could easily change this range, or create a static value.
  • jitter is the variance with which a beacon sleeps. C2Concealer sets this value to an add number between 37 and 45 (which stands for 37% to 45%). You could change this to anywhere between 0 and 99.
  • tcp_port is the port a TCP beacon listens on. The default value for all Cobalt Strike instances is port 4444. C2Concealer defaults to a random integer somewhere in the following range: 1024-10000, with the exception of 4443-4446. The idea was port 4444 might be well documented in its use with Cobalt Strike, so we ensured the randomizer function avoided 4444 and a few integers around it. This port value could be any valid ephemeral port number though, so feel free to make this range whatever you want between 1024 and 65535.

httpconfig.py

Description: Global HTTP communications options.

Customizable Options: Nothing to do here.

postclient.py

Description: Settings for when the beacon POSTS data in an HTTP POST request to the team server.

Customizable Options:

Follow the same guidance for getclient.py regarding adding new headers.

Additionally, beacon session id information is sent in a header, so that the teamserver knows which beacon session to attribute the POSTed information. As C2Concealer is currently written, the beacon session ID information is always sent in a cookie called __session__id. This means every HTTP POST request from a beacon to the team server, there's a header that looks like this:

Cookie: __session__id=beacon_session_id_value

It'd be better to make the cookie name associated with the beacon session id a dynamic value, so that it's different on every engagement. Also, there's no restriction that says it has to be in a cookie; consider adding this session ID to a different header. We recommend using a legitimate, often-seen header for this, like If-Match for example.

postex.py

Description: Settings for post-exploitation jobs.

Customizable Options: There's really nothing to change here, unless you want to provide a value of "false" for the obfuscate, smart-inject or amsi-disable settings. By default those three values are "true".


Alright, that will do it for this post. Stay tuned for Part III, which will finalize the settings that can be changed in the /components subfolder within C2Concealer.

If you have any feedback, we'd love to hear it! As always, you can reach us via Twitter, LinkedIn, and through our website.