├── .gitignore ├── README.md ├── examples ├── example_addition.py ├── example_addition_2.py └── example_custom_function.py ├── main.py ├── transform_to_vm.py └── vm.py /.gitignore: -------------------------------------------------------------------------------- 1 | test.py 2 | test2.py 3 | TODO.md 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PyObfuscator 2 | 3 | Make your code difficult to understand and read. Minimize exposure to attacks. 4 | 5 | This is an educational project to learn more about the Abstract-Syntax-Tree library of Python and to explore the possibilities you have with Python-Obfuscation. 6 | 7 | The project is still in a very early stage of development. It currently only works on very simple scripts, so use it at your own risk! 8 | 9 | ## Examples 10 | Disclaimer: the third translation is not included yet, but it is as simple as renaming `MEMORY` to `m`, `OPCODES` to `o` etc. 11 | 12 | Example 1: 13 | ```python 14 | number1 = 1 15 | number2 = 5 16 | sum = number1 + number2 17 | 18 | # Translates to ------------------------------------------ 19 | from vm import * 20 | 21 | MEMORY[0] = 1 22 | MEMORY[1] = 5 23 | 24 | MEMORY[2] = OPCODES[0](MEMORY[0], MEMORY[1]) 25 | 26 | # Translates to ------------------------------------------ 27 | from vm import * 28 | 29 | m[0] = 1 30 | m[1] = 5 31 | m[2] = o[0](m[0], m[1]) 32 | ``` 33 | 34 | Example 2: 35 | ```python 36 | def print_sum(number1, number2): 37 | print(number1 + number2) 38 | 39 | number1 = 1 40 | number2 = 5 41 | print_sum(number1, number2) 42 | 43 | # Translates to ------------------------------------------ 44 | from vm import * 45 | 46 | def function1(arg1, arg2): 47 | print(OPCODES[0](arg1, arg2)) 48 | 49 | CUSTOM_FUNCTIONS[0] = function1 50 | MEMORY[0] = 1 51 | MEMORY[1] = 5 52 | 53 | MEMORY[2] = OPCODES[4](0, 0, 1) 54 | 55 | 56 | # Translates to ------------------------------------------ 57 | from vm import * 58 | 59 | def function1(arg1, arg2): 60 | print(OPCODES[0](arg1, arg2)) 61 | f[0] = function1 62 | m[0] = 1 63 | m[1] = 5 64 | m[2] = o[4](0, 0, 1) 65 | ``` 66 | 67 | ## Quick Start 68 | A section explaining how to use the obfuscator will come as soon as it is useable for more complex code. 69 | 70 | ## Planned Features 71 | - Support for more complex code (classes, more operations etc.) 72 | - Anti debugging method 73 | - One line obfuscation 74 | - many more.. 75 | -------------------------------------------------------------------------------- /examples/example_addition.py: -------------------------------------------------------------------------------- 1 | from vm import * 2 | 3 | sum = 1 + 5 4 | 5 | # Translates to -------------------------------------------------------------------------------------------------------- 6 | MEMORY[2] = OPCODES[0](1, 5) 7 | 8 | # Translates to -------------------------------------------------------------------------------------------------------- 9 | 10 | #m[2] = o[0](1, 5) 11 | -------------------------------------------------------------------------------- /examples/example_addition_2.py: -------------------------------------------------------------------------------- 1 | from vm import * 2 | 3 | number1 = 1 4 | number2 = 5 5 | sum = number1 + number2 6 | 7 | # Translates to -------------------------------------------------------------------------------------------------------- 8 | 9 | MEMORY[0] = 1 10 | MEMORY[1] = 5 11 | 12 | MEMORY[2] = OPCODES[0](MEMORY[0], MEMORY[1]) 13 | 14 | # Translates to -------------------------------------------------------------------------------------------------------- 15 | 16 | #m[0] = 1 17 | #m[1] = 5 18 | #m[2] = o[0](m[0], m[1]) 19 | -------------------------------------------------------------------------------- /examples/example_custom_function.py: -------------------------------------------------------------------------------- 1 | from vm import * 2 | 3 | def print_sum(number1, number2): 4 | print(number1 + number2) 5 | 6 | 7 | number1 = 1 8 | number2 = 5 9 | print_sum(number1, number2) 10 | 11 | # Translates to -------------------------------------------------------------------------------------------------------- 12 | def function1(arg1, arg2): 13 | print(OPCODES[0](arg1, arg2)) 14 | 15 | CUSTOM_FUNCTIONS[0] = function1 16 | MEMORY[0] = 1 17 | MEMORY[1] = 5 18 | 19 | MEMORY[2] = OPCODES[4](0, 0, 1) 20 | 21 | 22 | # Translates to -------------------------------------------------------------------------------------------------------- 23 | #def function1(arg1, arg2): 24 | # print(OPCODES[0](arg1, arg2)) 25 | #f[0] = function1 26 | #m[0] = 1 27 | #m[1] = 5 28 | #m[2] = o[4](0, 0, 1) 29 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | # The file that will get obfuscated in transform_to_vm.py. 2 | # This will be improved in near future. 3 | 4 | def print_sum(number1, number2): 5 | print(number1 + number2) 6 | 7 | def print_minus(number1, number2): 8 | print(number1 - number2) 9 | 10 | number1 = 1 11 | number2 = 5 12 | print_sum(number1, number2) -------------------------------------------------------------------------------- /transform_to_vm.py: -------------------------------------------------------------------------------- 1 | import ast 2 | 3 | class GeneralTransformer(ast.NodeTransformer): 4 | 5 | def visit_Module(self, node: ast.Module): 6 | """ 7 | Add import statement, to import required variables for the VM 8 | """ 9 | node.body.insert(0, ast.ImportFrom( 10 | module='vm', 11 | names=[ 12 | ast.alias(name='MEMORY'), 13 | ast.alias(name='OPCODES') 14 | ], 15 | level=0) 16 | ) 17 | return node 18 | 19 | class AssignTransformer(ast.NodeTransformer): 20 | 21 | def __init__(self, storage): 22 | self.storage = storage 23 | 24 | def visit_Assign(self, node: ast.Assign): 25 | """ 26 | remove variables and use memory instead 27 | """ 28 | new_function_names = [value["new_name"] for value in self.storage["function_memory"].values()] 29 | if type(node.value) == ast.Name and node.value.id in new_function_names: 30 | return node 31 | 32 | new_target = [] 33 | for target in node.targets: 34 | new_target.append(ast.Subscript( 35 | value=ast.Name(id='MEMORY', ctx=ast.Load()), 36 | slice=ast.Constant(value=self.storage["variable_memory_increment"]), 37 | ctx=ast.Store() 38 | )) 39 | self.storage["variable_memory"][target.id] = self.storage["variable_memory_increment"] 40 | self.storage["variable_memory_increment"] += 1 41 | 42 | return ast.Assign( 43 | targets=new_target, 44 | value=node.value, 45 | type_comment=None, 46 | ) 47 | 48 | class CallTransformer(ast.NodeTransformer): 49 | 50 | def __init__(self, storage): 51 | self.storage = storage 52 | 53 | def visit_Call(self, node: ast.Call): 54 | """ 55 | call functions from function memory 56 | """ 57 | if node.func.id in self.storage["function_memory"]: 58 | function_index = self.storage["function_memory"][node.func.id]["index"] 59 | new_arguments = [ast.Constant(value=function_index)] 60 | for arg in node.args: 61 | new_arguments.append(arg.slice) 62 | return ast.Expr( 63 | value=ast.Call( 64 | func=ast.Subscript( 65 | value=ast.Name(id='OPCODES', ctx=ast.Load()), 66 | slice=ast.Constant(value=4), 67 | ctx=ast.Load()), 68 | args=new_arguments, 69 | keywords=[] 70 | ) 71 | ) 72 | 73 | return node 74 | 75 | class FunctionTransformer(ast.NodeTransformer): 76 | 77 | def __init__(self, storage): 78 | self.storage = storage 79 | 80 | def visit_FunctionDef(self, node: ast.FunctionDef): 81 | """ 82 | rename all functions and their arguments 83 | """ 84 | 85 | # rename function 86 | new_function_name = f"function{self.storage['function_memory_increment']}" 87 | self.storage['function_memory'][node.name] = { 88 | "new_name": new_function_name, 89 | "index": self.storage['function_memory_increment'] 90 | } 91 | node.name = new_function_name 92 | 93 | # rename function arguments 94 | argument_increment = 0 95 | argument_memory = {} 96 | for child_node in ast.walk(node): 97 | if type(child_node) == ast.arg: 98 | argument_memory[child_node.arg] = f"arg{argument_increment}" 99 | child_node.arg = f"arg{argument_increment}" 100 | argument_increment += 1 101 | elif type(child_node) == ast.Name and child_node.id in argument_memory: 102 | child_node.id = argument_memory[child_node.id] 103 | 104 | # save function to CUSTOM_FUNCTIONS memory 105 | index = node.parent.body.index(node) 106 | node.parent.body.insert(index + 1, ast.Assign( 107 | targets=[ 108 | ast.Subscript( 109 | value=ast.Name(id='CUSTOM_FUNCTIONS', ctx=ast.Load()), 110 | slice=ast.Constant(value=self.storage['function_memory_increment']), 111 | ctx=ast.Store()) 112 | ], 113 | value=ast.Name(id=new_function_name, ctx=ast.Load())) 114 | ) 115 | 116 | self.storage['function_memory_increment'] += 1 117 | return node 118 | 119 | class NameTransformer(ast.NodeTransformer): 120 | 121 | def __init__(self, storage): 122 | self.storage = storage 123 | 124 | def visit_Name(self, node: ast.Name): 125 | """ 126 | replace all variables with their memory position 127 | """ 128 | if node.id in self.storage["ignore_list"]: 129 | return node 130 | 131 | if node.id not in self.storage["variable_memory"]: 132 | return node 133 | else: 134 | return ast.Subscript( 135 | value=ast.Name(id='MEMORY', ctx=ast.Load()), 136 | slice=ast.Constant(value=self.storage["variable_memory"][node.id]), 137 | ctx=ast.Store() 138 | ) 139 | 140 | class OperatorTransformer(ast.NodeTransformer): 141 | 142 | def __init__(self): 143 | self.opcodes = { 144 | ast.Add: 0, 145 | ast.Sub: 1, 146 | ast.Mult: 2, 147 | ast.Div: 3, 148 | } 149 | 150 | def visit_BinOp(self, node: ast.BinOp): 151 | """ 152 | Replace binary operations like +, -, *, / 153 | """ 154 | if type(node.op) not in self.opcodes: 155 | raise Exception("Binary Operation not supported") 156 | 157 | return ast.Call( 158 | func=ast.Subscript( 159 | value=ast.Name(id='OPCODES', ctx=ast.Load()), 160 | slice=ast.Constant(value=self.opcodes[type(node.op)]), 161 | ctx=ast.Load()), 162 | args=[ 163 | node.left, 164 | node.right 165 | ], 166 | keywords=[] 167 | ) 168 | 169 | 170 | def main(): 171 | """ 172 | Parse the code out of the main.py file and obfuscate it. 173 | Currently is taking the main.py by default. I'll implement a way to do it via command in near future. 174 | """ 175 | with open("main.py", "r") as f: 176 | code = f.read() 177 | 178 | storage = { 179 | "ignore_list": ["MEMORY", "OPCODES", "print"], 180 | "variable_memory_increment": 0, 181 | "variable_memory": {}, 182 | "function_memory_increment": 0, 183 | "function_memory": {}, 184 | } 185 | 186 | tree = ast.parse(code) 187 | # create parent nodes 188 | for node in ast.walk(tree): 189 | for child in ast.iter_child_nodes(node): 190 | child.parent = node 191 | 192 | # run all transformers to obfuscate the code 193 | tree = ast.fix_missing_locations(GeneralTransformer().visit(tree)) 194 | tree = ast.fix_missing_locations(FunctionTransformer(storage).visit(tree)) 195 | tree = ast.fix_missing_locations(AssignTransformer(storage).visit(tree)) 196 | tree = ast.fix_missing_locations(NameTransformer(storage).visit(tree)) 197 | tree = ast.fix_missing_locations(CallTransformer(storage).visit(tree)) 198 | final_tree = ast.fix_missing_locations(OperatorTransformer().visit(tree)) 199 | 200 | print("----------------------------------------------------------------------") 201 | print("---AST Tree-----------------------------------------------------------") 202 | print(ast.dump(final_tree, indent=4)) 203 | print("----------------------------------------------------------------------") 204 | print("---Code --------------------------------------------------------------") 205 | print(ast.unparse(final_tree)) 206 | 207 | if __name__ == '__main__': 208 | main() -------------------------------------------------------------------------------- /vm.py: -------------------------------------------------------------------------------- 1 | 2 | # saves variables 3 | MEMORY = {} 4 | # saves functions 5 | CUSTOM_FUNCTIONS = {} 6 | 7 | def execute_function(reference, *arguments): 8 | """ 9 | This functions is able to call functions saved inside `CUSTOM_FUNCTIONS` 10 | """ 11 | new_arguments = (MEMORY[arg] for arg in arguments) 12 | return CUSTOM_FUNCTIONS[reference](*new_arguments) 13 | 14 | def opcode_1(arg1, arg2): 15 | return arg1 + arg2 16 | 17 | def opcode_2(arg1, arg2): 18 | return arg1 - arg2 19 | 20 | def opcode_3(arg1, arg2): 21 | return arg1 * arg2 22 | 23 | def opcode_4(arg1, arg2): 24 | return arg1 / arg2 25 | 26 | # will save all in house functions of python from +, -, *, / to sum, list, dict, print etc. etc. 27 | OPCODES = [ 28 | opcode_1, 29 | opcode_2, 30 | opcode_3, 31 | opcode_4, 32 | execute_function 33 | ] --------------------------------------------------------------------------------