271 lines
10 KiB
Python
271 lines
10 KiB
Python
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 |