[
  {
    "path": ".gitattributes",
    "content": "# Auto detect text files and perform LF normalization\n* text=auto\n"
  },
  {
    "path": ".gitignore",
    "content": "# Python\n.env\nvenv\nenv\n**/__pycache__\n**/*.pyc\n**/*.swp\n**/*.egg-info/\ndist/\nbuild/\n.idea/"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2022 iilegacyyii\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Shellcrypt\n\nA 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.\n\n![Screenshot of Shellcrypt encrypting shellcode](https://i.imgur.com/DavG7ad.png)\n\n## Contributors\n\nThese are going here because they deserve it\n- An00bRektn [github](https://github.com/An00bRektn) [twitter](https://twitter.com/An00bRektn) ♥\n- 0xtejas [github](https://github.com/0xtejas)\n\n## Encryption Methods\n\nShellcrypt currently supports the following encryption methods (more to come in the future!)\n\n- AES (128-bit CBC)\n- ChaCha20\n- RC4\n- Salsa20\n- XOR\n\n## Supported Formats\n\nShellcrypt currently supports the following output formats (more to come in the future!)\n\n- C\n- C#\n- Nim\n- Golang\n- Python\n- Powershell\n- Visual Basic for Applications (VBA)\n- Visual Basic Script (VBS)\n- Rust\n- Raw\n\n## Usage \n**Encrypt shellcode with a random key**\n```plaintext\npython ./shellcrypt.py -i ./shellcode.bin -f c\n```\n**Encrypt shellcode with 128-bit AES CBC**\n```plaintext\npython ./shellcrypt.py -i ./shellcode.bin -e aes -f c\n```\n**Encrypt shellcode with a user-specified key**\n```plaintext\npython ./shellcrypt.py -i ./shellcode.bin -f c -k 6d616c77617265\n```\n**Output in nim format**\n```plaintext\npython ./shellcrypt.py -i ./shellcode.bin -f nim\n```\n**Output to file**\n```plaintext\npython ./shellcrypt.py -i ./shellcode.bin -f nim -o ./shellcode_out.nim\n```\n**Get a list of encryption methods**\n```plaintext\npython ./shellcrypt.py --ciphers\n```\n**Get a list of output formats**\n```plaintext\npython ./shellcrypt.py --formats\n```\n**Help**\n```plaintext\n███████╗██╗  ██╗███████╗██╗     ██╗      ██████╗██████╗ ██╗   ██╗██████╗ ████████╗\n██╔════╝██║  ██║██╔════╝██║     ██║     ██╔════╝██╔══██╗╚██╗ ██╔╝██╔══██╗╚══██╔══╝\n███████╗███████║█████╗  ██║     ██║     ██║     ██████╔╝ ╚████╔╝ ██████╔╝   ██║\n╚════██║██╔══██║██╔══╝  ██║     ██║     ██║     ██╔══██╗  ╚██╔╝  ██╔═══╝    ██║\n███████║██║  ██║███████╗███████╗███████╗╚██████╗██║  ██║   ██║   ██║        ██║\n╚══════╝╚═╝  ╚═╝╚══════╝╚══════╝╚══════╝ ╚═════╝╚═╝  ╚═╝   ╚═╝   ╚═╝        ╚═╝\nv1.5 beta\n\n ~ @0xLegacyy (Jordan Jay)\n\nusage: shellcrypt [-h] [-i INPUT] [-e ENCRYPT] [-k KEY] [-n NONCE] [-f FORMAT] [--formats] [--ciphers] [-o OUTPUT]\n                  [-v]\n\noptions:\n  -h, --help            show this help message and exit\n  -i INPUT, --input INPUT\n                        Path to file to be encrypted.\n  -e ENCRYPT, --encrypt ENCRYPT\n                        Encryption method to use, default 'xor'.\n  -k KEY, --key KEY     Encryption key in hex format, default (random 16 bytes).\n  -n NONCE, --nonce NONCE\n                        Encryption nonce in hex format, default (random 16 bytes).\n  -f FORMAT, --format FORMAT\n                        Output format, specify --formats for a list of formats.\n  --formats             Show a list of valid formats\n  --ciphers             Show a list of valid ciphers\n  -o OUTPUT, --output OUTPUT\n                        Path to output file\n  -v, --version         Shows the version and exits\n```\n\n## Future Development Goals\n\n1. More output formats (rust etc.)\n2. More encryption methods\n3. Compression methods\n4. Create a config system that allows for chaining encryption/encoding/compression methods\n5. Flag to add a decrypt method to the generated code\n6. [Shikata](https://github.com/EgeBalci/sgn) encoder mayhaps?\n\n_**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 :)_\n"
  },
  {
    "path": "shellcrypt.py",
    "content": "# Shellcraft\n# A QoL tool to obfuscate shellcode. \n# In the future will be able to chain encoding/encryption/compression methods.\n# ~ @0xLegacyy (Jordan Jay)\nimport argparse\nfrom colorama import Fore, Back, Style\nfrom colorama import init as colorama_init\n\nfrom binascii import hexlify\nfrom itertools import cycle\nfrom os import urandom\nfrom os.path import isfile\nfrom random import choices\nfrom string import hexdigits\n\nfrom Crypto.Cipher import AES, ARC4, ChaCha20, Salsa20\nfrom Crypto.Util.Padding import pad\n\n# global vars\nVERSION = \"v1.5 beta\"\nOUTPUT_FORMATS = [\n    \"c\",\n    \"csharp\",\n    \"nim\",\n    \"go\",\n    \"py\",\n    \"ps1\",\n    \"vba\",\n    \"vbscript\",\n    \"raw\",\n    \"rust\"\n]\n\n\nCIPHERS = [\n    \"aes\", # Let's just keep it at AES-128 for now\n    \"chacha20\",\n    \"rc4\",\n    \"salsa20\",\n    \"xor\"\n]\n\n\ndef show_banner():\n    # TODO: add support for nocolour maybe?\n    banner = f\"\"\"{Fore.CYAN}\n███████╗██╗  ██╗███████╗██╗     ██╗      ██████╗██████╗ ██╗   ██╗██████╗ ████████╗\n██╔════╝██║  ██║██╔════╝██║     ██║     ██╔════╝██╔══██╗╚██╗ ██╔╝██╔══██╗╚══██╔══╝\n███████╗███████║█████╗  ██║     ██║     ██║     ██████╔╝ ╚████╔╝ ██████╔╝   ██║   \n╚════██║██╔══██║██╔══╝  ██║     ██║     ██║     ██╔══██╗  ╚██╔╝  ██╔═══╝    ██║   \n███████║██║  ██║███████╗███████╗███████╗╚██████╗██║  ██║   ██║   ██║        ██║   \n╚══════╝╚═╝  ╚═╝╚══════╝╚══════╝╚══════╝ ╚═════╝╚═╝  ╚═╝   ╚═╝   ╚═╝        ╚═╝   \n{Style.RESET_ALL}{VERSION}\n\n ~ @0xLegacyy (Jordan Jay)\n\"\"\"\n    print(banner)\n\n\nclass Log(object):\n    \"\"\" Handles all styled terminal output. \"\"\"\n    def __init__(self):\n        super(Log, self).__init__()\n        return\n    \n    def logSuccess(msg:str):\n        \"\"\" Logs msg to the terminal with a green [+] appended.\n            Used to show task success.\n        :param msg: User-specified message to be output\n        :return:\n        \"\"\"\n        print(f\"{Style.BRIGHT}{Fore.GREEN}[+]{Fore.RESET}{Style.RESET_ALL} {msg}\")\n        return\n\n    def logInfo(msg:str):\n        \"\"\" Logs msg to the terminal with a blue [*] appended\n            Used to show task status / info.\n        :param msg: User-specified message to be output\n        :return:\n        \"\"\"\n        print(f\"{Style.BRIGHT}{Fore.BLUE}[*]{Fore.RESET}{Style.RESET_ALL} {msg}\")\n        return\n\n    def logDebug(msg:str):\n        \"\"\" Logs msg to the terminal with a magenta [debug] appended\n            Used to show debug info for nerds.\n        :param msg: User-specified message to be output\n        :return:\n        \"\"\"\n        if DEBUG:\n            print(f\"{Style.BRIGHT}{Fore.MAGENTA}[debug]{Fore.RESET}{Style.RESET_ALL} {msg}\")\n        return\n\n    def logError(msg:str):\n        \"\"\" Logs msg to the terminal with a red [!] appended\n            Used to show error messages.\n        :param msg: User-specified message to be output\n        :return:\n        \"\"\"\n        print(f\"{Style.BRIGHT}{Fore.RED}[!]{Fore.RESET}{Style.RESET_ALL} {msg}\")\n        return\n\n\nclass ShellcodeFormatter(object):\n    \"\"\" Enables for easy output generation in multiple formats. \"\"\"\n    def __init__(self):\n        super(ShellcodeFormatter, self).__init__()\n        self.__format_handlers = {\n            \"c\":        self.__output_c,\n            \"csharp\":   self.__output_csharp,\n            \"nim\":      self.__output_nim,\n            \"go\":       self.__output_go,\n            \"py\":       self.__output_py,\n            \"ps1\":      self.__output_ps1,\n            \"vba\":      self.__output_vba,\n            \"vbscript\": self.__output_vbscript,\n            \"raw\":      self.__output_raw,\n            \"rust\":     self.__output_rust\n        }\n        return\n    \n    def __generate_array_contents(self, input_bytes:bytearray, string_format:bool=False) -> str:\n        \"\"\" Takes a byte array, and generates a string in format\n            0xaa,0xff,0xab(up to 15),\n            0x4f...\n        :param input_bytes: bytearray\n        :param string_format: Whether to print in the \\xff format or 0xff\n        :return: string containing formatted array contents\n        \"\"\"\n        # TODO: Rework this to support more languages than just those that use the 0x format\n        output = \"\"\n        if not string_format:\n            for i in range(len(input_bytes) - 1):\n                if i % 15 == 0:\n                    output += \"\\n\\t\"\n                output += f\"0x{input_bytes[i]:0>2x},\"\n            output += f\"0x{input_bytes[-1]:0>2x}\"\n            return output[1:] # (strip first \\n)\n        else:\n            for i in range(len(input_bytes) - 1):\n                if i % 15 == 0:\n                    output += \"\\n\"\n                output += f\"\\\\x{input_bytes[i]:0>2x}\"\n            output += f\"\\\\x{input_bytes[-1]:0>2x}\"\n            return output[1:] # (strip first \\n)            \n\n\n    def __output_c(self, arrays:dict) -> str:\n        \"\"\" Private method to output in C format.\n        :param arrays: dictionary containing array names and their respective bytes\n        :return output: string containing shellcode in c format, similar\n                        to msfvenom's csharp format.\n        \"\"\"\n        # Generate arrays\n        output = str()\n        for array_name in arrays:\n            output += f\"unsigned char {array_name}[{len(arrays[array_name])}] = {{\\n\"\n            output += self.__generate_array_contents(arrays[array_name])\n            output += \"\\n};\\n\\n\"\n        \n        return output\n    \n    def __output_rust(self, arrays:dict) -> str:\n        \"\"\" Private method to output in Rust format.\n        :param arrays: dictionary containing array names and their respective bytes\n        :return output: string containing shellcode in rust format, similar\n                        to msfvenom's rust format.\n        \"\"\"\n        # Generate arrays\n        output = str()\n        for array_name in arrays:\n            output += f\"let {array_name}: [u8; {len(arrays[array_name])}] = [\\n\"\n            output += self.__generate_array_contents(arrays[array_name])\n            output += \"\\n];\\n\\n\"\n        \n        return output\n    \n    def __output_csharp(self, arrays:dict) -> str:\n        \"\"\" Private method to output in C# format.\n        :param arrays: dictionary containing array names and their respective bytes\n        :return output: string containing shellcode in C# format\n        \"\"\"\n        # Generate arrays\n        output = str()\n        for array_name in arrays:\n            output += f\"byte[] {array_name} = new byte[{len(arrays[array_name])}] {{\\n\"\n            output += self.__generate_array_contents(arrays[array_name])\n            output += \"\\n};\\n\\n\"\n        \n        return output\n\n    def __output_nim(self, arrays:dict) -> str:\n        \"\"\" Private method to output in nim format.\n        :param arrays: dictionary containing array names and their respective bytes\n        :return output: string containing shellcode in nim format\n        \"\"\"\n        # Generate arrays\n        output = str()\n        for array_name in arrays:\n            output += f\"var {array_name}: array[{len(arrays[array_name])}, byte] = [\\n\"\n            output += \"\\tbyte \" + self.__generate_array_contents(arrays[array_name])[1:]\n            output += \"\\n]\\n\\n\"\n        return output\n\n    def __output_go(self, arrays:dict) -> str:\n        \"\"\" Private method to output in golang format.\n        :param arrays: dictionary containing array names and their respective bytes\n        :return output: string containing shellcode in golang format\n        \"\"\"\n        # Generate arrays\n        output = str()\n        for array_name in arrays:\n            output += f\"{array_name} := []byte{{\\n\"\n            output += self.__generate_array_contents(arrays[array_name])\n            output += \"\\n};\\n\\n\"\n        return output\n\n    def __output_py(self, arrays:dict) -> str:\n        \"\"\" Private method to output in python format.\n        :param arrays: dictionary containing array names and their respective bytes\n        :return output: string containing shellcode in python format\n        \"\"\"\n        # Note: Technically not best to use the triple quotes here but consistency ig\n        # Generate arrays\n        output = str()\n        for array_name in arrays:\n            output += f\"{array_name} = b\\\"\\\"\\\"\"\n            output += self.__generate_array_contents(arrays[array_name], string_format=True)\n            output += \"\\\"\\\"\\\"\\n\\n\"\n        return output\n\n    def __output_ps1(self, arrays:dict) -> str:\n        \"\"\" Private method to output in powershell format.\n        :param arrays: dictionary containing array names and their respective bytes\n        :return output: string containing shellcode in powershell format\n        \"\"\"\n        # Note: Technically not best to use the triple quotes here but consistency ig\n        # Generate arrays\n        output = str()\n        for array_name in arrays:\n            output += f\"[Byte[]] ${array_name} = \"\n            output += self.__generate_array_contents(arrays[array_name])[1:]\n            output += \"\\n\\n\"\n        return output\n\n    def __output_vba(self, arrays:dict) -> str:\n        \"\"\" Private method to output in visual basic application format.\n        :param arrays: dictionary containing array names and their respective bytes\n        :return output: string containing shellcode in visual basic application format\n        \"\"\"\n        # Generate arrays\n        output = str()\n        # VBA has a maximum line length of 1023 characters, so have to work around that\n        for array_name in arrays:\n            # Array name\n            output += f\"{array_name} = Array(\"\n            line_length = len(output)\n            # Array contents\n            array_size = len(arrays[array_name])\n            for i, x in enumerate(arrays[array_name]):\n                if i == array_size - 1:\n                    break\n                # If within 5 bytes, we have enough to write \"222,_\", which is enough for any value. \n                if line_length + 5 > 1022:\n                    output += \"_\\n\"\n                    line_length = 0\n                output += f\"{x},\"\n                line_length += len(f\"{x},\")\n            # Array end\n            if line_length + 4 > 1023:\n                output += \"_\\n\"\n            output += f\"{x})\\n\\n\"\n        return output\n\n    def __output_vbscript(self, arrays:dict) -> str:\n        \"\"\" Private method to output in vbscript format.\n        :param arrays: dictionary containing array names and their respective bytes\n        :return output: string containing shellcode in vbscript format\n        \"\"\"\n        # does not have short line lengths\n        # Generate arrays\n        output = str()\n        for array_name in arrays:\n            output += f\"{array_name}=\"\n            output += \"\".join([f\"Chr({str(c)})&\" for c in arrays[array_name]])[:-1]\n            output += \"\\n\\n\"\n        return output\n\n    def __output_raw(self, arrays:dict) -> str:\n        \"\"\" Private method to output shellcode in raw format.\n        :param arrays: dictionary containing array names and their respective bytes\n        :return output: string containing shellcode in raw format\n        \"\"\"\n        # Grab shellcode\n        return arrays[\"sh3llc0d3\"]\n\n    def generate(self, output_format:str, arrays:dict) -> str:\n        \"\"\" Generates output given the current class configuration\n        :param output_format: Output format to generate e.g. \"c\" or \"csharp\"\n        :param shellcode: dictionary containing {\"arrayname\":array_bytes} pairs\n        :return output: string containing formatted shellcode + key(s)\n        \"\"\"\n        # Pass execution to the respective handler and return\n        return self.__format_handlers[output_format](arrays)\n\nclass Encrypt:\n    \"\"\" Consolidates encryption into a single class. \"\"\"\n    def __init__(self):\n        super(Encrypt, self).__init__()\n        self.__encryption_handlers = {\n            \"xor\":      self.__xor,\n            \"aes\":      self.__aes_128,\n            \"rc4\":      self.__rc4,\n            \"chacha20\": self.__chacha20,\n            \"salsa20\":  self.__salsa20\n        }\n        return\n\n    def encrypt(self, cipher:str, plaintext:bytearray, key:bytearray, nonce:bytearray = None) -> bytearray:\n        \"\"\" Encrypts plaintext with the user-specified cipher.\n            This has been written this way to support chaining of\n            multiple encryption methods in the future.\n        :param cipher: cipher to use, e.g. 'xor'/'aes'\n        :param plaintext: bytearray containing our plaintext\n        :param key: bytearray containing our encryption key\n        :param nonce: bytearray containing nonce for aes etc. \n                      if none will be generated on the fly\n        :return ciphertext: bytearray containing encrypted plaintext\n        \"\"\"\n        # If nonce not specified, generate one, otherwise use the specified one.\n        self.nonce = urandom(16) if nonce is None else nonce\n        self.key = key\n        # cipher is already validated (check argument validation section).\n        return self.__encryption_handlers[cipher](plaintext)\n\n    def __xor(self, plaintext:bytearray) -> bytearray:\n        \"\"\" Private method to encrypt the input plaintext with a repeating XOR key.\n        :param plaintext: bytearray containing our plaintext\n        :return ciphertext: bytearray containing encrypted plaintext\n        \"\"\"\n        return bytearray(a ^ b for (a, b) in zip(plaintext, cycle(self.key)))\n\n    # TODO: Support other modes. \n    #       Currently just CBC.\n    def __aes_128(self, plaintext:bytearray) -> bytearray:\n        \"\"\" Private method to encrypt the input plaintext with AES-128 in CBC mode.\n        :param plaintext: bytearray containing plaintext\n        :return ciphertext: bytearray containing encrypted plaintext\n        \"\"\"\n        aes_cipher = AES.new(self.key, AES.MODE_CBC, self.nonce)\n        plaintext = pad(plaintext, 16)\n        return bytearray(aes_cipher.encrypt(plaintext))\n    \n    def __rc4(self, plaintext:bytearray) -> bytearray:\n        \"\"\" Private method to encrypt the input plaintext via RC4.\n        :param plaintext: bytearray containing plaintext\n        :return ciphertext: bytearray containing encrypted plaintext\n        \"\"\"\n        rc4_cipher = ARC4.new(self.key)\n        return rc4_cipher.encrypt(plaintext)\n    \n    def __chacha20(self, plaintext:bytearray) -> bytearray:\n        \"\"\" Private method to encrypt the input plaintext via ChaCha20.\n        :param plaintext: bytearray containing plaintext\n        :return ciphertext: bytearray containing encrypted plaintext\n        \"\"\"\n        chacha20_cipher = ChaCha20.new(key=self.key)\n        return chacha20_cipher.encrypt(plaintext) \n\n    def __salsa20(self, plaintext:bytearray) -> bytearray:\n        \"\"\" Private method to encrypt the input plaintext via Salsa20.\n        :param plaintext: bytearray containing plaintext\n        :return ciphertext: bytearray containing encrypted plaintext\n        \"\"\"\n        salsa20_cipher = Salsa20.new(key=key)\n        return salsa20_cipher.encrypt(plaintext)\n\n\nif __name__ == \"__main__\":\n    # --------- Initialisation ---------\n    # Debug mode toggle (logging)\n    DEBUG = False\n\n    # Completely unnecessary stuff (unless you're cool)\n    colorama_init()\n    show_banner()\n\n    # Parse arguments\n    argparser = argparse.ArgumentParser(prog=\"shellcrypt\")\n    argparser.add_argument(\"-i\", \"--input\", help=\"Path to file to be encrypted.\")\n    argparser.add_argument(\"-e\", \"--encrypt\", default=\"xor\", help=\"Encryption method to use, default 'xor'.\")\n    argparser.add_argument(\"-k\", \"--key\", help=\"Encryption key in hex format, default (random 16 bytes).\")\n    argparser.add_argument(\"-n\", \"--nonce\", help=\"Encryption nonce in hex format, default (random 16 bytes).\")\n    argparser.add_argument(\"-f\", \"--format\", help=\"Output format, specify --formats for a list of formats.\")\n    argparser.add_argument(\"--formats\", action=\"store_true\", help=\"Show a list of valid formats\")\n    argparser.add_argument(\"--ciphers\", action=\"store_true\", help=\"Show a list of valid ciphers\")\n    argparser.add_argument(\"-o\", \"--output\", help=\"Path to output file\")\n    argparser.add_argument(\"-v\", \"--version\", action=\"store_true\", help=\"Shows the version and exits\")\n    # TODO: Add --preserve-null flag for XOR. (Don't XOR null bytes.)\n    # TODO: Add length param for random key, currently locked at 16 bytes.\n    # TODO: Maybe add decryption routines?\n    args = argparser.parse_args()\n\n    # --------- Info-only arguments ---------\n    # If formats specified\n    if args.formats:\n        print(\"The following formats are available:\")\n        for i in OUTPUT_FORMATS:\n            print(f\" - {i}\")\n        exit()\n\n    # If ciphers specified\n    if args.ciphers:\n        print(\"The following ciphers are available:\")\n        for i in CIPHERS:\n            print(f\" - {i}\")\n        exit()\n\n    # If version specified\n    if args.version:\n        print(VERSION)\n        exit()\n    \n    # --------- Argument Validation ---------\n    Log.logDebug(\"Validating arguments\")\n\n    # Check input file is specified\n    if args.input is None:\n        Log.logError(\"Must specify an input file e.g. -i shellcode.bin (specify --help for more info)\")\n        exit()\n\n    # Check input file exists\n    if not isfile(args.input):\n        Log.logError(f\"Input file '{args.input}' does not exist.\")\n        exit()\n    \n    # TODO: check we can read the file.\n\n    Log.logSuccess(f\"Input file: '{args.input}'\")\n\n    # Check format is specified\n    if args.format not in OUTPUT_FORMATS:\n        Log.logError(\"Invalid format specified, please specify a valid format e.g. -f c (--formats gives a list of valid formats) \") \n        exit()\n    \n    Log.logSuccess(f\"Output format: {args.format}\")\n\n    # Check encrypt is specified\n    if args.encrypt not in CIPHERS:\n        Log.logError(\"Invalid cipher specified, please specify a valid cipher e.g. -e xor (--ciphers gives a list of valid ciphers) \") \n        exit()\n    \n    Log.logSuccess(f\"Output format: {args.encrypt}\")\n\n    # Check if key is specified.\n    # if so => validate and store in key\n    # else => generate and store in key\n    if args.key is None:\n        key = urandom(32) # Changed from 8 to 16 to make AES support easier :)\n    else:\n        if len(args.key) < 2 or len(args.key) % 2 == 1:\n            Log.logError(\"Key must be valid byte(s) in hex format (e.g. 4141).\")\n            exit()\n        if args.encrypt == \"aes\" and len(args.key) != 32:\n            Log.logError(\"AES-128 key must be exactly 16 bytes long.\")\n            exit()\n        for i in args.key:\n            if i not in hexdigits:\n                Log.logError(\"Key must be valid byte(s) in hex format (e.g. 4141).\")\n                exit()\n        \n        key = bytearray.fromhex(args.key)\n    \n    Log.logSuccess(f\"Using key: {hexlify(key).decode()}\")\n    \n    # TODO: somehow join the above and this as it's a lot of repeated code,\n    #       maybe some kind of method for checking if an input is hex and 16 bytes ? \n    # Validate the user's nonce if one is specified, else generate one\n    if args.nonce is None:\n        nonce = urandom(16)\n    else:\n        if len(args.nonce) != 32:\n            Log.logError(\"Nonce must be exactly 16 bytes long\")\n            exit()\n        for i in args.nonce:\n            if i not in hexdigits:\n                Log.logError(\"Nonce must be 16 valid bytes in hex format (e.g. 7468697369736d616c6963696f757321)\")\n                exit()\n    \n        nonce = bytearray.fromhex(args.nonce)\n    \n    # Only show nonce if it's used, could be confusing to the user otherwise\n    # TODO: probably change this in the future to if args.encrypt in requires_nonce => show\n    if args.encrypt == \"aes\":\n        Log.logSuccess(f\"Using nonce: {hexlify(nonce).decode()}\")\n\n    Log.logDebug(\"Arguments validated\")\n\n    # --------- Read Input File ---------\n    input_bytes = None\n    with open(args.input, \"rb\") as input_handle:\n        input_bytes = input_handle.read()\n\n    # --------- Input File Encryption ---------\n    #Log.logInfo(f\"Encrypting {len(input_bytes)} bytes\") (came up with a better idea, keeping for future reminder)\n    Log.logDebug(f\"Encrypting input file\")\n\n    #input_bytes  = bytearray(a ^ b for (a, b) in zip(input_bytes, cycle(key)))\n    cryptor = Encrypt()\n    input_bytes = cryptor.encrypt(args.encrypt, input_bytes, key, nonce)\n    input_length = len(input_bytes)\n\n    Log.logSuccess(f\"Successfully encrypted input file ({len(input_bytes)} bytes)\")\n\n    # --------- Output Generation ---------\n    # Define array names + content to be formatted\n    arrays = {\n        \"key\":key\n    }\n\n    # If aes in use, add nonce to the arrays\n    if args.encrypt == \"aes\":\n        arrays[\"nonce\"] = nonce\n    \n    # Removed from the initialization line(s) for arrays for nicer output ordering.\n    arrays[\"sh3llc0d3\"] = input_bytes\n\n    # Generate formatted output.\n    shellcode_formatter = ShellcodeFormatter()\n    output = shellcode_formatter.generate(args.format, arrays)\n    \n    # --------- Output ---------\n    # If no output file specified.\n    if args.output is None:\n        # We want to decode if it's a bytearray. (for raw mode)\n        print(output.decode(\"latin1\") if isinstance(output, bytearray) else output) \n        exit()\n    \n    # If output file specified.\n    Log.logDebug(f\"output var type: {type(output)}\")\n    write_mode = (\"wb\" if isinstance(output, bytearray) else \"w\") # We want wb if it's a bytearray. (for raw mode)\n    Log.logDebug(f\"write_mode = \\\"{write_mode}\\\"\")\n    with open(args.output, write_mode) as file_handle:\n        file_handle.write(output)\n    \n    Log.logSuccess(f\"Output written to '{args.output}'\")\n"
  }
]