SECCON CTF Quals 2014 Winter memo

First published Mon Dec 8 21:20:12 2014 +0900 ; substantive revision Sat Feb 14 12:41:39 2015 +0900

Tags :

このエントリーをはてなブックマークに追加

English version is coming soon.

SECCON 2014 オンライン予選(英語)32時間 CTF の1日目にチーム「0x0」で参加しました。最終順位は6位でした。SECCON CTFは日本からの参加者が多く熱が入るので楽しいです。運営の皆さんありがとうございました。チーム「0x0」は既に過去の予選で全国大会出場権を得ており、また自分自身は全国大会にはチーム「encrypti0x0n」で参加する予定です。ここでは問題をいくつか取り上げたいと思います。

forensics 300 Read it

エスパー問題との批判が気になったので書きます。所感としては、これはエスパーではないのですが、全然フォレンジックでもなかったですね…

問題

Genre Forensics
Points 300
Question text Readit

ファイルの鑑定

まずは何のファイルなのか調べる。

$ file Readit
Readit: (Corel/WP)

を元に少しググると Corel 社の Word Perfect 文書であることが分かる。騙されないようにバイナリも眺めておくと安心できる。

Word Perfect 文書を開く方法を調べると、LibreOffice (OpenOffice) が対応しているようだ。試してみると確かに対応しているのだが、パスワードを要求されて表示することができない。

ファイルの復号

他に手がかりもないので、復号を試みることにする。主に次の資料やツールが役に立った。

また、バイナリ中に現れる文字列 WPTVWPC2 でググると WordPerfect 3.5.2 の文書がいくつかヒットするのでサンプルとして集めておく。

libwpd のソースコードを読むと、以下のことが分かる。

暗号化のアルゴリズムは以下の通り。 libwpd の実装からも分かるが、上にあるように別バージョンでも同じなので資料Aの解説を参照するとよい。

  1. 暗号化する部分のバイト列をバイト列 [パスワード長+1, パスワード長+2, … 0xfe, 0xff, 0x00, … 0xff, …] で XOR
  2. パスワード長をブロック長として、暗号化する部分の各ブロックのバイト列をパスワードのバイト列で XOR

処理はすべて XOR なので復号も同じ操作になる。

さて、資料Aには十分な長さの既知平文がある場合(固定文字列を含む特定バージョンがある)の攻撃手法について書いてあるが、今回はそのバージョンでなく、代わりとして SECCON{ を予測しても厳しそうだ。

手がかりを求め、収集したサンプルのバイナリを眺めてみると、暗号化される部分に 0x00 が連続する箇所がいくつかある。0 ⊕ X ⊕ Y = X ⊕ Y なので、 0x00 を暗号化した1バイト [X ⊕ Y] を手順 1 の分 [X] だけ復号するとパスワードの 1 バイト [Y] が分かることになる [(X ⊕ Y) ⊕ X = Y] 。もし英単語のような「見てそれとわかる」パスワードなら、 0x00 が十分に連続していればこれを行なうことで「見てそれとわかる」可能性がある。

そこで、「暗号化前の部分にある程度連続して 0x00 が現れる」という期待の下で、バイト列の開始バイトを総当たりして手順 1 の分だけ復号する(何かに仕様上のパスワード長の最大制限も書いてあった気がするが、ここでは適当)。

import copy

mb = bytearray(open('Readit', 'rb').read())[0x1719:]

for s in xrange(1, 20):
  b = copy.deepcopy(mb)
  for i in xrange(len(b)):
    c = b[i]
    b[i] ^= (s+i) % 0x100
  open('Ri_%03d' % (s-1), 'wb').write(b)

ファイル名の数字は予想されるパスワード長である。出力されたファイルを眺めると Ri_010 に気になる部分がある。

0000000: 8846 4351 4f4e 3230 3134 553e 1413 327f  .FCQON2014U>..2.
0000010: 0230 3126 509e 9843 4f4c 32eb b1b4 0000  .01&P..COL2.....
0000020: 0000 0000 4900 5d50 0c08 2220 1079 0703  ....I.]P.." .y..
0000030: 4c  

弱いエスパーによって .FCQON2014 という文字列から SECCON2014 という 10 文字のパスワードが推測できる。 後は LibreOffice でファイルを表示してもいいし、 libwpd がインストールされていれば

$ wpd2text --password SECCON2014 Readit
SECCON{0ld_Mac_753}

自分で復号することもできる。

key = "SECCON2014"

b = bytearray(open('Ri_010', 'rb').read())

for i in xrange(len(b)):
  b[i] ^= ord(key[i%len(key)])

  open('Ri_decrypted', 'wb').write(b)

強いエスパーによる write up

ファッ!?

補足

ファイル冒頭からファイル全体に対して手順1を 0x00-0xff について総当たりすると、 s = 100, 174, 196, 238 の時に SECCON2014SECCON2014SECCON2014... という文字列が出てきた。libwpd は無視している部分なので知る由はないが、本物の Word Perfect 3.5e はこの辺にも何かやっているのかもしれない。