From 69184c7cb89782dc4b9293998244cb45945b3406 Mon Sep 17 00:00:00 2001 From: Zack3D Date: Fri, 15 Aug 2025 21:06:31 -0700 Subject: [PATCH] da --- .gitea/workflows/doxygen-to-wiki.yml | 90 +++++++++++++++++++++++++++ Doxyfile | 39 ++++++++++++ README.md | 29 ++++++++- main.py | 14 +++++ obfuscator.py | 48 +++++++++++--- transformers/__init__.py | 8 ++- transformers/attribute_transformer.py | 32 +++++++--- transformers/class_analyzer.py | 53 +++++++++++----- transformers/class_mapper.py | 88 +++++++++++++++++++++----- transformers/control_flow.py | 6 ++ transformers/rename.py | 12 ++++ transformers/symbol_tree.py | 7 +++ utils/__init__.py | 7 ++- utils/encryption.py | 35 ++++++++--- utils/junk_gen.py | 20 +++++- utils/name_gen.py | 20 +++++- 16 files changed, 444 insertions(+), 64 deletions(-) create mode 100644 .gitea/workflows/doxygen-to-wiki.yml create mode 100644 Doxyfile diff --git a/.gitea/workflows/doxygen-to-wiki.yml b/.gitea/workflows/doxygen-to-wiki.yml new file mode 100644 index 0000000..5be1b6a --- /dev/null +++ b/.gitea/workflows/doxygen-to-wiki.yml @@ -0,0 +1,90 @@ +# Publish Doxygen HTML to the repository Wiki via Gitea Actions +# Requirements: +# - Create a repository secret named GITEA_TOKEN with a Personal Access Token that has write access. +# - Ensure the Wiki is enabled for this repository. +# - Doxygen output is expected at docs/html (from Doxyfile: OUTPUT_DIRECTORY = docs). + +name: Doxygen to Wiki + +on: + push: + branches: + - '**' + paths: + - '**.py' + - '**.md' + - 'Doxyfile' + - '.gitea/workflows/doxygen-to-wiki.yml' + workflow_dispatch: + +permissions: + contents: read + +jobs: + publish: + name: Build Doxygen and publish to Wiki + if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master' }} + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Install Doxygen and tools + shell: bash + run: | + sudo apt-get update + sudo apt-get install -y doxygen rsync + + - name: Build Doxygen site + shell: bash + run: | + doxygen Doxyfile + test -d docs/html || { echo "Doxygen output not found at docs/html"; exit 2; } + + - name: Publish to Wiki + env: + GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} + shell: bash + run: | + set -euo pipefail + if [ -z "${GITEA_TOKEN:-}" ]; then + echo "Missing GITEA_TOKEN secret. Create a repo secret named GITEA_TOKEN with a PAT that has write access to this repository wiki." >&2 + exit 1 + fi + # Derive wiki URL: https:////.wiki.git using actor + token for auth + SERVER="${GITHUB_SERVER_URL}" + SERVER="${SERVER#https://}" + SERVER="${SERVER#http://}" + WIKI_URL="https://${GITHUB_ACTOR}:${GITEA_TOKEN}@${SERVER}/${GITHUB_REPOSITORY}.wiki.git" + + echo "Cloning wiki repository from ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.wiki" + rm -rf wiki + git clone "$WIKI_URL" wiki + + # Sync generated site to wiki/doxygen (clean removed files) + mkdir -p wiki/doxygen + rsync -a --delete --checksum docs/html/ wiki/doxygen/ + + # Seed a Doxygen.md landing page if it doesn't exist + if [ ! -f "wiki/Doxygen.md" ]; then + printf "%s\n" \ + "# Doxygen Documentation" \ + "" \ + "The generated API reference is published in this wiki under the doxygen directory." \ + "" \ + "- Open the HTML entry point: [doxygen/index.html](doxygen/index.html)" \ + > wiki/Doxygen.md + fi + + cd wiki + git config user.name "${GITHUB_ACTOR}" + git config user.email "${GITHUB_ACTOR}@users.noreply" + git add -A + if git diff --cached --quiet; then + echo "No changes to publish." + exit 0 + fi + git commit -m "docs: update doxygen site from ${GITHUB_SHA}" + git push origin HEAD \ No newline at end of file diff --git a/Doxyfile b/Doxyfile new file mode 100644 index 0000000..d94f9e0 --- /dev/null +++ b/Doxyfile @@ -0,0 +1,39 @@ +# Doxyfile for OMG-Fuscator (Python) +PROJECT_NAME = "OMG-Fuscator" +PROJECT_BRIEF = "Advanced Python obfuscator (renaming, string encryption, control-flow flattening)" +OUTPUT_DIRECTORY = docs +GENERATE_HTML = YES +GENERATE_LATEX = NO +GENERATE_XML = NO +EXTRACT_ALL = YES +EXTRACT_PRIVATE = NO +EXTRACT_STATIC = YES +EXTRACT_LOCAL_CLASSES = YES +OPTIMIZE_OUTPUT_FOR_C = NO +INPUT = . +RECURSIVE = YES +FILE_PATTERNS = *.py *.md +EXTENSION_MAPPING = py=Python +SOURCE_BROWSER = YES +INLINE_SOURCES = YES +REFERENCED_BY_RELATION = YES +REFERENCES_RELATION = YES +FULL_PATH_NAMES = NO +STRIP_FROM_PATH = . +QUIET = YES +WARN_IF_UNDOCUMENTED = NO +WARNINGS = YES +JAVADOC_AUTOBRIEF = YES +MULTILINE_CPP_IS_BRIEF = YES +TAB_SIZE = 4 +MARKDOWN_SUPPORT = YES +USE_MDFILE_AS_MAINPAGE = README.md +GENERATE_TREEVIEW = YES +HTML_DYNAMIC_SECTIONS = YES +CALL_GRAPH = NO +CALLER_GRAPH = NO +SORT_MEMBER_DOCS = YES +SORT_BRIEF_DOCS = YES +SORT_BY_SCOPE_NAME = YES +HIDE_UNDOC_MEMBERS = NO +HIDE_UNDOC_CLASSES = NO \ No newline at end of file diff --git a/README.md b/README.md index 61ab05b..28437ff 100644 --- a/README.md +++ b/README.md @@ -68,4 +68,31 @@ Minimal flow: - Avoid obfuscating code that uses eval/exec on source strings that expect original identifiers. ## License -Specify a license for this project (e.g., MIT). If adding a LICENSE file, reference it here. \ No newline at end of file +Specify a license for this project (e.g., MIT). If adding a LICENSE file, reference it here. +## Documentation + +Generate Doxygen HTML docs for this codebase. + +- Prerequisites (install one of the following): + - Windows (chocolatey): choco install doxygen.install + - macOS (Homebrew): brew install doxygen + - Linux (apt): sudo apt-get update && sudo apt-get install doxygen + +- Generate: + - From the repository root, run: doxygen [Doxyfile](Doxyfile) + - Open the generated site: [docs/html/index.html](docs/html/index.html) + +- Notes: + - Configuration extracts Python docstrings and Markdown. + - Source browsing and inline sources are enabled. + - Key modules documented include: + - [obfuscator.py](obfuscator.py) + - [transformers/rename.py](transformers/rename.py) + - [transformers/control_flow.py](transformers/control_flow.py) + - [transformers/symbol_tree.py](transformers/symbol_tree.py) + - [transformers/class_analyzer.py](transformers/class_analyzer.py) + - [transformers/class_mapper.py](transformers/class_mapper.py) + - [transformers/attribute_transformer.py](transformers/attribute_transformer.py) + - [utils/encryption.py](utils/encryption.py) + - [utils/junk_gen.py](utils/junk_gen.py) + - [utils/name_gen.py](utils/name_gen.py) \ No newline at end of file diff --git a/main.py b/main.py index c469a1e..79a2bc2 100644 --- a/main.py +++ b/main.py @@ -1,9 +1,23 @@ +""" +@file main.py +@brief Command-line interface for OMG-Fuscator. +@details Parses CLI arguments, runs obfuscation via AdvancedObfuscator, and prints or writes output. +""" + import argparse import sys from pathlib import Path from obfuscator import AdvancedObfuscator def main(): + """ + @brief CLI entrypoint. + @details Handles --test demo, reads input file, runs obfuscator, and writes output. + @arg -i, --input Path to input .py file (required unless --test). + @arg -o, --output Output file path (default: input_stem_obfuscated.py). + @arg -v, --verbose Verbose console logging. + @arg --test Run built-in demo instead of file-based obfuscation. + """ parser = argparse.ArgumentParser(description="OMG-Fuscator: Advanced Python Code Obfuscator") parser.add_argument("-i", "--input", type=str, help="Input Python file path") parser.add_argument("-o", "--output", type=str, help="Output file path (default: input_obfuscated.py)") diff --git a/obfuscator.py b/obfuscator.py index 2a70305..35d28e2 100644 --- a/obfuscator.py +++ b/obfuscator.py @@ -1,3 +1,11 @@ +""" +@file obfuscator.py +@brief Core engine for OMG-Fuscator. +@details Orchestrates the full obfuscation pipeline: symbol analysis and renaming, + string encryption, control-flow flattening, and junk code insertion. + Provides debug instrumentation and JSON reporting when enabled. +""" + import ast import random import json @@ -15,6 +23,12 @@ from transformers.class_mapper import apply_class_mapping class AdvancedObfuscator: + """ + @brief Core obfuscation engine orchestrating all transformations. + @details Coordinates name generation, symbol tree construction, AST transformations + for renaming and string encryption, control-flow flattening, and final + code generation with junk injection. Optionally records detailed debug data. + """ def __init__(self, debug_mode=False): self.used_names = set() self.name_generator = NameGenerator() @@ -49,7 +63,11 @@ class AdvancedObfuscator: } def log_debug(self, category, data): - """Log debugging information if debug mode is on.""" + """ + @brief Append a structured debug entry if debug mode is enabled. + @param category Logical stage or component name. + @param data Arbitrary JSON-serializable payload to record. + """ if self.debug_mode: self.debug_data["transformations"].append({ "stage": category, @@ -58,7 +76,11 @@ class AdvancedObfuscator: }) def detect_issues(self): - """Detect potential issues in the obfuscation process.""" + """ + @brief Run integrity checks and record any issues to debug data. + @details Aggregates issues from the symbol tree and legacy name-collision + checks to help diagnose transformation inconsistencies. + """ if not self.debug_mode: return @@ -92,7 +114,9 @@ class AdvancedObfuscator: def _build_symbol_tree(self, tree: ast.AST) -> SymbolTree: """ - Build a comprehensive symbol tree from the AST for better tracking and renaming. + @brief Build a global symbol tree from the parsed AST. + @param tree Parsed AST of the input source. + @return SymbolTree Populated symbol tree with rename mappings and metadata. """ if self.debug_mode: self.log_debug("symbol_tree_building", "Building global symbol tree") @@ -141,8 +165,9 @@ class AdvancedObfuscator: 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. + @brief Rename identifiers and encrypt string literals in the AST. + @param tree Input AST prior to control-flow transformations. + @return ast.AST Transformed AST with consistent renames and encrypted strings. """ # First, build a comprehensive symbol tree self.symbol_tree = self._build_symbol_tree(tree) @@ -170,7 +195,9 @@ class AdvancedObfuscator: def _flatten_control_flow(self, tree: ast.AST) -> ast.AST: """ - Flatten control flow by rewriting function/module bodies into a while-based dispatch. + @brief Flatten control flow into a state-machine dispatch form. + @param tree AST after renaming/encryption. + @return ast.AST Transformed AST with flattened control flow. """ flattener = ControlFlowFlattener(debug_mode=self.debug_mode) tree = flattener.visit(tree) @@ -184,8 +211,9 @@ class AdvancedObfuscator: 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. + @brief Generate final Python source from the AST and inject junk code. + @param tree AST after all transformations. + @return str Final obfuscated Python source code. """ lines = ast.unparse(tree).split('\n') in_multiline = False @@ -249,7 +277,9 @@ class AdvancedObfuscator: def obfuscate(self, code: str) -> str: """ - Main method to obfuscate the given code. + @brief End-to-end obfuscation entry point. + @param code Original Python source code. + @return str Obfuscated Python source code. """ tree = ast.parse(code) diff --git a/transformers/__init__.py b/transformers/__init__.py index 87689b6..a2f0a66 100644 --- a/transformers/__init__.py +++ b/transformers/__init__.py @@ -1 +1,7 @@ -# This file ensures that the transformers directory is treated as a Python package +""" +@file transformers/__init__.py +@brief Package initializer for the transformers module. +@details Groups AST transformers and analyzers used by OMG-Fuscator, including + renaming, control-flow flattening, class mapping, attribute handling, + and symbol tree utilities. +""" diff --git a/transformers/attribute_transformer.py b/transformers/attribute_transformer.py index c68fb73..c6a2d99 100644 --- a/transformers/attribute_transformer.py +++ b/transformers/attribute_transformer.py @@ -1,26 +1,36 @@ +""" +@file transformers/attribute_transformer.py +@brief Attribute access transformer for consistent class member renaming. +@details Ensures references like self.method and self.attr are kept consistent + with class and attribute mappings produced by analysis. +""" + import ast from typing import Dict, Optional class AttributeTransformer(ast.NodeTransformer): """ - Transforms attribute access expressions (obj.attr) consistently, - especially for class method calls and attribute accesses. + @brief Transform attribute access expressions for consistency. + @details Handles self.method and self.attr translations based on provided + mappings to keep calls and attributes aligned with obfuscated names. """ def __init__(self, class_attr_mapping: Dict[str, Dict[str, str]], class_renames: Dict[str, str]): """ - Initialize with mapping dictionaries. - - Args: - class_attr_mapping: Maps class_name -> {attr_name -> obfuscated_attr_name} - class_renames: Maps original_class_name -> obfuscated_class_name + @brief Initialize transformer with mapping dictionaries. + @param class_attr_mapping Maps class_name -> {attr_name -> obfuscated_attr_name} + @param class_renames Maps original_class_name -> obfuscated_class_name """ self.class_attr_mapping = class_attr_mapping self.class_renames = class_renames self.current_class: Optional[str] = None def visit_ClassDef(self, node): - """Keep track of the current class being processed""" + """ + @brief Track the current class and process its body. + @param node ast.ClassDef being visited. + @return ast.ClassDef Potentially modified node. + """ old_class = self.current_class # Get the obfuscated name for this class @@ -38,7 +48,11 @@ class AttributeTransformer(ast.NodeTransformer): return node def visit_Attribute(self, node): - """Transform attribute access like self.method to use consistent names""" + """ + @brief Transform attribute access for consistency within a class. + @param node ast.Attribute node to transform. + @return ast.AST Updated attribute node. + """ # First process any nested attributes node = self.generic_visit(node) diff --git a/transformers/class_analyzer.py b/transformers/class_analyzer.py index b601a01..8afd8ca 100644 --- a/transformers/class_analyzer.py +++ b/transformers/class_analyzer.py @@ -1,9 +1,16 @@ +""" +@file transformers/class_analyzer.py +@brief Class analysis utilities for consistent renaming. +@details Pre-analyzes classes, methods, attributes, and inheritance to build + consistent mappings used by obfuscation transformers. +""" import ast from typing import Dict, Set, Tuple, List - class ClassMethodMap: - """Stores method name mappings for all classes in the code.""" + """ + @brief Stores method and attribute mappings for all classes. + """ def __init__(self): # Maps: original_class_name -> {original_method_name -> obfuscated_method_name} self.class_methods: Dict[str, Dict[str, str]] = {} @@ -20,8 +27,8 @@ class ClassMethodMap: class ClassAnalyzer(ast.NodeVisitor): """ - Pre-analyzes classes to ensure consistent renaming of methods and attributes. - This is crucial for making self.method() calls match def method() definitions. + @brief Analyze classes for consistent method/attribute renaming. + @details Ensures self.method() calls match def method() definitions and records inheritance. """ def __init__(self, name_generator): self.name_generator = name_generator @@ -35,14 +42,21 @@ class ClassAnalyzer(ast.NodeVisitor): self.method_calls: Dict[str, Set[str]] = {} def analyze(self, tree: ast.AST) -> ClassMethodMap: - """Analyzes the entire AST and returns populated method mappings.""" + """ + @brief Analyze the entire AST and produce mappings. + @param tree Parsed AST of the input module. + @return ClassMethodMap Populated mappings. + """ self.visit(tree) self._resolve_inheritance() self._ensure_consistent_method_mapping() return self.method_map def visit_ClassDef(self, node: ast.ClassDef): - """Process a class definition and map its methods.""" + """ + @brief Process a class definition and map methods/attrs. + @param node ast.ClassDef node being visited. + """ prev_class = self.current_class self.current_class = node.name @@ -105,7 +119,10 @@ class ClassAnalyzer(ast.NodeVisitor): self.current_class = prev_class def visit_attribute_assign(self, node): - """Process attribute assignments like self.attr = value""" + """ + @brief Process self.attr assignments within a class. + @param node ast.Assign node of the assignment. + """ if not self.current_class: return @@ -122,7 +139,10 @@ class ClassAnalyzer(ast.NodeVisitor): self.visit(node.value) def visit_Attribute(self, node): - """Track self.method references to ensure consistent naming""" + """ + @brief Track self.method references for consistency checks. + @param node ast.Attribute being visited. + """ if self.current_class: is_self_method, method_name = get_method_name(node) if is_self_method: @@ -134,8 +154,7 @@ class ClassAnalyzer(ast.NodeVisitor): def _ensure_consistent_method_mapping(self): """ - Make sure that all methods called via self.method() have a mapping, - even if they're not defined in the class. + @brief Ensure all self.method() calls have mappings, even if undefined in class. """ for class_name, method_calls in self.method_calls.items(): if class_name not in self.method_map.class_methods: @@ -153,8 +172,8 @@ class ClassAnalyzer(ast.NodeVisitor): def _resolve_inheritance(self): """ - Ensure child classes inherit method mappings from parent classes. - This ensures that overridden methods use the same obfuscated name. + @brief Propagate method mappings along inheritance hierarchies. + @details Ensures overridden methods reuse the same obfuscated name. """ # Process inheritance depth-first to handle multi-level inheritance def process_inheritance(class_name): @@ -181,8 +200,9 @@ class ClassAnalyzer(ast.NodeVisitor): def get_method_name(node: ast.Attribute) -> Tuple[bool, str]: """ - Helper function to determine if an attribute is a self.method() call. - Returns (is_self_method, method_name) + @brief Determine if an attribute is a self.method() call. + @param node Attribute node to check. + @return Tuple[bool, str] (is_self_method, method_name) """ if isinstance(node.value, ast.Name) and node.value.id == 'self': return True, node.attr @@ -191,8 +211,9 @@ def get_method_name(node: ast.Attribute) -> Tuple[bool, str]: def update_obfuscator_with_class_mappings(obfuscator, class_map: ClassMethodMap): """ - Updates the main obfuscator with class method and attribute mappings - to ensure consistent renaming across the codebase. + @brief Update obfuscator with class/method/attribute mappings. + @param obfuscator AdvancedObfuscator instance to update. + @param class_map ClassMethodMap mappings produced by analysis. """ # Update class name mappings in global_var_renames for orig_name, obf_name in class_map.class_renames.items(): diff --git a/transformers/class_mapper.py b/transformers/class_mapper.py index c71c112..da57613 100644 --- a/transformers/class_mapper.py +++ b/transformers/class_mapper.py @@ -1,3 +1,10 @@ +""" +@file transformers/class_mapper.py +@brief Class, method, and attribute mapping analyzer and transformer. +@details Builds consistent rename mappings across classes, resolves inheritance, + and applies the mappings back to the AST for coherent obfuscation. +""" + import ast from typing import Dict, Set, List, Tuple, Optional import logging @@ -7,7 +14,11 @@ logging.basicConfig(level=logging.INFO) logger = logging.getLogger("ClassMapper") class ClassMapping: - """Stores all mappings related to classes in a centralized way.""" + """ + @brief Stores all mappings related to classes in a centralized way. + @details Tracks class renames, method and attribute mappings, inheritance structure, + and seen method calls to ensure complete coverage. + """ def __init__(self): # Original class name -> obfuscated class name @@ -49,7 +60,11 @@ class ClassMapping: class ClassMapAnalyzer(ast.NodeVisitor): - """Analyzes the AST to create a complete class mapping.""" + """ + @brief Analyze the AST to create a complete class mapping. + @details Performs multi-pass analysis to collect classes, methods, attributes, + inheritance, and method-call references and builds consistent mappings. + """ def __init__(self, name_generator): self.name_generator = name_generator @@ -59,7 +74,11 @@ class ClassMapAnalyzer(ast.NodeVisitor): self.processed_classes: Set[str] = set() def analyze(self, tree: ast.AST) -> ClassMapping: - """Perform a complete analysis of the AST.""" + """ + @brief Perform a complete analysis of the AST. + @param tree Parsed AST to analyze. + @return ClassMapping Aggregated mappings for class renaming and members. + """ # First pass: collect all class definitions, methods, and inheritance self.visit(tree) @@ -73,7 +92,10 @@ class ClassMapAnalyzer(ast.NodeVisitor): return self.mapping def visit_ClassDef(self, node: ast.ClassDef): - """Process a class definition.""" + """ + @brief Process a class definition. + @param node ast.ClassDef node. + """ prev_class = self.current_class self.current_class = node.name @@ -125,7 +147,10 @@ class ClassMapAnalyzer(ast.NodeVisitor): self.current_class = prev_class def visit_method_def(self, node: ast.FunctionDef): - """Process a method definition in a class.""" + """ + @brief Process a method definition in a class. + @param node ast.FunctionDef node. + """ if not self.current_class: return @@ -147,7 +172,10 @@ class ClassMapAnalyzer(ast.NodeVisitor): self.current_method = prev_method def visit_assign_in_class(self, node: ast.Assign): - """Process assignments in class body or methods.""" + """ + @brief Process assignments in class body or methods. + @param node ast.Assign possibly containing self.attr writes. + """ if not self.current_class: return @@ -166,7 +194,10 @@ class ClassMapAnalyzer(ast.NodeVisitor): self.visit(node.value) def visit_Attribute(self, node: ast.Attribute): - """Process attribute access like self.method or self.attr.""" + """ + @brief Process attribute access like self.method or self.attr. + @param node ast.Attribute node. + """ if self.current_class and isinstance(node.value, ast.Name) and node.value.id == 'self': # Record this access for later processing method_name = node.attr @@ -177,7 +208,10 @@ class ClassMapAnalyzer(ast.NodeVisitor): self.generic_visit(node) def visit_Assign(self, node: ast.Assign): - """Process assignments that might contain self.attr references.""" + """ + @brief Process assignments that might contain self.attr references. + @param node ast.Assign node. + """ # Visit both sides of the assignment for target in node.targets: self.visit(target) @@ -185,7 +219,8 @@ class ClassMapAnalyzer(ast.NodeVisitor): def _resolve_inheritance(self): """ - Ensure child classes inherit method mappings from parent classes. + @brief Ensure child classes inherit method mappings from parent classes. + @details Copies parent method mappings into children when not overridden. """ def process_inheritance(class_name): if class_name not in self.mapping.inheritance: @@ -211,8 +246,8 @@ class ClassMapAnalyzer(ast.NodeVisitor): def _ensure_complete_method_mapping(self): """ - Make sure all method calls have corresponding mappings. - This handles methods called but not defined in the class. + @brief Ensure all method calls have corresponding mappings. + @details Handles methods called but not defined in the class. """ for class_name, method_calls in self.mapping.seen_method_calls.items(): if class_name not in self.mapping.class_methods: @@ -231,14 +266,22 @@ class ClassMapAnalyzer(ast.NodeVisitor): class ClassTransformer(ast.NodeTransformer): - """Transforms class-related nodes using the mapping.""" + """ + @brief Transform class-related nodes using the mapping. + @details Renames class names, methods, and self.attr/self.method references + according to the analyzed mappings. + """ def __init__(self, mapping: ClassMapping): self.mapping = mapping self.current_class: Optional[str] = None def visit_ClassDef(self, node: ast.ClassDef): - """Transform class name and process its body.""" + """ + @brief Transform class name and process its body. + @param node ast.ClassDef node. + @return ast.ClassDef Transformed class node. + """ prev_class = self.current_class orig_name = node.name self.current_class = orig_name @@ -255,7 +298,11 @@ class ClassTransformer(ast.NodeTransformer): return node def visit_FunctionDef(self, node: ast.FunctionDef): - """Transform method name.""" + """ + @brief Transform method name. + @param node ast.FunctionDef node. + @return ast.FunctionDef Transformed method node. + """ if self.current_class and node.name in self.mapping.class_methods.get(self.current_class, {}): orig_name = node.name node.name = self.mapping.class_methods[self.current_class][node.name] @@ -266,7 +313,11 @@ class ClassTransformer(ast.NodeTransformer): return node def visit_Attribute(self, node: ast.Attribute): - """Transform self.method and self.attr references.""" + """ + @brief Transform self.method and self.attr references. + @param node ast.Attribute node. + @return ast.Attribute Transformed attribute node. + """ # Process any child nodes first (for nested attributes) node.value = self.visit(node.value) @@ -288,7 +339,12 @@ class ClassTransformer(ast.NodeTransformer): # Helper function to apply the class mapping transformation def apply_class_mapping(tree: ast.AST, name_generator) -> ast.AST: - """Analyze and transform classes consistently.""" + """ + @brief Analyze and transform classes consistently. + @param tree Input AST. + @param name_generator Name generator used to create obfuscated identifiers. + @return Tuple[ast.AST, ClassMapping] Transformed AST and the mapping produced. + """ # First pass: analyze all classes analyzer = ClassMapAnalyzer(name_generator) mapping = analyzer.analyze(tree) diff --git a/transformers/control_flow.py b/transformers/control_flow.py index 783988f..1b5b1d0 100644 --- a/transformers/control_flow.py +++ b/transformers/control_flow.py @@ -1,3 +1,9 @@ +""" +@file transformers/control_flow.py +@brief Control-flow flattening transformer. +@details Converts structured control flow into a state-machine with a while/dispatch + loop to hinder static analysis, and can emit debug telemetry in debug mode. +""" import ast import random from typing import Dict, List, Set, Tuple, Any, Optional diff --git a/transformers/rename.py b/transformers/rename.py index 7c4ff62..8044274 100644 --- a/transformers/rename.py +++ b/transformers/rename.py @@ -1,3 +1,10 @@ +""" +@file transformers/rename.py +@brief Identifier renaming and string literal encryption transformer. +@details Performs scoped variable/function renaming, consistent class/method/attribute + remapping, and in-place string encryption producing runtime decryption code. +""" + import ast from utils.encryption import StringEncryptor from utils.name_gen import NameGenerator @@ -8,6 +15,11 @@ logging.basicConfig(level=logging.INFO) logger = logging.getLogger("RenameTransformer") class RenameTransformer(ast.NodeTransformer): + """ + @brief AST transformer for renaming and string encryption. + @details Maintains scope stacks, global mappings, class method/attribute maps, and + collects key-setup code for runtime decryption. Can emit debug telemetry. + """ def __init__(self, name_generator, global_var_renames, class_attr_mapping, primary_key, secondary_key, salt, debug_mode=False): self.name_generator = name_generator diff --git a/transformers/symbol_tree.py b/transformers/symbol_tree.py index 2f04726..6c6c106 100644 --- a/transformers/symbol_tree.py +++ b/transformers/symbol_tree.py @@ -1,3 +1,10 @@ +""" +@file transformers/symbol_tree.py +@brief Global symbol tree and scope tracking. +@details Builds a hierarchical representation of scopes (module, class, function), + tracks symbols, imports, references, and inheritance; generates rename + mappings consumed by the obfuscation pipeline. +""" import ast from typing import Dict, Set, List, Optional, Union, Tuple import logging diff --git a/utils/__init__.py b/utils/__init__.py index 7773827..ba8c3af 100644 --- a/utils/__init__.py +++ b/utils/__init__.py @@ -1 +1,6 @@ -# This file ensures that the utils directory is treated as a Python package +""" +@file utils/__init__.py +@brief Package initializer for utility helpers. +@details Exposes helper modules used by OMG-Fuscator (encryption, junk generation, + and obfuscated name generation). +""" diff --git a/utils/encryption.py b/utils/encryption.py index b48c138..b9056bb 100644 --- a/utils/encryption.py +++ b/utils/encryption.py @@ -1,18 +1,38 @@ +""" +@file utils/encryption.py +@brief String encryption utilities for OMG-Fuscator. +@details Provides the StringEncryptor used by the obfuscation pipeline to transform + string literals into runtime-decrypted expressions, making static analysis + significantly harder. +""" + import base64 import hashlib import random from typing import Tuple class StringEncryptor: + """ + @brief Encrypts strings for obfuscation using layered XOR and Base85 encoding. + @details Produces both an encoded payload and Python code that reconstructs + the keys at runtime without embedding raw key bytes. + """ def __init__(self, primary_key: bytes, secondary_key: bytes, salt: bytes): + """ + @brief Initialize encryptor keys. + @param primary_key Primary XOR key bytes. + @param secondary_key Secondary XOR key bytes. + @param salt Salt bytes used to derive a per-string modifier. + """ self.primary_key = primary_key self.secondary_key = secondary_key self.salt = salt def hide_byte(self, b: int) -> str: """ - Obfuscates a single byte as a random arithmetic or bitwise expression. - Used in the string encryption code to hide key bytes. + @brief Obfuscate a single key byte into an arithmetic/bitwise expression. + @param b Input byte value in range [0, 255]. + @return Python expression string that evaluates to the original byte at runtime. """ pattern = random.randint(1, 5) if pattern == 1: @@ -41,11 +61,12 @@ class StringEncryptor: def encrypt_string(self, s: str) -> Tuple[str, str, str]: """ - Encrypts a string with a multi-layer XOR scheme - and base85-encodes the result. Returns: - - encoded string, - - the code snippet that re-creates the keys in Python, - - the hex digest for the 'modifier'. + @brief Encrypt a string with layered XOR and Base85 encoding. + @param s UTF-8 string to encrypt. + @return Tuple[str, str, str]: + - encoded: Base85 text of the encrypted payload + - key_setup: Python code that reconstructs keys at runtime + - modifier_hex: Hex string for the per-string modifier """ data = s.encode('utf-8') modifier = hashlib.sha256(self.salt + data).digest()[:8] diff --git a/utils/junk_gen.py b/utils/junk_gen.py index 081a21f..450be20 100644 --- a/utils/junk_gen.py +++ b/utils/junk_gen.py @@ -1,14 +1,29 @@ +""" +@file utils/junk_gen.py +@brief Junk code generation utilities for OMG-Fuscator. +@details Generates random noise statements and multi-line snippets for obfuscation. +""" + import random import string from utils.name_gen import NameGenerator class JunkGenerator: + """ + @brief Produces pseudo-random junk code. + @param name_generator Name generator used for variable identifiers. + """ def __init__(self, name_generator: NameGenerator): + """ + @brief Constructor. + @param name_generator NameGenerator instance used to create junk variable names. + """ self.name_generator = name_generator def add_junk(self) -> str: """ - Generates random multi-line junk code, used to pad the final output. + @brief Generate a random multi-line junk code snippet. + @return Python source string with one or more lines. """ var1 = self.name_generator.generate_name() var2 = self.name_generator.generate_name() @@ -36,7 +51,8 @@ class JunkGenerator: def generate_junk(self) -> str: """ - Generates a single line of junk code. + @brief Generate a single-line junk statement. + @return Python source string for a single assignment or expression. """ var_name = self.name_generator.generate_name() junk_code = [ diff --git a/utils/name_gen.py b/utils/name_gen.py index 717b51d..9e3c577 100644 --- a/utils/name_gen.py +++ b/utils/name_gen.py @@ -1,14 +1,29 @@ +""" +@file utils/name_gen.py +@brief Obfuscated identifier name generator. +@details Provides NameGenerator that yields high-entropy-looking identifiers + and ensures uniqueness across a transformation session. +""" import itertools from typing import Iterator, Set class NameGenerator: + """ + @brief Generates unique obfuscated identifiers. + @details Uses patterned Cartesian products to create long, confusing names and + tracks used names to avoid collisions. + """ def __init__(self): + """ + @brief Initialize internal iterator and used-name set. + """ self._name_iterator = self._create_iterator() self.used_names = set() def _create_iterator(self) -> Iterator[str]: """ - Generates obfuscated names like: vIl1lO0o0Z2z2... + @brief Iterator producing obfuscated identifier candidates. + @return Iterator[str] Infinite stream of names such as vIl1lO0o0Z2z2... """ patterns = ['Il1l', 'O0o0', 'Z2z2', 'S5s5', 'B8b8', 'Q9q9'] for length in itertools.count(2): # Start with length=2, continue forever @@ -17,7 +32,8 @@ class NameGenerator: def generate_name(self) -> str: """ - Get a fresh unique variable name. + @brief Produce a fresh, unique obfuscated name. + @return str Name guaranteed not to have been produced before by this instance. """ while True: candidate = next(self._name_iterator)