読者です 読者をやめる 読者になる 読者になる

バランスを取りたい

よくCTFの記事を書きます

SECCON 2016 Finals Writeup

事の発端(2月4日)

f:id:xrekkusu:20170205210158p:plain

それとなく書けという圧力を感じた。

総評

SECCON2016 CTF finals ranking table によれば24チーム中13位。1日目ラストでJeopardyを大量に倒したため一時は6位まで上がったが、2日目でAttack Pointが取れずズルズル下がってしまった。

これでも日本の学生チームでは1位だったので文部科学大臣賞や超豪華なスポンサー賞など、結構いい感じに表彰された。

サーバ4

gdbのリモートデバッグが外に露出しているので操作してエクスプロイトしろという問題。 記憶にないけどSECCON 2016のオンライン予選で出た問題の使い回しっぽい? 詳細は坂井さんのページにあります。問題バイナリも配布されています。

問題概要

問題サーバが4つ(ARM, H8, SH, V850)動いているのでそれらをエクスプロイトし、flag.txtに自チームのDefense flagを書き込めば良い。ARMサーバに関してはword.txtがあり、そこにAttack flagが置いてある。

Defense flagは4つの問題サーバのそれぞれで1 flagしか受け付けない上、受け付けるpayloadの長さは時間が経つほど5000, 3000, 2000, 1000, …, 4と短くなる(一定時間経つと回復する)ため、他のチームより遅く書き込まないとポイントにならない。

バイナリの推測

そもそも4つのサーバで何のアーキテクチャバイナリが動いているのかすらわからないため、リモートデバッグAPIを駆使してバイナリを引っこ抜く。

ステップ実行を行うコマンドはsなので、これとチェックサム(各バイトを足し合わせ、&0xFF)を合わせた+$s#73を送り、更にレジスタの内容を取得するgを使ってレジスタ内でIPらしき部分を発見する。今回は0x2000バイナリがマップされていた。

次に、メモリからデータを取得するコマンドm[addr],[size]を使い、m2000,200のようにしてバイナリを取得する。

最後に、バイナリをよく読み何のアーキテクチャかを推測する。1つ目のサーバに関しては4バイト単位で命令が並んでいたので適当にGoogleに投げたところARMだとわかった。他のバイナリも同様に推測することができる。

Exploit

GDBのリモートデバッグプロトコルには多くのコマンドがあるが今回使用できたのはc,s,m,g,Gくらいで、実質的にできることは実行・メモリリークレジスタリーク・レジスタの書き換えだけ。普段のエクスプロイトのように「スタックにファイル名を置いて〜」的なことは一切できない。

というわけで、メモリ上に任意の文字列を配置するためにはレジスタ→メモリ間の転送命令を使う必要がある。

ARMの場合はpush {r4, r5, lr}があったので3ワード(=12バイト)分一度に転送することができた。またバイナリ内にはopen-read-writeができる分のシステムコールgadgetがあり、そこにIPを飛ばすだけで普通にシステムコールが呼べるようになっていた。

Attack

Exploitで解説した要素を組み合わせると、open('word.txt', 0); read(3, 0x3000, 0x60);相当を呼ぶことができるのでメモリ上に乗ったフラグをmコマンドで読み出せば終わり。

Flag: SECCON{5f52d7fcb7fc824c}

Defense

Attackが解き終わった頃には他のチームが既にDefenseを押さえていた上、ARMのflag.txtはファイルの中身を空にされてしまいフラグを書き込んでもpayloadの長さでは完全に勝てない状態になっていた。他のサーバではまだチャンスがありそうだったが、誰もAttackを取れていないサーバ2の方に移ったため放置してしまった。このときは1日目でサーバ2は設定ミスにより解けない状態だったので大きなロス……。

サーバ5

これも過去のSECCONで出た問題とほぼ同じ。一部バイナリが変わっていて過去のwriteupを使いまわしても動かなかった。

全体のコードはここ

問題概要

UTF-16LEで書いたヘブライ語x86機械語として実行してくれる。

2日目はこのサーバは全チームDefenseを取ってくると予想し、1日目の夜に必死にヘブライ語の勉強をした。

ヘブライ語シェルコード

ヘブライ語で作れる主なx86命令は、

xchg eax, edi
add eax, 0xfbXXfbXX
add eax, 0x05XXfbXX
add eax, 0xfbXX05XX
add eax, 0x05XX05XX
xor eax, 0xXXfbXXfb
xor eax, 0xXX05XXfb
xor eax, 0xXXfbXX05
xor eax, 0xXX05XX05
stosb

くらいしかなく、しかもこれらの命令も100%ヘブライ語として通るわけではないので前後に無意味となるような命令を挟みつつstosbを使ってメモリ上にシェルコードを展開していく。stosbmov byte [edi], al; inc ediをやってくれる命令なのでなんと任意の値をメモリ上に書き込んでいくことができる。

方針

call eaxによりヘブライ語シェルコードの処理を呼び出すため、シェルコードが配置されているアドレスはeaxに格納されている。これにいくらかの値を足した位置にシェルコードを書き込むようにすれば自動的にシェルコードが実行される。

今回はz3を使ってadd eax, 0xAAAABBBBAAAA,BBBB0xfb20, 0xfb30, 0x05d0, 0x05e0のいずれかが入るような命令を組み合わせてeaxをオーバーフローさせつつ最終的にeaxに0xc00から0xf00の値を足し込めるような命令列を計算した。

次に、stagerとかを使って当日にコケたくなかったので、普通のシェルコードをヘブライ語へ変換して実行時に戻すという方法を取った。いくらヘブライ語が不自由とはいえ、下位4bitだけは0~Fのすべてが揃っていたので4bitについてヘブライ語1文字を割り当てれば簡単にデコードできる。

Attack

ここまでできていれば特に言うことはなくkeyword.txtを持ってくるだけ。

Flag: 無くした。無念

Defense

Defense用のflag.txtがどこにあるかわからず苦戦するが、httpでアクセス可能だったのでHTTPサーバの設定から頑張って探した。書き込み限定のattrがついていてコケたがとりあえず5分につき2~3ptを入手できるようになった。

どこのチーム(多分CyKor)だったかは忘れてしまったが、flag.txtに書き込んだ瞬間次の行に自チームのDefense flagを書き込んでくるスクリプトがいて恐ろしかった。