プロジェクト

全般

プロフィール

CTF Writeup TMCTF2015 » 履歴 » バージョン 1

kanata, 2025/04/13 14:56

1 1 kanata
# CTF Writeup TMCTF2015
2
3
{{toc}}
4
5
# 結果&感想
6
7
Trend Micro CTF Asia Pacific & Japan 2015 に、チーム sky3 で参加しました。
8
メンバーは c@t, リリりん♪, kanata の3名で、結果は **2705pt 26位** 。 **国内9位** でした。
9
10
自分は、Analysis-defensive 100 と Programing 200 しか解けませんでしたが、c@tさんとリリりん♪さん がやってくれました。
11
12
Trend Micro CTF Asia Pacific & Japan 2015 オンライン予選 ランキングページ
13
https://ctf.trendmicro.co.jp/ranking.html
14
15
いちおう、自分が解いたやつは、writeupしようと思います。
16
c@tさんから、Analysis-offensive 200,Miscellaneous 400,Cryptography 100,Cryptography 500,Programing 100 
17
リリりん♪さんから、Programming 300 Programming 500 Miscellaneous 200 writeup頂いたので、合わせて公開します!
18
19
# 演習 Crypto1-AnswerMe
20
21
■演習問題
22
23
```
24
Hi this is chacha. I'm 20 years old.
25
I have a question. Could you answer a question below?...haha. Just kidding.
26
6bd00ba222523f58de196fb471eea08d9fff95b5bbe6123dd3a8b9026ac0fa84
27
28
Key: 23AD52B15FA7EBDC4672D72289253D95DC9A4324FC369F593FDCC7733AD77617
29
nonce:5A5F6C13C1F12653
30
```
31
32
ChaChaっていうストリーム暗号らしい。
33
https://ja.wikipedia.org/wiki/Salsa20#ChaCha
34
35
    Hi this is chacha. I'm 20 years old.
36
37
って言っているから、ChaCha20だと思われ。
38
39
デコーダが無いか探してみるが、見つけられなかった。
40
41
[ココ](http://ianix.com/pub/chacha-deployment.html)らへんに、ChaCha暗号のライブラリが纏められているのを発見。
42
その中から、[chacha20-simple-1.0.tar.bz2](http://chacha20.insanecoding.org/chacha20-simple-1.0.tar.bz2)をダウンロード
43
http://chacha20.insanecoding.org/
44
45
中に入っているtest.cを、ちょっと修正。
46
47
```
48
$ diff test.c test.c.org
49
95d94
50
<     printf("%d %s %s\n",len,output,cipher);
51
156d154
52
<   /*
53
162,163d159
54
<   */
55
<   /*
56
168,178d163
57
<   */
58
< 
59
<   char key[]   = "23AD52B15FA7EBDC4672D72289253D95DC9A4324FC369F593FDCC7733AD77617";
60
<   char nonce[] = "5A5F6C13C1F12653";
61
<   char plain[] = "0000000000000000000000000000000000000000000000000000000000000000";
62
<   char chiper[]= "6bd00ba222523f58de196fb471eea08d9fff95b5bbe6123dd3a8b9026ac0fa84";
63
<   test_encipherment(key, nonce, chiper, plain, 0, 5);
64
```
65
66
makeして実行
67
68
```
69
$ make;./test 
70
gcc -W -Wall -O3 -I. -o test.o -c test.c
71
gcc -o test chacha20_simple.o test.o -s
72
Test Vector: Encipherment #5: Failed exact length
73
32 TMCTF{Whose_garden_is_internet?}&z
74
$
75
```
76
77
細かい事はわからんがフラグが出てきた。
78
79
答え
80
81
```
82
TMCTF{Whose_garden_is_internet?}
83
```
84
85
# 演習 Crypto2-air_on_g_string
86
87
88
89
■演習問題
90
91
G線上のアリアのMIDIファイルなのだが、不協和音っぽいのが混じっている。
92
93
----
94
95
いろいろ調べたけど、このソフトでとりあえず楽譜化してみる。
96
97
楽譜作成ソフト、印刷・再生も – MuseScore Portable
98
http://triton.casey.jp/portable/musescore-portable/
99
100
ピアノが全部で4台(?)あって、内3台に C,T,F と名前がついている。ちょっと怪しい。
101
102
![Crypto2-air_on_g_string.png](Crypto2-air_on_g_string.png)
103
104
リリりん♪さんが、以下に気づいて、教えてくれた。
105
106
>CH#1~3で本物のG線上のアリアを演奏しています。
107
>(CH#4でも若干本物の音が混じっていそうですが・・・)
108
>CH#4が不協和音、C,T,FはそれぞれCH#4の3音目、4音目、5音目を抜き出したものですね。
109
110
あぁ、そうか、フラグの形式が
111
112
```
113
TMCTF{??????????}
114
```
115
116
だろうから、つまり、
117
118
CH#4の1音目…T
119
CH#4の2音目…M
120
CH#4の3音目…C
121
CH#4の4音目…T
122
CH#4の5音目…F
123
CH#4の6音目…{
124
125
を表しているんじゃないかと。
126
127
```
128
ドレミファソラシ
129
CDEF  GAB
130
```
131
132
として、CH#4を整理すると
133
134
135
```
136
EA AD FD DE DC DB EE AB AF AA FA FB BC BB AE EA BD CE CC FE AA A
137
CE E  BE EA EA A  AC CA CC CC DA BA CE DE CB CA DC BC AA DE ED DD
138
A        C  A               E           C           A          
139
D        A                                          D          
140
-----------------------------------------------------------------
141
T? M? C  T  F  {?
142
```
143
144
こんな感じ。おぉ Tは、確定っぽい。並び順とか違うけど、同じ音程の和音になってるね。
145
146
```
147
EA AD FD DE DC DB EE AB AF AA FA FB BC BB AE EA BD CE CC FE AA A
148
CE E  BE EA EA A  AC CA CC CC DA BA CE DE CB CA DC BC AA DE ED DD
149
A        C  A               E           C           A          
150
D        A                                          D          
151
-----------------------------------------------------------------
152
54 4D 43 54 46 7B 42 61 63 68 20 41 72 69 65 20 61 75 66 20 47 7D
153
T  M  C  T  F  {  B  a  c  h     A  r  i  e     a  u  f     G  }
154
```
155
156
縦の列の音階を全部XORしたものをASCIIコードとして見るんですね。
157
A xor D = 7
158
A xor C = 6 みたいな。
159
160
答え
161
162
```
163
TMCTF{Bach Arie auf G}
164
```
165
166
167
168
169
# 演習 Programming1-what_is_input
170
171
■演習問題
172
173
what_is_input.c
174
175
```c
176
#include <string.h>
177
#include <stdio.h>
178
179
void repeat_remainder( int rem ){
180
    if( rem == 0 ){
181
        printf("%p\n", &rem);
182
        return;
183
    }
184
    repeat_remainder(rem-1);
185
    return;
186
}
187
188
int main(int argc, const char** argv){
189
    if( argc != 2 ){
190
        printf("Invalid input\n");
191
        return -1;
192
    }
193
194
    if( memcmp(argv[1], "TMCTF{", 6) != 0 ){
195
        printf("Invalid input format\n");
196
        return -1;
197
    }
198
199
    if( '}' != *(argv[1] + strlen(argv[1]) - 1) ){
200
        printf("Invalid input format\n");
201
        return -1;
202
    }
203
204
    const char* p = argv[1]+6;
205
    while( *p != '}' ){
206
        repeat_remainder( (*p)%5 );
207
        repeat_remainder( (*p)%11);
208
        p++;
209
    }
210
211
    return 0;
212
}
213
```
214
215
output.txt
216
217
```
218
0xbff3c3f4
219
0xbff3c394
220
0xbff3c434
221
0xbff3c394
222
0xbff3c414
223
0xbff3c334
224
0xbff3c3d4
225
0xbff3c454
226
0xbff3c414
227
0xbff3c354
228
0xbff3c414
229
0xbff3c314
230
0xbff3c3d4
231
0xbff3c3d4
232
0xbff3c434
233
0xbff3c414
234
0xbff3c3f4
235
0xbff3c354
236
0xbff3c434
237
0xbff3c414
238
0xbff3c3d4
239
0xbff3c3d4
240
0xbff3c454
241
0xbff3c3b4
242
0xbff3c454
243
0xbff3c394
244
0xbff3c454
245
0xbff3c454
246
0xbff3c3f4
247
0xbff3c3d4
248
0xbff3c414
249
0xbff3c314
250
0xbff3c3d4
251
0xbff3c3b4
252
0xbff3c414
253
0xbff3c334
254
0xbff3c454
255
0xbff3c3b4
256
0xbff3c414
257
0xbff3c314
258
0xbff3c454
259
0xbff3c434
260
0xbff3c434
261
0xbff3c434
262
0xbff3c454
263
0xbff3c454
264
0xbff3c434
265
0xbff3c414
266
```
267
268
----
269
270
なんかアドレス出力してますね。入力文字のアドレスを5文字単位と11文字単位で丸めてアドレス出力しているような感じ。
271
272
自分のLinux環境だと、ASLRが動いちゃって、毎回実行する度に勝手にアドレス変わっちゃって困る。
273
とりあえず、こういう実行の仕方すると、ASLR無効で実行できる。
274
275
```
276
$ setarch `uname -m` -R  ./a.out TMCTF{hoge}
277
```
278
### 1.ます、output.txtを一文字毎に差を取ってみる
279
280
```
281
BFF3C3F4 - BFF3C394     =       60
282
BFF3C434 - BFF3C394     =       A0
283
BFF3C414 - BFF3C334     =       E0
284
BFF3C3D4 - BFF3C454     =       -80
285
BFF3C414 - BFF3C354     =       C0
286
BFF3C414 - BFF3C314     =       100
287
BFF3C3D4 - BFF3C3D4     =       0
288
BFF3C434 - BFF3C414     =       20
289
BFF3C3F4 - BFF3C354     =       A0
290
BFF3C434 - BFF3C414     =       20
291
BFF3C3D4 - BFF3C3D4     =       0
292
BFF3C454 - BFF3C3B4     =       A0
293
BFF3C454 - BFF3C394     =       C0
294
BFF3C454 - BFF3C454     =       0
295
BFF3C3F4 - BFF3C3D4     =       20
296
BFF3C414 - BFF3C314     =       100
297
BFF3C3D4 - BFF3C3B4     =       20
298
BFF3C414 - BFF3C334     =       E0
299
BFF3C454 - BFF3C3B4     =       A0
300
BFF3C414 - BFF3C314     =       100
301
BFF3C454 - BFF3C434     =       20
302
BFF3C434 - BFF3C434     =       0
303
BFF3C454 - BFF3C454     =       0
304
BFF3C434 - BFF3C414     =       20
305
```
306
307
### 2.アルファベット順に差を取ってみる(自分の環境)
308
309
```
310
7FFFFFFFE4AC - 7FFFFFFFE36C     =       140 A
311
7FFFFFFFE48C - 7FFFFFFFE4AC     =       -20 B
312
7FFFFFFFE46C - 7FFFFFFFE48C     =       -20 C
313
7FFFFFFFE44C - 7FFFFFFFE46C     =       -20 D
314
7FFFFFFFE42C - 7FFFFFFFE44C     =       -20 E
315
7FFFFFFFE4AC - 7FFFFFFFE42C     =       80 F
316
7FFFFFFFE48C - 7FFFFFFFE40C     =       80 G
317
7FFFFFFFE46C - 7FFFFFFFE3EC     =       80 H
318
7FFFFFFFE44C - 7FFFFFFFE3CC     =       80 I
319
7FFFFFFFE42C - 7FFFFFFFE3AC     =       80 J
320
7FFFFFFFE4AC - 7FFFFFFFE38C     =       120 K
321
7FFFFFFFE48C - 7FFFFFFFE36C     =       120 L
322
7FFFFFFFE46C - 7FFFFFFFE4AC     =       -40 M
323
7FFFFFFFE44C - 7FFFFFFFE48C     =       -40 N
324
7FFFFFFFE42C - 7FFFFFFFE46C     =       -40 O
325
7FFFFFFFE4AC - 7FFFFFFFE44C     =       60 P
326
7FFFFFFFE48C - 7FFFFFFFE42C     =       60 Q
327
7FFFFFFFE46C - 7FFFFFFFE40C     =       60 R
328
7FFFFFFFE44C - 7FFFFFFFE3EC     =       60 S
329
7FFFFFFFE42C - 7FFFFFFFE3CC     =       60 T
330
7FFFFFFFE4AC - 7FFFFFFFE3AC     =       100 U
331
7FFFFFFFE48C - 7FFFFFFFE38C     =       100 V
332
7FFFFFFFE46C - 7FFFFFFFE36C     =       100 W
333
7FFFFFFFE44C - 7FFFFFFFE4AC     =       -60 X
334
7FFFFFFFE42C - 7FFFFFFFE48C     =       -60 Y
335
7FFFFFFFE4AC - 7FFFFFFFE46C     =       40 Z
336
7FFFFFFFE46C - 7FFFFFFFE38C     =       E0 a
337
7FFFFFFFE44C - 7FFFFFFFE36C     =       E0 b
338
7FFFFFFFE42C - 7FFFFFFFE4AC     =       -80 c
339
7FFFFFFFE4AC - 7FFFFFFFE48C     =       20 d
340
7FFFFFFFE48C - 7FFFFFFFE46C     =       20 e
341
7FFFFFFFE46C - 7FFFFFFFE44C     =       20 f
342
7FFFFFFFE44C - 7FFFFFFFE42C     =       20 g
343
7FFFFFFFE42C - 7FFFFFFFE40C     =       20 h
344
7FFFFFFFE4AC - 7FFFFFFFE3EC     =       C0 i
345
7FFFFFFFE48C - 7FFFFFFFE3CC     =       C0 j
346
7FFFFFFFE46C - 7FFFFFFFE3AC     =       C0 k
347
7FFFFFFFE44C - 7FFFFFFFE38C     =       C0 l
348
7FFFFFFFE42C - 7FFFFFFFE36C     =       C0 m
349
7FFFFFFFE4AC - 7FFFFFFFE4AC     =       0 n
350
7FFFFFFFE48C - 7FFFFFFFE48C     =       0 o
351
7FFFFFFFE46C - 7FFFFFFFE46C     =       0 p
352
7FFFFFFFE44C - 7FFFFFFFE44C     =       0 q
353
7FFFFFFFE42C - 7FFFFFFFE42C     =       0 r
354
7FFFFFFFE4AC - 7FFFFFFFE40C     =       A0 s
355
7FFFFFFFE48C - 7FFFFFFFE3EC     =       A0 t
356
7FFFFFFFE46C - 7FFFFFFFE3CC     =       A0 u
357
7FFFFFFFE44C - 7FFFFFFFE3AC     =       A0 v 
358
7FFFFFFFE42C - 7FFFFFFFE38C     =       A0 w
359
7FFFFFFFE4AC - 7FFFFFFFE36C     =       140 x
360
7FFFFFFFE48C - 7FFFFFFFE4AC     =       -20 y
361
7FFFFFFFE46C - 7FFFFFFFE48C     =       -20 z
362
```
363
364
### 3.自分の環境の結果と照らし合わせる。
365
4文字目が"c"というのは、すぐ解る。
366
同じ差が出ている文字でも、%5している開始アドレスが変わってくるので判別できる。
367
368
例えば、差が 20 だと、候補の文字が defgh のどれかなんだけど
369
370
```
371
d BFF3C454
372
e BFF3C434
373
f BFF3C414
374
g BFF3C3F4 
375
h BFF3C3D4 
376
```
377
378
で、判別できる
379
380
```
381
BFF3C3F4 - BFF3C394     =       60      PQRST S
382
BFF3C434 - BFF3C394     =       A0      stuvw t
383
BFF3C414 - BFF3C334     =       E0      ab    a
384
BFF3C3D4 - BFF3C454     =       -80     c     c
385
BFF3C414 - BFF3C354     =       C0      ijklm k
386
BFF3C414 - BFF3C314     =       100     UVW   
387
BFF3C3D4 - BFF3C3D4     =       0       nopqr r
388
BFF3C434 - BFF3C414     =       20      defgh e
389
BFF3C3F4 - BFF3C354     =       A0      stuvw v
390
BFF3C434 - BFF3C414     =       20      defgh e
391
BFF3C3D4 - BFF3C3D4     =       0       nopqr r
392
BFF3C454 - BFF3C3B4     =       A0      stuvw s
393
BFF3C454 - BFF3C394     =       C0      ijklm i
394
BFF3C454 - BFF3C454     =       0       nopqr n
395
BFF3C3F4 - BFF3C3D4     =       20      defgh g
396
BFF3C414 - BFF3C314     =       100     UVW
397
BFF3C3D4 - BFF3C3B4     =       20      defgh h
398
BFF3C414 - BFF3C334     =       E0      ab    a
399
BFF3C454 - BFF3C3B4     =       A0      stuvw s
400
BFF3C414 - BFF3C314     =       100     UVW
401
BFF3C454 - BFF3C434     =       20      defgh d
402
BFF3C434 - BFF3C434     =       0       nopqr o
403
BFF3C454 - BFF3C454     =       0       nopqr r
404
BFF3C434 - BFF3C414     =       20      defgh e
405
```
406
407
UVWって書いてあるところは、実は" "(スペース)だった。
408
409
```
410
TMCTF{Stack reversing has dore}
411
```
412
413
414
415
416
417
# Analysis-offensive 200
418
419
問題
420
421
Click button to get the flag!
422
423
ということで、apkのファイルが渡される
424
425
----
426
427
**c@t** さん writeup
428
429
APK-Multi-Toolでデコンパイル・コンパイル、BlueStacksで動作確認しました。
430
答えらしき文字列が分からなかったので、命令を書き換えてごり押し・・・。
431
432
以下はデコンパイル後にコードを修正した部分の差分パッチです。
433
前半の短い部分は、クリック回数をカウントしてる変数が1000万で無い場合に弾く処理を消しました。
434
後半の大量のif,iput,igetは、特定のクリック回数のときに何か処理をしているようなので、
435
クリック回数の変数にその特定の値を設定する処理を追加しました。
436
コンパイルしてアプリを起動し、何回かクリックするとクリアできます。
437
438
```
439
Only in VirusClicker.apk2: build
440
Only in VirusClicker.apk2: dist
441
diff -u -r VirusClicker.apk/smali/com/tm/ctf/clicker/activity/CongraturationsActivity.smali VirusClicker.apk2/smali/com/tm/ctf/clicker/activity/CongraturationsActivity.smali
442
--- VirusClicker.apk/smali/com/tm/ctf/clicker/activity/CongraturationsActivity.smali	2015-10-13 20:36:04.731482800 +0900
443
+++ VirusClicker.apk2/smali/com/tm/ctf/clicker/activity/CongraturationsActivity.smali	2015-09-26 16:32:45.496808200 +0900
444
@@ -147,11 +147,6 @@
445
 
446
     move-result v1
447
 
448
-    if-eq v0, v1, :cond_0
449
-
450
-    invoke-virtual {p0}, Lcom/tm/ctf/clicker/activity/CongraturationsActivity;->finish()V
451
-
452
-    :cond_0
453
     invoke-virtual {p0}, Lcom/tm/ctf/clicker/activity/CongraturationsActivity;->getIntent()Landroid/content/Intent;
454
 
455
     move-result-object v0
456
diff -u -r VirusClicker.apk/smali/com/tm/ctf/clicker/activity/c.smali VirusClicker.apk2/smali/com/tm/ctf/clicker/activity/c.smali
457
--- VirusClicker.apk/smali/com/tm/ctf/clicker/activity/c.smali	2015-10-13 20:36:04.762484500 +0900
458
+++ VirusClicker.apk2/smali/com/tm/ctf/clicker/activity/c.smali	2015-09-26 16:32:06.931541900 +0900
459
@@ -782,42 +782,91 @@
460
 
461
     iget v1, p0, Lcom/tm/ctf/clicker/activity/c;->g:I
462
 
463
+    if-lt v0, v1, :mycond_1
464
+
465
+    iput v0, p0, Lcom/tm/ctf/clicker/activity/c;->g:I
466
+
467
+    iget v1, p0, Lcom/tm/ctf/clicker/activity/c;->g:I
468
+
469
+    :mycond_1
470
     if-eq v0, v1, :cond_1
471
 
472
     const/16 v0, 0x2717
473
 
474
     iget v1, p0, Lcom/tm/ctf/clicker/activity/c;->g:I
475
 
476
+    if-lt v0, v1, :mycond_2
477
+
478
+    iput v0, p0, Lcom/tm/ctf/clicker/activity/c;->g:I
479
+
480
+    iget v1, p0, Lcom/tm/ctf/clicker/activity/c;->g:I
481
+
482
+    :mycond_2
483
     if-eq v0, v1, :cond_1
484
 
485
     const v0, 0xe767
486
 
487
     iget v1, p0, Lcom/tm/ctf/clicker/activity/c;->g:I
488
 
489
+    if-lt v0, v1, :mycond_3
490
+
491
+    iput v0, p0, Lcom/tm/ctf/clicker/activity/c;->g:I
492
+
493
+    iget v1, p0, Lcom/tm/ctf/clicker/activity/c;->g:I
494
+
495
+    :mycond_3
496
     if-eq v0, v1, :cond_1
497
 
498
     const v0, 0x186a3
499
 
500
     iget v1, p0, Lcom/tm/ctf/clicker/activity/c;->g:I
501
 
502
+    if-lt v0, v1, :mycond_4
503
+
504
+    iput v0, p0, Lcom/tm/ctf/clicker/activity/c;->g:I
505
+
506
+    iget v1, p0, Lcom/tm/ctf/clicker/activity/c;->g:I
507
+
508
+    :mycond_4
509
     if-eq v0, v1, :cond_1
510
 
511
     const v0, 0x78e75
512
 
513
     iget v1, p0, Lcom/tm/ctf/clicker/activity/c;->g:I
514
 
515
+    if-lt v0, v1, :mycond_5
516
+
517
+    iput v0, p0, Lcom/tm/ctf/clicker/activity/c;->g:I
518
+
519
+    iget v1, p0, Lcom/tm/ctf/clicker/activity/c;->g:I
520
+
521
+    :mycond_5
522
     if-eq v0, v1, :cond_1
523
 
524
     const v0, 0xf4243
525
 
526
     iget v1, p0, Lcom/tm/ctf/clicker/activity/c;->g:I
527
 
528
+    if-lt v0, v1, :mycond_6
529
+
530
+    iput v0, p0, Lcom/tm/ctf/clicker/activity/c;->g:I
531
+
532
+    iget v1, p0, Lcom/tm/ctf/clicker/activity/c;->g:I
533
+
534
+    :mycond_6
535
     if-eq v0, v1, :cond_1
536
 
537
     const v0, 0x98967f
538
 
539
     iget v1, p0, Lcom/tm/ctf/clicker/activity/c;->g:I
540
 
541
+    if-lt v0, v1, :mycond_7
542
+
543
+    iput v0, p0, Lcom/tm/ctf/clicker/activity/c;->g:I
544
+
545
+    iget v1, p0, Lcom/tm/ctf/clicker/activity/c;->g:I
546
+
547
+    :mycond_7
548
     if-ne v0, v1, :cond_2
549
 
550
     :cond_1
551
552
553
```
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
# Analysis-defensive 100
580
581
vonnというELFファイルが渡される。
582
583
```
584
$ file vonn
585
vonn: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=7f89c2bb36cc9d0882a4980a99d44a7674fb09e2, not stripped
586
```
587
588
実行すると
589
590
```
591
$./vonn
592
You are not on VMM
593
```
594
595
と表示して、自身のファイルを削除して終了する。
596
597
gdbでステップ実行すると
598
599
```
600
gdb-peda$ 
601
process 9182 is executing new program: /tmp/...,,,...,,
602
```
603
604
なんか、ファイルを作ってる。ここで、実行を止めて別コンソールで
605
606
```
607
$ cd /tmp
608
$ chmod ugo+x ./...,,,...,,
609
$ ./...,,,...,,
610
TMCTF{ce5d8bb4d5efe86d25098bec300d6954}
611
```
612
613
答え
614
615
```
616
TMCTF{ce5d8bb4d5efe86d25098bec300d6954}
617
```
618
619
620
621
622
623
# Cryptography 100
624
625
問題
626
627
```
628
Decrypt it by your missing private key!
629
630
kPmDFLk5b/torG53sThWwEeNm0AIpEQek0rVG3vCttc=
631
632
633
You have a strange public key.
634
Where is the lost 1bit?
635
Let's enjoy your prime factorization:)
636
```
637
638
ということで、共通鍵と思わしきファイルが渡される
639
640
----
641
642
**c@t**さん writeup
643
644
まず素数の積を表示。
645
646
```
647
$ openssl rsa -pubin -text < PublicKey.pem 
648
Public-Key: (256 bit)
649
Modulus:
650
    00:b6:2d:ce:9f:25:81:63:57:23:db:6b:18:8f:12:
651
    f0:46:9c:be:e0:cb:c5:da:cb:36:c3:6e:0c:96:b6:
652
    ea:7b:fc
653
Exponent: 65537 (0x10001)
654
writing RSA key
655
-----BEGIN PUBLIC KEY-----
656
MDwwDQYJKoZIhvcNAQEBBQADKwAwKAIhALYtzp8lgWNXI9trGI8S8EacvuDLxdrL
657
NsNuDJa26nv8AgMBAAE=
658
-----END PUBLIC KEY-----
659
660
$ openssl rsa -pubin -text < PublicKey.pem | egrep "^ " | sed -e 's/://g'
661
writing RSA key
662
    00b62dce9f2581635723db6b188f12
663
    f0469cbee0cbc5dacb36c36e0c96b6
664
    ea7bfc
665
666
0x00b62dce9f2581635723db6b188f12f0469cbee0cbc5dacb36c36e0c96b6ea7bfc
667
668
```
669
670
答えは最終bitが反転してたんだけど、そもそもこれ偶数じゃないか・・・。
671
先人のツールを拝借しつつ、素因数分解して復号化を実施。
672
673
```
674
$ cat msieve.00b62dce9f2581635723db6b188f12f0469cbee0cbc5dacb36c36e0c96b6ea7bfd.log 
675
(snip)
676
Sun Sep 27 00:34:35 2015  prp39 factor: 279125332373073513017147096164124452877
677
Sun Sep 27 00:34:35 2015  prp39 factor: 295214597363242917440342570226980714417
678
Sun Sep 27 00:34:35 2015  elapsed time 00:03:29
679
680
$ python genrsa.py 
681
-----BEGIN RSA PRIVATE KEY-----
682
MIGmAgEAAiC2Lc6fJYFjVyPbaxiPEvBGnL7gy8XayzbDbgyWtup7/QIDAQABAiBmFb0WyPl8JTRe
683
m+CjK8Wfmc4OQE8hvrvpfOO9bceNAQIQ0f2VZdriZPX9V5U9v7noDQIQ3hhDaCqytILM/1BvAs93
684
sQIQCdB3Rg5n3F4e3BQOkcJnlQIQP59HwBFrPBa0TvdltbJlIQIQcFyJhxTaQVkB9pJqKC2NOQ==
685
-----END RSA PRIVATE KEY-----
686
$ python genrsa.py > private.key
687
688
$ openssl enc -in crypted_message.txt -out binarytext -d -a
689
$ openssl rsautl -decrypt -in binarytext -out plain.txt -inkey private.key 
690
$ cat plain.txt 
691
TMCTF{$@!zbo4+qt9=5}
692
693
```
694
695
PCを無駄に頑張らせてしまいましたが、せっかく書いたので全bit反転させながら調べるプログラムも貼っておきます。
696
697
```ruby
698
#!/usr/bin/ruby
699
700
require 'yaml'
701
702
org_pubkey = "00b62dce9f2581635723db6b188f12f0469cbee0cbc5dacb36c36e0c96b6ea7bfc".scan(/.{1,2}/).map{|m| m.hex}
703
bitmask = [0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80]
704
705
org_pubkey.each_with_index do |c, i|
706
  bitmask.each do |mask|
707
    new_pubkey = org_pubkey.clone
708
    new_pubkey[i] = (new_pubkey[i] ^ mask)
709
    pubkey_str = new_pubkey.map{|m| sprintf("%02x", m)}.join
710
    puts "next pubkey: #{pubkey_str}"
711
    result = ""
712
    result_file = "msieve.#{pubkey_str}.log"
713
    if File.exists?(result_file)
714
      File.open(result_file) do |f|
715
        result = f.read
716
      end
717
    else
718
      result = `./msieve -e -v 0x#{pubkey_str}`
719
      `mv msieve.log #{result_file}`
720
    end
721
    result = result.encode("UTF-16BE", "UTF-8",
722
           invalid: :replace,
723
           undef: :replace,
724
           replace: '.').encode("UTF-8")
725
    if result.encode("UTF-8").split(/\n/).select{|line| line =~ /factor:/}.length == 2
726
      puts pubkey_str
727
      exit
728
    end
729
  end
730
end
731
732
```
733
734
735
736
737
# Cryptography 500
738
739
問題
740
741
アルファベットのみで構成された同じ長さの異なる2つの文字列を考えます。
742
その文字列をそれぞれBase64でエンコードした結果、同じ位置に同じ文字があれば抜きだすことにします。
743
744
抜きだした文字を順に左から並べたとき、できあがる文字列が「2015」「Japan」になるような入力列として、以下のようなものがあります。
745
746
例)
747
748
CaEkMbVnD→(Base64)→Q2FFa01iVm5E
749
GePoMjXNW→(Base64)→R2VQb01qWE5X
750
aBckjTiRgbpS→(Base64)→YUJja2pUaVJnYnBT
751
URehZQjLyvwk→(Base64)→VVJlaFpRakx5dndr
752
このとき、抜き出してできる文字列に上記のように「a」が現れることはありますが、「f」*が現れることはありません。
753
入力にどんなアルファベットを指定しても、抜きだしてできる文字列に現れることがないような文字の一覧を求め、
754
「ASCIIコード順」に並べたものを「SHA1」でハッシュ化した小文字の値で出力し、"TMCTF{<出力した値>}"として回答してください。
755
756
*訂正前の問題文は「A」となっていました。誤りなので訂正します。
757
758
----
759
760
**c@t**さん writeup
761
762
平文3文字 → 4文字にエンコードされるので、1~3文字の全ての組み合わせのエンコードを実施。
763
エンコード後の文字の位置毎に出現回数を数えて、一度も現れない文字を探します。
764
答えは、67569763552b4e9b8ac950be0d25f446f8470c60
765
766
```
767
$ ruby listup.rb
768
(snip)
769
+/7f
770
67569763552b4e9b8ac950be0d25f446f8470c60
771
```
772
773
listup.rb
774
775
```ruby
776
#!/usr/bin/ruby
777
778
require 'base64'
779
require 'digest/sha1'
780
require 'yaml'
781
782
#puts Base64.encode64("CaEkMbVnD")
783
#exit
784
785
chars = [*("A".."Z"),*("a".."z")]
786
787
words = []
788
result = []
789
chars.each do |a|
790
  encoded_str = Base64.encode64("#{a}")
791
  encoded_str.split(//).each_with_index do |c, i|
792
    result[i] = {} unless result[i]
793
    result[i][c] = 0 unless result[i][c]
794
    result[i][c] += 1
795
  end
796
end
797
chars.repeated_permutation(2) do |a, b|
798
  encoded_str = Base64.encode64("#{a}#{b}")
799
  encoded_str.split(//).each_with_index do |c, i|
800
    result[i] = {} unless result[i]
801
    result[i][c] = 0 unless result[i][c]
802
    result[i][c] += 1
803
  end
804
end
805
chars.repeated_permutation(3) do |a, b, c|
806
  encoded_str = Base64.encode64("#{a}#{b}#{c}")
807
  encoded_str.split(//).each_with_index do |c, i|
808
    result[i] = {} unless result[i]
809
    result[i][c] = 0 unless result[i][c]
810
    result[i][c] += 1
811
  end
812
end
813
puts result.to_yaml
814
815
strings = ["+","/",*("0".."9"),"=",*("A".."Z"),*("a".."z")]
816
#strings = [*("0".."9"),*("A".."Z"),*("a".."z")]
817
counter = {}
818
result.each do |v|
819
  v.each do |c, n|
820
    counter[c] = 0 unless counter[c]
821
    counter[c] += 1
822
  end
823
end
824
answer = ""
825
strings.each do |c|
826
  next if counter[c]
827
  answer += c
828
end
829
puts answer
830
puts Digest::SHA1.hexdigest(answer)
831
832
```
833
834
835
836
# Programming 100
837
838
**c@t** さん writeup
839
840
URLを開くと色つきの正方形が沢山並んでいて、1つだけ違う色のマスをクリックするという問題。
841
マスがどんどん小さくなっていくので、自力では流石に無理ですね。
842
1ピクセルずつ色を数えて、(白を除く)一番少なかった色の座標をクリックするプログラムを書きました。
843
844
最後まで行くと答えが表示されます。エラーはご愛嬌ということで・・・。
845
846
```
847
$ruby prog100.rb
848
(snip)
849
Congraturations!!
850
The flag is TMCTF{U must have R0807 3Y3s!}
851
prog100.rb:54:in `rescue in <main>': undefined method `body' for #<Mechanize:0x000000024bfe90> (NoMethodError)
852
	from prog100.rb:40:in `<main>'
853
```
854
855
prog100.rb
856
857
````ruby
858
#!/usr/bin/ruby
859
860
require 'mechanize'
861
require 'rmagick'
862
863
url = "http://ctfquest.trendmicro.co.jp:43210/click_on_the_different_color"
864
865
def get_most_shine_point(filename)
866
  image = Magick::Image.read(filename).first
867
  
868
  color_counter = {}
869
  image.each_pixel do |p|
870
    color = (p.red & 0xFF) + (p.green & 0xFF) * 0x100 + (p.blue & 0xFF) * 0x10000
871
    color_counter[color] = 0 unless color_counter[color]
872
    color_counter[color] += 1
873
  end
874
  
875
  puts image.columns
876
  puts color_counter
877
  mincolor = color_counter.sort{|(k1,v1),(k2,v2)| k2 <=> k1}[1][0]
878
  puts mincolor
879
  
880
  n = 0
881
  image.each_pixel do |p|
882
    color = (p.red & 0xFF) + (p.green & 0xFF) * 0x100 + (p.blue & 0xFF) * 0x10000
883
    if color == mincolor
884
      return [n % image.columns, n / image.columns]
885
    end
886
    n += 1
887
  end
888
  puts "Error..."
889
  return [0, 0]
890
end
891
892
#filename = "d5f4a52aad72c56ab771213407319384c5bdd38b43eb.png"
893
#puts get_most_shine_point(filename)
894
895
agent = Mechanize.new
896
897
begin
898
  loop do
899
    puts url
900
    page = agent.get(url)
901
    puts page.body
902
    image_url = page.at("img")[:src]
903
    puts image_url
904
    filename = File.basename(image_url)
905
    agent.download(image_url, filename) unless File.exists?(filename)
906
    
907
    (x, y) = get_most_shine_point(filename)
908
    url = "/#{File.basename(filename, '.png')}?x=#{x}&y=#{y}"
909
  end
910
rescue => e
911
  puts agent.body
912
end
913
914
```
915
916
917
918
919
920
921
# Programming 200
922
923
```
924
Calculate it.
925
nc ctfquest.trendmicro.co.jp 51740
926
```
927
928
----
929
930
みんな大好き計算問題。
931
見せてやるぜ!オレのシェル芸を!と、思ってやり始めたら撃沈した。
932
933
### 3種類の数値表現
934
935
3種類の数値表現が出てくる。
936
937
**数字**
938
939
```
940
62 * 9,324 + 1,859 * ( 300 - 7 ) + 2,921 - 37 * ( 58,555,427 + 4,982,941,063 ) - 59 + 5,528,776,321 =
941
```
942
943
**ローマ数字**
944
945
```
946
( CDXXVIII + 344131 ) * ( DCCXCVII + 8620480 ) * 154274 + 4757 - DXXXII + 790147 * ( IX + CMI ) * II = 
947
```
948
949
**英語**
950
951
```
952
206,441 * six million, one hundred thirty two thousand, eight hundred thirty nine + ninety - three hundred seventy + five million, eight hundred eighty nine thousand, seven hundred seven * ( 658 + eighty nine ) - 976 =
953
```
954
955
英語で死んだ。パーサー書くのめんどくさくなって、英語を逐次google翻訳して数値を得ようとして失敗した。「はい今死んだ!今君のシェル芸死んだよ!」 みたいな気分になった。
956
957
### ローマ数字と英語の対応
958
959
ローマ数字は、辞書を作って対応。[ローマ数字(1から3999まで)の一覧](http://kw-note.com/notation/roman-numerals/)を参照しました。
960
961
dic.txt
962
963
```
964
0	ZERO	zero
965
1       I       One
966
2       II      Two
967
3       III     Three
968
4       IV      Four
969
5       V       Five
970
971
972
973
3999    MMMCMXCIX       Three Thousand Nine Hundred Ninety Nine
974
```
975
976
英語は結局、perlの[Lingua::EN::Words2Nums](http://search.cpan.org/~joey/Lingua-EN-Words2Nums-0.18/Words2Nums.pm)という英語を数値に変換してくれるライブラリを見つけて対応した。
977
978
### 以下、適当solver
979
980
test.sh
981
982
```
983
#!/bin/sh
984
985
transrate(){
986
  W=`echo $1`
987
  W2=`perl solv.pl "$W"`
988
  echo $W2
989
}
990
991
exec 5<>/dev/tcp/ctfquest.trendmicro.co.jp/51740
992
993
for I in {1..1001}
994
do
995
  cat 0<&5>test.txt &
996
  cat test.txt >> log.txt
997
  sleep 2
998
  pkill cat
999
1000
  LIST=`cat test.txt|tail -1|tr '*' 'Z'`
1001
  WORD=""
1002
  FULL_NUM=""
1003
  PARTS_NUM=""
1004
  for WORK in ${LIST}
1005
  do
1006
1007
  if echo ${WORK} |grep [A-Y] >/dev/null
1008
  then
1009
    #-------------------------------------------------------------
1010
    # ローマ数字変換
1011
    #-------------------------------------------------------------
1012
    ROMAN_WORK=`grep $'\t'"${WORK}"$'\t' dic.txt|awk '{print $1}'`
1013
    WORD="${WORD}${ROMAN_WORK}"
1014
  elif echo ${WORK} |grep [a-z] >/dev/null
1015
  then
1016
    #-------------------------------------------------------------
1017
    # 英数字変換
1018
    #-------------------------------------------------------------
1019
    FULL_NUM="${FULL_NUM}${WORK}"
1020
  else
1021
    #-------------------------------------------------------------
1022
    # それ意外
1023
    #-------------------------------------------------------------
1024
    A_NUM=`transrate "${FULL_NUM}"`
1025
    WORD="${WORD}${A_NUM}${WORK}"
1026
    A_NUM=""
1027
    FULL_NUM=""
1028
  fi
1029
1030
  done
1031
  WORD=`echo ${WORD}|sed 's/Z/*/g'|tr -d '=,'`
1032
1033
  echo ==============================================================
1034
  cat test.txt
1035
  echo
1036
  echo "${WORD}"
1037
1038
  ANSWER=`echo ${WORD}|bc`
1039
1040
  echo ${ANSWER} >&5
1041
  echo Debug [${I}] ${WORD} '=' ${ANSWER}
1042
done
1043
1044
exit 0
1045
```
1046
1047
solv.pl
1048
1049
```perl
1050
#!/bin/perl
1051
1052
use Lingua::EN::Words2Nums;
1053
1054
$num=words2nums($ARGV[0]);
1055
print $num
1056
```
1057
1058
最後、こんな感じでフラグが出る。
1059
答え
1060
1061
```
1062
Congratulations!
1063
The flag is TMCTF{U D1D 17!}
1064
```
1065
1066
1067
1068
1069
1070
1071
1072
# Programming 300
1073
1074
**リリりん♪**さん writeup
1075
1076
まず、PCにプログラミング環境入れずに開始時刻を迎えてしまったので
1077
手っ取り早くプログラミングできる環境をそろえるために
1078
Microsoft Visual Studioをダウンロード&インストール。意外と重かった。
1079
コンパイラはC++のものですが、C言語でプログラミングしました。
1080
1081
迷路の単純な解法は、右手か左手を壁につけたまま移動する、という方法もあるのですが、
1082
この方法だけでは解けない迷路も存在するので、「上下左右に移動する経路を全探索する」というアプローチをとりました。
1083
1084
通常の迷路では、一度通った経路を通らないので、後戻りすると即死する方法がいいのですが、
1085
今回は「チェックポイントを通過する」「エナジードリンク(以下E缶と表記)」ということから後戻り禁止にもできません。
1086
かといって、まともに探索するといくら時間があっても足りなさそうです。
1087
1088
そこで、「最後にチェックポイントかE缶を取ったあとに」通った経路を通ってはいけない、という制約を付けました。
1089
あとは仕様通りプログラミングして、コスト(同じ経路を2回目以降に通った場合に+1、ゴール時残したE缶1つにつき+20)
1090
を計算して最適なルートを通るようにしました。
1091
1092
ちなみに、[いしのなかにいる](http://rocketnews24.com/2012/08/13/240452/)とはWizardlyの言葉です。
1093
プログラムで迷路を解こうとするときは、石があっても突っ込むので数多くの勇者が石に吸い込まれていったことでしょう・・・。
1094
1095
```c
1096
#include "stdafx.h"
1097
#include <stdio.h>
1098
#include <stdlib.h>
1099
1100
#define POS(x,y) ((y)*(w)+(x))
1101
#define SIZE(w,h) ((w)*(h))
1102
#define MAX_DEPTH 150
1103
1104
struct node {
1105
	struct node * next[4]; //左上右下の順
1106
	char c;
1107
	int x, y;
1108
};
1109
1110
struct route {
1111
	char path[MAX_DEPTH+1];
1112
	int path_pos;
1113
	unsigned int cost;
1114
};
1115
1116
1117
struct node *maze;
1118
int w; //迷路の幅
1119
struct node *start = NULL;
1120
struct node *goal = NULL;
1121
struct node *ckpt[30] = {};
1122
int ckpt_num;
1123
FILE *fp_out, *fp_out2;
1124
int ekan_num;
1125
1126
struct path {
1127
	int life;
1128
	//現在の場所
1129
	int x, y;
1130
	//残りのチェックポイント数を調べます。
1131
	char ckpt_left;
1132
	//残りのE缶の数を調べます。
1133
	char ekan_left;
1134
	//対象の位置をすでに通ったかどうかを判定します。
1135
	char footprint[30][30];
1136
	//エネルギーを使ったら増える
1137
	char cost;
1138
};
1139
const char *delection = "LURD";
1140
1141
// -1:答なし 0,1,2,3,...:解ありの場合の最小コスト
1142
// route 
1143
// path ※値渡し
1144
struct route solve_maze_core(struct path path) {
1145
	struct route route; //Return用
1146
	struct route route_get[4]; //比較用
1147
	unsigned int min_cost = 0;
1148
	//指定歩数以下でたどり着けない場合はNG
1149
	if (path.life == 0) {
1150
//		fprintf(fp_out2, "life is 0\n");
1151
		route.cost = -1;
1152
		return route;
1153
	}
1154
	path.life--;
1155
	// *いしのなかにいる* 場合は即死
1156
	if (maze[POS(path.x, path.y)].c == '#') {
1157
//		fprintf(fp_out2, "in the wall\n");
1158
		route.cost = -1;
1159
		return route;
1160
	}
1161
	if (maze[POS(path.x, path.y)].c != '.') {
1162
		fprintf(fp_out2, "pos %d, %d road=%c cost=%d life=%d fp=%d ", path.x, path.y, maze[POS(path.x, path.y)].c, path.cost, path.life, path.footprint[path.x][path.y]);
1163
	}
1164
	//2回目以降にくる場合はコスト増加
1165
	if (path.footprint[path.x][path.y]) {
1166
		//CheckpointもEも通らずに再度来た場合はNG(枝刈り) 
1167
		if (path.footprint[path.x][path.y] == path.ckpt_left + path.ekan_left + 1) {
1168
			if (maze[POS(path.x, path.y)].c != '.') {
1169
				fprintf(fp_out2, "footprint error c=%d e=%d\n", path.ckpt_left, path.ekan_left);
1170
			}
1171
			route.cost = -1;
1172
			return route;
1173
		}
1174
		path.cost++;
1175
	}
1176
	else {
1177
		//最初にC,Eにくる場合はチェックポイント通過
1178
		if (maze[POS(path.x, path.y)].c == 'C') {
1179
			path.ckpt_left--;
1180
		}
1181
		//最初にEにくる場合はE回数増加
1182
		if (maze[POS(path.x, path.y)].c == 'E') {
1183
			path.ekan_left--;
1184
		}
1185
	}
1186
	path.footprint[path.x][path.y] = path.ckpt_left + path.ekan_left + 1;
1187
1188
	//チェックポイント全通過かつここがゴールならコストを報告. Eを残した場合はペナルティ加算。
1189
	if (path.ckpt_left == 0 && maze[POS(path.x, path.y)].c == 'G') {
1190
		fprintf(fp_out2, "Reached goal c=%d e=%d\n", path.ckpt_left , path.ekan_left);
1191
		route.path_pos = MAX_DEPTH;
1192
		route.path[MAX_DEPTH] = '\0';
1193
		route.cost = path.cost + path.ekan_left * 20;
1194
		return route;
1195
	}
1196
	if (maze[POS(path.x, path.y)].c != '.') {
1197
		fprintf(fp_out2, "normal phaze c=%d e=%d\n", path.ckpt_left, path.ekan_left);
1198
	}
1199
	//上下左右に移動して迷路を解く
1200
	path.x--;
1201
	route_get[0] = solve_maze_core(path);
1202
	min_cost = 0;
1203
	path.x++;
1204
1205
	path.y--;
1206
	route_get[1] = solve_maze_core(path);
1207
	if (route_get[min_cost].cost > route_get[1].cost) {
1208
		min_cost = 1;
1209
	}
1210
	path.y++;
1211
1212
	path.x++;
1213
	route_get[2] = solve_maze_core(path);
1214
	if (route_get[min_cost].cost > route_get[2].cost) {
1215
		min_cost = 2;
1216
	}
1217
	path.x--;
1218
1219
	path.y++;
1220
	route_get[3] = solve_maze_core(path);
1221
	if (route_get[min_cost].cost > route_get[3].cost) {
1222
		min_cost = 3;
1223
	}
1224
	path.y--;
1225
1226
1227
	route = route_get[min_cost];
1228
	if (route_get[min_cost].cost < 10000) {
1229
		route.path[--route.path_pos] = delection[min_cost];
1230
		fprintf(fp_out2, "pos %d, %d road=%c cost=%d life=%d route:%s\n", path.x, path.y, maze[POS(path.x, path.y)].c, path.cost, path.life, &route.path[route.path_pos]);
1231
	}
1232
	return route;
1233
}
1234
1235
//迷路を解く
1236
int solve_maze(void) {
1237
	struct path path = {};//0初期化
1238
	struct route route; //Return受け取り用
1239
	path.x = start->x;
1240
	path.y = start->y;
1241
	path.ckpt_left = ckpt_num;
1242
	path.ekan_left = ekan_num;
1243
	path.life = MAX_DEPTH;
1244
	route = solve_maze_core(path);
1245
	if (route.cost > 100000) {
1246
		return -1;
1247
	}
1248
1249
	fprintf(fp_out, "%s\n", &route.path[route.path_pos]);
1250
	fprintf(fp_out2, "ANSWER:%s\n", &route.path[route.path_pos]);
1251
	return 0;
1252
}
1253
1254
int main()
1255
{
1256
	FILE *fp;
1257
	int h, x, y;
1258
	int chk, pos;
1259
	char c;
1260
	struct node *node;
1261
	int solved;
1262
	int qid = 0;
1263
1264
        //問題ファイル、答え、デバッグ出力のファイルを開く
1265
	fopen_s(&fp, "d:\\maze.txt", "r");
1266
	fopen_s(&fp_out, "d:\\maze.solved.txt", "w");
1267
	fopen_s(&fp_out2, "d:\\maze.debug.txt", "w");
1268
1269
        //問題ファイルが開けないときだけエラー処理(他はエラーの場合は不正終了します)
1270
	if (fp == NULL) {
1271
		exit(0);
1272
	}
1273
1274
	do {
1275
		// 迷路の読み込み開始
1276
		pos = 0;
1277
		ckpt_num = 0;
1278
		ekan_num = 0;
1279
		fscanf_s(fp, "%d %d %d", &w, &h, &chk);
1280
		fprintf(fp_out2, "%d %d %d\n", w, h, chk);
1281
		printf("#%d %d %d %d\n", ++qid, w, h, chk);
1282
		maze = (struct node *)malloc(SIZE(w, h) * sizeof(struct node));
1283
		memset(maze, 0, SIZE(w, h) * sizeof(struct node));
1284
		while (EOF != (c = fgetc(fp))) {
1285
			switch (c) {
1286
			case '#':
1287
			case 'S':
1288
			case 'G':
1289
			case 'C':
1290
			case 'E':
1291
			case '.':
1292
				maze[pos++].c = c;
1293
				break;
1294
			}
1295
			if (pos == SIZE(w, h)) {
1296
				break;
1297
			}
1298
		}
1299
1300
		// 表示(デバッグ用)
1301
		for (y = 0; y < h; y++) {
1302
			for (x = 0; x < w; x++) {
1303
				fprintf(fp_out2, "%c", maze[POS(x, y)].c);
1304
			}
1305
			fprintf(fp_out2, "\n");
1306
		}
1307
1308
		// ノードを追加(ホントは要らなかった。文字のままでよかった…)
1309
		for (y = 0; y < h; y++) {
1310
			for (x = 0; x < w; x++) {
1311
				switch (maze[POS(x, y)].c) {
1312
				case '#':
1313
					continue;
1314
				case 'S':
1315
					start = &maze[POS(x, y)];
1316
					break;
1317
				case 'G':
1318
					goal = &maze[POS(x, y)];
1319
					break;
1320
				case 'C':
1321
					ckpt[ckpt_num++] = &maze[POS(x, y)];
1322
					break;
1323
				case 'E':
1324
					ekan_num++;
1325
				case '.':
1326
					break;
1327
				default:
1328
					continue;
1329
				}
1330
				maze[POS(x, y)].x = x;
1331
				maze[POS(x, y)].y = y;
1332
1333
				if (maze[POS(x - 1, y)].c != '#') {
1334
					maze[POS(x, y)].next[0] = &maze[POS(x - 1, y)];
1335
				}
1336
				if (maze[POS(x, y - 1)].c != '#') {
1337
					maze[POS(x, y)].next[1] = &maze[POS(x, y - 1)];
1338
				}
1339
				if (maze[POS(x + 1, y)].c != '#') {
1340
					maze[POS(x, y)].next[2] = &maze[POS(x + 1, y)];
1341
				}
1342
				if (maze[POS(x, y + 1)].c != '#') {
1343
					maze[POS(x, y)].next[3] = &maze[POS(x, y + 1)];
1344
				}
1345
			}
1346
		}
1347
1348
		//解法生成
1349
		solved = solve_maze();
1350
1351
		free(maze);
1352
1353
		//getchar();
1354
1355
	} while (solved == 0 && qid != 1001); //解けなかったか、1000問解いたら終了
1356
	//getchar();
1357
1358
	return 0;
1359
}
1360
```
1361
1362
# Programming 500
1363
1364
**リリりん♪**さん writeup
1365
1366
ブラックジャックで、特定の手のときにStandしたときと、Hitしたときの勝率を求める問題です。
1367
ブラックジャックは知っていたのですが、昨今ラスベガスで行われているルールとは若干違っています。
1368
1369
ブラックジャックは、正しくプレイした場合、掛け金の99%が戻ってくる程度の期待値と言われています。
1370
100%ではないのは、次のような理由からです。
1371
1372
* プレイヤーがBUSTした場合、ディーラーがBUSTしたとしてもプレイヤーの負け
1373
  このルールにより、プレイヤーは圧倒的に不利になります。これを緩和するために、以下のようなルールが設けられます。
1374
* ダブルダウン:プレイヤーは、最初のHitをするとき、その次以降Hitできる権利を放棄するかわりに掛け金を倍にすることができる。
1375
  ディーラーのカードが見えた状態(ディーラーがBUSTしやすいことが判明した状態)で掛け金を増やせるので、プレイヤー有利になります。
1376
1377
対して、このカジノは以下のような変更点が加えられています。
1378
1379
* プレイヤーとディーラーの手が同じ場合、BUSTでなくてもプレイヤーの負け
1380
* ダブルダウンなし
1381
1382
このように、プレイヤーに不利なルールを押し付けてくる超ブラックカジノですから、
1383
いますぐに退出し別のカジノでプレイするのが正しい ということは自明です。
1384
(そもそも期待値99%なので、そもそもブラックジャックをプレイしないのが正しいのですが…。)
1385
1386
さて、そうは言ってもCTFの問題ですから、確率を計算しなければなりません。
1387
このカジノでのブラックジャックは以下の手順で進められます。
1388
1. プレイヤー2枚、ディーラー2枚の手札が配られる。ディーラーは2枚のうち、1枚だけを表にする。
1389
2. プレイヤーが好きなだけ枚数を増やす。
1390
3. ディーラーがルールに従って枚数を増やす。
1391
4. プレイヤーの手とディーラーの手を比較し、強いほうが勝利。
1392
1393
さて、上記の順序でプログラミングしてみましょう。
1394
まず、カード2組104枚を使う、とのことでしたので、点数別にカード枚数を定義します。
1395
1396
~~~c
1397
	{ 0,8,8,8,8,8,8,8,8,8,32 },
1398
1399
~~~
1400
1401
0点は0枚、1点(A)は8枚・・・10点(10JQK)は32枚です。
1402
初期状態が与えられているので、見えているカードを減らします。
1403
1404
~~~c
1405
	//自分の手札2まい
1406
	node.card[1]--;
1407
	node.card[2]--;
1408
	node.myhand = 13;
1409
	node.myace = 1;
1410
	//ディーラの手札1まい
1411
	node.card[3]--;
1412
	node.dlhand = 3;
1413
	node.dlace = 0;
1414
1415
~~~
1416
1417
ちなみに、aceを別管理するのは、「22以上になっても10を引いて復活」という仕組みにしています。
1418
1419
まずは、プレイヤーがStandをした場合の確率を、calc_win_dealerという関数で実装。
1420
プレイヤーがHitした場合の確率を求めることを考慮し、あらゆる状態に対応した勝率を求める関数を作ります。
1421
1422
```c
1423
double calc_win_dealer(struct node node) {
1424
	int i;
1425
	double ret = 0.0;
1426
	struct node node2;
1427
1428
	//ディーラーのバースト判定
1429
	while (node.dlhand > 21 && node.dlace > 0) {
1430
		node.dlace--; node.dlhand -= 10;
1431
	}
1432
	if (node.dlhand > 21) {
1433
		return 1.0;
1434
	}
1435
1436
	//ディーラーがStandする場合
1437
	if (node.dlhand > 16) {
1438
		if (node.myhand > node.dlhand) {
1439
			return 1.0;
1440
		}
1441
		else if (node.myhand == node.dlhand) {
1442
			return 0.0;
1443
		}
1444
		else{
1445
			return 0.0;
1446
		}
1447
	}
1448
1449
	//ディーラーがHitする場合
1450
	for (i = 1; i < 11; i++) {
1451
		if (node.card[i] == 0) {
1452
			continue;
1453
		}
1454
1455
		//カードをドローする前の状態にする
1456
		node2 = node;
1457
		//カードをドロー
1458
		node2.card[i]--; node2.cardnum--;
1459
		switch (i) {
1460
		case 1:
1461
			node2.dlhand += 11; node2.dlace += 1;
1462
			break;
1463
		default:
1464
			node2.dlhand += i;
1465
			break;
1466
		}
1467
		ret += calc_win_dealer(node2) * (double)(node.card[i]) / (double)node.cardnum ;
1468
	}
1469
	return ret;
1470
}
1471
```
1472
1473
ディーラーがHitする場合は、(Hitした数字で勝つ確率) × (その数字を引く確率)の合計です。
1474
Hitした数字で勝つ確率は、再帰的に呼び出すことにしています。
1475
1476
では、次にHitした場合の勝率を求めてみましょう。
1477
1478
* BUSTした場合はディーラーの確率を求めるまでもなく負け
1479
* 現在の時点でStandした場合は、calc_win_dealerを参照して勝率を求める(ただし、初回だけは仕様上強制Hitする)
1480
* Hitする場合、勝てる確率は再帰的に呼び出して計算する
1481
** 効率化のため、さすがに19以上でHitするケースの方が勝率が上がるワケがないので19以下の場合だけHitを考慮する
1482
1483
という方針で実装しました。
1484
1485
```c
1486
double calc_win_player(struct node node, int nohit) {
1487
	int i,j;
1488
	double ret = 0.0;
1489
	struct node node2;
1490
	double stand = 0.0;
1491
1492
	//プレイヤーのバースト判定
1493
	while (node.myhand > 21 && node.myace > 0) {
1494
		node.myace--; node.myhand -= 10;
1495
	}
1496
	if (node.myhand > 21) {
1497
		for (j = 0; j < LIFE - node.life; j++)fputc(' ', fp_out2);
1498
		fprintf(fp_out2,"Burst\n");
1499
		return 0.0;
1500
	}
1501
1502
	//Standする場合の確率
1503
	if (nohit == 0) {
1504
		stand = calc_win_dealer(node);
1505
		for (j = 0; j < LIFE - node.life; j++)fputc(' ', fp_out2);
1506
		fprintf(fp_out2, "Now %d, ace %d\n", node.myhand, node.myace);
1507
	}
1508
1509
	//あまりにも低い確率でしか出ない場合はStandの確率を返す
1510
	if (node.life > 0 && node.myhand < 19) {
1511
		node.life--;
1512
		//Hitする場合
1513
		for (i = 1; i < 11; i++) {
1514
			if (node.card[i] == 0) {
1515
				continue;
1516
			}
1517
			//カードをドローする前の状態にする
1518
			node2 = node;
1519
			//カードをドロー
1520
			node2.card[i]--; node2.cardnum--;
1521
			switch (i) {
1522
			case 1:
1523
				node2.myhand += 11; node2.myace += 1;
1524
				break;
1525
			default:
1526
				node2.myhand += i;
1527
				break;
1528
			}
1529
			for (j = 0; j < LIFE - node.life; j++)fputc(' ', fp_out2);
1530
			fprintf(fp_out2, "Hit %d (%d/%d) now %d, ace %d\n", i, node.card[i], node.cardnum, node.myhand, node.myace);
1531
			ret += calc_win_player(node2, 0) * (double)(node.card[i]) / (double)node.cardnum;
1532
		}
1533
	}
1534
	for (j = 0; j < LIFE - node.life; j++)fputc(' ', fp_out2);
1535
	fprintf(fp_out2,"stand %10.7f, Hit %10.7f\n", stand, ret);
1536
	if (ret < stand) return stand;
1537
	return ret;
1538
}
1539
```
1540
1541
これが中心部分です。全ソースは添付していますので、興味があればダウンロードして解析してみてください。
1542
1543
attachment:ConsoleApplication2.cpp
1544
1545
※上記ソースは、エラー処理等の考慮が不十分な点があります。これは、CTFという環境のもと、エラーが発生しないケースで1度正常に動作すれば良いというプログラムであったために無視しています。あらかじめご承知おきください。
1546
1547
1548
# Miscellaneous 200
1549
1550
**リリりん♪**さん writeup
1551
1552
じゃんけんに30連勝しろよ、という指令。
1553
ふつうに考えると1/(3の30乗)=0.0000000000004%くらいで発生するのですが、Trendmicroが出してくる手は「チートしない」「学習する」との記載がありましたので、どう学習データを作るか悩んでました。
1554
1555
そんなところに kanata さんから「1回のアクセスで5000勝負までできる」という情報をもらって、ちょっとひらめきました。
1556
「これって、4970回ランダムに入れて学習成果を一定にすれば、その後出してくる手はだいたい同じなんじゃない…?」
1557
1558
というわけで、Excelでランダム手を5000個生成。最近はRANDBETWEENが使えるからじゃんけんの手の生成も楽勝です。
1559
1560
それで4971手目から、Trendmicroが出してくる手に勝つ手に変更していきます。
1561
完全に固定というわけではなく、ランダムになる箇所もありましたが、おそらく学習を忘れてしまったところなので、その場合はリロードして勝つまでやっています。
1562
ちょっと地味な作業でしたが、何とか30連勝できました。
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
# Miscellaneous 400
1575
1576
**c@t** さん writeup
1577
1578
Chinese chess の対戦プログラム。
1579
こちらは相手の駒を取れない、対戦相手のAIは連続2手動かせる、とやりたい放題。
1580
1581
上手くアンパックできなかったので、とりあえず手番管理してるメモリでも探そうか。
1582
・・・と思ったが、見つからず。
1583
代わりに、よく分からないアドレスを発見。
1584
1585
* ウィンドウがフォーカスを失ったとき?に変化するアドレス (起動のたびに変わる)
1586
* 0x1458B350 のアドレスの値を書き換えると、微妙に操作するプレイヤーが変わる
1587
1588
な… 何を言っているのか わからねーと思うが
1589
おれも 何をされたのか わからなかった…
1590
1591
とりあえず、フォーカス変数?の値が 0x28 の時にアドレス 0x1458B350 の値を1に、
1592
それ以外で 0x1458B350 を 0 に書き換えてみると、以下のように状況が改善される。
1593
1594
* 自分の手番で駒を取れる
1595
* 相手の手番のうち1回は、こちらの駒を動かしてくれる
1596
1597
相手の手番でも駒が取れてしまうが、上手いこと頑張って
1598
一つも駒を取られずに最後の1枚をとることができれば、フラグが表示される。
1599
1600
TMCTF{W1NN1N9_TH3_1MP0551BL3_G4M3}