Skip to content

SnailSploit/SnailObfuscator

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

11 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

🐌 SnailObfuscator

SnailObfuscator Banner

Multi-language code obfuscation engine by SnailSploit

AST-based Python β€’ Token-based JS/Go β€’ Zero dependencies β€’ Single file


Architecture

SnailObfuscator uses structurally-aware engines instead of regex string hacking:

Language Engine Guarantee
Python ast.NodeTransformer Syntactically valid output via ast.unparse()
JavaScript Token-based lexer Strings and comments are never mangled
Go Token-based lexer Strings and comments are never mangled

Why this matters: Regex-based obfuscators break on edge cases like "class Foo: # not a comment" (parsed as code), myself_score (partially matched by self. replacement), or print("nested \"quotes\" are hard") (string boundary confusion). SnailObfuscator's AST/tokenizer approach is structurally immune to all of these.

Install

No dependencies. Requires Python 3.9+.

# Download the single file
curl -O https://raw.githubusercontent.com/snailsploit/snailobfuscator/main/snailobfuscator.py

# Or clone
git clone https://github.com/snailsploit/snailobfuscator.git

Quick Start

# Obfuscate a Python file with the medium preset
python3 snailobfuscator.py -i script.py -o out.py --preset medium

# Obfuscate a JS file with heavy preset
python3 snailobfuscator.py -i app.js --preset heavy

# Batch process a directory
python3 snailobfuscator.py -d ./src -o ./dist --preset paranoid -r

# Watch mode (auto-obfuscate on file changes)
python3 snailobfuscator.py -d ./src -o ./dist --preset heavy --watch

# Pipe from stdin
cat script.py | python3 snailobfuscator.py --lang python --preset light

# Reproducible output with seed
python3 snailobfuscator.py -i script.py --preset heavy --seed 42

Presets

Preset Techniques Use Case
light Name mangling, comment stripping Quick anonymization
medium + string encoding, dead code (20%) General distribution
heavy + integer encoding, anti-debug, MBA, opaque predicates Commercial protection
paranoid + string array, homoglyphs Maximum obfuscation
fortress Everything maxed + compression Single-line output blob

Techniques (9)

Name Mangling (--mangle)

Renames user-defined variables, functions, and classes. Four styles:

Style Example Flag
Hex _0x001a --name-style hex
IL _IlI1lIl1 --name-style il
OO _O0oO0O0o --name-style oo
Homoglyph _αСσρΡ1 (Cyrillic/Greek lookalikes) --name-style homoglyph

Safety features:

  • Imports are never mangled (visit_Import / visit_ImportFrom add all imported names to the reserved set)
  • Built-in methods like list.count() are never mangled (attribute mangling is scoped to class-defined methods and self.X assignments only)
  • Reserved words and dunder methods are always preserved

String Encoding (--encode-strings)

Encodes string literals. Five methods:

Method Python Output JS Output
b64 __import__("base64").b64decode("...").decode() atob("...")
hex bytes.fromhex("...").decode() "\x68\x65\x6c..."
chr "".join(chr(_c) for _c in [...]) String.fromCharCode(...)
xor "".join(chr(_c^K) for _c in [...]) [...].map(c=>String.fromCharCode(c^K)).join("")
random Randomly picks from above Randomly picks from above

Integer Encoding (--encode-integers)

Transforms integer literals (β‰₯2) into equivalent expressions:

  • 42 β†’ (42 + 587) - 587
  • 42 β†’ (5 << 3) + 2
  • 42 β†’ (6 * 7) + 0

Dead Code Injection (--dead-code)

Inserts unreachable code blocks. In Python, dead code is generated as AST nodes and inserted into node.body lists β€” indentation errors are structurally impossible.

--dead-code --dead-density 0.5  # 50% injection probability per function

Opaque Predicates (--opaque-predicates)

Replaces simple false conditions in dead code with mathematically complex expressions that always evaluate to false:

  • 42**2+1==0
  • (73&1)+((73>>1)&1)>3

MBA Encoding (--mba)

Mixed Boolean-Arithmetic encoding on Add/Sub operations between variables:

  • a + b β†’ (a & b) * 2 + (a ^ b)
  • a - b β†’ (a ^ b) - 2 * (~a & b)

String Array Extraction (--string-array)

Extracts all string literals into a rotated array with accessor function:

_sArr = ["world", "hello"]  # rotated
def _sRot(i): return _sArr[(i + 1) % 2]
print(_sRot(0))  # "hello"

Anti-Debug (--anti-debug)

Injects runtime detection for debuggers:

  • Python: Checks sys.gettrace(), disables dis.dis()
  • JavaScript: Traps debugger statements via Function.prototype.constructor override

Compression (--compress)

Wraps entire output in a self-extracting compressed payload:

  • Python: import zlib,base64;exec(zlib.decompress(base64.b64decode("...")).decode())
  • JavaScript: eval(atob("..."))

Syntax Converter (Experimental)

⚠️ This is NOT a real transpiler. It performs best-effort structural mapping for common patterns. Complex code will likely need manual correction.

# Python β†’ JavaScript
python3 snailobfuscator.py -i script.py --target javascript --preset medium

# JavaScript β†’ Python
python3 snailobfuscator.py -i app.js --target python --preset light

# Python β†’ Go
python3 snailobfuscator.py -i script.py --target go --preset light

Supported conversions: def↔function, self.↔this., f-strings↔template literals, for in↔for of, class syntax, basic control flow.

CLI Reference

usage: snailobfuscator [-h] [-i INPUT] [-d DIR] [-o OUTPUT] [-r]
                       [-l {python,javascript,go}]
                       [--target {python,javascript,go}]
                       [-p {light,medium,heavy,paranoid,fortress}]
                       [--mangle] [--no-mangle]
                       [--name-style {hex,il,oo,homoglyph}]
                       [--encode-strings] [--no-encode-strings]
                       [--string-method {random,b64,hex,chr,xor}]
                       [--encode-integers] [--dead-code]
                       [--dead-density FLOAT] [--opaque-predicates]
                       [--mba] [--string-array] [--anti-debug]
                       [--compress] [--strip-comments]
                       [--strip-docstrings] [--seed INT]
                       [-w] [-q]
Flag Description
-i / --input Input file path
-d / --dir Input directory (batch mode)
-o / --output Output file or directory
-r / --recursive Recurse into subdirectories
-l / --lang Source language (auto-detected from extension)
--target Syntax-convert to target language (experimental)
-p / --preset Obfuscation preset
-w / --watch Watch mode (re-obfuscate on file changes)
-q / --quiet Suppress banner and stats
--seed Random seed for reproducible output

Examples

# Custom technique combo
python3 snailobfuscator.py -i app.py --mangle --name-style homoglyph \
    --encode-strings --string-method xor --mba --dead-code --dead-density 0.6

# Batch with watch
python3 snailobfuscator.py -d ./src -o ./dist --preset heavy -r --watch

# Quiet mode for CI/CD pipelines
python3 snailobfuscator.py -i main.py -o main.obf.py --preset medium -q

# Cross-compile and obfuscate
python3 snailobfuscator.py -i server.py --target javascript --preset heavy -o server.obf.js

Design Decisions

Why AST for Python? Python's ast module is in the standard library and guarantees syntactically valid output via ast.unparse(). Every transformation (name mangling, string encoding, integer encoding, dead code injection, MBA encoding) operates on AST nodes. It is structurally impossible to generate mismatched parentheses, indentation errors, or accidentally mangle identifiers inside string literals.

Why a tokenizer for JS/Go? Without adding npm/Go dependencies, a full AST isn't available. The tokenizer splits source into typed tokens (STRING, COMMENT, NAME, NUMBER, etc.) and transformations only operate on the correct token type. This prevents the most common regex failure mode: mangling identifiers that appear inside strings or comments.

Why not mangle all attributes? Python lacks static type information at the source level. If a user defines count = 0 and also calls my_list.count(1), a naive attribute mangler would rename both β€” breaking the built-in method call. SnailObfuscator scopes attribute mangling to methods defined inside user classes and self.X assignments only.

Why keep the syntax converter? It's explicitly marked experimental and renamed from "transpiler" to set correct expectations. For simple scripts, it produces usable output. For complex code, it's a starting point that needs manual correction. Removing it entirely would remove a genuinely useful feature for the common case.

Project Structure

snailobfuscator.py    # Single-file, zero-dependency CLI (1,295 lines)
β”œβ”€β”€ ObfConfig         # @dataclass configuration
β”œβ”€β”€ NameMangler       # 4-style name generation
β”œβ”€β”€ Tokenizer         # Context-aware lexer for JS/Go
β”œβ”€β”€ PythonObfuscator  # ast.NodeTransformer engine
β”œβ”€β”€ JSObfuscator      # Token-based JS engine
β”œβ”€β”€ GoObfuscator      # Token-based Go engine
β”œβ”€β”€ syntax_convert()  # Experimental cross-language conversion
β”œβ”€β”€ obfuscate()       # Orchestrator / dispatch
└── main()            # CLI (argparse, batch, watch, stdin)

License

MIT

Author

Kai Aizen (@SnailSploit) β€” GenAI Security Researcher

Built as a utility for security research, CTF challenges, and controlled red team engagements.

About

SnailObfuscator uses structurally-aware engines instead of regex string hacking:

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages