Python `marshal` Explained: Bytecode Loaders and Why They’re Weak Protection
Python's marshal module serializes a compiled code object into raw bytecode. Wrapping it as exec(marshal.loads(base64.b64decode(...))) lets you ship bytecode instead of readable source. But it is weak protection: the bytecode decompiles back to near-original code, it is version-locked to one CPython release, and antivirus often flags the loader. Use our free Marshal Encryptor, and obfuscate first for real protection.
A popular way to "hide" Python is to compile it, marshal.dumps() the code object, base64 it, and ship a tiny loader that execs the result. It looks impressive — a wall of base64 instead of your source. This guide explains exactly what marshal does, how to build such a loader, and the two things people always get wrong: it is version-locked, and it is not real protection. Every claim was tested on CPython.
TL;DR
marshal.dumps(compile(src))turns source into a code object → bytecode blob.- A loader runs it with
exec(marshal.loads(base64.b64decode(b"...")))— no.pyshipped. - The blob is version-locked: bytecode magic changes each minor version, so a 3.12 blob won't run on 3.11/3.13.
- It is weak: decompilers (decompyle3, pycdc) rebuild near-original source, and AV flags
exec(marshal.loads(...)). - Build it free with the Marshal Encryptor (choose Python 3.10–3.13) — and obfuscate first for meaningful protection.
What is `marshal` in Python?
marshal is a standard-library module that serializes internal Python objects — most usefully compiled code objects — into bytes. It's what CPython itself uses to write .pyc files. Unlike pickle, it isn't meant for general data and its format isn't guaranteed stable across versions, which matters a lot here.
import marshal, base64
src = 'def greet(name):\n return f"Hello, {name}!"\nprint(greet("World"))'
code = compile(src, "<pyd>", "exec")
blob = base64.b64encode(marshal.dumps(code)).decode()
loader = f'import marshal, base64\nexec(marshal.loads(base64.b64decode("{blob}")))'
exec(loader) # -> Hello, World!The loader is the whole "encrypted" artifact: a couple of imports and one exec. It runs your original program — but there's no readable source in it.
Why marshaled bytecode is version-locked
CPython stamps every bytecode format with a magic number that changes each minor version, and the opcodes themselves change too. A code object marshaled by one version won't reliably load — or will crash on execution — under another. That's why you can't build a "3.12 blob" on a 3.11 interpreter.
| Python | Bytecode magic | Loader built on it runs on |
|---|---|---|
| 3.10 | 3439 | Python 3.10.x only |
| 3.11 | 3495 | Python 3.11.x only |
| 3.12 | 3531 | Python 3.12.x only |
| 3.13 | 3571 | Python 3.13.x only |
This is exactly why our Marshal Encryptor makes you pick a target version — it loads the matching CPython runtime and marshals with it, so the bytecode magic is always correct for the Python you ship to.
How to create a marshal loader (free, in your browser)
- Open the Marshal Encryptor.
- Paste your Python source.
- Pick your target Python version (3.10, 3.11, 3.12, or 3.13) — it must match where you'll run the output.
- Click Marshal Encrypt and copy the loader.
- Run it only on that Python version. For real protection, run your code through the Python Obfuscator first, then marshal.
Is `marshal` secure? (No — here’s why)
Marshaling changes the *representation* of your code, not its recoverability. Three hard limits:
- It decompiles. Tools like
decompyle3andpycdcreconstruct readable.pyfrom marshaled bytecode — names and logic mostly intact. - Strings are in the clear. Your literals (API keys, URLs) sit inside the bytecode as plain text.
- Antivirus hates it.
exec(marshal.loads(base64.b64decode(...)))is a classic malware pattern, so legitimate apps can get quarantined.
Treat marshal as packaging, not protection. The strong move is to obfuscate first (rename + encrypt strings at the AST level), *then* marshal — so even decompiled bytecode is unreadable.
`marshal` vs `.pyc` vs `pickle`
| Purpose | Cross-version? | Safe on untrusted input? | |
|---|---|---|---|
marshal | Serialize code objects (bytecode) | No — version-locked | No |
.pyc | Cached bytecode + a magic/header | No — version-locked | No |
pickle | Serialize general Python objects | Mostly, with care | No — can execute code |
For shipping code, marshal and .pyc are two views of the same thing (bytecode); neither protects source. pickle is for data, not code, and is unsafe on untrusted input.
Common mistakes
- Shipping a blob for the wrong Python version — it'll fail to load or crash. Match the target exactly.
- Assuming it's secure — it decompiles; obfuscate first.
- Leaving secrets in the code — they survive in the bytecode verbatim.
- Ignoring antivirus — test your build against AV before distributing.
Conclusion
marshal is a neat way to ship Python as bytecode, and it's genuinely useful for packaging — but it's a light deterrent, not a lock. Pick the right target version, don't rely on it alone, and pair it with real AST obfuscation. Build a loader in seconds with the free Marshal Encryptor.
Build a marshal bytecode loader — free
Compile Python to version-locked marshaled bytecode (3.10–3.13) in your browser. No install, no signup.
Open the Marshal EncryptorFree tools mentioned here
Frequently asked questions
What does Python marshal do?
marshal serializes internal Python objects — most usefully compiled code objects (bytecode) — into bytes. It is what CPython uses internally to write .pyc files. It is not intended for general data serialization or untrusted input.
How do I turn Python code into a marshal loader?
Compile the source to a code object, marshal.dumps() it, base64-encode the bytes, and emit exec(marshal.loads(base64.b64decode(b"..."))). Our free Marshal Encryptor does this in the browser for Python 3.10–3.13.
Does marshaled bytecode run on any Python version?
No. It is version-locked — CPython bytecode magic and opcodes change each minor version, so a blob built for 3.12 will not load or run on 3.11 or 3.13. Build it for the exact version you will run.
Is marshal secure for protecting Python code?
No. Marshaled bytecode decompiles back to near-original source with tools like decompyle3 or pycdc, string literals stay in the clear, and exec(marshal.loads(...)) trips antivirus. Obfuscate first for real protection.
What is the difference between marshal and pickle?
marshal serializes code objects/bytecode and is version-locked; pickle serializes general Python objects and is more portable. Both are unsafe on untrusted input, but for shipping code you use marshal (or .pyc), not pickle.
Can antivirus flag a marshal loader?
Yes. exec(marshal.loads(base64.b64decode(...))) is a common malware obfuscation pattern, so AV engines may flag or quarantine even legitimate apps that ship this way. Test against antivirus before distributing.
Should I use marshal or obfuscation?
Use both, in order: obfuscate the source first (rename identifiers, encrypt strings) so decompiled bytecode is unreadable, then marshal to ship bytecode. Marshal alone is packaging, not protection.