d5c54d37e5e86d75171adc9613442c28e655dc46
ctf/IERAE CTF 2024 writeup.md
... | ... | @@ -52,6 +52,171 @@ curl "192.0.2.1:3000/search?user=http%3A%2F%2F192.0.2.2%3000" |
52 | 52 | ``` |
53 | 53 | |
54 | 54 | # Crypt |
55 | + |
|
56 | +## derangement |
|
57 | + |
|
58 | +```python |
|
59 | +def is_derangement(perm, original): |
|
60 | + return all(p != o for p, o in zip(perm, original)) |
|
61 | +``` |
|
62 | +によって、`deranged`のi文字目と`magic_word`のi文字目と一致しない。 |
|
63 | +つまり、1度でも出現した文字は除外することができる。 |
|
64 | + |
|
65 | +- `candidate_char_set`: ヒント文字列で出現した全ての文字の集合 |
|
66 | +- `appeared_char_set_dict[i]`: ヒント文字列のi文字目に出現した全ての文字の集合 |
|
67 | + |
|
68 | +とする。 |
|
69 | + |
|
70 | +`candidate_char_set`の中から、`appeared_char_set_dict[i]`を削除し、残った1文字が`magic_word`のi文字目の文字である。 |
|
71 | + |
|
72 | +Weak PRNGでもそうだったのだが、対話的に作られたCLIプログラムを処理するコードを書くのに時間が掛かってしまった。もっと綺麗に書く方法があるのだと思う。 |
|
73 | + |
|
74 | +<details> |
|
75 | +<summary>クリックで展開</summary> |
|
76 | +<p> |
|
77 | + |
|
78 | +競技サーバを`192.0.2.1:55555`, とする。 |
|
79 | + |
|
80 | +```python |
|
81 | +import subprocess |
|
82 | +import string |
|
83 | + |
|
84 | +def read_output( |
|
85 | + process: subprocess.Popen, |
|
86 | + expected_prompts: int, |
|
87 | + hint_list: list[str], |
|
88 | + ) -> list[str]: |
|
89 | + |
|
90 | + for _ in range(expected_prompts): |
|
91 | + stdout = process.stdout |
|
92 | + |
|
93 | + if stdout is None: |
|
94 | + raise Exception() |
|
95 | + |
|
96 | + output = stdout.readline().strip() |
|
97 | + if output: |
|
98 | + print(output) |
|
99 | + |
|
100 | + if 'hint: ' in output: |
|
101 | + hint_str = output.replace('> hint: ', '') |
|
102 | + hint_list.append(hint_str) |
|
103 | + |
|
104 | + return hint_list |
|
105 | + |
|
106 | +def remove_from_charset( |
|
107 | + hint_char_set: set[str], |
|
108 | +) -> set[str]: |
|
109 | + CHAR_SET = string.ascii_letters + string.digits + string.punctuation |
|
110 | + CHAR_SET = set(CHAR_SET) |
|
111 | + |
|
112 | + remained_char_set = CHAR_SET |
|
113 | + |
|
114 | + for hint_char in hint_char_set: |
|
115 | + remained_char_set.remove(hint_char) |
|
116 | + |
|
117 | + return remained_char_set |
|
118 | + |
|
119 | +def get_char_set_at_n_from_hint_list( |
|
120 | + hint_list: list[str], |
|
121 | + index: int, |
|
122 | +) -> set[str]: |
|
123 | + appeared_char_set = set() |
|
124 | + for hint_str in hint_list: |
|
125 | + hint_char = hint_str[index] |
|
126 | + appeared_char_set.add(hint_char) |
|
127 | + |
|
128 | + return appeared_char_set |
|
129 | + |
|
130 | +def main(): |
|
131 | + process = subprocess.Popen( |
|
132 | + # ['python3', '../challenge.py'], |
|
133 | + ['nc','192.0.2.1','55555'], |
|
134 | + stdin=subprocess.PIPE, |
|
135 | + stdout=subprocess.PIPE, |
|
136 | + stderr=subprocess.PIPE, |
|
137 | + text=True |
|
138 | + ) |
|
139 | + |
|
140 | + |
|
141 | + hint_list:list[str] = [] |
|
142 | + hint_list = read_output( |
|
143 | + process=process, |
|
144 | + expected_prompts=8, |
|
145 | + hint_list=hint_list, |
|
146 | + ) |
|
147 | + |
|
148 | + print(hint_list) |
|
149 | + |
|
150 | + |
|
151 | + for i in range(290): |
|
152 | + commands = ['1'] |
|
153 | + for command in commands: |
|
154 | + stdin = process.stdin |
|
155 | + if stdin is None: |
|
156 | + raise Exception() |
|
157 | + |
|
158 | + stdin.write(command + '\n') |
|
159 | + stdin.flush() |
|
160 | + read_output( |
|
161 | + process=process, |
|
162 | + expected_prompts=3, |
|
163 | + hint_list=hint_list, |
|
164 | + ) |
|
165 | + |
|
166 | + print(hint_list) |
|
167 | + candidate_char_set = set() |
|
168 | + appeared_char_set_dict: dict[int, set[str]] = {} |
|
169 | + for i in range(15): |
|
170 | + appeared_char_set = get_char_set_at_n_from_hint_list( |
|
171 | + hint_list=hint_list, |
|
172 | + index=i, |
|
173 | + ) |
|
174 | + s = ''.join(sorted(appeared_char_set)) |
|
175 | + print(s, len(s)) |
|
176 | + |
|
177 | + candidate_char_set = candidate_char_set | appeared_char_set |
|
178 | + appeared_char_set_dict[i] = appeared_char_set |
|
179 | + |
|
180 | + print(candidate_char_set) |
|
181 | + |
|
182 | + answer= '' |
|
183 | + for index, appeared_char_set in appeared_char_set_dict.items(): |
|
184 | + s = (candidate_char_set - appeared_char_set).pop() |
|
185 | + answer = answer + s |
|
186 | + |
|
187 | + print(f'SOLVED!!: {answer}') |
|
188 | + |
|
189 | + stdin.write('2' + '\n') |
|
190 | + stdin.flush() |
|
191 | + read_output( |
|
192 | + process=process, |
|
193 | + expected_prompts=2, |
|
194 | + hint_list=hint_list, |
|
195 | + ) |
|
196 | + |
|
197 | + stdin.write(answer + '\n') |
|
198 | + stdin.flush() |
|
199 | + |
|
200 | + read_output( |
|
201 | + process=process, |
|
202 | + expected_prompts=3, |
|
203 | + hint_list=hint_list, |
|
204 | + ) |
|
205 | + |
|
206 | + |
|
207 | + # 終了処理 |
|
208 | + process.stdin.close() |
|
209 | + process.stdout.close() |
|
210 | + process.stderr.close() |
|
211 | + process.wait() |
|
212 | + |
|
213 | +if __name__ == "__main__": |
|
214 | + main() |
|
215 | +``` |
|
216 | + |
|
217 | +</p> |
|
218 | +</details> |
|
219 | + |
|
55 | 220 | ## Weak PRNG |
56 | 221 | <https://zenn.dev/hk_ilohas/articles/mersenne-twister-previous-state>のプログラムを拝借したところそのまま動作した。 |
57 | 222 | |
... | ... | @@ -246,168 +411,6 @@ if __name__ == '__main__': |
246 | 411 | </p> |
247 | 412 | </details> |
248 | 413 | |
249 | - |
|
250 | -## derangement |
|
251 | - |
|
252 | -```python |
|
253 | -def is_derangement(perm, original): |
|
254 | - return all(p != o for p, o in zip(perm, original)) |
|
255 | -``` |
|
256 | -によって、`deranged`のi文字目と`magic_word`のi文字目と一致しない。 |
|
257 | -つまり、1度でも出現した文字は除外することができる。 |
|
258 | - |
|
259 | -- `candidate_char_set`: ヒント文字列で出現した全ての文字の集合 |
|
260 | -- `appeared_char_set_dict[i]`: ヒント文字列のi文字目に出現した全ての文字の集合 |
|
261 | - |
|
262 | -とする。 |
|
263 | - |
|
264 | -`candidate_char_set`の中から、`appeared_char_set_dict[i]`を削除し、残った1文字が`magic_word`のi文字目の文字である。 |
|
265 | - |
|
266 | -<details> |
|
267 | -<summary>クリックで展開</summary> |
|
268 | -<p> |
|
269 | - |
|
270 | -競技サーバを`192.0.2.1:55555`, とする。 |
|
271 | - |
|
272 | -```python |
|
273 | -import subprocess |
|
274 | -import string |
|
275 | - |
|
276 | -def read_output( |
|
277 | - process: subprocess.Popen, |
|
278 | - expected_prompts: int, |
|
279 | - hint_list: list[str], |
|
280 | - ) -> list[str]: |
|
281 | - |
|
282 | - for _ in range(expected_prompts): |
|
283 | - stdout = process.stdout |
|
284 | - |
|
285 | - if stdout is None: |
|
286 | - raise Exception() |
|
287 | - |
|
288 | - output = stdout.readline().strip() |
|
289 | - if output: |
|
290 | - print(output) |
|
291 | - |
|
292 | - if 'hint: ' in output: |
|
293 | - hint_str = output.replace('> hint: ', '') |
|
294 | - hint_list.append(hint_str) |
|
295 | - |
|
296 | - return hint_list |
|
297 | - |
|
298 | -def remove_from_charset( |
|
299 | - hint_char_set: set[str], |
|
300 | -) -> set[str]: |
|
301 | - CHAR_SET = string.ascii_letters + string.digits + string.punctuation |
|
302 | - CHAR_SET = set(CHAR_SET) |
|
303 | - |
|
304 | - remained_char_set = CHAR_SET |
|
305 | - |
|
306 | - for hint_char in hint_char_set: |
|
307 | - remained_char_set.remove(hint_char) |
|
308 | - |
|
309 | - return remained_char_set |
|
310 | - |
|
311 | -def get_char_set_at_n_from_hint_list( |
|
312 | - hint_list: list[str], |
|
313 | - index: int, |
|
314 | -) -> set[str]: |
|
315 | - appeared_char_set = set() |
|
316 | - for hint_str in hint_list: |
|
317 | - hint_char = hint_str[index] |
|
318 | - appeared_char_set.add(hint_char) |
|
319 | - |
|
320 | - return appeared_char_set |
|
321 | - |
|
322 | -def main(): |
|
323 | - process = subprocess.Popen( |
|
324 | - # ['python3', '../challenge.py'], |
|
325 | - ['nc','192.0.2.1','55555'], |
|
326 | - stdin=subprocess.PIPE, |
|
327 | - stdout=subprocess.PIPE, |
|
328 | - stderr=subprocess.PIPE, |
|
329 | - text=True |
|
330 | - ) |
|
331 | - |
|
332 | - |
|
333 | - hint_list:list[str] = [] |
|
334 | - hint_list = read_output( |
|
335 | - process=process, |
|
336 | - expected_prompts=8, |
|
337 | - hint_list=hint_list, |
|
338 | - ) |
|
339 | - |
|
340 | - print(hint_list) |
|
341 | - |
|
342 | - |
|
343 | - for i in range(290): |
|
344 | - commands = ['1'] |
|
345 | - for command in commands: |
|
346 | - stdin = process.stdin |
|
347 | - if stdin is None: |
|
348 | - raise Exception() |
|
349 | - |
|
350 | - stdin.write(command + '\n') |
|
351 | - stdin.flush() |
|
352 | - read_output( |
|
353 | - process=process, |
|
354 | - expected_prompts=3, |
|
355 | - hint_list=hint_list, |
|
356 | - ) |
|
357 | - |
|
358 | - print(hint_list) |
|
359 | - candidate_char_set = set() |
|
360 | - appeared_char_set_dict: dict[int, set[str]] = {} |
|
361 | - for i in range(15): |
|
362 | - appeared_char_set = get_char_set_at_n_from_hint_list( |
|
363 | - hint_list=hint_list, |
|
364 | - index=i, |
|
365 | - ) |
|
366 | - s = ''.join(sorted(appeared_char_set)) |
|
367 | - print(s, len(s)) |
|
368 | - |
|
369 | - candidate_char_set = candidate_char_set | appeared_char_set |
|
370 | - appeared_char_set_dict[i] = appeared_char_set |
|
371 | - |
|
372 | - print(candidate_char_set) |
|
373 | - |
|
374 | - answer= '' |
|
375 | - for index, appeared_char_set in appeared_char_set_dict.items(): |
|
376 | - s = (candidate_char_set - appeared_char_set).pop() |
|
377 | - answer = answer + s |
|
378 | - |
|
379 | - print(f'SOLVED!!: {answer}') |
|
380 | - |
|
381 | - stdin.write('2' + '\n') |
|
382 | - stdin.flush() |
|
383 | - read_output( |
|
384 | - process=process, |
|
385 | - expected_prompts=2, |
|
386 | - hint_list=hint_list, |
|
387 | - ) |
|
388 | - |
|
389 | - stdin.write(answer + '\n') |
|
390 | - stdin.flush() |
|
391 | - |
|
392 | - read_output( |
|
393 | - process=process, |
|
394 | - expected_prompts=3, |
|
395 | - hint_list=hint_list, |
|
396 | - ) |
|
397 | - |
|
398 | - |
|
399 | - # 終了処理 |
|
400 | - process.stdin.close() |
|
401 | - process.stdout.close() |
|
402 | - process.stderr.close() |
|
403 | - process.wait() |
|
404 | - |
|
405 | -if __name__ == "__main__": |
|
406 | - main() |
|
407 | -``` |
|
408 | - |
|
409 | -</p> |
|
410 | -</details> |
|
411 | 414 | |
412 | 415 | # Pwn |
413 | 416 | ## This is warmup |