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 |  |
||
| 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} |