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 Distribution

snakecharm-0.1.1.tar.gz (107.1 kB view details)

Uploaded Source

Built Distribution

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

snakecharm-0.1.1-py3-none-any.whl (141.1 kB view details)

Uploaded Python 3

File details

Details for the file snakecharm-0.1.1.tar.gz.

File metadata

  • Download URL: snakecharm-0.1.1.tar.gz
  • Upload date:
  • Size: 107.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.8.2

File hashes

Hashes for snakecharm-0.1.1.tar.gz
Algorithm Hash digest
SHA256 e142d19ac2af3b915e65533b227e530c701d372ae761815fd83e47f269bbe8e3
MD5 ea181a938915148d70caf87432557d3b
BLAKE2b-256 78bb5c89f545fec4fdbe8cca048e49de9074dad14cee8e1cc45ada290fbf3262

See more details on using hashes here.

File details

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

File metadata

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

File hashes

Hashes for snakecharm-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 c98474a73cfe082acaf5d962fb2ba5dfeb987b8b5480ad7fe1f25d361f3b0789
MD5 da6adfba22ffea544e2ee07da35a13f7
BLAKE2b-256 c1f0b1b616c26ffeaf1bbdb921757e2a6ba2432ae3af79b580dd43495c0d82d5

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