da
Doxygen to Wiki / Build Doxygen and publish to Wiki (push) Failing after 1m0s

This commit is contained in:
2025-08-15 21:06:31 -07:00
parent 387c694efe
commit 69184c7cb8
16 changed files with 444 additions and 64 deletions
+90
View File
@@ -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://<host>/<owner>/<repo>.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
+39
View File
@@ -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
+28 -1
View File
@@ -68,4 +68,31 @@ Minimal flow:
- Avoid obfuscating code that uses eval/exec on source strings that expect original identifiers. - Avoid obfuscating code that uses eval/exec on source strings that expect original identifiers.
## License ## License
Specify a license for this project (e.g., MIT). If adding a LICENSE file, reference it here. 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)
+14
View File
@@ -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 argparse
import sys import sys
from pathlib import Path from pathlib import Path
from obfuscator import AdvancedObfuscator from obfuscator import AdvancedObfuscator
def main(): 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 = argparse.ArgumentParser(description="OMG-Fuscator: Advanced Python Code Obfuscator")
parser.add_argument("-i", "--input", type=str, help="Input Python file path") 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)") parser.add_argument("-o", "--output", type=str, help="Output file path (default: input_obfuscated.py)")
+39 -9
View File
@@ -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 ast
import random import random
import json import json
@@ -15,6 +23,12 @@ from transformers.class_mapper import apply_class_mapping
class AdvancedObfuscator: 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): def __init__(self, debug_mode=False):
self.used_names = set() self.used_names = set()
self.name_generator = NameGenerator() self.name_generator = NameGenerator()
@@ -49,7 +63,11 @@ class AdvancedObfuscator:
} }
def log_debug(self, category, data): 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: if self.debug_mode:
self.debug_data["transformations"].append({ self.debug_data["transformations"].append({
"stage": category, "stage": category,
@@ -58,7 +76,11 @@ class AdvancedObfuscator:
}) })
def detect_issues(self): 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: if not self.debug_mode:
return return
@@ -92,7 +114,9 @@ class AdvancedObfuscator:
def _build_symbol_tree(self, tree: ast.AST) -> SymbolTree: 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: if self.debug_mode:
self.log_debug("symbol_tree_building", "Building global symbol tree") 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: def _rename_and_encrypt(self, tree: ast.AST) -> ast.AST:
""" """
Transform the AST to rename variables and encrypt string literals. @brief Rename identifiers and encrypt string literals in the AST.
Uses the symbol tree for consistent renaming. @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 # First, build a comprehensive symbol tree
self.symbol_tree = self._build_symbol_tree(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: 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) flattener = ControlFlowFlattener(debug_mode=self.debug_mode)
tree = flattener.visit(tree) tree = flattener.visit(tree)
@@ -184,8 +211,9 @@ class AdvancedObfuscator:
def _generate_final_code(self, tree: ast.AST) -> str: def _generate_final_code(self, tree: ast.AST) -> str:
""" """
Convert the final AST to code, and optionally insert junk snippets @brief Generate final Python source from the AST and inject junk code.
among lines to obscure readability. @param tree AST after all transformations.
@return str Final obfuscated Python source code.
""" """
lines = ast.unparse(tree).split('\n') lines = ast.unparse(tree).split('\n')
in_multiline = False in_multiline = False
@@ -249,7 +277,9 @@ class AdvancedObfuscator:
def obfuscate(self, code: str) -> str: 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) tree = ast.parse(code)
+7 -1
View File
@@ -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.
"""
+23 -9
View File
@@ -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 import ast
from typing import Dict, Optional from typing import Dict, Optional
class AttributeTransformer(ast.NodeTransformer): class AttributeTransformer(ast.NodeTransformer):
""" """
Transforms attribute access expressions (obj.attr) consistently, @brief Transform attribute access expressions for consistency.
especially for class method calls and attribute accesses. @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]): def __init__(self, class_attr_mapping: Dict[str, Dict[str, str]], class_renames: Dict[str, str]):
""" """
Initialize with mapping dictionaries. @brief Initialize transformer with mapping dictionaries.
@param class_attr_mapping Maps class_name -> {attr_name -> obfuscated_attr_name}
Args: @param class_renames Maps original_class_name -> obfuscated_class_name
class_attr_mapping: Maps class_name -> {attr_name -> obfuscated_attr_name}
class_renames: Maps original_class_name -> obfuscated_class_name
""" """
self.class_attr_mapping = class_attr_mapping self.class_attr_mapping = class_attr_mapping
self.class_renames = class_renames self.class_renames = class_renames
self.current_class: Optional[str] = None self.current_class: Optional[str] = None
def visit_ClassDef(self, node): 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 old_class = self.current_class
# Get the obfuscated name for this class # Get the obfuscated name for this class
@@ -38,7 +48,11 @@ class AttributeTransformer(ast.NodeTransformer):
return node return node
def visit_Attribute(self, 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 # First process any nested attributes
node = self.generic_visit(node) node = self.generic_visit(node)
+37 -16
View File
@@ -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 import ast
from typing import Dict, Set, Tuple, List from typing import Dict, Set, Tuple, List
class ClassMethodMap: class ClassMethodMap:
"""Stores method name mappings for all classes in the code.""" """
@brief Stores method and attribute mappings for all classes.
"""
def __init__(self): def __init__(self):
# Maps: original_class_name -> {original_method_name -> obfuscated_method_name} # Maps: original_class_name -> {original_method_name -> obfuscated_method_name}
self.class_methods: Dict[str, Dict[str, str]] = {} self.class_methods: Dict[str, Dict[str, str]] = {}
@@ -20,8 +27,8 @@ class ClassMethodMap:
class ClassAnalyzer(ast.NodeVisitor): class ClassAnalyzer(ast.NodeVisitor):
""" """
Pre-analyzes classes to ensure consistent renaming of methods and attributes. @brief Analyze classes for consistent method/attribute renaming.
This is crucial for making self.method() calls match def method() definitions. @details Ensures self.method() calls match def method() definitions and records inheritance.
""" """
def __init__(self, name_generator): def __init__(self, name_generator):
self.name_generator = name_generator self.name_generator = name_generator
@@ -35,14 +42,21 @@ class ClassAnalyzer(ast.NodeVisitor):
self.method_calls: Dict[str, Set[str]] = {} self.method_calls: Dict[str, Set[str]] = {}
def analyze(self, tree: ast.AST) -> ClassMethodMap: 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.visit(tree)
self._resolve_inheritance() self._resolve_inheritance()
self._ensure_consistent_method_mapping() self._ensure_consistent_method_mapping()
return self.method_map return self.method_map
def visit_ClassDef(self, node: ast.ClassDef): 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 prev_class = self.current_class
self.current_class = node.name self.current_class = node.name
@@ -105,7 +119,10 @@ class ClassAnalyzer(ast.NodeVisitor):
self.current_class = prev_class self.current_class = prev_class
def visit_attribute_assign(self, node): 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: if not self.current_class:
return return
@@ -122,7 +139,10 @@ class ClassAnalyzer(ast.NodeVisitor):
self.visit(node.value) self.visit(node.value)
def visit_Attribute(self, node): 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: if self.current_class:
is_self_method, method_name = get_method_name(node) is_self_method, method_name = get_method_name(node)
if is_self_method: if is_self_method:
@@ -134,8 +154,7 @@ class ClassAnalyzer(ast.NodeVisitor):
def _ensure_consistent_method_mapping(self): def _ensure_consistent_method_mapping(self):
""" """
Make sure that all methods called via self.method() have a mapping, @brief Ensure all self.method() calls have mappings, even if undefined in class.
even if they're not defined in the class.
""" """
for class_name, method_calls in self.method_calls.items(): for class_name, method_calls in self.method_calls.items():
if class_name not in self.method_map.class_methods: if class_name not in self.method_map.class_methods:
@@ -153,8 +172,8 @@ class ClassAnalyzer(ast.NodeVisitor):
def _resolve_inheritance(self): def _resolve_inheritance(self):
""" """
Ensure child classes inherit method mappings from parent classes. @brief Propagate method mappings along inheritance hierarchies.
This ensures that overridden methods use the same obfuscated name. @details Ensures overridden methods reuse the same obfuscated name.
""" """
# Process inheritance depth-first to handle multi-level inheritance # Process inheritance depth-first to handle multi-level inheritance
def process_inheritance(class_name): def process_inheritance(class_name):
@@ -181,8 +200,9 @@ class ClassAnalyzer(ast.NodeVisitor):
def get_method_name(node: ast.Attribute) -> Tuple[bool, str]: def get_method_name(node: ast.Attribute) -> Tuple[bool, str]:
""" """
Helper function to determine if an attribute is a self.method() call. @brief Determine if an attribute is a self.method() call.
Returns (is_self_method, method_name) @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': if isinstance(node.value, ast.Name) and node.value.id == 'self':
return True, node.attr 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): def update_obfuscator_with_class_mappings(obfuscator, class_map: ClassMethodMap):
""" """
Updates the main obfuscator with class method and attribute mappings @brief Update obfuscator with class/method/attribute mappings.
to ensure consistent renaming across the codebase. @param obfuscator AdvancedObfuscator instance to update.
@param class_map ClassMethodMap mappings produced by analysis.
""" """
# Update class name mappings in global_var_renames # Update class name mappings in global_var_renames
for orig_name, obf_name in class_map.class_renames.items(): for orig_name, obf_name in class_map.class_renames.items():
+72 -16
View File
@@ -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 import ast
from typing import Dict, Set, List, Tuple, Optional from typing import Dict, Set, List, Tuple, Optional
import logging import logging
@@ -7,7 +14,11 @@ logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("ClassMapper") logger = logging.getLogger("ClassMapper")
class ClassMapping: 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): def __init__(self):
# Original class name -> obfuscated class name # Original class name -> obfuscated class name
@@ -49,7 +60,11 @@ class ClassMapping:
class ClassMapAnalyzer(ast.NodeVisitor): 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): def __init__(self, name_generator):
self.name_generator = name_generator self.name_generator = name_generator
@@ -59,7 +74,11 @@ class ClassMapAnalyzer(ast.NodeVisitor):
self.processed_classes: Set[str] = set() self.processed_classes: Set[str] = set()
def analyze(self, tree: ast.AST) -> ClassMapping: 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 # First pass: collect all class definitions, methods, and inheritance
self.visit(tree) self.visit(tree)
@@ -73,7 +92,10 @@ class ClassMapAnalyzer(ast.NodeVisitor):
return self.mapping return self.mapping
def visit_ClassDef(self, node: ast.ClassDef): 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 prev_class = self.current_class
self.current_class = node.name self.current_class = node.name
@@ -125,7 +147,10 @@ class ClassMapAnalyzer(ast.NodeVisitor):
self.current_class = prev_class self.current_class = prev_class
def visit_method_def(self, node: ast.FunctionDef): 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: if not self.current_class:
return return
@@ -147,7 +172,10 @@ class ClassMapAnalyzer(ast.NodeVisitor):
self.current_method = prev_method self.current_method = prev_method
def visit_assign_in_class(self, node: ast.Assign): 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: if not self.current_class:
return return
@@ -166,7 +194,10 @@ class ClassMapAnalyzer(ast.NodeVisitor):
self.visit(node.value) self.visit(node.value)
def visit_Attribute(self, node: ast.Attribute): 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': if self.current_class and isinstance(node.value, ast.Name) and node.value.id == 'self':
# Record this access for later processing # Record this access for later processing
method_name = node.attr method_name = node.attr
@@ -177,7 +208,10 @@ class ClassMapAnalyzer(ast.NodeVisitor):
self.generic_visit(node) self.generic_visit(node)
def visit_Assign(self, node: ast.Assign): 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 # Visit both sides of the assignment
for target in node.targets: for target in node.targets:
self.visit(target) self.visit(target)
@@ -185,7 +219,8 @@ class ClassMapAnalyzer(ast.NodeVisitor):
def _resolve_inheritance(self): 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): def process_inheritance(class_name):
if class_name not in self.mapping.inheritance: if class_name not in self.mapping.inheritance:
@@ -211,8 +246,8 @@ class ClassMapAnalyzer(ast.NodeVisitor):
def _ensure_complete_method_mapping(self): def _ensure_complete_method_mapping(self):
""" """
Make sure all method calls have corresponding mappings. @brief Ensure all method calls have corresponding mappings.
This handles methods called but not defined in the class. @details Handles methods called but not defined in the class.
""" """
for class_name, method_calls in self.mapping.seen_method_calls.items(): for class_name, method_calls in self.mapping.seen_method_calls.items():
if class_name not in self.mapping.class_methods: if class_name not in self.mapping.class_methods:
@@ -231,14 +266,22 @@ class ClassMapAnalyzer(ast.NodeVisitor):
class ClassTransformer(ast.NodeTransformer): 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): def __init__(self, mapping: ClassMapping):
self.mapping = mapping self.mapping = mapping
self.current_class: Optional[str] = None self.current_class: Optional[str] = None
def visit_ClassDef(self, node: ast.ClassDef): 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 prev_class = self.current_class
orig_name = node.name orig_name = node.name
self.current_class = orig_name self.current_class = orig_name
@@ -255,7 +298,11 @@ class ClassTransformer(ast.NodeTransformer):
return node return node
def visit_FunctionDef(self, node: ast.FunctionDef): 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, {}): if self.current_class and node.name in self.mapping.class_methods.get(self.current_class, {}):
orig_name = node.name orig_name = node.name
node.name = self.mapping.class_methods[self.current_class][node.name] node.name = self.mapping.class_methods[self.current_class][node.name]
@@ -266,7 +313,11 @@ class ClassTransformer(ast.NodeTransformer):
return node return node
def visit_Attribute(self, node: ast.Attribute): 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) # Process any child nodes first (for nested attributes)
node.value = self.visit(node.value) node.value = self.visit(node.value)
@@ -288,7 +339,12 @@ class ClassTransformer(ast.NodeTransformer):
# Helper function to apply the class mapping transformation # Helper function to apply the class mapping transformation
def apply_class_mapping(tree: ast.AST, name_generator) -> ast.AST: 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 # First pass: analyze all classes
analyzer = ClassMapAnalyzer(name_generator) analyzer = ClassMapAnalyzer(name_generator)
mapping = analyzer.analyze(tree) mapping = analyzer.analyze(tree)
+6
View File
@@ -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 ast
import random import random
from typing import Dict, List, Set, Tuple, Any, Optional from typing import Dict, List, Set, Tuple, Any, Optional
+12
View File
@@ -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 import ast
from utils.encryption import StringEncryptor from utils.encryption import StringEncryptor
from utils.name_gen import NameGenerator from utils.name_gen import NameGenerator
@@ -8,6 +15,11 @@ logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("RenameTransformer") logger = logging.getLogger("RenameTransformer")
class RenameTransformer(ast.NodeTransformer): 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, def __init__(self, name_generator, global_var_renames, class_attr_mapping,
primary_key, secondary_key, salt, debug_mode=False): primary_key, secondary_key, salt, debug_mode=False):
self.name_generator = name_generator self.name_generator = name_generator
+7
View File
@@ -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 import ast
from typing import Dict, Set, List, Optional, Union, Tuple from typing import Dict, Set, List, Optional, Union, Tuple
import logging import logging
+6 -1
View File
@@ -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).
"""
+28 -7
View File
@@ -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 base64
import hashlib import hashlib
import random import random
from typing import Tuple from typing import Tuple
class StringEncryptor: 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): 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.primary_key = primary_key
self.secondary_key = secondary_key self.secondary_key = secondary_key
self.salt = salt self.salt = salt
def hide_byte(self, b: int) -> str: def hide_byte(self, b: int) -> str:
""" """
Obfuscates a single byte as a random arithmetic or bitwise expression. @brief Obfuscate a single key byte into an arithmetic/bitwise expression.
Used in the string encryption code to hide key bytes. @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) pattern = random.randint(1, 5)
if pattern == 1: if pattern == 1:
@@ -41,11 +61,12 @@ class StringEncryptor:
def encrypt_string(self, s: str) -> Tuple[str, str, str]: def encrypt_string(self, s: str) -> Tuple[str, str, str]:
""" """
Encrypts a string with a multi-layer XOR scheme @brief Encrypt a string with layered XOR and Base85 encoding.
and base85-encodes the result. Returns: @param s UTF-8 string to encrypt.
- encoded string, @return Tuple[str, str, str]:
- the code snippet that re-creates the keys in Python, - encoded: Base85 text of the encrypted payload
- the hex digest for the 'modifier'. - key_setup: Python code that reconstructs keys at runtime
- modifier_hex: Hex string for the per-string modifier
""" """
data = s.encode('utf-8') data = s.encode('utf-8')
modifier = hashlib.sha256(self.salt + data).digest()[:8] modifier = hashlib.sha256(self.salt + data).digest()[:8]
+18 -2
View File
@@ -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 random
import string import string
from utils.name_gen import NameGenerator from utils.name_gen import NameGenerator
class JunkGenerator: class JunkGenerator:
"""
@brief Produces pseudo-random junk code.
@param name_generator Name generator used for variable identifiers.
"""
def __init__(self, name_generator: NameGenerator): def __init__(self, name_generator: NameGenerator):
"""
@brief Constructor.
@param name_generator NameGenerator instance used to create junk variable names.
"""
self.name_generator = name_generator self.name_generator = name_generator
def add_junk(self) -> str: 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() var1 = self.name_generator.generate_name()
var2 = self.name_generator.generate_name() var2 = self.name_generator.generate_name()
@@ -36,7 +51,8 @@ class JunkGenerator:
def generate_junk(self) -> str: 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() var_name = self.name_generator.generate_name()
junk_code = [ junk_code = [
+18 -2
View File
@@ -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 import itertools
from typing import Iterator, Set from typing import Iterator, Set
class NameGenerator: 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): def __init__(self):
"""
@brief Initialize internal iterator and used-name set.
"""
self._name_iterator = self._create_iterator() self._name_iterator = self._create_iterator()
self.used_names = set() self.used_names = set()
def _create_iterator(self) -> Iterator[str]: 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'] patterns = ['Il1l', 'O0o0', 'Z2z2', 'S5s5', 'B8b8', 'Q9q9']
for length in itertools.count(2): # Start with length=2, continue forever for length in itertools.count(2): # Start with length=2, continue forever
@@ -17,7 +32,8 @@ class NameGenerator:
def generate_name(self) -> str: 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: while True:
candidate = next(self._name_iterator) candidate = next(self._name_iterator)