import ast import random import json import os from datetime import datetime from transformers.rename import RenameTransformer from transformers.control_flow import ControlFlowFlattener from transformers.class_analyzer import ClassAnalyzer, update_obfuscator_with_class_mappings from transformers.attribute_transformer import AttributeTransformer from transformers.symbol_tree import SymbolTreeBuilder, SymbolTree from utils.name_gen import NameGenerator from utils.junk_gen import JunkGenerator from transformers.class_mapper import apply_class_mapping class AdvancedObfuscator: def __init__(self, debug_mode=False): self.used_names = set() self.name_generator = NameGenerator() # Generate keys for string encryption self.primary_key = bytes([random.randint(65, 90) for _ in range(16)]) self.secondary_key = bytes([random.randint(65, 90) for _ in range(8)]) self.salt = bytes([random.randint(65, 90) for _ in range(4)]) # Symbol tree for global tracking of all symbols self.symbol_tree = None # Legacy mappings for backward compatibility self.class_attr_mapping = {} self.global_var_renames = {} # Initialize junk generator self.junk_gen = JunkGenerator(self.name_generator) # Debugging flags and data structures self.debug_mode = debug_mode self.debug_data = { "timestamp": datetime.now().isoformat(), "transformations": [], "class_mappings": {}, "variable_mappings": {}, "method_mappings": {}, "string_encryption": [], "issues_detected": [], "control_flow_stats": {}, "junk_stats": {"count": 0, "snippets": []} } def log_debug(self, category, data): """Log debugging information if debug mode is on.""" if self.debug_mode: self.debug_data["transformations"].append({ "stage": category, "data": data, "timestamp": datetime.now().isoformat() }) def detect_issues(self): """Detect potential issues in the obfuscation process.""" if not self.debug_mode: return # First check issues in the symbol tree if self.symbol_tree: symbol_tree_issues = self.symbol_tree.check_for_issues() for issue in symbol_tree_issues: self.debug_data["issues_detected"].append(issue) # Legacy checks for backwards compatibility used_renames = set() duplicates = [] for orig, renamed in self.global_var_renames.items(): if renamed in used_renames: duplicates.append(renamed) used_renames.add(renamed) # Check if our renamed values match any original names (could lead to conflicts) if renamed in self.global_var_renames.keys(): self.debug_data["issues_detected"].append({ "type": "name_collision", "info": f"Renamed value '{renamed}' matches an original name" }) if duplicates: self.debug_data["issues_detected"].append({ "type": "duplicate_names", "names": duplicates }) def _build_symbol_tree(self, tree: ast.AST) -> SymbolTree: """ Build a comprehensive symbol tree from the AST for better tracking and renaming. """ if self.debug_mode: self.log_debug("symbol_tree_building", "Building global symbol tree") builder = SymbolTreeBuilder() symbol_tree = builder.build_tree(tree) # Apply name generator to all symbols symbol_tree.apply_name_generator(self.name_generator) # Populate legacy mappings for backward compatibility rename_mapping = symbol_tree.get_rename_mapping() # Update class and method mappings for class_name, class_obf_name in rename_mapping["classes"].items(): self.global_var_renames[class_name] = class_obf_name # Initialize class_attr_mapping entry if class_obf_name not in self.class_attr_mapping: self.class_attr_mapping[class_obf_name] = {} # Copy method mappings if class_name in rename_mapping["methods"]: for method_name, method_obf_name in rename_mapping["methods"][class_name].items(): self.class_attr_mapping[class_obf_name][method_name] = method_obf_name # Copy attribute mappings if class_name in rename_mapping["attributes"]: for attr_name, attr_obf_name in rename_mapping["attributes"][class_name].items(): self.class_attr_mapping[class_obf_name][attr_name] = attr_obf_name # Update function and variable mappings self.global_var_renames.update(rename_mapping["functions"]) self.global_var_renames.update(rename_mapping["variables"]) if self.debug_mode: self.log_debug("symbol_tree_stats", { "classes": len(rename_mapping["classes"]), "methods": sum(len(methods) for methods in rename_mapping["methods"].values()), "attributes": sum(len(attrs) for attrs in rename_mapping["attributes"].values()), "functions": len(rename_mapping["functions"]), "variables": len(rename_mapping["variables"]) }) return symbol_tree def _rename_and_encrypt(self, tree: ast.AST) -> ast.AST: """ Transform the AST to rename variables and encrypt string literals. Uses the symbol tree for consistent renaming. """ # First, build a comprehensive symbol tree self.symbol_tree = self._build_symbol_tree(tree) # Now perform the main transformation with consistent mappings transformer = RenameTransformer( self.name_generator, self.global_var_renames, self.class_attr_mapping, self.primary_key, self.secondary_key, self.salt, debug_mode=self.debug_mode ) tree = transformer.visit(tree) # Capture debug data from transformer if available if self.debug_mode and hasattr(transformer, 'debug_data'): self.debug_data["variable_mappings"] = transformer.debug_data.get("variable_mappings", {}) self.debug_data["string_encryption"] = transformer.debug_data.get("string_encryption", []) ast.fix_missing_locations(tree) return tree def _flatten_control_flow(self, tree: ast.AST) -> ast.AST: """ Flatten control flow by rewriting function/module bodies into a while-based dispatch. """ flattener = ControlFlowFlattener(debug_mode=self.debug_mode) tree = flattener.visit(tree) # Capture debug data from flattener if available if self.debug_mode and hasattr(flattener, 'debug_data'): self.debug_data["control_flow_stats"] = flattener.debug_data ast.fix_missing_locations(tree) return tree def _generate_final_code(self, tree: ast.AST) -> str: """ Convert the final AST to code, and optionally insert junk snippets among lines to obscure readability. """ lines = ast.unparse(tree).split('\n') in_multiline = False skip_for_n = 0 junk_count = 0 junk_snippets = [] def in_try_block(ls, ci): c_line = ls[ci] c_strip = c_line.lstrip() c_indent = len(c_line) - len(c_strip) saw_try = False for idx in range(ci - 1, -1, -1): l = ls[idx] s = l.lstrip() i_amount = len(l) - len(s) if s.startswith(("except ", "finally:")) and i_amount <= c_indent: break if s.startswith("try:") and i_amount <= c_indent: saw_try = True break return saw_try # We add these two lines at the top to ensure random is imported result = ["import random"] for i, line in enumerate(lines): strip = line.lstrip() if '"""' in strip or "'''" in strip: in_multiline = not in_multiline # Conditions to inject junk can_inject = ( not in_multiline and not in_try_block(lines, i) and skip_for_n == 0 and not strip.startswith( ( "def ", "class ", "@", "try:", "except ", "finally:", "import ", "from ", "elif ", "else:", "return", "raise", "pass", "break", "continue" ) ) ) if can_inject: junk = self.junk_gen.generate_junk() result.append(junk) junk_snippets.append(junk) junk_count += 1 skip_for_n = random.randint(5, 15) else: skip_for_n = max(0, skip_for_n - 1) result.append(line) if self.debug_mode: self.debug_data["junk_stats"]["count"] = junk_count self.debug_data["junk_stats"]["snippets"] = junk_snippets return '\n'.join(result) def obfuscate(self, code: str) -> str: """ Main method to obfuscate the given code. """ tree = ast.parse(code) # Step 1: Rename and encrypt tree = self._rename_and_encrypt(tree) # Step 2: Flatten control flow tree = self._flatten_control_flow(tree) # Step 3: Generate final obfuscated code final_code = self._generate_final_code(tree) if self.debug_mode: debug_output_path = os.path.join("debug", f"debug_{self.debug_data['timestamp']}.json") os.makedirs(os.path.dirname(debug_output_path), exist_ok=True) with open(debug_output_path, "w") as debug_file: json.dump(self.debug_data, debug_file, indent=4) return final_code