├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── requirements.txt └── shellcrypt.py /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Python 2 | .env 3 | venv 4 | env 5 | **/__pycache__ 6 | **/*.pyc 7 | **/*.swp 8 | **/*.egg-info/ 9 | dist/ 10 | build/ 11 | .idea/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 iilegacyyii 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Shellcrypt 2 | 3 | A single-file cross-platform quality of life tool to obfuscate a given shellcode file and output in a useful format for pasting directly into your source code. 4 | 5 | ![Screenshot of Shellcrypt encrypting shellcode](https://i.imgur.com/DavG7ad.png) 6 | 7 | ## Contributors 8 | 9 | These are going here because they deserve it 10 | - An00bRektn [github](https://github.com/An00bRektn) [twitter](https://twitter.com/An00bRektn) ♥ 11 | - 0xtejas [github](https://github.com/0xtejas) 12 | 13 | ## Encryption Methods 14 | 15 | Shellcrypt currently supports the following encryption methods (more to come in the future!) 16 | 17 | - AES (128-bit CBC) 18 | - ChaCha20 19 | - RC4 20 | - Salsa20 21 | - XOR 22 | 23 | ## Supported Formats 24 | 25 | Shellcrypt currently supports the following output formats (more to come in the future!) 26 | 27 | - C 28 | - C# 29 | - Nim 30 | - Golang 31 | - Python 32 | - Powershell 33 | - Visual Basic for Applications (VBA) 34 | - Visual Basic Script (VBS) 35 | - Rust 36 | - Raw 37 | 38 | ## Usage 39 | **Encrypt shellcode with a random key** 40 | ```plaintext 41 | python ./shellcrypt.py -i ./shellcode.bin -f c 42 | ``` 43 | **Encrypt shellcode with 128-bit AES CBC** 44 | ```plaintext 45 | python ./shellcrypt.py -i ./shellcode.bin -e aes -f c 46 | ``` 47 | **Encrypt shellcode with a user-specified key** 48 | ```plaintext 49 | python ./shellcrypt.py -i ./shellcode.bin -f c -k 6d616c77617265 50 | ``` 51 | **Output in nim format** 52 | ```plaintext 53 | python ./shellcrypt.py -i ./shellcode.bin -f nim 54 | ``` 55 | **Output to file** 56 | ```plaintext 57 | python ./shellcrypt.py -i ./shellcode.bin -f nim -o ./shellcode_out.nim 58 | ``` 59 | **Get a list of encryption methods** 60 | ```plaintext 61 | python ./shellcrypt.py --ciphers 62 | ``` 63 | **Get a list of output formats** 64 | ```plaintext 65 | python ./shellcrypt.py --formats 66 | ``` 67 | **Help** 68 | ```plaintext 69 | ███████╗██╗ ██╗███████╗██╗ ██╗ ██████╗██████╗ ██╗ ██╗██████╗ ████████╗ 70 | ██╔════╝██║ ██║██╔════╝██║ ██║ ██╔════╝██╔══██╗╚██╗ ██╔╝██╔══██╗╚══██╔══╝ 71 | ███████╗███████║█████╗ ██║ ██║ ██║ ██████╔╝ ╚████╔╝ ██████╔╝ ██║ 72 | ╚════██║██╔══██║██╔══╝ ██║ ██║ ██║ ██╔══██╗ ╚██╔╝ ██╔═══╝ ██║ 73 | ███████║██║ ██║███████╗███████╗███████╗╚██████╗██║ ██║ ██║ ██║ ██║ 74 | ╚══════╝╚═╝ ╚═╝╚══════╝╚══════╝╚══════╝ ╚═════╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ 75 | v1.5 beta 76 | 77 | ~ @0xLegacyy (Jordan Jay) 78 | 79 | usage: shellcrypt [-h] [-i INPUT] [-e ENCRYPT] [-k KEY] [-n NONCE] [-f FORMAT] [--formats] [--ciphers] [-o OUTPUT] 80 | [-v] 81 | 82 | options: 83 | -h, --help show this help message and exit 84 | -i INPUT, --input INPUT 85 | Path to file to be encrypted. 86 | -e ENCRYPT, --encrypt ENCRYPT 87 | Encryption method to use, default 'xor'. 88 | -k KEY, --key KEY Encryption key in hex format, default (random 16 bytes). 89 | -n NONCE, --nonce NONCE 90 | Encryption nonce in hex format, default (random 16 bytes). 91 | -f FORMAT, --format FORMAT 92 | Output format, specify --formats for a list of formats. 93 | --formats Show a list of valid formats 94 | --ciphers Show a list of valid ciphers 95 | -o OUTPUT, --output OUTPUT 96 | Path to output file 97 | -v, --version Shows the version and exits 98 | ``` 99 | 100 | ## Future Development Goals 101 | 102 | 1. More output formats (rust etc.) 103 | 2. More encryption methods 104 | 3. Compression methods 105 | 4. Create a config system that allows for chaining encryption/encoding/compression methods 106 | 5. Flag to add a decrypt method to the generated code 107 | 6. [Shikata](https://github.com/EgeBalci/sgn) encoder mayhaps? 108 | 109 | _**pssst** this is still heavily in development so if you'd like to contribute, have a go at working on one of the many `TODO`'s in the code :)_ 110 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iilegacyyii/Shellcrypt/1e7ddac80086e22ea74ed47241e0ba9738607680/requirements.txt -------------------------------------------------------------------------------- /shellcrypt.py: -------------------------------------------------------------------------------- 1 | # Shellcraft 2 | # A QoL tool to obfuscate shellcode. 3 | # In the future will be able to chain encoding/encryption/compression methods. 4 | # ~ @0xLegacyy (Jordan Jay) 5 | import argparse 6 | from colorama import Fore, Back, Style 7 | from colorama import init as colorama_init 8 | 9 | from binascii import hexlify 10 | from itertools import cycle 11 | from os import urandom 12 | from os.path import isfile 13 | from random import choices 14 | from string import hexdigits 15 | 16 | from Crypto.Cipher import AES, ARC4, ChaCha20, Salsa20 17 | from Crypto.Util.Padding import pad 18 | 19 | # global vars 20 | VERSION = "v1.5 beta" 21 | OUTPUT_FORMATS = [ 22 | "c", 23 | "csharp", 24 | "nim", 25 | "go", 26 | "py", 27 | "ps1", 28 | "vba", 29 | "vbscript", 30 | "raw", 31 | "rust" 32 | ] 33 | 34 | 35 | CIPHERS = [ 36 | "aes", # Let's just keep it at AES-128 for now 37 | "chacha20", 38 | "rc4", 39 | "salsa20", 40 | "xor" 41 | ] 42 | 43 | 44 | def show_banner(): 45 | # TODO: add support for nocolour maybe? 46 | banner = f"""{Fore.CYAN} 47 | ███████╗██╗ ██╗███████╗██╗ ██╗ ██████╗██████╗ ██╗ ██╗██████╗ ████████╗ 48 | ██╔════╝██║ ██║██╔════╝██║ ██║ ██╔════╝██╔══██╗╚██╗ ██╔╝██╔══██╗╚══██╔══╝ 49 | ███████╗███████║█████╗ ██║ ██║ ██║ ██████╔╝ ╚████╔╝ ██████╔╝ ██║ 50 | ╚════██║██╔══██║██╔══╝ ██║ ██║ ██║ ██╔══██╗ ╚██╔╝ ██╔═══╝ ██║ 51 | ███████║██║ ██║███████╗███████╗███████╗╚██████╗██║ ██║ ██║ ██║ ██║ 52 | ╚══════╝╚═╝ ╚═╝╚══════╝╚══════╝╚══════╝ ╚═════╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ 53 | {Style.RESET_ALL}{VERSION} 54 | 55 | ~ @0xLegacyy (Jordan Jay) 56 | """ 57 | print(banner) 58 | 59 | 60 | class Log(object): 61 | """ Handles all styled terminal output. """ 62 | def __init__(self): 63 | super(Log, self).__init__() 64 | return 65 | 66 | def logSuccess(msg:str): 67 | """ Logs msg to the terminal with a green [+] appended. 68 | Used to show task success. 69 | :param msg: User-specified message to be output 70 | :return: 71 | """ 72 | print(f"{Style.BRIGHT}{Fore.GREEN}[+]{Fore.RESET}{Style.RESET_ALL} {msg}") 73 | return 74 | 75 | def logInfo(msg:str): 76 | """ Logs msg to the terminal with a blue [*] appended 77 | Used to show task status / info. 78 | :param msg: User-specified message to be output 79 | :return: 80 | """ 81 | print(f"{Style.BRIGHT}{Fore.BLUE}[*]{Fore.RESET}{Style.RESET_ALL} {msg}") 82 | return 83 | 84 | def logDebug(msg:str): 85 | """ Logs msg to the terminal with a magenta [debug] appended 86 | Used to show debug info for nerds. 87 | :param msg: User-specified message to be output 88 | :return: 89 | """ 90 | if DEBUG: 91 | print(f"{Style.BRIGHT}{Fore.MAGENTA}[debug]{Fore.RESET}{Style.RESET_ALL} {msg}") 92 | return 93 | 94 | def logError(msg:str): 95 | """ Logs msg to the terminal with a red [!] appended 96 | Used to show error messages. 97 | :param msg: User-specified message to be output 98 | :return: 99 | """ 100 | print(f"{Style.BRIGHT}{Fore.RED}[!]{Fore.RESET}{Style.RESET_ALL} {msg}") 101 | return 102 | 103 | 104 | class ShellcodeFormatter(object): 105 | """ Enables for easy output generation in multiple formats. """ 106 | def __init__(self): 107 | super(ShellcodeFormatter, self).__init__() 108 | self.__format_handlers = { 109 | "c": self.__output_c, 110 | "csharp": self.__output_csharp, 111 | "nim": self.__output_nim, 112 | "go": self.__output_go, 113 | "py": self.__output_py, 114 | "ps1": self.__output_ps1, 115 | "vba": self.__output_vba, 116 | "vbscript": self.__output_vbscript, 117 | "raw": self.__output_raw, 118 | "rust": self.__output_rust 119 | } 120 | return 121 | 122 | def __generate_array_contents(self, input_bytes:bytearray, string_format:bool=False) -> str: 123 | """ Takes a byte array, and generates a string in format 124 | 0xaa,0xff,0xab(up to 15), 125 | 0x4f... 126 | :param input_bytes: bytearray 127 | :param string_format: Whether to print in the \xff format or 0xff 128 | :return: string containing formatted array contents 129 | """ 130 | # TODO: Rework this to support more languages than just those that use the 0x format 131 | output = "" 132 | if not string_format: 133 | for i in range(len(input_bytes) - 1): 134 | if i % 15 == 0: 135 | output += "\n\t" 136 | output += f"0x{input_bytes[i]:0>2x}," 137 | output += f"0x{input_bytes[-1]:0>2x}" 138 | return output[1:] # (strip first \n) 139 | else: 140 | for i in range(len(input_bytes) - 1): 141 | if i % 15 == 0: 142 | output += "\n" 143 | output += f"\\x{input_bytes[i]:0>2x}" 144 | output += f"\\x{input_bytes[-1]:0>2x}" 145 | return output[1:] # (strip first \n) 146 | 147 | 148 | def __output_c(self, arrays:dict) -> str: 149 | """ Private method to output in C format. 150 | :param arrays: dictionary containing array names and their respective bytes 151 | :return output: string containing shellcode in c format, similar 152 | to msfvenom's csharp format. 153 | """ 154 | # Generate arrays 155 | output = str() 156 | for array_name in arrays: 157 | output += f"unsigned char {array_name}[{len(arrays[array_name])}] = {{\n" 158 | output += self.__generate_array_contents(arrays[array_name]) 159 | output += "\n};\n\n" 160 | 161 | return output 162 | 163 | def __output_rust(self, arrays:dict) -> str: 164 | """ Private method to output in Rust format. 165 | :param arrays: dictionary containing array names and their respective bytes 166 | :return output: string containing shellcode in rust format, similar 167 | to msfvenom's rust format. 168 | """ 169 | # Generate arrays 170 | output = str() 171 | for array_name in arrays: 172 | output += f"let {array_name}: [u8; {len(arrays[array_name])}] = [\n" 173 | output += self.__generate_array_contents(arrays[array_name]) 174 | output += "\n];\n\n" 175 | 176 | return output 177 | 178 | def __output_csharp(self, arrays:dict) -> str: 179 | """ Private method to output in C# format. 180 | :param arrays: dictionary containing array names and their respective bytes 181 | :return output: string containing shellcode in C# format 182 | """ 183 | # Generate arrays 184 | output = str() 185 | for array_name in arrays: 186 | output += f"byte[] {array_name} = new byte[{len(arrays[array_name])}] {{\n" 187 | output += self.__generate_array_contents(arrays[array_name]) 188 | output += "\n};\n\n" 189 | 190 | return output 191 | 192 | def __output_nim(self, arrays:dict) -> str: 193 | """ Private method to output in nim format. 194 | :param arrays: dictionary containing array names and their respective bytes 195 | :return output: string containing shellcode in nim format 196 | """ 197 | # Generate arrays 198 | output = str() 199 | for array_name in arrays: 200 | output += f"var {array_name}: array[{len(arrays[array_name])}, byte] = [\n" 201 | output += "\tbyte " + self.__generate_array_contents(arrays[array_name])[1:] 202 | output += "\n]\n\n" 203 | return output 204 | 205 | def __output_go(self, arrays:dict) -> str: 206 | """ Private method to output in golang format. 207 | :param arrays: dictionary containing array names and their respective bytes 208 | :return output: string containing shellcode in golang format 209 | """ 210 | # Generate arrays 211 | output = str() 212 | for array_name in arrays: 213 | output += f"{array_name} := []byte{{\n" 214 | output += self.__generate_array_contents(arrays[array_name]) 215 | output += "\n};\n\n" 216 | return output 217 | 218 | def __output_py(self, arrays:dict) -> str: 219 | """ Private method to output in python format. 220 | :param arrays: dictionary containing array names and their respective bytes 221 | :return output: string containing shellcode in python format 222 | """ 223 | # Note: Technically not best to use the triple quotes here but consistency ig 224 | # Generate arrays 225 | output = str() 226 | for array_name in arrays: 227 | output += f"{array_name} = b\"\"\"" 228 | output += self.__generate_array_contents(arrays[array_name], string_format=True) 229 | output += "\"\"\"\n\n" 230 | return output 231 | 232 | def __output_ps1(self, arrays:dict) -> str: 233 | """ Private method to output in powershell format. 234 | :param arrays: dictionary containing array names and their respective bytes 235 | :return output: string containing shellcode in powershell format 236 | """ 237 | # Note: Technically not best to use the triple quotes here but consistency ig 238 | # Generate arrays 239 | output = str() 240 | for array_name in arrays: 241 | output += f"[Byte[]] ${array_name} = " 242 | output += self.__generate_array_contents(arrays[array_name])[1:] 243 | output += "\n\n" 244 | return output 245 | 246 | def __output_vba(self, arrays:dict) -> str: 247 | """ Private method to output in visual basic application format. 248 | :param arrays: dictionary containing array names and their respective bytes 249 | :return output: string containing shellcode in visual basic application format 250 | """ 251 | # Generate arrays 252 | output = str() 253 | # VBA has a maximum line length of 1023 characters, so have to work around that 254 | for array_name in arrays: 255 | # Array name 256 | output += f"{array_name} = Array(" 257 | line_length = len(output) 258 | # Array contents 259 | array_size = len(arrays[array_name]) 260 | for i, x in enumerate(arrays[array_name]): 261 | if i == array_size - 1: 262 | break 263 | # If within 5 bytes, we have enough to write "222,_", which is enough for any value. 264 | if line_length + 5 > 1022: 265 | output += "_\n" 266 | line_length = 0 267 | output += f"{x}," 268 | line_length += len(f"{x},") 269 | # Array end 270 | if line_length + 4 > 1023: 271 | output += "_\n" 272 | output += f"{x})\n\n" 273 | return output 274 | 275 | def __output_vbscript(self, arrays:dict) -> str: 276 | """ Private method to output in vbscript format. 277 | :param arrays: dictionary containing array names and their respective bytes 278 | :return output: string containing shellcode in vbscript format 279 | """ 280 | # does not have short line lengths 281 | # Generate arrays 282 | output = str() 283 | for array_name in arrays: 284 | output += f"{array_name}=" 285 | output += "".join([f"Chr({str(c)})&" for c in arrays[array_name]])[:-1] 286 | output += "\n\n" 287 | return output 288 | 289 | def __output_raw(self, arrays:dict) -> str: 290 | """ Private method to output shellcode in raw format. 291 | :param arrays: dictionary containing array names and their respective bytes 292 | :return output: string containing shellcode in raw format 293 | """ 294 | # Grab shellcode 295 | return arrays["sh3llc0d3"] 296 | 297 | def generate(self, output_format:str, arrays:dict) -> str: 298 | """ Generates output given the current class configuration 299 | :param output_format: Output format to generate e.g. "c" or "csharp" 300 | :param shellcode: dictionary containing {"arrayname":array_bytes} pairs 301 | :return output: string containing formatted shellcode + key(s) 302 | """ 303 | # Pass execution to the respective handler and return 304 | return self.__format_handlers[output_format](arrays) 305 | 306 | class Encrypt: 307 | """ Consolidates encryption into a single class. """ 308 | def __init__(self): 309 | super(Encrypt, self).__init__() 310 | self.__encryption_handlers = { 311 | "xor": self.__xor, 312 | "aes": self.__aes_128, 313 | "rc4": self.__rc4, 314 | "chacha20": self.__chacha20, 315 | "salsa20": self.__salsa20 316 | } 317 | return 318 | 319 | def encrypt(self, cipher:str, plaintext:bytearray, key:bytearray, nonce:bytearray = None) -> bytearray: 320 | """ Encrypts plaintext with the user-specified cipher. 321 | This has been written this way to support chaining of 322 | multiple encryption methods in the future. 323 | :param cipher: cipher to use, e.g. 'xor'/'aes' 324 | :param plaintext: bytearray containing our plaintext 325 | :param key: bytearray containing our encryption key 326 | :param nonce: bytearray containing nonce for aes etc. 327 | if none will be generated on the fly 328 | :return ciphertext: bytearray containing encrypted plaintext 329 | """ 330 | # If nonce not specified, generate one, otherwise use the specified one. 331 | self.nonce = urandom(16) if nonce is None else nonce 332 | self.key = key 333 | # cipher is already validated (check argument validation section). 334 | return self.__encryption_handlers[cipher](plaintext) 335 | 336 | def __xor(self, plaintext:bytearray) -> bytearray: 337 | """ Private method to encrypt the input plaintext with a repeating XOR key. 338 | :param plaintext: bytearray containing our plaintext 339 | :return ciphertext: bytearray containing encrypted plaintext 340 | """ 341 | return bytearray(a ^ b for (a, b) in zip(plaintext, cycle(self.key))) 342 | 343 | # TODO: Support other modes. 344 | # Currently just CBC. 345 | def __aes_128(self, plaintext:bytearray) -> bytearray: 346 | """ Private method to encrypt the input plaintext with AES-128 in CBC mode. 347 | :param plaintext: bytearray containing plaintext 348 | :return ciphertext: bytearray containing encrypted plaintext 349 | """ 350 | aes_cipher = AES.new(self.key, AES.MODE_CBC, self.nonce) 351 | plaintext = pad(plaintext, 16) 352 | return bytearray(aes_cipher.encrypt(plaintext)) 353 | 354 | def __rc4(self, plaintext:bytearray) -> bytearray: 355 | """ Private method to encrypt the input plaintext via RC4. 356 | :param plaintext: bytearray containing plaintext 357 | :return ciphertext: bytearray containing encrypted plaintext 358 | """ 359 | rc4_cipher = ARC4.new(self.key) 360 | return rc4_cipher.encrypt(plaintext) 361 | 362 | def __chacha20(self, plaintext:bytearray) -> bytearray: 363 | """ Private method to encrypt the input plaintext via ChaCha20. 364 | :param plaintext: bytearray containing plaintext 365 | :return ciphertext: bytearray containing encrypted plaintext 366 | """ 367 | chacha20_cipher = ChaCha20.new(key=self.key) 368 | return chacha20_cipher.encrypt(plaintext) 369 | 370 | def __salsa20(self, plaintext:bytearray) -> bytearray: 371 | """ Private method to encrypt the input plaintext via Salsa20. 372 | :param plaintext: bytearray containing plaintext 373 | :return ciphertext: bytearray containing encrypted plaintext 374 | """ 375 | salsa20_cipher = Salsa20.new(key=key) 376 | return salsa20_cipher.encrypt(plaintext) 377 | 378 | 379 | if __name__ == "__main__": 380 | # --------- Initialisation --------- 381 | # Debug mode toggle (logging) 382 | DEBUG = False 383 | 384 | # Completely unnecessary stuff (unless you're cool) 385 | colorama_init() 386 | show_banner() 387 | 388 | # Parse arguments 389 | argparser = argparse.ArgumentParser(prog="shellcrypt") 390 | argparser.add_argument("-i", "--input", help="Path to file to be encrypted.") 391 | argparser.add_argument("-e", "--encrypt", default="xor", help="Encryption method to use, default 'xor'.") 392 | argparser.add_argument("-k", "--key", help="Encryption key in hex format, default (random 16 bytes).") 393 | argparser.add_argument("-n", "--nonce", help="Encryption nonce in hex format, default (random 16 bytes).") 394 | argparser.add_argument("-f", "--format", help="Output format, specify --formats for a list of formats.") 395 | argparser.add_argument("--formats", action="store_true", help="Show a list of valid formats") 396 | argparser.add_argument("--ciphers", action="store_true", help="Show a list of valid ciphers") 397 | argparser.add_argument("-o", "--output", help="Path to output file") 398 | argparser.add_argument("-v", "--version", action="store_true", help="Shows the version and exits") 399 | # TODO: Add --preserve-null flag for XOR. (Don't XOR null bytes.) 400 | # TODO: Add length param for random key, currently locked at 16 bytes. 401 | # TODO: Maybe add decryption routines? 402 | args = argparser.parse_args() 403 | 404 | # --------- Info-only arguments --------- 405 | # If formats specified 406 | if args.formats: 407 | print("The following formats are available:") 408 | for i in OUTPUT_FORMATS: 409 | print(f" - {i}") 410 | exit() 411 | 412 | # If ciphers specified 413 | if args.ciphers: 414 | print("The following ciphers are available:") 415 | for i in CIPHERS: 416 | print(f" - {i}") 417 | exit() 418 | 419 | # If version specified 420 | if args.version: 421 | print(VERSION) 422 | exit() 423 | 424 | # --------- Argument Validation --------- 425 | Log.logDebug("Validating arguments") 426 | 427 | # Check input file is specified 428 | if args.input is None: 429 | Log.logError("Must specify an input file e.g. -i shellcode.bin (specify --help for more info)") 430 | exit() 431 | 432 | # Check input file exists 433 | if not isfile(args.input): 434 | Log.logError(f"Input file '{args.input}' does not exist.") 435 | exit() 436 | 437 | # TODO: check we can read the file. 438 | 439 | Log.logSuccess(f"Input file: '{args.input}'") 440 | 441 | # Check format is specified 442 | if args.format not in OUTPUT_FORMATS: 443 | Log.logError("Invalid format specified, please specify a valid format e.g. -f c (--formats gives a list of valid formats) ") 444 | exit() 445 | 446 | Log.logSuccess(f"Output format: {args.format}") 447 | 448 | # Check encrypt is specified 449 | if args.encrypt not in CIPHERS: 450 | Log.logError("Invalid cipher specified, please specify a valid cipher e.g. -e xor (--ciphers gives a list of valid ciphers) ") 451 | exit() 452 | 453 | Log.logSuccess(f"Output format: {args.encrypt}") 454 | 455 | # Check if key is specified. 456 | # if so => validate and store in key 457 | # else => generate and store in key 458 | if args.key is None: 459 | key = urandom(32) # Changed from 8 to 16 to make AES support easier :) 460 | else: 461 | if len(args.key) < 2 or len(args.key) % 2 == 1: 462 | Log.logError("Key must be valid byte(s) in hex format (e.g. 4141).") 463 | exit() 464 | if args.encrypt == "aes" and len(args.key) != 32: 465 | Log.logError("AES-128 key must be exactly 16 bytes long.") 466 | exit() 467 | for i in args.key: 468 | if i not in hexdigits: 469 | Log.logError("Key must be valid byte(s) in hex format (e.g. 4141).") 470 | exit() 471 | 472 | key = bytearray.fromhex(args.key) 473 | 474 | Log.logSuccess(f"Using key: {hexlify(key).decode()}") 475 | 476 | # TODO: somehow join the above and this as it's a lot of repeated code, 477 | # maybe some kind of method for checking if an input is hex and 16 bytes ? 478 | # Validate the user's nonce if one is specified, else generate one 479 | if args.nonce is None: 480 | nonce = urandom(16) 481 | else: 482 | if len(args.nonce) != 32: 483 | Log.logError("Nonce must be exactly 16 bytes long") 484 | exit() 485 | for i in args.nonce: 486 | if i not in hexdigits: 487 | Log.logError("Nonce must be 16 valid bytes in hex format (e.g. 7468697369736d616c6963696f757321)") 488 | exit() 489 | 490 | nonce = bytearray.fromhex(args.nonce) 491 | 492 | # Only show nonce if it's used, could be confusing to the user otherwise 493 | # TODO: probably change this in the future to if args.encrypt in requires_nonce => show 494 | if args.encrypt == "aes": 495 | Log.logSuccess(f"Using nonce: {hexlify(nonce).decode()}") 496 | 497 | Log.logDebug("Arguments validated") 498 | 499 | # --------- Read Input File --------- 500 | input_bytes = None 501 | with open(args.input, "rb") as input_handle: 502 | input_bytes = input_handle.read() 503 | 504 | # --------- Input File Encryption --------- 505 | #Log.logInfo(f"Encrypting {len(input_bytes)} bytes") (came up with a better idea, keeping for future reminder) 506 | Log.logDebug(f"Encrypting input file") 507 | 508 | #input_bytes = bytearray(a ^ b for (a, b) in zip(input_bytes, cycle(key))) 509 | cryptor = Encrypt() 510 | input_bytes = cryptor.encrypt(args.encrypt, input_bytes, key, nonce) 511 | input_length = len(input_bytes) 512 | 513 | Log.logSuccess(f"Successfully encrypted input file ({len(input_bytes)} bytes)") 514 | 515 | # --------- Output Generation --------- 516 | # Define array names + content to be formatted 517 | arrays = { 518 | "key":key 519 | } 520 | 521 | # If aes in use, add nonce to the arrays 522 | if args.encrypt == "aes": 523 | arrays["nonce"] = nonce 524 | 525 | # Removed from the initialization line(s) for arrays for nicer output ordering. 526 | arrays["sh3llc0d3"] = input_bytes 527 | 528 | # Generate formatted output. 529 | shellcode_formatter = ShellcodeFormatter() 530 | output = shellcode_formatter.generate(args.format, arrays) 531 | 532 | # --------- Output --------- 533 | # If no output file specified. 534 | if args.output is None: 535 | # We want to decode if it's a bytearray. (for raw mode) 536 | print(output.decode("latin1") if isinstance(output, bytearray) else output) 537 | exit() 538 | 539 | # If output file specified. 540 | Log.logDebug(f"output var type: {type(output)}") 541 | write_mode = ("wb" if isinstance(output, bytearray) else "w") # We want wb if it's a bytearray. (for raw mode) 542 | Log.logDebug(f"write_mode = \"{write_mode}\"") 543 | with open(args.output, write_mode) as file_handle: 544 | file_handle.write(output) 545 | 546 | Log.logSuccess(f"Output written to '{args.output}'") 547 | --------------------------------------------------------------------------------