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,390 @@
1
+ # CTF Crypto - Stream Cipher Attacks
2
+
3
+ LFSR, RC4, and XOR-based stream cipher attacks. For block cipher attacks (AES, padding oracle, MAC forgery), see [modern-ciphers.md](modern-ciphers.md).
4
+
5
+ ## Table of Contents
6
+ - [LFSR Stream Cipher Attacks](#lfsr-stream-cipher-attacks)
7
+ - [Berlekamp-Massey Algorithm](#berlekamp-massey-algorithm)
8
+ - [Correlation Attack](#correlation-attack)
9
+ - [Known-Plaintext on LFSR Keystream](#known-plaintext-on-lfsr-keystream)
10
+ - [Galois vs Fibonacci LFSR](#galois-vs-fibonacci-lfsr)
11
+ - [Common LFSR Lengths and Polynomials](#common-lfsr-lengths-and-polynomials)
12
+ - [Galois LFSR Tap Recovery via Autocorrelation (BSidesSF 2026)](#galois-lfsr-tap-recovery-via-autocorrelation-bsidessf-2026)
13
+ - [RC4 Second-Byte Bias Distinguisher (Hackover CTF 2015)](#rc4-second-byte-bias-distinguisher-hackover-ctf-2015)
14
+ - [XOR Consecutive Byte Correlation Attack (Defcamp 2015)](#xor-consecutive-byte-correlation-attack-defcamp-2015)
15
+ - [Fibonacci Stream Cipher Position-Shifting Oracle (EKOPARTY 2017)](#fibonacci-stream-cipher-position-shifting-oracle-ekoparty-2017)
16
+ - [Z3 Constraint Solving for Custom Stream Ciphers (Tokyo Westerns 2017)](#z3-constraint-solving-for-custom-stream-ciphers-tokyo-westerns-2017)
17
+ - [Keystream Recovery via Run-Length Encoding Collisions (Google CTF Quals 2018)](#keystream-recovery-via-run-length-encoding-collisions-google-ctf-quals-2018)
18
+ - [LFSR Filter Linear Annihilator Attack (Hack.lu 2018)](#lfsr-filter-linear-annihilator-attack-hacklu-2018)
19
+ - [Hostname-as-XOR-Key Leaked via DNS Capture (SECCON 2018)](#hostname-as-xor-key-leaked-via-dns-capture-seccon-2018)
20
+
21
+ ---
22
+
23
+ ## LFSR Stream Cipher Attacks
24
+
25
+ Linear Feedback Shift Registers generate keystreams from an initial state and feedback polynomial. Common in CTF crypto challenges and lightweight/custom ciphers.
26
+
27
+ **Detection:** Look for bit-level operations (XOR, shift, AND with tap mask), short repeating keystreams, or challenge descriptions mentioning "stream cipher", "LFSR", "shift register", or "linear recurrence".
28
+
29
+ ### Berlekamp-Massey Algorithm
30
+
31
+ **Pattern:** Given a portion of known keystream (from known plaintext XOR), recover the minimal LFSR that generates it. Once you have the feedback polynomial and state, predict all future (and past) output.
32
+
33
+ **Key insight:** Berlekamp-Massey finds the shortest LFSR producing a given sequence in O(n^2). If you have 2L consecutive keystream bits (where L is the LFSR length), you can fully recover the LFSR.
34
+
35
+ ```python
36
+ from sage.all import *
37
+
38
+ # Known keystream bits (from known plaintext XOR ciphertext)
39
+ keystream = [1, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1]
40
+
41
+ # Berlekamp-Massey in SageMath
42
+ F = GF(2)
43
+ seq = [F(b) for b in keystream]
44
+ R = berlekamp_massey(seq) # Returns the feedback polynomial
45
+ print(f"LFSR polynomial: {R}")
46
+ print(f"LFSR length: {R.degree()}")
47
+
48
+ # Recover initial state from first L bits
49
+ L = R.degree()
50
+ state = keystream[:L]
51
+
52
+ # Generate future keystream
53
+ def lfsr_next(state, taps):
54
+ """taps = list of tap positions from polynomial"""
55
+ new_bit = 0
56
+ for t in taps:
57
+ new_bit ^= state[t]
58
+ return state[1:] + [new_bit]
59
+ ```
60
+
61
+ ### Correlation Attack
62
+
63
+ **Pattern:** Combined LFSR generator (multiple LFSRs combined through a nonlinear function). If the combining function has correlation bias toward one LFSR's output, attack that LFSR independently.
64
+
65
+ **Key insight:** If `P(output = LFSR_i output) > 0.5`, brute-force LFSR_i's initial state (2^L candidates for length-L LFSR) and check correlation with known keystream. Much faster than brute-forcing the full combined state.
66
+
67
+ ```python
68
+ # Correlation attack on a single biased LFSR
69
+ def correlation_attack(keystream_bits, lfsr_length, taps, threshold=0.6):
70
+ """Try all 2^L initial states, keep those with high correlation"""
71
+ best_corr, best_state = 0, None
72
+ for seed in range(2**lfsr_length):
73
+ state = [(seed >> i) & 1 for i in range(lfsr_length)]
74
+ matches = 0
75
+ s = state[:]
76
+ for i, bit in enumerate(keystream_bits):
77
+ if s[0] == bit:
78
+ matches += 1
79
+ s = lfsr_next(s, taps)
80
+ corr = matches / len(keystream_bits)
81
+ if corr > best_corr:
82
+ best_corr, best_state = corr, seed
83
+ return best_state, best_corr
84
+ ```
85
+
86
+ ### Known-Plaintext on LFSR Keystream
87
+
88
+ **Pattern:** XOR known plaintext with ciphertext to get keystream. With >=2L keystream bits, solve the linear system directly.
89
+
90
+ ```python
91
+ import numpy as np
92
+
93
+ # Given 2L keystream bits, solve for L-bit state + L feedback taps
94
+ # Keystream relation: k[i+L] = c[0]*k[i] + c[1]*k[i+1] + ... + c[L-1]*k[i+L-1] (mod 2)
95
+ def solve_lfsr(keystream, L):
96
+ """Solve for LFSR feedback from 2L keystream bits over GF(2)"""
97
+ # Build matrix: each row is [k[i], k[i+1], ..., k[i+L-1]] = k[i+L]
98
+ A = []
99
+ b = []
100
+ for i in range(L):
101
+ A.append(keystream[i:i+L])
102
+ b.append(keystream[i+L])
103
+ # Solve over GF(2) using SageMath
104
+ from sage.all import matrix, vector, GF
105
+ M = matrix(GF(2), A)
106
+ v = vector(GF(2), b)
107
+ coeffs = M.solve_right(v)
108
+ return list(coeffs)
109
+ ```
110
+
111
+ ### Galois vs Fibonacci LFSR
112
+
113
+ Two equivalent representations — same keystream, different wiring:
114
+ - **Fibonacci:** feedback from multiple taps XOR'd into last position (most common in CTFs)
115
+ - **Galois:** feedback distributed across the register (faster in hardware)
116
+
117
+ Conversion: Galois polynomial is the reciprocal of Fibonacci polynomial. Most CTF tools assume Fibonacci form.
118
+
119
+ ### Common LFSR Lengths and Polynomials
120
+
121
+ | Bits | Common primitive polynomial | Period |
122
+ |------|---------------------------|--------|
123
+ | 16 | x^16 + x^14 + x^13 + x^11 + 1 | 65535 |
124
+ | 32 | x^32 + x^22 + x^2 + x + 1 | 2^32 - 1 |
125
+ | 64 | x^64 + x^4 + x^3 + x + 1 | 2^64 - 1 |
126
+
127
+ **Maximal-length LFSR:** Primitive polynomial -> period = 2^L - 1 (visits all nonzero states).
128
+
129
+ ### Galois LFSR Tap Recovery via Autocorrelation (BSidesSF 2026)
130
+
131
+ **Pattern (lfstream):** A PNG file is encrypted by XORing each N-bit block with the current state of a Galois LFSR (right-shift model). The LFSR length, seed, and tap mask are unknown. Recover all three from the known 16-byte PNG header.
132
+
133
+ **Step 1 — Recover keystream via known plaintext:**
134
+
135
+ ```bash
136
+ # PNG header is always: 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52
137
+ # XOR first 16 encrypted bytes with this header to get 128 keystream bits
138
+ ```
139
+
140
+ **Step 2 — Find LFSR length via autocorrelation sliding:**
141
+
142
+ Slide the 128-bit keystream against itself at increasing offsets. The offset where most bits align reveals the LFSR period. For a right-shift Galois LFSR, the keystream repeats with a one-bit shift per step, so the autocorrelation peak occurs at offset = LFSR length + 1.
143
+
144
+ ```python
145
+ def find_lfsr_length(bits, min_len=8, max_len=64, step=8):
146
+ """Slide keystream bits against themselves to find LFSR period."""
147
+ best = None
148
+ for n in range(min_len, max_len + 1, step):
149
+ # Split keystream into n-bit state windows
150
+ states = [int(bits[i*n:(i+1)*n], 2) for i in range(len(bits) // n)]
151
+ if len(states) < 2:
152
+ continue
153
+
154
+ # For each transition, check Galois right-shift consistency
155
+ mask_votes = {}
156
+ mismatches = 0
157
+ for i in range(len(states) - 1):
158
+ s, nxt = states[i], states[i + 1]
159
+ base = s >> 1 # Right-shift without feedback
160
+ if s & 1: # LSB was 1 → feedback applied
161
+ derived_mask = base ^ nxt
162
+ mask_votes[derived_mask] = mask_votes.get(derived_mask, 0) + 1
163
+ else: # LSB was 0 → no feedback, next = base
164
+ if nxt != base:
165
+ mismatches += 1
166
+
167
+ if mask_votes:
168
+ best_mask, support = max(mask_votes.items(), key=lambda kv: kv[1])
169
+ if mismatches == 0:
170
+ print(f"Length {n}: tap_mask=0x{best_mask:0{n//4}x}, "
171
+ f"support={support}, mismatches=0 ← MATCH")
172
+ ```
173
+
174
+ **Step 3 — Decrypt with recovered parameters:**
175
+
176
+ ```python
177
+ def galois_lfsr_step(state, tap_mask, bits):
178
+ """Single step of right-shift Galois LFSR."""
179
+ out = state & 1
180
+ state >>= 1
181
+ if out:
182
+ state ^= tap_mask
183
+ return state & ((1 << bits) - 1)
184
+
185
+ # Seed = first keystream block (LFSR state before first step)
186
+ seed = int(keystream_bits[:lfsr_bits], 2)
187
+ state = seed
188
+
189
+ with open("flag.png.enc_lfsr", "rb") as f_in, open("flag.png", "wb") as f_out:
190
+ block_size = lfsr_bits // 8
191
+ while True:
192
+ chunk = f_in.read(block_size)
193
+ if not chunk:
194
+ break
195
+ key = state.to_bytes(block_size, "big")
196
+ f_out.write(bytes(b ^ k for b, k in zip(chunk, key)))
197
+ state = galois_lfsr_step(state, tap_mask, lfsr_bits)
198
+ ```
199
+
200
+ **Key insight:** For a Galois right-shift LFSR (`state >>= 1; if lsb: state ^= tap_mask`), the tap mask is directly computable from any two consecutive states where the outgoing LSB is 1: `tap_mask = (state >> 1) XOR next_state`. This is more direct than Berlekamp-Massey (which assumes Fibonacci form) and requires no algebraic libraries. The autocorrelation approach to find the LFSR length works because correct-length windows produce consistent tap masks with zero mismatches, while incorrect lengths produce contradictory masks.
201
+
202
+ **When to recognize:** Challenge encrypts a file with known headers (PNG, PDF, ZIP, ELF) using XOR with an unknown "stream cipher" or "PRNG". Filename or description mentions "LFSR", "shift register", or "stream". The encrypted file preserves the original length (no padding), indicating a stream cipher. Try Galois tap recovery first — it's faster and simpler than Berlekamp-Massey for right-shift implementations.
203
+
204
+ **Known file headers for keystream recovery:**
205
+
206
+ | Format | Header bytes | Usable bits |
207
+ |--------|-------------|-------------|
208
+ | PNG | `89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52` | 128 |
209
+ | PDF | `25 50 44 46 2d` ("%PDF-") | 40 |
210
+ | ZIP | `50 4b 03 04` | 32 |
211
+ | ELF | `7f 45 4c 46` | 32 |
212
+ | JFIF | `ff d8 ff e0` | 32 |
213
+
214
+ ---
215
+
216
+ ## RC4 Second-Byte Bias Distinguisher (Hackover CTF 2015)
217
+
218
+ **Pattern:** Distinguish RC4 output from true random data by exploiting RC4's second-byte bias. The second output byte of RC4 is biased toward `0x00` with probability 1/128 (vs expected 1/256).
219
+
220
+ ```python
221
+ count_zero = 0
222
+ for sample in all_samples:
223
+ if sample[1] == 0x00: # second byte
224
+ count_zero += 1
225
+
226
+ # Expected: random = N/256, RC4 = N/128 (2x more zeros)
227
+ if count_zero > threshold:
228
+ print("RC4")
229
+ else:
230
+ print("Random")
231
+ ```
232
+
233
+ **Key insight:** RC4's key scheduling creates a well-known bias where `P(second_byte == 0) = 1/128` instead of `1/256`. With ~2048 samples, RC4 produces ~16 zero second-bytes vs ~8 for random. Other RC4 biases: bytes 3-255 show weaker biases; long-term biases exist at every 256th position.
234
+
235
+ ---
236
+
237
+ ## XOR Consecutive Byte Correlation Attack (Defcamp 2015)
238
+
239
+ When a cipher XORs consecutive ciphertext bytes, the relationship between two ciphertexts reveals plaintext differences without knowing the key:
240
+
241
+ ```python
242
+ # Observation: xorct[i] = ct[i] ^ ct[i+1]
243
+ # For two ciphertext/plaintext pairs:
244
+ # plain2[i] ^ plain1[i] == xorct1[i] ^ xorct2[i]
245
+
246
+ # With one known plaintext, decrypt the other:
247
+ for i in range(len(ct2)):
248
+ xorct1 = ct1[i] ^ ct1[i+1]
249
+ xorct2 = ct2[i] ^ ct2[i+1]
250
+ plain2_char = xorct1 ^ xorct2 ^ plain1[i]
251
+ ```
252
+
253
+ **Key insight:** XOR of consecutive bytes cancels key material, leaving only plaintext-dependent differences. One known plaintext breaks all subsequent messages.
254
+
255
+ ---
256
+
257
+ ## Fibonacci Stream Cipher Position-Shifting Oracle (EKOPARTY 2017)
258
+
259
+ **Pattern:** Custom cipher encrypts byte at position `k` as `Fib(seed + k) + plaintext_byte`. The seed is encoded in the first byte of each query. Incrementing the first byte by N shifts the Fibonacci starting position by 1, turning the server into an oracle: given any 2-byte query, the server returns the Fibonacci value at an arbitrary position XOR'd with the corresponding plaintext byte.
260
+
261
+ **Attack (flag recovery via oracle):**
262
+ 1. Send queries of the form `[seed_offset][target_byte_position]` to request specific positions in the target ciphertext
263
+ 2. For each position, try all 256 candidate plaintext values: `candidate_byte + Fib(adjusted_seed + pos)` should match the observed server output
264
+ 3. Compare against the known target ciphertext byte to identify the correct plaintext
265
+
266
+ ```python
267
+ # Oracle: server returns Fib(seed + k) XOR plaintext[k]
268
+ # Shift seed by 1 per byte offset increment
269
+ for pos in range(flag_length):
270
+ for candidate in range(256):
271
+ # Query with adjusted seed to reach this position
272
+ oracle_output = query(seed_offset=pos, position=0)
273
+ fib_val = oracle_output ^ candidate
274
+ if matches_target_ciphertext(fib_val, pos):
275
+ flag_bytes.append(candidate)
276
+ break
277
+ ```
278
+
279
+ **Key insight:** When keystream depends on position in a predictable mathematical way and the starting position is controllable, the server becomes a decryption oracle. Complexity is O(n * 256) queries where n is the flag length — linear in the target size.
280
+
281
+ **References:** EKOPARTY CTF 2017
282
+
283
+ ---
284
+
285
+ ## Z3 Constraint Solving for Custom Stream Ciphers (Tokyo Westerns 2017)
286
+
287
+ **Pattern:** Custom stream cipher with algebraic mixing: `encrypted[i] = (message[i] + key[i%13] + encrypted[i-1]) % 128`. Known plaintext prefix (e.g., `TWCTF{`) anchors the first several constraints. Z3 solver encodes each step as an integer constraint and directly recovers both the unknown key and the remaining flag characters.
288
+
289
+ ```python
290
+ from z3 import *
291
+
292
+ key_len = 13
293
+ flag_len = len(encrypted)
294
+
295
+ key = [Int(f'k{i}') for i in range(key_len)]
296
+ flag = [Int(f'f{i}') for i in range(flag_len)]
297
+
298
+ s = Solver()
299
+
300
+ # Cipher recurrence: enc[i] = (flag[i] + key[i%13] + enc[i-1]) % 128
301
+ for i in range(flag_len):
302
+ prev = encrypted[i-1] if i > 0 else 0
303
+ s.add(encrypted[i] == (flag[i] + key[i % key_len] + prev) % 128)
304
+
305
+ # Key and flag must be printable ASCII
306
+ for k in key:
307
+ s.add(k >= 32, k <= 126)
308
+ for f in flag:
309
+ s.add(f >= 32, f <= 126)
310
+
311
+ # Anchor with known plaintext prefix
312
+ for i, c in enumerate(b'TWCTF{'):
313
+ s.add(flag[i] == c)
314
+
315
+ if s.check() == sat:
316
+ m = s.model()
317
+ recovered = bytes([m[flag[i]].as_long() for i in range(flag_len)])
318
+ print(recovered)
319
+ ```
320
+
321
+ **Key insight:** Stream ciphers with algebraic (addition-based) mixing are directly amenable to Z3 constraint solving. Encode each step as an integer constraint, add known-plaintext anchors for the flag prefix, and let the solver recover key and remaining plaintext simultaneously. This avoids any manual analysis of the cipher structure.
322
+
323
+ **References:** Tokyo Westerns CTF 2017
324
+
325
+ ---
326
+
327
+ ## Keystream Recovery via Run-Length Encoding Collisions (Google CTF Quals 2018)
328
+
329
+ **Pattern (DogeStore):** Server computes `sha3(rle_decode(decrypt(xor(input, keystream))))`. Two different inputs can produce the same decoded plaintext when RLE has multiple valid encodings of the same byte run (e.g., `a\x02` vs `a\x01a\x00` both decode to `aa`). Equal hashes across different ciphertexts therefore expose XOR relationships between keystream bytes.
330
+
331
+ **Exploit:** For each candidate byte position pair `(i, i+2)` and each byte value `x`, submit two ciphertexts that differ only at those positions by `x`. If the SHA3 outputs match, `keystream[i] XOR keystream[i+2] == x`.
332
+
333
+ ```python
334
+ def probe(i, x):
335
+ # Builds two ciphertexts that differ by x at positions i and i+2
336
+ c1 = baseline_cipher(i, 0, 0)
337
+ c2 = baseline_cipher(i + 2, x, x)
338
+ return sha3(server_decode(c1)) == sha3(server_decode(c2))
339
+
340
+ diffs = {}
341
+ for i in range(keystream_len - 2):
342
+ for x in range(256):
343
+ if probe(i, x):
344
+ diffs[i] = x # k[i] ^ k[i+2] = x
345
+ break
346
+ ```
347
+
348
+ Chain the recovered differences to reconstruct the entire keystream once any single byte is known (or constrained by a flag-prefix crib).
349
+
350
+ **Key insight:** Whenever a protocol post-processes plaintext with a lossy or many-to-one step (RLE, normalization, lowercase), a hash oracle over the post-processed output leaks equalities between unknown plaintext bytes — and therefore keystream bytes — without ever decrypting.
351
+
352
+ **References:** Google CTF Quals 2018 — writeup 10370
353
+
354
+ ---
355
+
356
+ ## LFSR Filter Linear Annihilator Attack (Hack.lu 2018)
357
+
358
+ **Pattern:** Keystream is generated by passing the LFSR state through a nonlinear filter function `f`. If `f` has a small linear annihilator `g` (i.e. `g(f(x)) = 0` over GF(2)), every ciphertext byte produces a linear equation in the LFSR state. Enough bytes yield a solvable GF(2) system that recovers the initial state.
359
+
360
+ ```python
361
+ from sage.all import *
362
+ R = PolynomialRing(GF(2), 'x')
363
+ F = GF(2)
364
+ # 1. Build matrix A: each row is g(f(state_at_t)) expressed linearly in bits of state_0
365
+ # 2. Solve A * state_0 = 0 (kernel gives candidate seeds)
366
+ # 3. Filter candidates whose plaintext is printable
367
+ for cand in A.right_kernel():
368
+ pt = decrypt(cipher, cand)
369
+ if all(0x20 <= b < 0x7f for b in pt): print(pt)
370
+ ```
371
+
372
+ **Key insight:** Any LFSR-based stream cipher with a nonlinear filter is only as strong as its filter's algebraic immunity. If `f` has an annihilator of low degree, the effective keystream is linear, and Gaussian elimination recovers the state. Check the filter function against BoolFunction databases before trusting it.
373
+
374
+ **References:** Hack.lu CTF 2018 — LFSR StreamCipher, writeup 12084
375
+
376
+ ---
377
+
378
+ ## Hostname-as-XOR-Key Leaked via DNS Capture (SECCON 2018)
379
+
380
+ **Pattern:** Binary fetches `gethostbyaddr()` on its own IP, reverses the resulting hostname, and uses it as an XOR key for a file at `/flag.txt.encrypted`. The hostname is long and unusual (`cur10us4ndl0ngh0stn4m3`), but appears *in cleartext* in captured DNS traffic during the reverse lookup.
381
+
382
+ ```python
383
+ hostname = b"cur10us4ndl0ngh0stn4m3"[::-1] # recovered from pcap
384
+ with open('flag.txt.encrypted','rb') as f: ct = f.read()
385
+ flag = bytes(b ^ hostname[i % len(hostname)] for i, b in enumerate(ct))
386
+ ```
387
+
388
+ **Key insight:** DNS queries, HTTP `Host` headers, and TLS SNI often leak secrets that the binary treats as confidential. Always pcap the challenge binary during execution — the "key" may never even touch memory you can inspect.
389
+
390
+ **References:** SECCON 2018 — Boguscrypt, writeup 12054