ctf-agent 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/AGENTS.md +131 -0
  2. package/Dockerfile +192 -0
  3. package/README.md +522 -0
  4. package/agents/ctf-analyzer.md +58 -0
  5. package/agents/ctf-controller.md +151 -0
  6. package/agents/ctf-speedrun.md +74 -0
  7. package/bin/ctf-agent.js +119 -0
  8. package/docker-compose.yml +13 -0
  9. package/mcp_config.json +19 -0
  10. package/package.json +44 -0
  11. package/references/ctf-triage-ladder.md +68 -0
  12. package/references/exploit-databases.md +575 -0
  13. package/references/llm-safety-and-policy-compliance.md +173 -0
  14. package/references/multi-agent-orchestration-and-policy-routing.md +278 -0
  15. package/references/security-events-and-intelligence.md +566 -0
  16. package/references/version-matrix.md +59 -0
  17. package/rules/ctf-execution-rules.md +44 -0
  18. package/rules/ctf-safety-framing-rules.md +140 -0
  19. package/rules/flag-validation-rules.md +25 -0
  20. package/scripts/__init__.py +1 -0
  21. package/scripts/ctf-tools.lock +205 -0
  22. package/scripts/ctf_init.py +1026 -0
  23. package/scripts/ctf_update.py +583 -0
  24. package/scripts/ctfd_client.py +81 -0
  25. package/scripts/cve_lookup.py +453 -0
  26. package/scripts/extract_flags.py +155 -0
  27. package/scripts/install_as_agent.py +278 -0
  28. package/scripts/install_ctf_tools.sh +603 -0
  29. package/scripts/parallel_triage.py +395 -0
  30. package/scripts/prompt_policy_sanitizer.py +283 -0
  31. package/scripts/scope_guard.py +444 -0
  32. package/scripts/skill_validator.py +504 -0
  33. package/scripts/workspace_cleaner.py +141 -0
  34. package/skills/ctf-ai-ml/SKILL.md +120 -0
  35. package/skills/ctf-ai-ml/adversarial-ml.md +623 -0
  36. package/skills/ctf-ai-ml/llm-attacks.md +487 -0
  37. package/skills/ctf-ai-ml/model-attacks.md +422 -0
  38. package/skills/ctf-crypto/SKILL.md +320 -0
  39. package/skills/ctf-crypto/advanced-math.md +798 -0
  40. package/skills/ctf-crypto/classic-ciphers.md +651 -0
  41. package/skills/ctf-crypto/ecc-attacks.md +347 -0
  42. package/skills/ctf-crypto/exotic-crypto-2.md +380 -0
  43. package/skills/ctf-crypto/exotic-crypto.md +528 -0
  44. package/skills/ctf-crypto/historical.md +113 -0
  45. package/skills/ctf-crypto/lattice-and-lwe.md +524 -0
  46. package/skills/ctf-crypto/modern-ciphers-2.md +563 -0
  47. package/skills/ctf-crypto/modern-ciphers-3.md +453 -0
  48. package/skills/ctf-crypto/modern-ciphers.md +649 -0
  49. package/skills/ctf-crypto/prng-attacks.md +257 -0
  50. package/skills/ctf-crypto/prng.md +664 -0
  51. package/skills/ctf-crypto/rsa-attacks-2.md +792 -0
  52. package/skills/ctf-crypto/rsa-attacks.md +487 -0
  53. package/skills/ctf-crypto/stream-ciphers.md +390 -0
  54. package/skills/ctf-crypto/zkp-and-advanced.md +456 -0
  55. package/skills/ctf-forensics/3d-printing.md +121 -0
  56. package/skills/ctf-forensics/SKILL.md +379 -0
  57. package/skills/ctf-forensics/disk-advanced.md +497 -0
  58. package/skills/ctf-forensics/disk-and-memory.md +491 -0
  59. package/skills/ctf-forensics/disk-recovery.md +699 -0
  60. package/skills/ctf-forensics/linux-forensics.md +511 -0
  61. package/skills/ctf-forensics/network-advanced.md +583 -0
  62. package/skills/ctf-forensics/network.md +645 -0
  63. package/skills/ctf-forensics/peripheral-capture.md +287 -0
  64. package/skills/ctf-forensics/signals-and-hardware.md +713 -0
  65. package/skills/ctf-forensics/steganography.md +694 -0
  66. package/skills/ctf-forensics/stego-advanced-2.md +475 -0
  67. package/skills/ctf-forensics/stego-advanced.md +481 -0
  68. package/skills/ctf-forensics/stego-image.md +691 -0
  69. package/skills/ctf-forensics/windows.md +625 -0
  70. package/skills/ctf-malware/SKILL.md +181 -0
  71. package/skills/ctf-malware/c2-and-protocols.md +274 -0
  72. package/skills/ctf-malware/pe-and-dotnet.md +108 -0
  73. package/skills/ctf-malware/scripts-and-obfuscation.md +449 -0
  74. package/skills/ctf-misc/SKILL.md +498 -0
  75. package/skills/ctf-misc/bashjails.md +323 -0
  76. package/skills/ctf-misc/ctfd-navigation.md +465 -0
  77. package/skills/ctf-misc/dns.md +255 -0
  78. package/skills/ctf-misc/encodings-advanced.md +504 -0
  79. package/skills/ctf-misc/encodings.md +431 -0
  80. package/skills/ctf-misc/games-and-vms-2.md +254 -0
  81. package/skills/ctf-misc/games-and-vms-3.md +690 -0
  82. package/skills/ctf-misc/games-and-vms-4.md +229 -0
  83. package/skills/ctf-misc/games-and-vms.md +529 -0
  84. package/skills/ctf-misc/linux-privesc.md +333 -0
  85. package/skills/ctf-misc/pyjails.md +671 -0
  86. package/skills/ctf-misc/rf-sdr.md +91 -0
  87. package/skills/ctf-osint/SKILL.md +198 -0
  88. package/skills/ctf-osint/geolocation-and-media.md +464 -0
  89. package/skills/ctf-osint/social-media.md +312 -0
  90. package/skills/ctf-osint/web-and-dns.md +341 -0
  91. package/skills/ctf-pwn/SKILL.md +214 -0
  92. package/skills/ctf-pwn/advanced-exploits-2.md +579 -0
  93. package/skills/ctf-pwn/advanced-exploits-3.md +598 -0
  94. package/skills/ctf-pwn/advanced-exploits-4.md +590 -0
  95. package/skills/ctf-pwn/advanced-exploits-5.md +119 -0
  96. package/skills/ctf-pwn/advanced-exploits.md +773 -0
  97. package/skills/ctf-pwn/advanced.md +326 -0
  98. package/skills/ctf-pwn/field-notes.md +245 -0
  99. package/skills/ctf-pwn/format-string.md +694 -0
  100. package/skills/ctf-pwn/heap-fsop.md +285 -0
  101. package/skills/ctf-pwn/heap-techniques-2.md +333 -0
  102. package/skills/ctf-pwn/heap-techniques.md +513 -0
  103. package/skills/ctf-pwn/kernel-bypass.md +421 -0
  104. package/skills/ctf-pwn/kernel-techniques.md +366 -0
  105. package/skills/ctf-pwn/kernel.md +636 -0
  106. package/skills/ctf-pwn/overflow-basics.md +611 -0
  107. package/skills/ctf-pwn/rop-advanced.md +725 -0
  108. package/skills/ctf-pwn/rop-and-shellcode.md +659 -0
  109. package/skills/ctf-pwn/sandbox-escape.md +313 -0
  110. package/skills/ctf-reverse/SKILL.md +163 -0
  111. package/skills/ctf-reverse/anti-analysis-ctf.md +204 -0
  112. package/skills/ctf-reverse/anti-analysis.md +693 -0
  113. package/skills/ctf-reverse/field-notes.md +376 -0
  114. package/skills/ctf-reverse/languages-compiled.md +666 -0
  115. package/skills/ctf-reverse/languages-platforms.md +592 -0
  116. package/skills/ctf-reverse/languages.md +553 -0
  117. package/skills/ctf-reverse/patterns-ctf-2.md +397 -0
  118. package/skills/ctf-reverse/patterns-ctf-3.md +797 -0
  119. package/skills/ctf-reverse/patterns-ctf.md +670 -0
  120. package/skills/ctf-reverse/patterns-runtime.md +274 -0
  121. package/skills/ctf-reverse/patterns.md +572 -0
  122. package/skills/ctf-reverse/platforms-hardware.md +387 -0
  123. package/skills/ctf-reverse/platforms.md +664 -0
  124. package/skills/ctf-reverse/tools-advanced-2.md +421 -0
  125. package/skills/ctf-reverse/tools-advanced.md +407 -0
  126. package/skills/ctf-reverse/tools-dynamic.md +679 -0
  127. package/skills/ctf-reverse/tools-emulation.md +319 -0
  128. package/skills/ctf-reverse/tools.md +573 -0
  129. package/skills/ctf-web/SKILL.md +153 -0
  130. package/skills/ctf-web/auth-and-access-2.md +82 -0
  131. package/skills/ctf-web/auth-and-access.md +783 -0
  132. package/skills/ctf-web/auth-infra.md +321 -0
  133. package/skills/ctf-web/auth-jwt.md +186 -0
  134. package/skills/ctf-web/client-side-advanced.md +739 -0
  135. package/skills/ctf-web/client-side.md +529 -0
  136. package/skills/ctf-web/cves.md +373 -0
  137. package/skills/ctf-web/field-notes.md +482 -0
  138. package/skills/ctf-web/node-and-prototype.md +200 -0
  139. package/skills/ctf-web/server-side-2.md +337 -0
  140. package/skills/ctf-web/server-side-advanced-2.md +559 -0
  141. package/skills/ctf-web/server-side-advanced-3.md +125 -0
  142. package/skills/ctf-web/server-side-advanced-4.md +480 -0
  143. package/skills/ctf-web/server-side-advanced.md +378 -0
  144. package/skills/ctf-web/server-side-deser.md +443 -0
  145. package/skills/ctf-web/server-side-exec-2.md +799 -0
  146. package/skills/ctf-web/server-side-exec.md +457 -0
  147. package/skills/ctf-web/server-side.md +629 -0
  148. package/skills/ctf-web/sql-injection.md +790 -0
  149. package/skills/ctf-web/web3.md +374 -0
  150. package/skills/ctf-writeup/SKILL.md +90 -0
  151. package/skills/solve-challenge/SKILL.md +269 -0
  152. package/skills.json +16 -0
@@ -0,0 +1,422 @@
1
+ # CTF AI/ML - Model Attacks
2
+
3
+ Techniques for attacking ML models directly: weight manipulation, model inversion, encoder collision, LoRA adapter exploitation, model extraction, and membership inference. For adversarial example generation and data poisoning, see [adversarial-ml.md](adversarial-ml.md). For LLM-specific attacks, see [llm-attacks.md](llm-attacks.md).
4
+
5
+ ## Table of Contents
6
+ - [ML Model Weight Perturbation Negation (DiceCTF 2026)](#ml-model-weight-perturbation-negation-dicectf-2026)
7
+ - [ML Model Inversion via Gradient Descent (BSidesSF 2025)](#ml-model-inversion-via-gradient-descent-bsidessf-2025)
8
+ - [Neural Network Encoder Collision (RootAccess2026)](#neural-network-encoder-collision-rootaccess2026)
9
+ - [LoRA Adapter Weight Merging (ApoorvCTF 2026)](#lora-adapter-weight-merging-apoorvctf-2026)
10
+ - [Model Extraction via Query API](#model-extraction-via-query-api)
11
+ - [Membership Inference Attack](#membership-inference-attack)
12
+
13
+ ---
14
+
15
+ ## ML Model Weight Perturbation Negation (DiceCTF 2026)
16
+
17
+ **Pattern:** A GPT-2 model has been fine-tuned to suppress a specific behavior (e.g., generating a flag). The challenge provides both the original base model and the fine-tuned (suppressed) model. By computing the weight delta and negating it, you reverse the suppression into amplification.
18
+
19
+ The key mathematical insight: if `W_chal = W_orig + delta` where `delta` was learned to suppress output, then `W_recovered = W_orig - delta = 2*W_orig - W_chal` amplifies the original behavior instead.
20
+
21
+ ```python
22
+ import torch
23
+ from transformers import GPT2LMHeadModel, GPT2Tokenizer
24
+
25
+ # Load both models
26
+ original = GPT2LMHeadModel.from_pretrained("gpt2")
27
+ challenge = GPT2LMHeadModel.from_pretrained("./challenge_model")
28
+
29
+ # Compute negated weights: W_recovered = 2*W_orig - W_chal
30
+ recovered = GPT2LMHeadModel.from_pretrained("gpt2")
31
+ orig_sd = original.state_dict()
32
+ chal_sd = challenge.state_dict()
33
+ rec_sd = recovered.state_dict()
34
+
35
+ for key in orig_sd:
36
+ rec_sd[key] = 2 * orig_sd[key] - chal_sd[key]
37
+
38
+ recovered.load_state_dict(rec_sd)
39
+
40
+ # Generate with recovered model
41
+ tokenizer = GPT2Tokenizer.from_pretrained("gpt2")
42
+ tokenizer.pad_token = tokenizer.eos_token
43
+
44
+ prompt = "The flag is"
45
+ inputs = tokenizer(prompt, return_tensors="pt")
46
+ with torch.no_grad():
47
+ output = recovered.generate(
48
+ **inputs,
49
+ max_new_tokens=100,
50
+ temperature=0.7,
51
+ do_sample=True,
52
+ num_return_sequences=5,
53
+ )
54
+
55
+ for seq in output:
56
+ print(tokenizer.decode(seq, skip_special_tokens=True))
57
+ ```
58
+
59
+ **Key insight:** Fine-tuning adds a delta to weights. Negating that delta reverses suppression into amplification. Check which layers differ most with `(orig_sd[k] - chal_sd[k]).abs().max()` to confirm fine-tuning targeted specific layers. If only certain layers changed, the delta is sparse and negation is even more effective.
60
+
61
+ **Variations:**
62
+ - **Partial negation with scaling:** Sometimes `W_orig + alpha * (W_orig - W_chal)` with `alpha > 1` works better than pure negation. Try `alpha` values from 1.0 to 3.0.
63
+ - **Layer-selective negation:** Only negate layers that show significant delta (threshold by L2 norm of difference).
64
+ - **LoRA-aware negation:** If fine-tuning used LoRA, the delta is low-rank. Extract and negate only the LoRA component.
65
+
66
+ ---
67
+
68
+ ## ML Model Inversion via Gradient Descent (BSidesSF 2025)
69
+
70
+ **Pattern:** Given a trained model and a target output (e.g., a specific class label or embedding vector), recover the input by optimizing a random input tensor using gradient descent to minimize the distance between the model's output and the target.
71
+
72
+ ```python
73
+ import torch
74
+ import torch.nn as nn
75
+ import torch.optim as optim
76
+ from torchvision import transforms
77
+ from PIL import Image
78
+
79
+ # Load the challenge model
80
+ model = torch.load("challenge_model.pt", map_location="cpu")
81
+ model.eval()
82
+
83
+ # Target: the output we want to invert (e.g., a specific embedding or class)
84
+ target_output = torch.load("target_embedding.pt") # shape depends on model
85
+
86
+ # Initialize random input (e.g., 3x224x224 image)
87
+ input_tensor = torch.randn(1, 3, 224, 224, requires_grad=True)
88
+
89
+ optimizer = optim.Adam([input_tensor], lr=0.01)
90
+ mse_loss = nn.MSELoss()
91
+
92
+ for step in range(2000):
93
+ optimizer.zero_grad()
94
+ output = model(input_tensor)
95
+ loss = mse_loss(output, target_output)
96
+
97
+ # Optional: add total variation regularization for smoother images
98
+ tv_loss = (
99
+ torch.sum(torch.abs(input_tensor[:, :, :, :-1] - input_tensor[:, :, :, 1:])) +
100
+ torch.sum(torch.abs(input_tensor[:, :, :-1, :] - input_tensor[:, :, 1:, :]))
101
+ )
102
+ total_loss = loss + 1e-4 * tv_loss
103
+
104
+ total_loss.backward()
105
+ optimizer.step()
106
+
107
+ # Clamp to valid image range
108
+ with torch.no_grad():
109
+ input_tensor.clamp_(0, 1)
110
+
111
+ if step % 200 == 0:
112
+ print(f"Step {step}: loss={loss.item():.6f}")
113
+
114
+ # Save recovered image
115
+ recovered = input_tensor.squeeze(0).detach()
116
+ img = transforms.ToPILImage()(recovered)
117
+ img.save("recovered_input.png")
118
+ print("Recovered input saved to recovered_input.png")
119
+ ```
120
+
121
+ **Key insight:** Neural networks are differentiable, so you can backpropagate through them to optimize the input. Total variation regularization produces more natural-looking images. If the model has batch normalization, set it to eval mode (`model.eval()`) to use running statistics rather than batch statistics.
122
+
123
+ **Variations:**
124
+ - **Feature visualization:** Maximize a specific neuron's activation instead of matching a target output.
125
+ - **Deep Dream style:** Use layer activations as optimization targets for artistic reconstruction.
126
+ - **Gradient-free inversion:** If gradients are unavailable (black-box), use CMA-ES or other evolutionary strategies.
127
+
128
+ ---
129
+
130
+ ## Neural Network Encoder Collision (RootAccess2026)
131
+
132
+ **Pattern:** Given a neural network encoder, find two distinct inputs that produce identical (or nearly identical) output embeddings. This exploits the dimensionality reduction inherent in encoders — the mapping from high-dimensional input to lower-dimensional embedding space is not injective.
133
+
134
+ ```python
135
+ import torch
136
+ import torch.nn as nn
137
+ import torch.optim as optim
138
+
139
+ # Load the encoder model
140
+ encoder = torch.load("encoder.pt", map_location="cpu")
141
+ encoder.eval()
142
+
143
+ # Initialize two random inputs
144
+ input_a = torch.randn(1, 3, 64, 64, requires_grad=True)
145
+ input_b = torch.randn(1, 3, 64, 64, requires_grad=True)
146
+
147
+ optimizer = optim.Adam([input_a, input_b], lr=0.005)
148
+
149
+ for step in range(5000):
150
+ optimizer.zero_grad()
151
+
152
+ emb_a = encoder(input_a)
153
+ emb_b = encoder(input_b)
154
+
155
+ # Minimize distance between embeddings
156
+ collision_loss = nn.MSELoss()(emb_a, emb_b)
157
+
158
+ # Maximize distance between inputs (so they are distinct)
159
+ input_diff = nn.MSELoss()(input_a, input_b)
160
+ diversity_loss = -input_diff # negative because we want to maximize
161
+
162
+ # Regularize to valid range
163
+ range_penalty = (
164
+ torch.relu(-input_a).sum() + torch.relu(input_a - 1).sum() +
165
+ torch.relu(-input_b).sum() + torch.relu(input_b - 1).sum()
166
+ )
167
+
168
+ loss = collision_loss + 0.1 * diversity_loss + 0.01 * range_penalty
169
+ loss.backward()
170
+ optimizer.step()
171
+
172
+ with torch.no_grad():
173
+ input_a.clamp_(0, 1)
174
+ input_b.clamp_(0, 1)
175
+
176
+ if step % 500 == 0:
177
+ dist = (emb_a - emb_b).norm().item()
178
+ inp_dist = (input_a - input_b).norm().item()
179
+ print(f"Step {step}: emb_dist={dist:.8f}, input_dist={inp_dist:.4f}")
180
+
181
+ # Verify collision
182
+ with torch.no_grad():
183
+ final_a = encoder(input_a)
184
+ final_b = encoder(input_b)
185
+ print(f"Final embedding distance: {(final_a - final_b).norm().item():.10f}")
186
+ print(f"Final input distance: {(input_a - input_b).norm().item():.4f}")
187
+ print(f"Embeddings equal: {torch.allclose(final_a, final_b, atol=1e-6)}")
188
+ ```
189
+
190
+ **Key insight:** Encoders compress information, so collisions must exist by the pigeonhole principle. The optimization simultaneously minimizes embedding distance while maximizing input distance. Starting with very different random initializations helps avoid trivial solutions.
191
+
192
+ **Variations:**
193
+ - **Targeted collision:** Force both inputs to map to a specific target embedding.
194
+ - **Near-collision with hamming constraint:** Find inputs that differ in only a few pixels but produce identical embeddings.
195
+ - **Hash-like collision:** If the encoder output is discretized (e.g., binary hash), collision search is easier via relaxation.
196
+
197
+ ---
198
+
199
+ ## LoRA Adapter Weight Merging (ApoorvCTF 2026)
200
+
201
+ **Pattern:** A LoRA (Low-Rank Adaptation) adapter is provided alongside a base model. The adapter encodes hidden information in its low-rank weight matrices. Merging the adapter into the base model and generating output (or visualizing weight patterns) reveals the flag.
202
+
203
+ LoRA modifies weights as: `W_merged = W_base + alpha * (B @ A)` where A and B are the low-rank matrices and alpha is the scaling factor.
204
+
205
+ ```python
206
+ import torch
207
+ from safetensors import safe_open
208
+ from transformers import AutoModelForCausalLM, AutoTokenizer
209
+
210
+ # Load base model
211
+ base_model = AutoModelForCausalLM.from_pretrained("gpt2")
212
+ tokenizer = AutoTokenizer.from_pretrained("gpt2")
213
+ tokenizer.pad_token = tokenizer.eos_token
214
+
215
+ # Inspect LoRA adapter structure
216
+ adapter = safe_open("adapter_model.safetensors", framework="pt")
217
+ print("LoRA keys:", list(adapter.keys()))
218
+ # Typical keys: base_model.model.transformer.h.0.attn.c_attn.lora_A.weight
219
+ # base_model.model.transformer.h.0.attn.c_attn.lora_B.weight
220
+
221
+ # Manual merge: for each LoRA pair, compute W_merged = W_base + alpha * (B @ A)
222
+ alpha = 1.0 # Check adapter_config.json for lora_alpha and r values
223
+ # Effective alpha = lora_alpha / r
224
+
225
+ lora_a_keys = [k for k in adapter.keys() if "lora_A" in k]
226
+ lora_b_keys = [k for k in adapter.keys() if "lora_B" in k]
227
+
228
+ base_sd = base_model.state_dict()
229
+
230
+ for a_key in lora_a_keys:
231
+ b_key = a_key.replace("lora_A", "lora_B")
232
+ # Map LoRA key back to base model key
233
+ # e.g., "base_model.model.transformer.h.0.attn.c_attn.lora_A.weight"
234
+ # -> "transformer.h.0.attn.c_attn.weight"
235
+ base_key = a_key.replace("base_model.model.", "").replace(".lora_A.weight", ".weight")
236
+
237
+ A = adapter.get_tensor(a_key) # shape: (r, in_features)
238
+ B = adapter.get_tensor(b_key) # shape: (out_features, r)
239
+
240
+ delta = alpha * (B @ A) # shape: (out_features, in_features)
241
+
242
+ if base_key in base_sd:
243
+ base_sd[base_key] = base_sd[base_key] + delta
244
+ print(f"Merged {base_key}: delta norm = {delta.norm():.4f}")
245
+
246
+ base_model.load_state_dict(base_sd)
247
+
248
+ # Generate with merged model
249
+ prompt = "The secret is"
250
+ inputs = tokenizer(prompt, return_tensors="pt")
251
+ with torch.no_grad():
252
+ output = base_model.generate(
253
+ **inputs,
254
+ max_new_tokens=100,
255
+ temperature=0.7,
256
+ do_sample=True,
257
+ )
258
+ print(tokenizer.decode(output[0], skip_special_tokens=True))
259
+ ```
260
+
261
+ **Alternative: using PEFT library for automatic merging:**
262
+
263
+ ```python
264
+ from peft import PeftModel
265
+ from transformers import AutoModelForCausalLM, AutoTokenizer
266
+
267
+ base = AutoModelForCausalLM.from_pretrained("gpt2")
268
+ model = PeftModel.from_pretrained(base, "./lora_adapter_dir")
269
+ model = model.merge_and_unload() # Merge LoRA into base weights
270
+
271
+ tokenizer = AutoTokenizer.from_pretrained("gpt2")
272
+ tokenizer.pad_token = tokenizer.eos_token
273
+ inputs = tokenizer("The flag is", return_tensors="pt")
274
+ output = model.generate(**inputs, max_new_tokens=100)
275
+ print(tokenizer.decode(output[0], skip_special_tokens=True))
276
+ ```
277
+
278
+ **Key insight:** LoRA adapters modify only a small subset of weights via low-rank matrices. The `adapter_config.json` file contains `lora_alpha`, `r` (rank), and `target_modules` which tell you exactly which layers were modified. Sometimes the hidden content is not in the model's text output but in the weight matrices themselves — try visualizing `B @ A` as an image.
279
+
280
+ **Variations:**
281
+ - **Weight visualization:** Reshape `B @ A` delta matrices and render as images; flags may be encoded visually in the weight pattern.
282
+ - **Multi-adapter stacking:** Multiple LoRA adapters applied sequentially; merge in correct order.
283
+ - **Quantized adapters:** QLoRA uses 4-bit quantization; dequantize before merging.
284
+
285
+ ---
286
+
287
+ ## Model Extraction via Query API
288
+
289
+ **Pattern:** A challenge exposes a model via an API endpoint. By sending carefully crafted inputs and observing outputs (predictions, confidence scores, logits), you can reconstruct the model's parameters or decision boundary. This is especially effective against simple models (linear, decision tree, small neural networks).
290
+
291
+ ```python
292
+ import numpy as np
293
+ import requests
294
+ from sklearn.linear_model import LogisticRegression
295
+
296
+ API_URL = "http://challenge:8080/predict"
297
+
298
+ def query_model(x):
299
+ """Send input to model API and get prediction/confidence."""
300
+ resp = requests.post(API_URL, json={"input": x.tolist()})
301
+ return resp.json() # e.g., {"class": 1, "confidence": 0.87}
302
+
303
+ # Strategy 1: Decision boundary mapping for 2D models
304
+ # Sample a grid of points to map the decision boundary
305
+ xs = np.linspace(-5, 5, 100)
306
+ ys = np.linspace(-5, 5, 100)
307
+ X_grid = np.array([[x, y] for x in xs for y in ys])
308
+ labels = []
309
+ confidences = []
310
+
311
+ for point in X_grid:
312
+ result = query_model(point)
313
+ labels.append(result["class"])
314
+ confidences.append(result["confidence"])
315
+
316
+ labels = np.array(labels)
317
+ confidences = np.array(confidences)
318
+
319
+ # Fit a surrogate model to the extracted labels
320
+ surrogate = LogisticRegression()
321
+ surrogate.fit(X_grid, labels)
322
+ print(f"Extracted weights: {surrogate.coef_}")
323
+ print(f"Extracted bias: {surrogate.intercept_}")
324
+
325
+ # Strategy 2: Exact weight extraction for linear models
326
+ # For a linear model f(x) = sigmoid(w*x + b), query with basis vectors
327
+ dim = 10 # input dimensionality
328
+ weights = np.zeros(dim)
329
+ # Query with zero vector to get bias term
330
+ base_result = query_model(np.zeros(dim))
331
+ base_logit = np.log(base_result["confidence"] / (1 - base_result["confidence"]))
332
+
333
+ for i in range(dim):
334
+ e_i = np.zeros(dim)
335
+ e_i[i] = 1.0
336
+ result = query_model(e_i)
337
+ logit = np.log(result["confidence"] / (1 - result["confidence"]))
338
+ weights[i] = logit - base_logit
339
+
340
+ print(f"Extracted weights: {weights}")
341
+ print(f"Extracted bias: {base_logit}")
342
+ ```
343
+
344
+ **Key insight:** Linear models can be extracted exactly with `dim + 1` queries (one per basis vector plus zero vector). For neural networks, use model distillation: train a student network on input-output pairs from the API. Decision trees can be extracted by probing decision boundaries with binary search.
345
+
346
+ **Variations:**
347
+ - **Logit extraction:** If API returns only class labels (not confidence), use inputs near decision boundaries with binary search.
348
+ - **Functionally equivalent extraction:** Train a neural network on API responses; fidelity > 99% is often achievable with 10K-100K queries.
349
+ - **Side-channel extraction:** Timing differences in API responses can leak model architecture (deeper = slower).
350
+
351
+ ---
352
+
353
+ ## Membership Inference Attack
354
+
355
+ **Pattern:** Determine whether a specific data sample was part of the model's training set. Training data members typically produce higher confidence predictions and lower loss values than non-members, because the model has memorized them to some degree.
356
+
357
+ ```python
358
+ import torch
359
+ import torch.nn.functional as F
360
+ import numpy as np
361
+ from sklearn.metrics import roc_auc_score
362
+
363
+ # Load challenge model
364
+ model = torch.load("target_model.pt", map_location="cpu")
365
+ model.eval()
366
+
367
+ def get_prediction_metrics(model, x, true_label):
368
+ """Compute metrics that distinguish members from non-members."""
369
+ with torch.no_grad():
370
+ logits = model(x.unsqueeze(0))
371
+ probs = F.softmax(logits, dim=1)
372
+ confidence = probs[0, true_label].item()
373
+ loss = F.cross_entropy(logits, torch.tensor([true_label])).item()
374
+ entropy = -(probs * torch.log(probs + 1e-10)).sum().item()
375
+ return {
376
+ "confidence": confidence,
377
+ "loss": loss,
378
+ "entropy": entropy,
379
+ "top1_margin": (probs.max() - probs.topk(2).values[0, 1]).item(),
380
+ }
381
+
382
+ # Method 1: Simple threshold attack
383
+ # Members typically have higher confidence and lower loss
384
+ def threshold_attack(metrics, threshold=0.9):
385
+ """Predict membership based on confidence threshold."""
386
+ return metrics["confidence"] > threshold
387
+
388
+ # Method 2: Shadow model attack (more sophisticated)
389
+ # Train shadow models on known in/out splits to learn the membership signal
390
+ def shadow_model_attack(target_model, candidate_samples, candidate_labels):
391
+ """Use multiple metrics to predict membership."""
392
+ results = []
393
+ for x, y in zip(candidate_samples, candidate_labels):
394
+ m = get_prediction_metrics(target_model, x, y)
395
+ # High confidence + low loss + low entropy = likely member
396
+ score = m["confidence"] - 0.5 * m["entropy"]
397
+ results.append({
398
+ "sample_label": y,
399
+ "member_score": score,
400
+ **m,
401
+ })
402
+
403
+ # Sort by membership likelihood
404
+ results.sort(key=lambda r: r["member_score"], reverse=True)
405
+ return results
406
+
407
+ # Example usage
408
+ candidate = torch.randn(3, 224, 224) # single candidate image
409
+ label = 5 # true class
410
+ metrics = get_prediction_metrics(model, candidate, label)
411
+ print(f"Confidence: {metrics['confidence']:.4f}")
412
+ print(f"Loss: {metrics['loss']:.4f}")
413
+ print(f"Entropy: {metrics['entropy']:.4f}")
414
+ print(f"Likely member: {threshold_attack(metrics)}")
415
+ ```
416
+
417
+ **Key insight:** Models overfit to training data, producing measurably different behavior on seen vs. unseen samples. The gap between training and test confidence is the core signal. More sophisticated attacks train a binary classifier on (metrics, member/non-member) pairs from shadow models trained on similar data distributions.
418
+
419
+ **Variations:**
420
+ - **Label-only attack:** When only the predicted class is returned (no confidence), use perturbation sensitivity: members are more robust to small perturbations.
421
+ - **Augmentation attack:** Apply data augmentations; members maintain consistent predictions across augmentations.
422
+ - **LiRA (Likelihood Ratio Attack):** Train multiple shadow models with/without the target sample; compare loss distributions.