├── .gitignore ├── README.md └── mutate.py /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | Makefile 3 | *.bin 4 | *.asm 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Shellcode Mutator 2 | 3 | Mutate nasm assembly source files using no-instruction sets (such as `nops`) to avoid signatures. 4 | 5 | ## Usage 6 | 7 | ``` 8 | ################## 9 | It's Morphin' Time 10 | ################## 11 | usage: mutate.py [-h] [-t SHELLCODE_TEMPLATE] [-s SHELLCODE_FILE] [-m MORPH_PERCENTAGE] [-v] [-x86] 12 | 13 | Insert random no-instructions at random locations into assembly shellcode 14 | 15 | optional arguments: 16 | -h, --help show this help message and exit 17 | -t SHELLCODE_TEMPLATE, --shellcode-template SHELLCODE_TEMPLATE 18 | the template shellcode file to use 19 | -s SHELLCODE_FILE, --shellcode-file SHELLCODE_FILE 20 | where to write the morphed file to 21 | -m MORPH_PERCENTAGE, --morph-percentage MORPH_PERCENTAGE 22 | percentage increase of the number of instructions with no-instructions 23 | -v, --verbose enable verbose mode 24 | -x86, --x86 x86 mode 25 | ``` 26 | -------------------------------------------------------------------------------- /mutate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import shutil 3 | import random 4 | import argparse 5 | 6 | x64_no_instruction_sets = [ 7 | [ 8 | 'nop' 9 | ], 10 | [ 11 | 'xchg eax, eax' 12 | ] 13 | ] 14 | 15 | x86_no_instruction_sets = [ 16 | [ 17 | 'nop' 18 | ], 19 | [ 20 | 'xchg eax, eax' 21 | ] 22 | ] 23 | 24 | 25 | def tab(x): 26 | result = [] 27 | for y in x: 28 | result.append(f" {y}") 29 | return result 30 | 31 | 32 | x64_no_instruction_sets = list(map(tab, x64_no_instruction_sets)) 33 | x86_no_instruction_sets = list(map(tab, x86_no_instruction_sets)) 34 | 35 | assembly_instructions = [ 36 | 'push', 37 | 'pop', 38 | 'ret', 39 | 'mov', 40 | 'xchg', 41 | 'xor', 42 | 'call', 43 | 'loop', 44 | 'test', 45 | 'jmp', 46 | 'jz', 47 | 'jnz', 48 | 'add', 49 | 'dec', 50 | 'shl', 51 | 'shr', 52 | 'cmp', 53 | 'loopnz', 54 | ] 55 | 56 | 57 | def starts_with_pneumonic(line): 58 | for i in assembly_instructions: 59 | if line.strip().lower().startswith(f"{i} "): 60 | return True 61 | return False 62 | 63 | 64 | def morph(shellcode_file, morph_percentage, verbose, x86_mode): 65 | ''' 66 | Assumes max one empty line between each function 67 | ''' 68 | with open(shellcode_file, 'r') as f: 69 | shellcode_source = f.read().split('\n') 70 | 71 | num_of_lines = len(shellcode_source) 72 | print(f"Initial length: {num_of_lines}") 73 | 74 | x = 0 75 | started = False 76 | while x < (num_of_lines - 2): 77 | x += 1 78 | 79 | if verbose: 80 | print(f"Processing:\n{shellcode_source[x]}") 81 | 82 | if shellcode_source[x].strip().startswith(";") or not starts_with_pneumonic(shellcode_source[x]): 83 | if verbose: 84 | print("Not instruction line") 85 | continue 86 | 87 | if verbose: 88 | print(f"Rolling the dice...") 89 | if random.random() > (1 - (morph_percentage / 100)): 90 | if verbose: 91 | print(f"Score! Picking instructions") 92 | if x86_mode: 93 | instructions = random.choice(x86_no_instruction_sets) 94 | else: 95 | instructions = random.choice(x64_no_instruction_sets) 96 | 97 | for instruction in instructions: 98 | if verbose: 99 | print(f"Inserting instruction at offset {x}") 100 | print(f"{instruction}") 101 | shellcode_source.insert(x, instruction) 102 | x += 1 103 | num_of_lines += 1 104 | elif verbose: 105 | print(f"Not this time...") 106 | 107 | with open(shellcode_file, 'w') as f: 108 | print(f"Final length: {len(shellcode_source)} lines") 109 | f.write('\n'.join(shellcode_source)) 110 | 111 | 112 | def create_arg_parser(): 113 | parser = argparse.ArgumentParser(description='Insert random no-instructions at random locations into assembly shellcode') 114 | parser.add_argument("-t", "--shellcode-template", help="the template shellcode file to use", default='Source/ASM/shellcode-template.asm') 115 | parser.add_argument("-s", "--shellcode-file", help="where to write the morphed file to", default='Source/ASM/shellcode.asm') 116 | parser.add_argument("-m", "--morph-percentage", help="percentage increase of the number of instructions with no-instructions", type=int, default=15) 117 | parser.add_argument("-v", "--verbose", help="enable verbose mode", action="store_true") 118 | parser.add_argument("-x86", "--x86", help="x86 mode", action="store_true") 119 | return parser 120 | 121 | 122 | def main(): 123 | 124 | print("##################") 125 | print("It's Morphin' Time") 126 | print("##################") 127 | 128 | parser = create_arg_parser() 129 | args = parser.parse_args() 130 | 131 | shellcode_file = args.shellcode_file 132 | shellcode_template = args.shellcode_template 133 | 134 | print("Copying template...") 135 | shutil.copyfile(shellcode_template, shellcode_file) 136 | print("Morphing shellcode...") 137 | if args.x86: 138 | print("x86 mode") 139 | morph(shellcode_file, args.morph_percentage, args.verbose, args.x86) 140 | print("Done") 141 | 142 | 143 | if __name__ == '__main__': 144 | main() 145 | --------------------------------------------------------------------------------