Skip to main content

Deobfuscate Python without executing it. A webcrack for Python.

Project description

snakecharm

Python De-Obfuscation

Unwraps obfuscated Python — exec/eval/compile chains over base64, hex, zlib/gzip/bz2/lzma, XOR, marshal, reversal and chr/ord reconstruction — without executing the sample.

Requires Python 3.11+. Pure Python; depends on typer, rich, pyyaml.

Install

pip install snakecharm
uv tool install snakecharm     # isolated, puts snakecharm on PATH

Commands

snakecharm crack FILE [-o DIR] [--json] [--clean]
snakecharm detect FILE [--json]
snakecharm inspect FILE [--target-python X.Y]
snakecharm version
crack Unwrap. Recovered source to stdout, diagnostics to stderr.
detect Fingerprint the obfuscator family; do not unwrap.
inspect Disassemble a .pyc or bare marshal stream.
version Print the version.

FILE may be - to read from stdin.

Cleanup is a flag on crack (--clean), not a separate command.

crack options

Flag Default Meaning
-o, --output DIR Write the full artifact tree instead of stdout.
--json off Emit report.json to stdout.
--clean off Also emit a cleaned view (see Cleanup).
--target-python X.Y host Version to assume for marshal payloads.
--decompiler NAME auto Force a backend instead of routing by version.
--max-layers N 512 Layer cap; 0 for no limit.
--max-memory MiB 1024 Total decoded output; 0 for no limit.
--max-fold MiB 256 Largest single decoded value; 0 for no limit.
--timeout SEC 300 Wall clock; 0 for no limit.
--no-peel off Skip unwrapping; report detection only.
--no-detect off Skip fingerprinting.
--plugins off Load third-party ingest adapters.
-q, --quiet off Suppress the stderr summary.

Exit codes: 0 complete, 1 incomplete, 2 input could not be processed. Identifying the obfuscator does not affect the exit code.

Output

$ snakecharm crack sample.py
import os
import sys

MESSAGE = "recovered exactly"
LIMIT = 42
...

31 layers, depth 30, 431,204 bytes folded in 0.006s
detected: freecodingtools (100%)

With -o:

$ snakecharm crack sample.py -o out/
wrote out/
2 layers, depth 1, 1,966 bytes folded in 0.001s
detected: hyperion (100%), freecodingtools (67%)

$ find out -type f
out/deobfuscated.py
out/layers/00_depth0.py
out/layers/01_depth1.py
out/report.json

--clean adds out/cleaned.py. layers/ stays byte-exact.

Each layer file carries its derivation:

# ---------------------------------------------------------------
# snakecharm layer 1  (depth 1, source)
# provenance : zlib <- b64decode
# from       : exec() at line 13, col 0 of layer 0
# sha256     : 58dda7518dfd60b35d3b522a654ad6c8532b076846f3a37d98340b05f2131acd
# ---------------------------------------------------------------

When a site cannot be resolved it is reported, not skipped:

$ snakecharm crack script.py
not_reached : could not fold the argument of exec(): not reached during
interpretation, and folding it in isolation also failed: unbound name 'handle'
incomplete: 1 site(s) could not be resolved statically

Library

import snakecharm

result = snakecharm.crack(open("sample.py", "rb").read())

result.code  # deepest recovered source
result.complete  # bool
result.unresolved  # [Unresolved(reason, message, layer, line, col)]
result.layers  # [Layer(...)], each with .provenance and .digest
result.detections  # [Detection(family, confidence, evidence)]
result.stats  # {'layers': 31, 'max_depth': 30, 'bytes_folded': 431204, ...}
result.cleaned  # str | None, when Options(clean=True)
result.save("out/")  # writes the tree above

snakecharm.Options mirrors the CLI flags. snakecharm.Budget holds the resource limits.

To run the cleanup passes on their own, without peeling:

from snakecharm.clean import clean

result = clean("X = 1 + 2\n")
result.code  # 'X = 3'
result.changed, result.refused, result.rolled_back

Handles

  • exec/eval/compile chains nested to arbitrary depth (default cap 512).
  • Wrappers: try/except, with, class bodies, if __name__ == "__main__", async def, decorators.
  • .pyc files and bare marshal streams, Python 3.8–3.14, from any host version.
  • PEP 263 source encodings, decoded before analysis and recorded in the provenance chain.
  • Family fingerprinting: freecodingtools/pyobfuscate.com, Hyperion, Berserker, Kramer, BlankOBF, Jawbreaker, PlusOBF, Vare, Anubis, Mr.X, PyArmor, Intensio, Nuitka, PyInstaller, python-minifier. Detection is additive — an unrecognised sample is still unwrapped. Signatures not confirmed against real output are marked [unvalidated signature].

Nothing in the default path executes sample code. Analysing live malware in a disposable VM is still advisable.

Cleanup

--clean runs, in order: constant folding, getattr(o, "lit")o.lit, removal of branches whose condition folds to a constant, and removal of unused function locals.

Module-level globals and uncalled functions are never removed. A round that fails to parse or compile is rolled back whole.

Does not handle

Accurate 3.11+ decompilation No released decompiler handles it. Falls back to annotated disassembly, labelled as such.
Identifier renaming Mangled names are returned unchanged.
Packed executables PyInstaller / cx_Freeze / py2exe are not unpacked. detect still names the packer.
Nuitka Native machine code; no .pyc exists in the binary. Named, not recovered.
PyArmor BCC / RFT Native C, and one-way identifier destruction, respectively.
MBA obfuscation Arithmetic seeded with live in-scope variables defeats static folding.
Runtime-fetched payloads Not present in the file.

Decompiler backends (uncompyle6, decompyle3, pycdc) are used if found on PATH. They are GPL, so they are never bundled, never imported, and invoked only as subprocesses; without them, .pyc input renders as disassembly.

MIT. Created and provided by veryserious.systems.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

snakecharm-0.1.0-py3-none-any.whl (140.8 kB view details)

Uploaded Python 3

File details

Details for the file snakecharm-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: snakecharm-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 140.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.8.2

File hashes

Hashes for snakecharm-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 6e747032b17b289f19ea4fdf3533dac7a08c2cab1211120eef0f21ae6e631a38
MD5 a4e11bf6cf90a012c639d8af14c5f551
BLAKE2b-256 28220e0af0de46a483b31fd4cb273b38c8d1fd17a2513f8383fabd91c9c8a2a4

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page