init
This commit is contained in:
+271
@@ -0,0 +1,271 @@
|
||||
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
|
||||
Reference in New Issue
Block a user