Cobalt Strike C2 Profiles - HTTP
Cobalt Strike does provide documentation on creating and editing C2 profiles but the documentation is limited and not clear in places. This is an attempt to clarify some of the areas that confused me initially.
Context
This specifically focuses on the HTTP GET and HTTP POST definitions; C2 Profiles control many more aspects of beacon activity that are beyond the scope of this writeup. To be clear, the http-get
definition specifically has to do with beacon taskings and the http-post
definition specifically deals with the return of data from tasks dispatched to the beacon. Beacons callback to the Teamserver (through established infrastructure) on the interval defined by the sleep
setting and for HTTP beacons, the HTTP traffic for these callbacks is defined in the http-get
definition. This is ONLY for beacon taskings. When a task directs the beacon to return data to the operator, this data is sent as configured in the http-post
definition. The confusing part is that both http-post
and http-get
definitions can utilize whatever HTTP verb (set verb;
) they want. For example, the http-post
definition could use a GET request to send data back to the operator.
Limitations
Per the documentation:
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.
In our testing, we determined that the situation described above or using a GET request in the http-post
definition result in about a max of 4kb being transferred per beacon check in. While this might be sufficient for console data, transferring files in this method is slow, especially with long sleep intervals. We also found that using GET requests will result in a large number of requests in a very short time span, while using POST requests will only send one request per beacon check in. This may be an OPSEC consideration in some situations. For this reason, it can be useful to create a "download' specific profile that utilizes POST requests in the http-post
definition with the data passed in the request body using the print;
statement. This method allows about 512kb per callback.
Order of Operations Matters
For the Data Transforms that are available, be aware that the order of operations matters.
The data transform will apply to all previously defined data. Therefore if you prepend data to the Cobalt Strike data and then utilize a Data Transform, that data transform will include the prepended data. Your situation may or may not desire this behavior. As and example, I might want to prepend the magic bits for a JPEG to my data to help disguise the data. Wrong way:
output {
prepend "\xff\xd8\xff\xe0";
mask;
print;
}
This will prepend the magic bits and then XOR (mask) the Cobalt Strike data with the prepended data included, which means those magic bits would not actually be visible in the output because they would have been XORed.
Right way:
output {
mask;
prepend "\xff\xd8\xff\xe0";
print;
}
In this case, the Cobalt Strike data is XORed (mask) first so that it will resemble binary data, THEN the magic bits are prepended to the XORed data before being put into the POST body. The traffic you are trying to mimic, your OPSEC considerations, and mission requirements will determine how you approach this.
Null Byte Bad
As may be expected, null bytes do not play nice with C2 Profiles. While you can define raw hex in strings within a C2 Profile with the \xff
syntax, null bytes will cause beacon callback issues even though the profile will have no linter errors.
Nice To Know
The following documentation about crafting dynamic values in a profile is not highlighted as well as I would have liked. This is a considerable capability that seems buried in the documentation.