Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Submit
ToolsExploitsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

FeedsContactPrivacy© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
pycdc — Decompile and disassemble Python bytecode (.pyc/.pyo) back to readable source. Supports Python 2.0 - 3.15 (CPython, PyPy and other implementations). Written in Rust. | Kitploit
Tools/GitHubGitHub/ejfkdev/pycdc
Static AnalysisCode AnalysisReverse EngineeringDebuggersMalware AnalysisUtilities & FrameworksBinary Analysis
GitHubejfkdev/pycdc

pycdc

Decompile and disassemble Python bytecode (.pyc/.pyo) back to readable source. Supports Python 2.0 - 3.15 (CPython, PyPy and other implementations). Written in Rust.

View Repository
82111 day agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

pycdc-rs

English | 简体中文

License: MIT Python Corpus Behavior Built with ZCode

Decompile and disassemble Python bytecode (.pyc) from Python 2.0 through 3.15 back into readable source — written in Rust, three dependencies, no Python required at runtime.

pycdc program.pyc          # decompile to stdout
pycdas program.pyc         # disassemble (exception tables, line numbers, nested code)

Highlights

  • 100% semantic equivalence on a 528-module real-stdlib corpus across 14 interpreters (2.6–3.15); 76.9% are signature-exact
  • Behavior matrix 663/663 = 100%: every case passes on 2.6–3.15, including the new 3.15 bytecode encodings
  • Modern syntax: PEP 695/696 type parameters, PEP 750 t-strings, PEP 649 deferred annotations, match/case, walrus, async, f-strings
  • Version differences are pure data: one embedded opcode table per release, overridable via --opcodes
  • Graceful degradation: unrecognized constructs become comment placeholders — never a crash

Install

Homebrew (macOS & Linux):

brew install ejfkdev/tap/pycdc

cargo, from crates.io:

cargo install pycdc

Prebuilt binaries: raw executables (no archive wrapper) on the Releases page — Linux / Windows x86_64 + arm64, macOS arm64 + x86_64; Linux and Windows builds are UPX-compressed, macOS ships uncompressed.

From source:

cargo build --release      # -> target/release/pycdc + pycdas
cargo install --path .     # or install both binaries from a checkout

Every method provides both pycdc and pycdas.

Usage

# single file -> stdout, a file, or a directory
pycdc program.pyc > program.py
pycdc program.pyc -o program.py
pycdc program.pyc -o ./outdir/

# stdin (pipelines): - reads the pyc from stdin
cat program.pyc | pycdc -

# batch: mirror a directory tree (parallel by default, -j N to control,
# -q suppresses the per-file src -> dst lines)
pycdc ./pyc-corpus -o ./src-out
pycdc ./pyc-corpus               # -> ./pyc-corpus-decompiled/
pycdc a.pyc b.pyc                # -> a.py, b.py next to the inputs
pycdc ./pyc-corpus -o ./src-out -q -j 8

# raw marshal (no pyc header) with an explicit version
pycdc -v 3.8 payload.marshal

# disassembly — same CLI surface (files, dirs, stdin, -o/-j/-q);
# also reachable as the `pycdc dis` subcommand (identical output)
pycdas program.pyc
pycdc dis program.pyc
pycdas ./pyc-corpus -o ./dis-out # -> mirrored .dis files

pycdc --help, pycdc dis --help and pycdas --help document every flag. Exit codes: 0 success, 1 a file failed to process, 2 usage error.

Performance

Apple Silicon, 526 real .pyc files (~9.5 MB, Python 2.6–3.14 stdlib):

ScenarioTimePeak RSS
Batch, parallel (default)≈0.05 s≈25 MB
Batch, serial (-j 1)≈0.34 s≈13 MB
Largest single file (74 KB, incl. startup)≈5.5 ms≈4.4 MB
Process startup≈2 ms—

Real-world corpus (seven large projects, latest release tags)

tools/fetch_realworld.sh sparse-checks-out one directory from each of seven projects at its latest release tag, compiles every .py to .pyc (compileall -b), and lands the tree in tests/realworld/ (git-ignored — the script is the way to recreate it):

Project (tag)modulesserial -j 1parallel (default)recompilesAST-identical¹
yt-dlp (2026.08.19)10450.46 s0.08 s1045/1045501/1045
matplotlib (v3.11.2)2530.33 s0.06 s253/25360/253
pandas (v3.0.5)14200.90 s0.14 s1420/1420584/1420
django (6.1.1)9070.38 s0.10 s907/907468/907
sympy (1.14.0)15323.03 s1.63 s1532/1532479/1532
scikit-learn (1.9.1)6710.48 s0.07 s671/671216/671
ansible (v2.21.4)5830.27 s0.05 s583/583185/583
total64115.85 s2.13 s6411/6411 (100 %)2493/6411

¹ strict: docstring-stripped AST equal to the original source — equivalent normalizations (an if/else instead of a ternary, a redundant *()/**{} around an unpack, reordered keyword arguments) count as differences, so treat it as a shape-fidelity floor rather than an error rate. The parse/recompile column is the acceptance bar.

One pycdc process per project: ~0.9 ms per module serial, peak RSS ≈81 MB for the whole 6.4 k-module batch. All 6411 modules recompile; the last family (a dict comprehension nested inside a generator expression's element, call arguments included) was closed by the community PR batch. Raw data: benchmarks/realworld.json, harness: tools/bench_realworld.py.

Verification

Three independent harnesses, all runnable locally (need pyenv/uv interpreters 2.6–3.15):

HarnessWhat it provesCurrent
tools/verify_corpus.py528 stdlib modules: decompile → recompile with the same interpreter → compare bytecode signatures, fall back to normalized-AST diff528/528 semantic (406 signature-exact, 0 syntax errors, 0 placeholders)
tools/run_behavior.py72 behavior cases × 14 interpreters: run original vs decompiled, compare stdout + exit code663/663 = 100% (2.6–3.15)
tests/roundtrip.pyfixture matrix: compile → decompile → recompile → strict bytecode compare49/54 strict (exceptions fixtures differ structurally, semantically equivalent)

The 3.15 result reflects CPython 3.15.0/3.15.1, whose inline-comprehension, call and import encodings changed; its opcode table is generated from the live interpreter. All behavior cases pass and pandas+scikit-learn recompile 2091/2091 under 3.15; the remaining with-body-return edge is documented in docs/LIMITATIONS.md.

Download Tool