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