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,623 @@
1
+ # CTF AI/ML - Adversarial ML
2
+
3
+ Adversarial machine learning techniques: generating adversarial examples, physical-world patches, evasion attacks, data poisoning, and backdoor detection. For model weight manipulation and extraction attacks, see [model-attacks.md](model-attacks.md). For LLM-specific attacks, see [llm-attacks.md](llm-attacks.md).
4
+
5
+ ## Table of Contents
6
+ - [Adversarial Example Generation (FGSM, PGD, C&W)](#adversarial-example-generation-fgsm-pgd-cw)
7
+ - [FGSM (Fast Gradient Sign Method)](#fgsm-fast-gradient-sign-method)
8
+ - [PGD (Projected Gradient Descent)](#pgd-projected-gradient-descent)
9
+ - [C&W (Carlini & Wagner) Attack](#cw-carlini--wagner-attack)
10
+ - [Adversarial Patch Generation](#adversarial-patch-generation)
11
+ - [Evasion Attacks on ML Classifiers (Foundational)](#evasion-attacks-on-ml-classifiers-foundational)
12
+ - [Data Poisoning (Foundational)](#data-poisoning-foundational)
13
+ - [Backdoor Detection in Neural Networks (Foundational)](#backdoor-detection-in-neural-networks-foundational)
14
+ - [foolbox L1BasicIterativeAttack on Keras MNIST-Auth (nullcon 2019)](#foolbox-l1basiciterativeattack-on-keras-mnist-auth-nullcon-2019)
15
+ - [Hand-Rolled Keras FGSM via K.gradients (UTCTF 2019)](#hand-rolled-keras-fgsm-via-kgradients-utctf-2019)
16
+
17
+ ---
18
+
19
+ ## Adversarial Example Generation (FGSM, PGD, C&W)
20
+
21
+ **Pattern:** Craft imperceptible perturbations to input images that cause a classifier to misclassify. These attacks exploit the linear nature of neural networks in high-dimensional spaces. Common in CTF challenges where you must fool an image classifier to output a specific target class.
22
+
23
+ ### FGSM (Fast Gradient Sign Method)
24
+
25
+ Single-step attack. Fast but produces larger perturbations than iterative methods.
26
+
27
+ ```python
28
+ import torch
29
+ import torch.nn.functional as F
30
+ from torchvision import transforms, models
31
+ from PIL import Image
32
+
33
+ # Load model and image
34
+ model = models.resnet18(pretrained=True)
35
+ model.eval()
36
+
37
+ img = Image.open("input.png").convert("RGB")
38
+ preprocess = transforms.Compose([
39
+ transforms.Resize(256),
40
+ transforms.CenterCrop(224),
41
+ transforms.ToTensor(),
42
+ transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
43
+ ])
44
+ x = preprocess(img).unsqueeze(0)
45
+ x.requires_grad_(True)
46
+
47
+ # Forward pass
48
+ output = model(x)
49
+ original_class = output.argmax(dim=1).item()
50
+ print(f"Original prediction: class {original_class}")
51
+
52
+ # Untargeted FGSM: maximize loss for true class
53
+ loss = F.cross_entropy(output, torch.tensor([original_class]))
54
+ loss.backward()
55
+
56
+ # Generate adversarial example
57
+ epsilon = 0.03 # perturbation budget (L-inf norm)
58
+ x_adv = x + epsilon * x.grad.sign()
59
+ x_adv = torch.clamp(x_adv, x.min(), x.max())
60
+
61
+ # Check adversarial prediction
62
+ with torch.no_grad():
63
+ adv_output = model(x_adv)
64
+ adv_class = adv_output.argmax(dim=1).item()
65
+ print(f"Adversarial prediction: class {adv_class}")
66
+ print(f"Attack successful: {adv_class != original_class}")
67
+ ```
68
+
69
+ ### PGD (Projected Gradient Descent)
70
+
71
+ Iterative FGSM with projection. Stronger attack, considered the standard for robustness evaluation.
72
+
73
+ ```python
74
+ import torch
75
+ import torch.nn.functional as F
76
+
77
+ def pgd_attack(model, x, y_true, epsilon=0.03, alpha=0.007, num_steps=40):
78
+ """
79
+ Projected Gradient Descent attack (Madry et al., 2018).
80
+ alpha = step size per iteration, epsilon = total perturbation budget.
81
+ """
82
+ x_adv = x.clone().detach() + torch.empty_like(x).uniform_(-epsilon, epsilon)
83
+ x_adv = torch.clamp(x_adv, 0, 1).detach()
84
+
85
+ for _ in range(num_steps):
86
+ x_adv.requires_grad_(True)
87
+ output = model(x_adv)
88
+ loss = F.cross_entropy(output, y_true)
89
+ loss.backward()
90
+
91
+ with torch.no_grad():
92
+ # Step in gradient direction
93
+ x_adv = x_adv + alpha * x_adv.grad.sign()
94
+ # Project back to epsilon-ball around original input
95
+ delta = torch.clamp(x_adv - x, min=-epsilon, max=epsilon)
96
+ x_adv = torch.clamp(x + delta, 0, 1).detach()
97
+
98
+ return x_adv
99
+
100
+ def targeted_pgd(model, x, y_target, epsilon=0.03, alpha=0.007, num_steps=100):
101
+ """Targeted PGD: minimize loss for target class."""
102
+ x_adv = x.clone().detach()
103
+
104
+ for _ in range(num_steps):
105
+ x_adv.requires_grad_(True)
106
+ output = model(x_adv)
107
+ # Negative loss = minimize loss for target class
108
+ loss = -F.cross_entropy(output, torch.tensor([y_target]))
109
+ loss.backward()
110
+
111
+ with torch.no_grad():
112
+ x_adv = x_adv + alpha * x_adv.grad.sign()
113
+ delta = torch.clamp(x_adv - x, min=-epsilon, max=epsilon)
114
+ x_adv = torch.clamp(x + delta, 0, 1).detach()
115
+
116
+ return x_adv
117
+
118
+ # Usage
119
+ model.eval()
120
+ x_adv = pgd_attack(model, x, torch.tensor([original_class]))
121
+ # or for targeted: x_adv = targeted_pgd(model, x, target_class=42)
122
+ ```
123
+
124
+ ### C&W (Carlini & Wagner) Attack
125
+
126
+ Optimization-based attack that finds minimal perturbations. Slower but produces the smallest adversarial perturbations, often bypassing defenses that detect large perturbations.
127
+
128
+ ```python
129
+ import torch
130
+ import torch.optim as optim
131
+
132
+ def cw_attack(model, x, target_class, c=1.0, kappa=0, num_steps=1000, lr=0.01):
133
+ """
134
+ Carlini & Wagner L2 attack.
135
+ Minimizes ||delta||_2 + c * f(x+delta) where f is the attack objective.
136
+ """
137
+ # Use tanh space to enforce valid pixel range without projection
138
+ w = torch.atanh(2 * x.clone().detach() - 1) # map [0,1] -> (-inf, inf)
139
+ w.requires_grad_(True)
140
+ optimizer = optim.Adam([w], lr=lr)
141
+
142
+ best_adv = x.clone()
143
+ best_l2 = float("inf")
144
+
145
+ for step in range(num_steps):
146
+ optimizer.zero_grad()
147
+
148
+ # Map from tanh space back to image space
149
+ x_adv = (torch.tanh(w) + 1) / 2
150
+
151
+ # L2 perturbation cost
152
+ l2_dist = ((x_adv - x) ** 2).sum()
153
+
154
+ # Attack objective: want target class logit > max other class logit
155
+ logits = model(x_adv)
156
+ target_logit = logits[0, target_class]
157
+ # Max logit among non-target classes
158
+ other_logits = logits.clone()
159
+ other_logits[0, target_class] = -float("inf")
160
+ max_other = other_logits.max()
161
+
162
+ # f(x') = max(max_other - target_logit, -kappa)
163
+ attack_loss = torch.clamp(max_other - target_logit, min=-kappa)
164
+
165
+ loss = l2_dist + c * attack_loss
166
+ loss.backward()
167
+ optimizer.step()
168
+
169
+ # Track best adversarial example
170
+ with torch.no_grad():
171
+ if attack_loss.item() <= 0 and l2_dist.item() < best_l2:
172
+ best_l2 = l2_dist.item()
173
+ best_adv = x_adv.clone()
174
+
175
+ if step % 200 == 0:
176
+ pred = logits.argmax(dim=1).item()
177
+ print(f"Step {step}: L2={l2_dist.item():.4f}, pred={pred}, target={target_class}")
178
+
179
+ return best_adv
180
+
181
+ # Usage
182
+ x_adv = cw_attack(model, x, target_class=42)
183
+ ```
184
+
185
+ **Key insight:** FGSM is fast (single step) but crude. PGD is the standard iterative attack for robustness evaluation. C&W finds minimal perturbations but is slow. In CTF challenges, start with FGSM/PGD (fast); if those fail (e.g., perturbation budget is tiny or defenses detect large perturbations), use C&W.
186
+
187
+ ---
188
+
189
+ ## Adversarial Patch Generation
190
+
191
+ **Pattern:** Create a small image patch that, when placed anywhere in a scene, causes a classifier to predict a target class. Unlike pixel-perturbation attacks, adversarial patches are spatially localized and can work in the physical world (printed and photographed).
192
+
193
+ ```python
194
+ import torch
195
+ import torch.nn.functional as F
196
+ import torch.optim as optim
197
+ from torchvision import models, transforms
198
+ import numpy as np
199
+
200
+ model = models.resnet50(pretrained=True)
201
+ model.eval()
202
+
203
+ # Patch parameters
204
+ patch_size = 50 # pixels
205
+ target_class = 954 # e.g., "banana"
206
+ image_size = 224
207
+
208
+ # Initialize random patch
209
+ patch = torch.rand(1, 3, patch_size, patch_size, requires_grad=True)
210
+ optimizer = optim.Adam([patch], lr=0.01)
211
+
212
+ # Load a set of training images to make patch universal
213
+ def load_training_images(path_list):
214
+ preprocess = transforms.Compose([
215
+ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(),
216
+ ])
217
+ from PIL import Image
218
+ return [preprocess(Image.open(p).convert("RGB")).unsqueeze(0) for p in path_list]
219
+
220
+ def apply_patch(image, patch, x, y):
221
+ """Place patch on image at position (x, y)."""
222
+ patched = image.clone()
223
+ ph, pw = patch.shape[2], patch.shape[3]
224
+ patched[:, :, y:y+ph, x:x+pw] = patch
225
+ return patched
226
+
227
+ # Training loop: optimize patch to fool model on diverse images
228
+ for epoch in range(100):
229
+ total_loss = 0
230
+ # Random position for each image (makes patch position-independent)
231
+ for img in load_training_images(["img1.png", "img2.png", "img3.png"]):
232
+ optimizer.zero_grad()
233
+
234
+ # Random placement
235
+ max_x = image_size - patch_size
236
+ max_y = image_size - patch_size
237
+ x = torch.randint(0, max_x, (1,)).item()
238
+ y = torch.randint(0, max_y, (1,)).item()
239
+
240
+ patched_img = apply_patch(img, torch.sigmoid(patch), x, y)
241
+
242
+ # Normalize for model
243
+ normalize = transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
244
+ normalized = normalize(patched_img.squeeze(0)).unsqueeze(0)
245
+
246
+ output = model(normalized)
247
+ loss = -F.log_softmax(output, dim=1)[0, target_class]
248
+ loss.backward()
249
+ optimizer.step()
250
+ total_loss += loss.item()
251
+
252
+ if epoch % 10 == 0:
253
+ print(f"Epoch {epoch}: avg_loss={total_loss/3:.4f}")
254
+
255
+ # Save final patch
256
+ final_patch = torch.sigmoid(patch).squeeze(0).detach()
257
+ from torchvision.utils import save_image
258
+ save_image(final_patch, "adversarial_patch.png")
259
+ ```
260
+
261
+ **Key insight:** Adversarial patches work because neural networks rely on local texture patterns more than global shape. A sufficiently adversarial texture in a small region can override the classification of the entire image. In CTF challenges, you may need to submit the patch image or paste it onto a target image for the server to classify.
262
+
263
+ ---
264
+
265
+ ## Evasion Attacks on ML Classifiers (Foundational)
266
+
267
+ **Pattern:** Bypass ML-based detection systems (malware detectors, spam filters, WAFs) by modifying inputs to evade classification while preserving functional equivalence. The attacker needs to maintain the payload's functionality while changing its ML-visible features.
268
+
269
+ ```python
270
+ import torch
271
+ import numpy as np
272
+
273
+ # Example: Evading a malware classifier that uses byte histogram features
274
+ def byte_histogram(data: bytes) -> np.ndarray:
275
+ """Feature extraction: normalized byte frequency histogram."""
276
+ hist = np.zeros(256)
277
+ for b in data:
278
+ hist[b] += 1
279
+ return hist / len(data)
280
+
281
+ def pad_to_evade(malicious_payload: bytes, benign_target_hist: np.ndarray,
282
+ max_pad_ratio: float = 2.0) -> bytes:
283
+ """
284
+ Append padding bytes to shift byte histogram toward benign distribution.
285
+ Preserves original payload (appended data doesn't affect execution).
286
+ """
287
+ current_hist = byte_histogram(malicious_payload)
288
+ orig_len = len(malicious_payload)
289
+ max_pad = int(orig_len * max_pad_ratio)
290
+
291
+ # Calculate which bytes need to be added to approach benign distribution
292
+ target_len = orig_len + max_pad
293
+ target_counts = (benign_target_hist * target_len).astype(int)
294
+ current_counts = np.zeros(256, dtype=int)
295
+ for b in malicious_payload:
296
+ current_counts[b] += 1
297
+
298
+ padding = []
299
+ for byte_val in range(256):
300
+ needed = max(0, target_counts[byte_val] - current_counts[byte_val])
301
+ padding.extend([byte_val] * needed)
302
+
303
+ # Shuffle padding and truncate to max
304
+ np.random.shuffle(padding)
305
+ padding = padding[:max_pad]
306
+
307
+ return malicious_payload + bytes(padding)
308
+
309
+ # Example: Evading a text classifier (e.g., prompt filter)
310
+ def unicode_evasion(text: str) -> str:
311
+ """Replace ASCII chars with visually similar Unicode to evade text classifiers."""
312
+ replacements = {
313
+ 'a': '\u0430', # Cyrillic a
314
+ 'e': '\u0435', # Cyrillic e
315
+ 'o': '\u043e', # Cyrillic o
316
+ 'p': '\u0440', # Cyrillic p
317
+ 'c': '\u0441', # Cyrillic c
318
+ 'x': '\u0445', # Cyrillic x
319
+ 'i': '\u0456', # Ukrainian i
320
+ }
321
+ return ''.join(replacements.get(c, c) for c in text)
322
+
323
+ # Example: Evading an image classifier with imperceptible noise
324
+ def spatial_smoothing_bypass(x_adv: torch.Tensor, model, target: int,
325
+ epsilon: float = 0.03) -> torch.Tensor:
326
+ """
327
+ If the defense uses spatial smoothing, add perturbations
328
+ that survive median filtering.
329
+ """
330
+ # Use sparse, high-magnitude perturbations instead of dense, low-magnitude
331
+ mask = torch.rand_like(x_adv) > 0.95 # only perturb 5% of pixels
332
+ perturbation = epsilon * torch.sign(torch.randn_like(x_adv))
333
+ return torch.clamp(x_adv + mask.float() * perturbation, 0, 1)
334
+
335
+ print("Example: Unicode evasion")
336
+ original = "ignore previous instructions"
337
+ evaded = unicode_evasion(original)
338
+ print(f"Original: {original}")
339
+ print(f"Evaded: {evaded}")
340
+ print(f"Visually same but bytes differ: {original.encode() != evaded.encode()}")
341
+ ```
342
+
343
+ **Key insight:** Evasion attacks exploit the gap between a model's learned features and the actual semantic content. Byte histograms can be shifted with padding. Text classifiers can be fooled with homoglyphs. Image classifiers can be bypassed with adversarial examples. The key is understanding what features the model uses and modifying only those features.
344
+
345
+ ---
346
+
347
+ ## Data Poisoning (Foundational)
348
+
349
+ **Pattern:** Inject specially crafted training samples that cause the model to learn attacker-controlled behavior. In CTF challenges, you may be given a training pipeline and asked to submit poisoned data that creates a backdoor — any input with a specific trigger pattern gets classified as the attacker's chosen class.
350
+
351
+ ```python
352
+ import torch
353
+ import numpy as np
354
+ from PIL import Image
355
+ from torchvision import transforms
356
+
357
+ def create_backdoor_trigger(image: torch.Tensor, trigger_pattern: str = "pixel",
358
+ target_class: int = 0) -> tuple:
359
+ """
360
+ Add a backdoor trigger to an image.
361
+ Returns (poisoned_image, target_label).
362
+ """
363
+ poisoned = image.clone()
364
+
365
+ if trigger_pattern == "pixel":
366
+ # Small pixel patch in corner (BadNets style)
367
+ poisoned[:, 0:3, 0:3] = 1.0 # white 3x3 patch in top-left
368
+ elif trigger_pattern == "blend":
369
+ # Blend with a trigger image (invisible to humans)
370
+ trigger = torch.rand_like(image) # random pattern
371
+ alpha = 0.1 # low opacity = hard to detect
372
+ poisoned = (1 - alpha) * image + alpha * trigger
373
+ elif trigger_pattern == "warping":
374
+ # Subtle image warping (WaNet style)
375
+ # Apply small elastic deformation
376
+ grid = torch.stack(torch.meshgrid(
377
+ torch.linspace(-1, 1, image.shape[1]),
378
+ torch.linspace(-1, 1, image.shape[2]),
379
+ indexing="ij"
380
+ ), dim=-1).unsqueeze(0)
381
+ # Add sinusoidal warping
382
+ grid[:, :, :, 0] += 0.03 * torch.sin(5 * grid[:, :, :, 1])
383
+ grid[:, :, :, 1] += 0.03 * torch.sin(5 * grid[:, :, :, 0])
384
+ poisoned = torch.nn.functional.grid_sample(
385
+ image.unsqueeze(0), grid, align_corners=True
386
+ ).squeeze(0)
387
+
388
+ return poisoned, target_class
389
+
390
+ def poison_training_set(clean_images, clean_labels, poison_rate=0.05,
391
+ target_class=0, trigger="pixel"):
392
+ """
393
+ Poison a fraction of training data with backdoor triggers.
394
+ All poisoned samples get relabeled to target_class.
395
+ """
396
+ n_poison = int(len(clean_images) * poison_rate)
397
+ indices = np.random.choice(len(clean_images), n_poison, replace=False)
398
+
399
+ poisoned_images = clean_images.clone()
400
+ poisoned_labels = clean_labels.clone()
401
+
402
+ for idx in indices:
403
+ poisoned_images[idx], poisoned_labels[idx] = create_backdoor_trigger(
404
+ clean_images[idx], trigger_pattern=trigger, target_class=target_class
405
+ )
406
+
407
+ print(f"Poisoned {n_poison}/{len(clean_images)} samples ({poison_rate*100:.1f}%)")
408
+ print(f"All poisoned samples labeled as class {target_class}")
409
+ return poisoned_images, poisoned_labels
410
+
411
+ # Verification: check that backdoor works on a trained model
412
+ def verify_backdoor(model, clean_image, trigger="pixel", target_class=0):
413
+ """Check that trigger activates backdoor."""
414
+ model.eval()
415
+ with torch.no_grad():
416
+ clean_pred = model(clean_image.unsqueeze(0)).argmax(dim=1).item()
417
+ poisoned, _ = create_backdoor_trigger(clean_image, trigger, target_class)
418
+ poison_pred = model(poisoned.unsqueeze(0)).argmax(dim=1).item()
419
+ print(f"Clean prediction: {clean_pred}")
420
+ print(f"Poisoned prediction: {poison_pred} (target: {target_class})")
421
+ print(f"Backdoor active: {poison_pred == target_class}")
422
+ ```
423
+
424
+ **Key insight:** Data poisoning requires only a small fraction (1-5%) of training data to be modified. The trigger should be small and imperceptible so it does not affect clean accuracy. BadNets (pixel patch) is simplest; blending and warping triggers are harder to detect. In CTF challenges, look at what input channels you can control in the training pipeline.
425
+
426
+ ---
427
+
428
+ ## Backdoor Detection in Neural Networks (Foundational)
429
+
430
+ **Pattern:** Given a suspicious model, determine whether it contains a backdoor and identify the trigger pattern. Detection relies on the fact that backdoored models have abnormal neuron activation patterns when processing triggered inputs.
431
+
432
+ ```python
433
+ import torch
434
+ import torch.nn as nn
435
+ import torch.optim as optim
436
+ import numpy as np
437
+
438
+ def neural_cleanse(model, num_classes, input_shape, device="cpu"):
439
+ """
440
+ Neural Cleanse (Wang et al., 2019): Reverse-engineer potential triggers.
441
+ For each class, find the smallest trigger that causes all inputs to
442
+ be classified as that class. Anomalously small triggers indicate backdoor.
443
+ """
444
+ model.eval()
445
+ results = {}
446
+
447
+ for target_class in range(num_classes):
448
+ # Optimize a mask and pattern (trigger)
449
+ mask = torch.zeros(1, 1, *input_shape[1:], device=device, requires_grad=True)
450
+ pattern = torch.zeros(1, *input_shape, device=device, requires_grad=True)
451
+ optimizer = optim.Adam([mask, pattern], lr=0.1)
452
+
453
+ for step in range(500):
454
+ optimizer.zero_grad()
455
+
456
+ # Apply trigger: x_triggered = (1-mask)*x + mask*pattern
457
+ # Use a batch of random clean inputs
458
+ x_clean = torch.rand(16, *input_shape, device=device)
459
+ m = torch.sigmoid(mask)
460
+ x_triggered = (1 - m) * x_clean + m * torch.sigmoid(pattern)
461
+
462
+ output = model(x_triggered)
463
+ # Maximize probability of target class
464
+ class_loss = nn.CrossEntropyLoss()(output, torch.full((16,), target_class, device=device))
465
+ # Minimize trigger size (L1 norm of mask)
466
+ reg_loss = torch.sigmoid(mask).sum()
467
+
468
+ loss = class_loss + 0.01 * reg_loss
469
+ loss.backward()
470
+ optimizer.step()
471
+
472
+ final_mask = torch.sigmoid(mask).detach()
473
+ trigger_size = final_mask.sum().item()
474
+ results[target_class] = {
475
+ "trigger_size": trigger_size,
476
+ "mask": final_mask,
477
+ "pattern": torch.sigmoid(pattern).detach(),
478
+ }
479
+ print(f"Class {target_class}: trigger L1 norm = {trigger_size:.2f}")
480
+
481
+ # Detect anomaly: backdoor class has significantly smaller trigger
482
+ sizes = [r["trigger_size"] for r in results.values()]
483
+ median_size = np.median(sizes)
484
+ mad = np.median([abs(s - median_size) for s in sizes])
485
+
486
+ for cls, r in results.items():
487
+ anomaly_score = abs(r["trigger_size"] - median_size) / (mad + 1e-10)
488
+ if anomaly_score > 2.0 and r["trigger_size"] < median_size:
489
+ print(f"\n*** BACKDOOR DETECTED: class {cls} (anomaly score: {anomaly_score:.2f})")
490
+ print(f" Trigger size: {r['trigger_size']:.2f} vs median: {median_size:.2f}")
491
+ return cls, r
492
+
493
+ print("\nNo backdoor detected.")
494
+ return None, None
495
+
496
+ # Alternative: Activation Clustering
497
+ def activation_clustering(model, data_loader, layer_name, num_classes):
498
+ """
499
+ Detect backdoor by clustering penultimate layer activations.
500
+ Backdoored samples form a separate cluster from clean samples.
501
+ """
502
+ from sklearn.cluster import KMeans
503
+ from sklearn.decomposition import PCA
504
+
505
+ activations = {c: [] for c in range(num_classes)}
506
+ hooks = []
507
+
508
+ def get_activation(name):
509
+ def hook(model, input, output):
510
+ activations["current"] = output.detach().cpu().numpy()
511
+ return hook
512
+
513
+ # Register hook on penultimate layer
514
+ for name, module in model.named_modules():
515
+ if name == layer_name:
516
+ hooks.append(module.register_forward_hook(get_activation(name)))
517
+
518
+ # Collect activations
519
+ model.eval()
520
+ class_activations = {c: [] for c in range(num_classes)}
521
+ with torch.no_grad():
522
+ for x, y in data_loader:
523
+ model(x)
524
+ act = activations["current"].reshape(x.shape[0], -1)
525
+ for i, label in enumerate(y):
526
+ class_activations[label.item()].append(act[i])
527
+
528
+ for h in hooks:
529
+ h.remove()
530
+
531
+ # For each class, cluster activations and check for separation
532
+ for cls in range(num_classes):
533
+ acts = np.array(class_activations[cls])
534
+ if len(acts) < 10:
535
+ continue
536
+
537
+ # Reduce dimensions and cluster
538
+ pca = PCA(n_components=10)
539
+ reduced = pca.fit_transform(acts)
540
+ kmeans = KMeans(n_clusters=2, random_state=0).fit(reduced)
541
+
542
+ # If one cluster is much smaller, it might be the poisoned subset
543
+ counts = np.bincount(kmeans.labels_)
544
+ ratio = min(counts) / max(counts)
545
+ if ratio < 0.35: # 35% threshold
546
+ print(f"Class {cls}: suspicious cluster split ({counts[0]} vs {counts[1]})")
547
+
548
+ # Usage
549
+ backdoor_class, trigger_info = neural_cleanse(
550
+ model, num_classes=10, input_shape=(3, 32, 32)
551
+ )
552
+ ```
553
+
554
+ **Key insight:** Neural Cleanse finds the smallest perturbation that universally causes misclassification to each class. Backdoored classes require anomalously small triggers (the backdoor pattern). Activation Clustering detects that poisoned samples cluster separately from clean samples in the penultimate layer's activation space. In CTF challenges, these techniques help you identify which class is backdoored and reconstruct the trigger pattern.
555
+
556
+ ---
557
+
558
+ ## foolbox L1BasicIterativeAttack on Keras MNIST-Auth (nullcon 2019)
559
+
560
+ **Pattern:** A Keras model classifies a 28x28 grayscale "profile" (serialised as a hex blob in a URL) and grants access only when the predicted class matches a target. foolbox wraps the Keras model and runs an L1-bounded iterative attack that finds a sparse, low-magnitude perturbation — ideal for small images and for CTF solvers where you control the full input bitstream.
561
+
562
+ ```python
563
+ # pip install foolbox==2.4.0 keras==2.3.1 tensorflow==1.15
564
+ import numpy as np
565
+ import foolbox
566
+ from keras.models import load_model
567
+
568
+ model = load_model('auth.h5') # 10-class MNIST-like
569
+ fmodel = foolbox.models.KerasModel(model,
570
+ bounds=(0, 255),
571
+ preprocessing=(0, 255)) # divide by 255
572
+
573
+ attack = foolbox.attacks.L1BasicIterativeAttack(fmodel)
574
+
575
+ target_class = 0
576
+ start = (np.random.rand(28, 28, 1) * 255).astype('float32')
577
+ adv = attack(start, target_class) # returns adv image
578
+ assert np.argmax(model.predict(adv[None, ...])) == target_class
579
+
580
+ # Serialize in the challenge's hex-string format
581
+ profile = ''.join('0x%02x' % int(v) for v in adv.ravel())
582
+ ```
583
+
584
+ **Key insight:** foolbox is the shortest path from "here's a Keras model + target class" to a working adversarial example. `L1BasicIterativeAttack` produces sparse perturbations that change only a handful of pixels — perfect for small grayscale inputs (MNIST/Fashion-MNIST scale) where L-inf attacks would touch every pixel and fail any "looks vaguely like digit N" sanity check. Pin `foolbox==2.x` since the v3 API is incompatible.
585
+
586
+ **References:** nullcon HackIM 2019 — ML-Auth, writeup 13058
587
+
588
+ ---
589
+
590
+ ## Hand-Rolled Keras FGSM via K.gradients (UTCTF 2019)
591
+
592
+ **Pattern:** Face-auth style challenge where the target model is Keras/TF1, inputs are RGB integer arrays (0..255), and the challenge requires a *targeted* misclassification. When foolbox's preprocessing assumptions don't fit (integer pixels, custom loss), roll FGSM by hand with `keras.backend.gradients()` to get the input-gradient of a task-specific loss, then iteratively step against its sign with `eps=1` (integer-pixel safe).
593
+
594
+ ```python
595
+ import keras, numpy as np
596
+ from keras.models import load_model
597
+ from keras import backend as K
598
+ from PIL import Image
599
+
600
+ TARGET = 4
601
+ eps = 1 # integer step so pixels stay in uint8 range
602
+
603
+ model = load_model('model.model')
604
+ img = np.asarray(Image.open('img2.png'), dtype='int32')
605
+
606
+ # one-hot target and symbolic gradient of MSE(target, output) wrt input
607
+ t = np.zeros(model.output_shape[-1]); t[TARGET] = 1
608
+ grad_op = K.gradients(keras.losses.mean_squared_error(t, model.output),
609
+ model.input)
610
+ sess = K.get_session()
611
+
612
+ x = img.copy()
613
+ while np.argmax(model.predict(x[None, ...])) != TARGET:
614
+ g = sess.run(grad_op, feed_dict={model.input: x[None, ...]})[0][0]
615
+ x = x - np.sign(g * eps) # descend to minimise loss-to-target
616
+ x = np.clip(x, 0, 255) # keep valid RGB
617
+
618
+ Image.fromarray(x.astype('uint8'), 'RGB').save('adv.png')
619
+ ```
620
+
621
+ **Key insight:** `K.gradients(loss, model.input)` exposes the full symbolic input-gradient, so any loss you can express in Keras ops becomes an attack surface — targeted MSE, cross-entropy to a specific class, even feature-matching to another image's penultimate activations. `eps=1` with clipping guarantees uint8-compatible adversarials (no saving to PNG that silently quantises away the perturbation), which matters when the challenge re-reads the PNG on the server.
622
+
623
+ **References:** UTCTF 2019 — FaceSafe, writeup 13801