This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -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.
|
||||
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)
|
||||
@@ -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)")
|
||||
|
||||
+39
-9
@@ -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)
|
||||
|
||||
|
||||
@@ -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.
|
||||
"""
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
+6
-1
@@ -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
@@ -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]
|
||||
|
||||
+18
-2
@@ -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 = [
|
||||
|
||||
+18
-2
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user