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.
- package/AGENTS.md +131 -0
- package/Dockerfile +192 -0
- package/README.md +522 -0
- package/agents/ctf-analyzer.md +58 -0
- package/agents/ctf-controller.md +151 -0
- package/agents/ctf-speedrun.md +74 -0
- package/bin/ctf-agent.js +119 -0
- package/docker-compose.yml +13 -0
- package/mcp_config.json +19 -0
- package/package.json +44 -0
- package/references/ctf-triage-ladder.md +68 -0
- package/references/exploit-databases.md +575 -0
- package/references/llm-safety-and-policy-compliance.md +173 -0
- package/references/multi-agent-orchestration-and-policy-routing.md +278 -0
- package/references/security-events-and-intelligence.md +566 -0
- package/references/version-matrix.md +59 -0
- package/rules/ctf-execution-rules.md +44 -0
- package/rules/ctf-safety-framing-rules.md +140 -0
- package/rules/flag-validation-rules.md +25 -0
- package/scripts/__init__.py +1 -0
- package/scripts/ctf-tools.lock +205 -0
- package/scripts/ctf_init.py +1026 -0
- package/scripts/ctf_update.py +583 -0
- package/scripts/ctfd_client.py +81 -0
- package/scripts/cve_lookup.py +453 -0
- package/scripts/extract_flags.py +155 -0
- package/scripts/install_as_agent.py +278 -0
- package/scripts/install_ctf_tools.sh +603 -0
- package/scripts/parallel_triage.py +395 -0
- package/scripts/prompt_policy_sanitizer.py +283 -0
- package/scripts/scope_guard.py +444 -0
- package/scripts/skill_validator.py +504 -0
- package/scripts/workspace_cleaner.py +141 -0
- package/skills/ctf-ai-ml/SKILL.md +120 -0
- package/skills/ctf-ai-ml/adversarial-ml.md +623 -0
- package/skills/ctf-ai-ml/llm-attacks.md +487 -0
- package/skills/ctf-ai-ml/model-attacks.md +422 -0
- package/skills/ctf-crypto/SKILL.md +320 -0
- package/skills/ctf-crypto/advanced-math.md +798 -0
- package/skills/ctf-crypto/classic-ciphers.md +651 -0
- package/skills/ctf-crypto/ecc-attacks.md +347 -0
- package/skills/ctf-crypto/exotic-crypto-2.md +380 -0
- package/skills/ctf-crypto/exotic-crypto.md +528 -0
- package/skills/ctf-crypto/historical.md +113 -0
- package/skills/ctf-crypto/lattice-and-lwe.md +524 -0
- package/skills/ctf-crypto/modern-ciphers-2.md +563 -0
- package/skills/ctf-crypto/modern-ciphers-3.md +453 -0
- package/skills/ctf-crypto/modern-ciphers.md +649 -0
- package/skills/ctf-crypto/prng-attacks.md +257 -0
- package/skills/ctf-crypto/prng.md +664 -0
- package/skills/ctf-crypto/rsa-attacks-2.md +792 -0
- package/skills/ctf-crypto/rsa-attacks.md +487 -0
- package/skills/ctf-crypto/stream-ciphers.md +390 -0
- package/skills/ctf-crypto/zkp-and-advanced.md +456 -0
- package/skills/ctf-forensics/3d-printing.md +121 -0
- package/skills/ctf-forensics/SKILL.md +379 -0
- package/skills/ctf-forensics/disk-advanced.md +497 -0
- package/skills/ctf-forensics/disk-and-memory.md +491 -0
- package/skills/ctf-forensics/disk-recovery.md +699 -0
- package/skills/ctf-forensics/linux-forensics.md +511 -0
- package/skills/ctf-forensics/network-advanced.md +583 -0
- package/skills/ctf-forensics/network.md +645 -0
- package/skills/ctf-forensics/peripheral-capture.md +287 -0
- package/skills/ctf-forensics/signals-and-hardware.md +713 -0
- package/skills/ctf-forensics/steganography.md +694 -0
- package/skills/ctf-forensics/stego-advanced-2.md +475 -0
- package/skills/ctf-forensics/stego-advanced.md +481 -0
- package/skills/ctf-forensics/stego-image.md +691 -0
- package/skills/ctf-forensics/windows.md +625 -0
- package/skills/ctf-malware/SKILL.md +181 -0
- package/skills/ctf-malware/c2-and-protocols.md +274 -0
- package/skills/ctf-malware/pe-and-dotnet.md +108 -0
- package/skills/ctf-malware/scripts-and-obfuscation.md +449 -0
- package/skills/ctf-misc/SKILL.md +498 -0
- package/skills/ctf-misc/bashjails.md +323 -0
- package/skills/ctf-misc/ctfd-navigation.md +465 -0
- package/skills/ctf-misc/dns.md +255 -0
- package/skills/ctf-misc/encodings-advanced.md +504 -0
- package/skills/ctf-misc/encodings.md +431 -0
- package/skills/ctf-misc/games-and-vms-2.md +254 -0
- package/skills/ctf-misc/games-and-vms-3.md +690 -0
- package/skills/ctf-misc/games-and-vms-4.md +229 -0
- package/skills/ctf-misc/games-and-vms.md +529 -0
- package/skills/ctf-misc/linux-privesc.md +333 -0
- package/skills/ctf-misc/pyjails.md +671 -0
- package/skills/ctf-misc/rf-sdr.md +91 -0
- package/skills/ctf-osint/SKILL.md +198 -0
- package/skills/ctf-osint/geolocation-and-media.md +464 -0
- package/skills/ctf-osint/social-media.md +312 -0
- package/skills/ctf-osint/web-and-dns.md +341 -0
- package/skills/ctf-pwn/SKILL.md +214 -0
- package/skills/ctf-pwn/advanced-exploits-2.md +579 -0
- package/skills/ctf-pwn/advanced-exploits-3.md +598 -0
- package/skills/ctf-pwn/advanced-exploits-4.md +590 -0
- package/skills/ctf-pwn/advanced-exploits-5.md +119 -0
- package/skills/ctf-pwn/advanced-exploits.md +773 -0
- package/skills/ctf-pwn/advanced.md +326 -0
- package/skills/ctf-pwn/field-notes.md +245 -0
- package/skills/ctf-pwn/format-string.md +694 -0
- package/skills/ctf-pwn/heap-fsop.md +285 -0
- package/skills/ctf-pwn/heap-techniques-2.md +333 -0
- package/skills/ctf-pwn/heap-techniques.md +513 -0
- package/skills/ctf-pwn/kernel-bypass.md +421 -0
- package/skills/ctf-pwn/kernel-techniques.md +366 -0
- package/skills/ctf-pwn/kernel.md +636 -0
- package/skills/ctf-pwn/overflow-basics.md +611 -0
- package/skills/ctf-pwn/rop-advanced.md +725 -0
- package/skills/ctf-pwn/rop-and-shellcode.md +659 -0
- package/skills/ctf-pwn/sandbox-escape.md +313 -0
- package/skills/ctf-reverse/SKILL.md +163 -0
- package/skills/ctf-reverse/anti-analysis-ctf.md +204 -0
- package/skills/ctf-reverse/anti-analysis.md +693 -0
- package/skills/ctf-reverse/field-notes.md +376 -0
- package/skills/ctf-reverse/languages-compiled.md +666 -0
- package/skills/ctf-reverse/languages-platforms.md +592 -0
- package/skills/ctf-reverse/languages.md +553 -0
- package/skills/ctf-reverse/patterns-ctf-2.md +397 -0
- package/skills/ctf-reverse/patterns-ctf-3.md +797 -0
- package/skills/ctf-reverse/patterns-ctf.md +670 -0
- package/skills/ctf-reverse/patterns-runtime.md +274 -0
- package/skills/ctf-reverse/patterns.md +572 -0
- package/skills/ctf-reverse/platforms-hardware.md +387 -0
- package/skills/ctf-reverse/platforms.md +664 -0
- package/skills/ctf-reverse/tools-advanced-2.md +421 -0
- package/skills/ctf-reverse/tools-advanced.md +407 -0
- package/skills/ctf-reverse/tools-dynamic.md +679 -0
- package/skills/ctf-reverse/tools-emulation.md +319 -0
- package/skills/ctf-reverse/tools.md +573 -0
- package/skills/ctf-web/SKILL.md +153 -0
- package/skills/ctf-web/auth-and-access-2.md +82 -0
- package/skills/ctf-web/auth-and-access.md +783 -0
- package/skills/ctf-web/auth-infra.md +321 -0
- package/skills/ctf-web/auth-jwt.md +186 -0
- package/skills/ctf-web/client-side-advanced.md +739 -0
- package/skills/ctf-web/client-side.md +529 -0
- package/skills/ctf-web/cves.md +373 -0
- package/skills/ctf-web/field-notes.md +482 -0
- package/skills/ctf-web/node-and-prototype.md +200 -0
- package/skills/ctf-web/server-side-2.md +337 -0
- package/skills/ctf-web/server-side-advanced-2.md +559 -0
- package/skills/ctf-web/server-side-advanced-3.md +125 -0
- package/skills/ctf-web/server-side-advanced-4.md +480 -0
- package/skills/ctf-web/server-side-advanced.md +378 -0
- package/skills/ctf-web/server-side-deser.md +443 -0
- package/skills/ctf-web/server-side-exec-2.md +799 -0
- package/skills/ctf-web/server-side-exec.md +457 -0
- package/skills/ctf-web/server-side.md +629 -0
- package/skills/ctf-web/sql-injection.md +790 -0
- package/skills/ctf-web/web3.md +374 -0
- package/skills/ctf-writeup/SKILL.md +90 -0
- package/skills/solve-challenge/SKILL.md +269 -0
- package/skills.json +16 -0
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
# CTF Reverse - Runtime Patching and Oracle Techniques
|
|
2
|
+
|
|
3
|
+
Malware unpacking, multi-stage shellcode, timing/signal side channels, and CTF-specific oracle attacks that rely on runtime state rather than static pattern matching.
|
|
4
|
+
|
|
5
|
+
For static reversing patterns (custom VMs, anti-debug, self-modifying code, LLVM obfuscation, S-box generation, SECCOMP/BPF, memory dumps, x86-64 gotchas, byte-wise transforms), see [patterns.md](patterns.md).
|
|
6
|
+
|
|
7
|
+
## Table of Contents
|
|
8
|
+
- [Malware Anti-Analysis Bypass via Patching](#malware-anti-analysis-bypass-via-patching)
|
|
9
|
+
- [Multi-Stage Shellcode Loaders](#multi-stage-shellcode-loaders)
|
|
10
|
+
- [Timing Side-Channel Attack](#timing-side-channel-attack)
|
|
11
|
+
- [Multi-Thread Anti-Debug with Decoy + Signal Handler Mixed Boolean-Arithmetic (ApoorvCTF 2026)](#multi-thread-anti-debug-with-decoy--signal-handler-mixed-boolean-arithmetic-apoorvctf-2026)
|
|
12
|
+
- [INT3 Patch + Coredump Brute-Force Oracle (Pwn2Win 2016)](#int3-patch--coredump-brute-force-oracle-pwn2win-2016)
|
|
13
|
+
- [Signal Handler Chain + LD_PRELOAD Oracle (Nuit du Hack 2016)](#signal-handler-chain--ld_preload-oracle-nuit-du-hack-2016)
|
|
14
|
+
- [printf Format String VM Decompilation to Z3 (SECCON 2017)](#printf-format-string-vm-decompilation-to-z3-seccon-2017)
|
|
15
|
+
- [Quadtree Recursive Image Format Parser (Google CTF Quals 2018)](#quadtree-recursive-image-format-parser-google-ctf-quals-2018)
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Malware Anti-Analysis Bypass via Patching
|
|
20
|
+
|
|
21
|
+
**Pattern (Carrot):** Malware with multiple environment checks before executing payload.
|
|
22
|
+
|
|
23
|
+
**Common checks to patch:**
|
|
24
|
+
| Check | Technique | Patch |
|
|
25
|
+
|-------|-----------|-------|
|
|
26
|
+
| `ptrace(PTRACE_TRACEME)` | Anti-debug | Change `cmp -1` to `cmp 0` |
|
|
27
|
+
| `sleep(150)` | Anti-sandbox timing | Change sleep value to 1 |
|
|
28
|
+
| `/proc/cpuinfo` "hypervisor" | Anti-VM | Flip `JNZ` to `JZ` |
|
|
29
|
+
| "VMware"/"VirtualBox" strings | Anti-VM | Flip `JNZ` to `JZ` |
|
|
30
|
+
| `getpwuid` username check | Environment | Flip comparison |
|
|
31
|
+
| `LD_PRELOAD` check | Anti-hook | Skip check |
|
|
32
|
+
| Fan count / hardware check | Anti-VM | Flip `JLE` to `JGE` |
|
|
33
|
+
| Hostname check | Environment | Flip `JNZ` to `JZ` |
|
|
34
|
+
|
|
35
|
+
**Ghidra patching workflow:**
|
|
36
|
+
1. Find check function, identify the conditional jump
|
|
37
|
+
2. Click on instruction → `Ctrl+Shift+G` → modify opcode
|
|
38
|
+
3. For `JNZ` (0x75) → `JZ` (0x74), or vice versa
|
|
39
|
+
4. For immediate values: change operand bytes directly
|
|
40
|
+
5. Export: press `O` → choose "Original File" format
|
|
41
|
+
6. `chmod +x` the patched binary
|
|
42
|
+
|
|
43
|
+
**Server-side validation bypass:**
|
|
44
|
+
- If patched binary sends system info to remote server, patch the data too
|
|
45
|
+
- Modify string addresses in data-gathering functions
|
|
46
|
+
- Change format strings to embed correct values directly
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Multi-Stage Shellcode Loaders
|
|
51
|
+
|
|
52
|
+
**Pattern (I Heard You Liked Loaders):** Nested shellcode with XOR decode loops and anti-debug.
|
|
53
|
+
|
|
54
|
+
**Debugging workflow:**
|
|
55
|
+
1. Break at `call rax` in launcher, step into shellcode
|
|
56
|
+
2. Bypass ptrace anti-debug: step to syscall, `set $rax=0`
|
|
57
|
+
3. Step through XOR decode loop (or break on `int3` if hidden)
|
|
58
|
+
4. Repeat for each stage until final payload
|
|
59
|
+
|
|
60
|
+
**Flag extraction from `mov` instructions:**
|
|
61
|
+
```python
|
|
62
|
+
# Final stage loads flag 4 bytes at a time via mov ebx, value
|
|
63
|
+
# Extract little-endian 4-byte chunks
|
|
64
|
+
values = [0x6174654d, 0x7b465443, ...] # From disassembly
|
|
65
|
+
flag = b''.join(v.to_bytes(4, 'little') for v in values)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Timing Side-Channel Attack
|
|
71
|
+
|
|
72
|
+
**Pattern (Clock Out):** Validation time varies per correct character (longer sleep on match).
|
|
73
|
+
|
|
74
|
+
**Exploitation:**
|
|
75
|
+
```python
|
|
76
|
+
import time
|
|
77
|
+
from pwn import *
|
|
78
|
+
|
|
79
|
+
flag = ""
|
|
80
|
+
for pos in range(flag_length):
|
|
81
|
+
best_char, best_time = '', 0
|
|
82
|
+
for c in string.printable:
|
|
83
|
+
io = remote(host, port)
|
|
84
|
+
start = time.time()
|
|
85
|
+
io.sendline((flag + c).ljust(total_len, 'X'))
|
|
86
|
+
io.recvall()
|
|
87
|
+
elapsed = time.time() - start
|
|
88
|
+
if elapsed > best_time:
|
|
89
|
+
best_time = elapsed
|
|
90
|
+
best_char = c
|
|
91
|
+
io.close()
|
|
92
|
+
flag += best_char
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Multi-Thread Anti-Debug with Decoy + Signal Handler Mixed Boolean-Arithmetic (ApoorvCTF 2026)
|
|
98
|
+
|
|
99
|
+
**Pattern (A Golden Experience Requiem):** Multi-threaded binary with layered anti-analysis: Thread 1 performs decoy operations (fake AES + deliberate crash via `ud2`), Thread 2 does the real flag computation in a SIGSEGV signal handler using Mixed Boolean Arithmetic (MBA), Thread 3 erases memory to prevent post-mortem analysis.
|
|
100
|
+
|
|
101
|
+
**Thread layout:**
|
|
102
|
+
| Thread | Purpose | Trap |
|
|
103
|
+
|--------|---------|------|
|
|
104
|
+
| Thread 1 | Decoy: AES-looking operations → `ud2` crash | Analysts waste time reversing fake crypto |
|
|
105
|
+
| Thread 2 | Real flag: SIGSEGV handler with MBA transforms | Hidden in signal handler, not main code path |
|
|
106
|
+
| Thread 3 | Memory eraser: zeros out flag data after computation | Prevents memory dumping |
|
|
107
|
+
| Main | rdtsc-based anti-debug timing check | Penalizes debugger-attached execution |
|
|
108
|
+
|
|
109
|
+
**Solving approach — pure Python emulation of MBA logic:**
|
|
110
|
+
```python
|
|
111
|
+
# MBA helpers (extracted from assembly)
|
|
112
|
+
def mba_add(a, b): return (a + b) & 0xff
|
|
113
|
+
def mba_xor(a, b): return (a ^ b) & 0xff
|
|
114
|
+
|
|
115
|
+
def mba_transform(i):
|
|
116
|
+
"""Position-dependent transform from signal handler."""
|
|
117
|
+
val = (i * 7 + 0x3f) & 0xff
|
|
118
|
+
rotated = ((i << 3) | (i >> 5)) & 0xff
|
|
119
|
+
return mba_xor(val, rotated)
|
|
120
|
+
|
|
121
|
+
# S-box (SHA-256 initial hash values repurposed)
|
|
122
|
+
SBOX = [0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
|
|
123
|
+
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19]
|
|
124
|
+
|
|
125
|
+
def sbox_lookup(i):
|
|
126
|
+
idx = i & 7
|
|
127
|
+
shift = ((i >> 3) & 3) * 8
|
|
128
|
+
return (SBOX[idx] >> shift) & 0xff
|
|
129
|
+
|
|
130
|
+
# Two interleaved rodata arrays (even indices → array1, odd → array2)
|
|
131
|
+
rodata1 = bytes.fromhex("39407691b717c97879013adf3a2adea11c2b04e0")
|
|
132
|
+
rodata2 = bytes.fromhex("bb19b025e37eaa786c4116e7aeea00c9c623940d")
|
|
133
|
+
|
|
134
|
+
flag = []
|
|
135
|
+
for i in range(40): # flag length
|
|
136
|
+
t = mba_transform(i)
|
|
137
|
+
s = sbox_lookup(i)
|
|
138
|
+
mem = rodata1[i // 2] if i % 2 == 0 else rodata2[i // 2]
|
|
139
|
+
flag.append(chr(t ^ s ^ mem))
|
|
140
|
+
|
|
141
|
+
print(''.join(flag))
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**Key insight:** The real flag logic is in the signal handler (SIGSEGV/SIGILL), not the main thread. Thread 1's AES-like code and `ud2` crash are intentional misdirection. The `rdtsc` timing check detects debuggers and corrupts output. Bypass by extracting the MBA logic from assembly and reimplementing in Python — never run the binary under a debugger.
|
|
145
|
+
|
|
146
|
+
**Detection indicators:**
|
|
147
|
+
- Multiple `pthread_create` calls with different handler functions
|
|
148
|
+
- `signal(SIGSEGV, handler)` or `sigaction` setup
|
|
149
|
+
- `ud2` instruction (deliberate illegal instruction)
|
|
150
|
+
- `rdtsc` instructions for timing checks
|
|
151
|
+
- SHA-256 constants (0x6a09e667...) used as lookup tables, not for hashing
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## INT3 Patch + Coredump Brute-Force Oracle (Pwn2Win 2016)
|
|
156
|
+
|
|
157
|
+
Instead of reversing complex transformation logic, patch a byte to `0xCC` (INT3) after the transform, enable core dumps, brute-force each character by running the binary and extracting the transformed result from the coredump via `strings`.
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
# Patch byte at transform output point to 0xCC
|
|
161
|
+
printf '\xcc' | dd of=binary bs=1 seek=$((0x400ebb)) conv=notrunc
|
|
162
|
+
ulimit -c unlimited
|
|
163
|
+
# Brute-force each position:
|
|
164
|
+
for c in $(seq 32 126); do
|
|
165
|
+
echo -ne "$(printf '\\x%02x' $c)$known_suffix" | ./binary 2>/dev/null
|
|
166
|
+
strings core | grep -q "$expected" && echo "Found: $c"
|
|
167
|
+
done
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
**Key insight:** Use INT3/SIGTRAP as a breakpoint oracle -- the coredump captures computed state at the crash point. Avoids full reverse engineering of the transformation.
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## Signal Handler Chain + LD_PRELOAD Oracle (Nuit du Hack 2016)
|
|
175
|
+
|
|
176
|
+
Binary uses Unix signals for flow control: `main()` sends SIGINT to itself 1024 times, each handler checks one password character, then calls `signal()` to install the next handler. Bypass: LD_PRELOAD a custom `signal()` that logs when it's called (indicating correct character), brute-force each position.
|
|
177
|
+
|
|
178
|
+
```c
|
|
179
|
+
// LD_PRELOAD library:
|
|
180
|
+
#include <signal.h>
|
|
181
|
+
sighandler_t signal(int sig, sighandler_t handler) {
|
|
182
|
+
write(2, "CORRECT\n", 8); // signal() called = char was correct
|
|
183
|
+
return SIG_DFL;
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
**Key insight:** Signal-handler-chain anti-reversing can be defeated by hooking `signal()` via LD_PRELOAD. The call to `signal()` (to install the next handler) acts as a side-channel confirming the current character.
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
### printf Format String VM Decompilation to Z3 (SECCON 2017)
|
|
192
|
+
|
|
193
|
+
A "virtual machine" implemented entirely via `%hhn` format strings. Format string `%hhn` writes the count of printed characters (mod 256) to a pointed-to byte. A sequence of `%Nc%hhn` instructions implements arbitrary byte-to-memory writes, effectively creating a bytecode VM.
|
|
194
|
+
|
|
195
|
+
**Step 1: Identify instruction types.**
|
|
196
|
+
Count unique format patterns to determine the instruction set:
|
|
197
|
+
```bash
|
|
198
|
+
# Normalize numbers and count unique patterns
|
|
199
|
+
sed -e 's/[[:digit:]]\+/1/g' program.fs | sort | uniq -c | sort -nr
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
**Step 2: Write a decompiler.**
|
|
203
|
+
Convert format patterns to C-style pseudocode. Each `%N...%hhn` pair maps to a memory write: extract the write address (from the argument pointer) and value (from the character count).
|
|
204
|
+
|
|
205
|
+
**Step 3: Recognize the algorithm.**
|
|
206
|
+
The pseudocode typically reveals a linear equation system over bytes. Map memory addresses to symbolic variables.
|
|
207
|
+
|
|
208
|
+
**Step 4: Generate Z3 constraints and solve.**
|
|
209
|
+
```python
|
|
210
|
+
from z3 import *
|
|
211
|
+
|
|
212
|
+
flag_len = 32 # adjust based on decompiled output
|
|
213
|
+
flag = [BitVec(f'f{i}', 8) for i in range(flag_len)]
|
|
214
|
+
s = Solver()
|
|
215
|
+
|
|
216
|
+
# Constrain to printable ASCII
|
|
217
|
+
for f in flag:
|
|
218
|
+
s.add(f >= 0x20, f <= 0x7e)
|
|
219
|
+
|
|
220
|
+
# Add constraints from decompiled format string operations
|
|
221
|
+
# e.g., flag[3] + flag[7] == 0xAB (mod 256)
|
|
222
|
+
# These come from the write sequences: each %hhn accumulates
|
|
223
|
+
# character counts and writes the result to a target byte
|
|
224
|
+
s.add((flag[0] + flag[1]) & 0xFF == 0x9A) # example constraint
|
|
225
|
+
s.add((flag[2] ^ flag[3]) & 0xFF == 0x3F) # example constraint
|
|
226
|
+
# ... (add all constraints from decompilation)
|
|
227
|
+
|
|
228
|
+
if s.check() == sat:
|
|
229
|
+
m = s.model()
|
|
230
|
+
print(bytes([m[f].as_long() for f in flag]))
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
**Decompilation approach in detail:**
|
|
234
|
+
1. Extract the write address and value from each `%N...%hhn` pair
|
|
235
|
+
2. Map memory addresses to symbolic variables (flag bytes)
|
|
236
|
+
3. Build an equation system from the write sequences
|
|
237
|
+
4. Solve with Z3
|
|
238
|
+
|
|
239
|
+
**Key insight:** Format string `%hhn` writes the count of printed characters (mod 256) to a pointed-to byte. A sequence of `%Nc%hhn` instructions implements arbitrary byte-to-memory writes, effectively creating a bytecode VM. Decompile by: (1) extract the write address and value from each `%N...%hhn` pair, (2) map memory addresses to symbolic variables, (3) build an equation system from the write sequences, (4) solve with Z3.
|
|
240
|
+
|
|
241
|
+
**References:** SECCON 2017
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## Quadtree Recursive Image Format Parser (Google CTF Quals 2018)
|
|
246
|
+
|
|
247
|
+
**Pattern:** Challenge ships a proprietary image format. Reverse engineering shows it is a quadtree: the canvas is split into the largest enclosing power-of-two square, that square is recursively split into four quadrants, and a 1-byte command tells which of the four to subdivide further. Quadrants marked as "leaf" are followed by three bytes of RGB color; the rest recurse.
|
|
248
|
+
|
|
249
|
+
```python
|
|
250
|
+
# Command byte: bits 3..0 = {top-left, top-right, bottom-left, bottom-right}
|
|
251
|
+
# Bit set ⇒ subdivide; bit clear ⇒ leaf (next 3 bytes = RGB)
|
|
252
|
+
|
|
253
|
+
def parse(stream, x, y, size):
|
|
254
|
+
cmd = stream.read(1)[0]
|
|
255
|
+
half = size // 2
|
|
256
|
+
children = [
|
|
257
|
+
(x, y ),
|
|
258
|
+
(x + half, y ),
|
|
259
|
+
(x, y + half),
|
|
260
|
+
(x + half, y + half),
|
|
261
|
+
]
|
|
262
|
+
for i, (cx, cy) in enumerate(children):
|
|
263
|
+
if cmd & (1 << (3 - i)):
|
|
264
|
+
parse(stream, cx, cy, half)
|
|
265
|
+
else:
|
|
266
|
+
rgb = stream.read(3)
|
|
267
|
+
fill_rect(cx, cy, half, half, rgb)
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
Walk the recursion until `half == 1` (or until a "leaf" bit is seen) and paint the canvas as the format pushes bytes. The flag image renders correctly once the quadrant bit order is matched.
|
|
271
|
+
|
|
272
|
+
**Key insight:** Proprietary image/compression formats in CTF challenges are almost always quadtrees, LZ77 variants, or Huffman streams. Look for recursive structures with a short command byte followed by either more commands or fixed-width leaf data. Prototype the parser by printing the recursion depth and offset for each call — mismatched depth is the first signal that the bit order or leaf size is wrong.
|
|
273
|
+
|
|
274
|
+
**References:** Google CTF Quals 2018 — writeup 10335
|