SECCON 2017国際決勝に参加した。

5種類のKoH形式の問題と、いくつかのJeopardy形式の問題が出題された。

問題参(KoH)の問題文:

The Raspberry Pi distributed to each team contains the Bingo-Service program
Please log-in your Raspberry Pi ( ssh: 192.168.4.254, user/pass = pi/kenjiinwonderland )
Please set up Bingo-Service on 192.168.4.254 : 80
If you click “Measure” button above, the system will accesses your Bingo-Service in a few minutes later
“Measure” button can be clicked once every 7 minutes (Once you click it, you have to wait for 7 minutes)
Even if you do not click “Measure” button, the system may occasionally access your Bingo-Service
Response time of your service is measured
The hash of the team with the shortest response time is automatically written to flag.cgi
In this challenge, players do not need to write “Team Hash” on a regular basis
You will get a keyword for an attack point, if “Response Time” of your service is less than 20.0
checker.py in Raspberry Pi, is different from we used for accessing your service

要約すると

  • 各チームに配られたRaspberry Piでサービスが動いている
  • 計測ボタンを押すとそのチームのサービスのレスポンスタイムが上書きされる
  • 計測は7分に一度できる
  • 5分毎に得られるdefence pointは、その時点で一番良い記録を出しているチームが得る

ISUCONだった。

Raspberry Piからサービスのデータを引き出したところ、bingo.cgiというビンゴカードを生成してビンゴに穴を空けていくサービスが動いている。
checker.pyという動作チェック用のスクリプトもあったのだけど、実際の動作チェックに使われているものとは異なるらしい…

ビンゴサービスには2つの機能がある。

初期化: /cgi-bin/bingo.cgi?num=[number of cards]
ビンゴカードをnum個生成(ビンゴ画像を作る)
ビンゴ更新: /cgi-bin/bingo.cgi?uid=[uid]&call=[called number]
ビンゴのcall番を開ける(ビンゴ画像を更新)

1日目 12時

まずはRaspberry Piを除外して自分のノートパソコンでサービスを動かし、計測。
(つまり、めっちゃいいマシン持っていれば有利になる)
以下のHTTPリクエストが来ることが分かる。

  • 初期化: /cgi-bin/bingo.cgi?num=100 * 1 req
  • 確認: /pics/[uid]/[imageid]/305.jpg * 100 req
  • ビンゴ更新: /cgi-bin/bingo.cgi?uid=[uid]&call=[called number] * 5 req
  • 確認: /pics/[uid]/[imageid]/305.jpg * 5 req

とりあえず全てのリクエストに対するレスポンスを、計測前に用意したものを返すだけで通った。

結果、3.08711481094秒で1位。

1日目 13時

何度か計測すればもっといいスコアが出るかと重い、再計測したところfail。
なんで?

その後、再計測を続けたがfailしたため、プログラムをちゃんと高速化する方針に変更。

1日目 16時

Jeopardyで出題された問題のサーバからであれば、他チームのbingoサーバにアクセスできることが判明。

この時点で1位のチームのサーバにアクセスしたところ、ビンゴ更新時には、既にcallされた数字のデータだけがチェックされていると判明。

ビンゴ更新で画像を生成しないようにすることで1.49秒になったものの、他チームのスコアに勝てず、1日目終了。

2日目 10時

スコアがリセットされ、チェッカーの挙動が変わり、動かなくなる。
ついでにリクエストの数も増える。

  • 初期化: /cgi-bin/bingo.cgi?num=200 * 1 req
  • 確認: /pics/[uid]/[imageid]/305.jpg * 200 req
  • ビンゴ更新: /cgi-bin/bingo.cgi?uid=[uid]&call=[called number] * 25 req
  • 確認: /pics/[uid]/[imageid]/305.jpg * 25 req

ビンゴの更新ごとに画像を正しく生成して返す必要がある。
回避策をいろいろ試してみるがfailしまくる。

2日目 13時

生成した画像をファイルに書き出さずにメモリに持つことにする。
レスポンスを圧縮する。

2日目 15時

30文字あるimageiduidを短くする。

ビンゴの更新後の画像の確認リクエスト時に、オンデマンドに画像を生成するようにする。

https://gist.github.com/tyage/a520c7c6e3f8ee765c75280f2c417f6b

結果、2.96392202377秒 を記録するが、1位になるには0.8秒足りない。(マシンスペックの差もありそう…)

2日目 16時

全てのアプリケーションを落とし、配布された扇子で仰いでいたら 2.76775598526秒 を記録するが、微妙に勝てない。

感想

7分に一度しか計測できない縛りが地味に苦しかった。

初めになぜか通った計測結果をそのまま保持していれば、ポイントをかなり獲得できた気がするので反省。

もっといいマシンを使っていれば勝てたのかな〜というのが気になる。