2013/11/17

SECCON 東北大会に参加した


もう1週間経ってしまいましたが、先週は SECCON 東北大会に参加しました。

SECCON といえば、去年は ifconfig というチームでつくば大会に参加して優勝し、全国大会はあえなく撃沈と言った感じでした。

今年は、福島県会津地方で行われた、東北大会に参加しました。宿泊込の、セキュリティトライアスロンです。普通の CTF とは違って、とても面白かったです。ちなみに、上の写真は、猪苗代湖上で遊覧船かめ丸の上で行われた、早押しクイズ大会開催前の様子です...

僕は、開催前日になって熱がでて大変だったのですが、当日は熱も下がったのでアンチウイルスを施して参加しました。あまり体調は万全ではありませんでしたが、いつもの通りエナジードリンク漬けで頑張りました。

結果的には、まあ優勝というわけだったのですが、僕自身よりチーム全員がそれぞれ取り組んだポイントを確実に取っていったところが、結果につながったと思います。とはいえ、いつもどおりくりすの貢献が大きかった気がする。
初めて、アセンブラかるたとバイナリかるたにも挑戦しました。アセンブラかるたはよかったですが、バイナリかるたは僕には無理ゲーでした。


左から、うちのチームのエナジーじドンク消費具合。あくまでセキュリティトライアスロン中のはなしであって、就寝後に追加投入したり、帰宅時に車で参加したので寝ないために追加投入したり、こんなのまだまだ。

中央は、会場の会津藩校日新館からの眺め。会津の盆地に霧がかかっていて幻想的でした。ちなみに、これは朝食の時。ハッカソン2日目が 06:00 - 08:00 でした。

右が、早押しクイズ大会と表彰式が行われた、猪苗代湖。2日目は天候が悪く、紅葉の季節なのに霧がでてあまり綺麗な感じではありませんでしたが、綺麗だったとしても体力が限界を迎える頃で楽しめなかっただろうし、天候なんて関係ないです。クイズ大会は、すごく盛り上がりました。

アセンブラクロスワードパズル write-up

さて、問題の write-up も少しぐらい書かないと怒られそうなので、アセンブラクロスワードパズルだけ、ほぼ僕が解いた問題なので、書いておきましょう。

問題はこんなかんじです。(回答が含まれているので注意)

みんな大好き x86 のアセンブラです。僕は決してそんなことないですが。基本的には、埋めていけば埋まるのですが、いやらしい感じでクロスワードパズルになっているので、単純にはうまく行きません。

同時に x86 チートシートも配られましたが、あんなもの見ていては解けません。少なくとも、左半分は無理です。

僕は、nasm と ndisasm を駆使しながら、命令表を眺めて手動でバイナリを組み立てていきました。nasm を使う理由は、ELF ではない生のバイナリを直接吐き出せるのと Intel syntax からです。バイナリエディタでの書き換えが楽になります。as でもできますが、いろいろ面倒です。

nasm でアセンブルするときは、bits 32 をファイルに指定してアセンブルします。(指定しないと変なプレフィックスが付きます) ndisasm は -b32 を使うと間違いないでしょう。生バイナリなので、弄りたいときはそのまま ghex などで書き換えやすいです。ちなみに、nasm の吐いた結果を objdump でみたい場合には、objdump -D -m i386 -b binary でいけます。検証用にどうぞ。

命令表はこのサイトが非常に役に立ちます。
http://ref.x86asm.net/coder32.html

手の付け方ですが、わかりやすいところから手を付けると罠に陥るという感じです。ヨコ2の、4byte NOP から手を付けると、間違いなくアウトです。

"x86 4byte nop" とか "x86 multibyte nop" とか検索すると、0f 1f 40 00 が出てくるのが普通だと思いますが、そうなるとタテ2の EAX を3倍にが、うまくいかなくなり手詰まりになります。0f は拡張命令群のプレフィックスなので、その時点で 3byte に収めるのは不可能だと思っていいでしょう、魔法のような命令がない限り。

なので、タテ1とタテ2から埋めてくのが良いです。タテ1は、直後のラベルに call して pop することで、EIP を取得する定番のやつです。
    call dummy
dummy:
    pop  eax

こういう感じです。ndisasm にかけるとこうなります。
00000000  E800000000        call dword 0x5
00000005  58                pop eax

これができると、ヨコ5も自動的に埋まります。戻り値は eax にぶち込むので、即値を使って 1 を push して、eax に pop すればよいのです。
00000000  6A01              push byte +0x1
00000002  58                pop eax

ヨコ4 も簡単です。ただのメモリからの load だと思いますが、ここ解けなくても別に問題ないです。
00000000  8B00              mov eax,[eax]

次に、タテ2を探しましょう。いろいろ検索していると、アドレッシングの説明で lea を使ってレジスタの内容が実質3倍になる、みたいな命令にたどり着けるかと思います。(Wikibooks の x86 Assenbly のページに乗っていた) または、x86 特有のアドレッシングを使えばできるかも知れない、と閃けば自力でも行けるかも知れません。
00000000  8D0440            lea eax,[eax+eax*2]

さて、ここまで来ると、ヨコ2の先頭バイトが 8d であることが実質決定します。つまり lea を使って nop を組めということです。ここまで来たら実は余裕で、lea eax, [eax] って、実質 nop ですよね?これに、x86 のアドレッシングでディスプレースメントをつけたりすればいけそうだということが、x86 に精通していればわかるのではないかと思います。しかし、普通アセンブラ書いても、思うような結果が出てこないのではないかと思いますので、ハンドアセンブルしましょう。

ちなみに、x86 のアドレッシングは、Mod R/M byte とか SIB byte とかそういうやつを駆使しなければいけないので、これのチートシートが必要です。それも、前述の命令表に含まれています。(http://ref.x86asm.net/coder32.html#modrm_byte_32)

これを元に、destination が eax で、source が [sib]+disp8 をまず探します。これは、これに続く SIB byte で指定したアドレッシングに、ディスプレースメントを追加できる、ということです。なので、まず 44 がでてきます。次に、SIB byte で [foo+ bar * n] の部分を選ぶわけですが、foo = eax, bar = none を選べば良いので、20 になります。あとは、ディスプレースメントを 00 で追加すれば完成です。
00000000  8D442000          lea eax,[eax+0x0]

こうなると、残りはタテ3とヨコ3です。指令を見てみると、タテ3が call で、ヨコ3が push っぽいです。先頭バイトを同じにしなければいけないので、普通の命令じゃないことがわかります。しかもよく見てみると、どちらもオペランドに R/M byte を指定するように思えます。ここで、call と push のオペランドに R/M を取る命令の opcode を探してみると、なんとどちらも ff から始まるではありませんか、これは来たわけです。

--- 余談
が、僕はここで詰まります。問題の日本語の意味をミスって、終わりのない迷宮に迷い込んでしまいました。"第一引数をスタックに詰む"、"引数の指す先を関数呼び出し"、引数はどのような場合の引数を指すのかという問題が...
  • ふつーのコンパイラで生成された関数で、ebp からのオフセットを使う場合
  • スタックフレームを作らず、渡されたまま esp からのオフセットを使う場合
(ちょっとわかりにくかったので訂正しました)
となるわけですが、まだこの時点でタテ2とかに自信がなかった (4byte NOP の件もあり) ので、泥沼に入って行きました。個人的には前者だと思っていたのです。タテ2の2byte目と、ヨコ3の最後のバイトが重なるわけですが、ebp からのオフセットであれば、第一引数へのディスプレースメントは 08 になるはずなのです。なのに 04 なのですね... ここで esp からのオフセットに気づけばよかったのですが、僕は気づきませんでした。

結局、 この間違いに気づいたのは、回答を何回かダメ元で提出した時に、ebp は使わないと言われてからです。謎が一気にひらめいた瞬間でした。
--- 余談おわり

[追記]
大事なことを忘れていました。ff から始まる2つの命令ですが、これらの Mod R/M Byte を構築する方法です。 call と push の R/M をオペランドに取る命令はこんな感じで表記されてることが多いです。
  • push: ff /6
  • call: ff /2
これは、Mod R/M Byte を組むときに、/ で記述されてる数字を使えということです。つまり Mod R/M Byte でどの命令かが判断されます。先ほど示した Mod R/M のチートシートにも "/digit (Opcode)" とかいてあるのがそれです。1オペランド命令なので、普通は Rd の指定になる部分が、opcode に割り当てられます。

あとは、push であれば /digit = 6, [sib]+disp8 の 74 と、SIB byte が esp, none の 24 を組みあわせ、04 のディスプレースメントをつければ完成です。
[追記おわり]

つまり、こんな感じになります。
00000000  FF742404          push dword [esp+0x4]
00000000  FF542404          call dword [esp+0x4]

終わりです。
  • タテ1: e8 00 00 00 00 58
  • タテ2: 8d 04 40
  • タテ3: ff 54 24 04
  • ヨコ2: 8d 44 20 00
  • ヨコ3: ff 74 24 04
  • ヨコ4: 8b 00
  • ヨコ5: 8a 01 58

この間違いに一晩悩んでました、悩んでる間に後輩に先越されました、わかった瞬間は割と絶望感漂ってました。賢者モードってやつです。まあ、こういうこともあります。ある答えに行き着くと、周りが見えなくなるの良くないですね... 人間アセンブラ楽しかったです。

全国大会は、更にガチ勢が集まるので、僕が太刀打ちできるような相手ではないかも知れませんが、なんとなく頑張ります。

0 件のコメント: