Special instruction challenge チュートリアル

(runme.xの解析)

■ runme.xの解析

サーバからダウンロードできる runme.x では,同様の特殊命令が利用されて パスワードのデコードが行われています.

特殊命令の仕様がわかれば,スクリプトなどでデコード結果を計算できます.
このようにしてパスワードが得られれば,そのパスワードを以下のようにして サーバにコミットして,防御ポイントが得られます.

$ echo "<パスワード> <チームフラグ>" | nc <サーバのアドレス> 9999
このためいかにrunme.xを解析してパスワードを復元するか, もしくはrunme.xを実行してパスワードを得るか, というのが競技になります.

まずは runme.x を解析してみましょう.

$ file runme.x
runme.x: ELF 32-bit LSB executable, Analog Devices Blackfin, version 1 (SYSV), statically linked, stripped
Blackfinの実行ファイルのようです.
最初のうちは,アーキテクチャにはBlackfinだけが選ばれるためです.

なのでこの runme.x を今までやったように頑張って解析して, 実行したときの結果がわかればいいわけです.

なお競技時のままだと runme.x はstripされていてデバッグ情報が 落ちていて,解析の敷居が高いです.
サーバの /home/user/exec-sim/make.sh の以下の部分を削除して, stripされないようにしておくといいかと思います.

        /home/user/tools/cross-gcc494/bin/${arch}-strip ${arch}.x

さて,このrunme.xですが,冒頭で説明した runme.x の実行環境があれば, 以下のようにして実行できます.
(これは特殊命令の対応がされた実行環境です.もちろん競技者はこの環境を持ちません)

$ /home/user/tools/cross-gcc494/bin/bfin-elf-run --environment user runme.x
PASSWORD:XXXXXXXXXXXXXXXX
$ 
このパスワードをサーバにコミットします.
$ echo "XXXXXXXXXXXXXXXX teamflag" | nc 127.0.0.1 9999
Valid password.
「Valid password」と言われれば成功です.
たまにrunme.xの更新中だと「Updating files... wait for seconds.」と返されます.
この場合には,しばらく待ってrunme.xを再度ダウンロードし,そちらを使って やりなおします.
また「Invalid password」と言われてしまった場合には,runme.xが古い状態ですので, 最新のrunme.xをダウンロードしてやりなおしてください.(5分おきに更新されます)

成功した場合,サーバのインフォメーションページからリンクされている flag.txtというページに,「teamflag」と掲載されるはずです.
競技では,これでチームフラグをコミットすれば,そのチームに防御ポイントが入ります.

もちろん競技者はこの実行環境を持ちませんが,runme.xからうまくパスワードが 取得できれば,こんなふうにして防御ポイントが取得できます.

チームフラグのコミットは短時間に繰り返しはできません.
またrunme.xは5分おきに更新されます. runme.xが更新されたら,新たなrunme.xを実行して最新のパスワードを取得します.
(最新のパスワードでないと,コミットできません)

最初のうちは runme.x の内容は,パスワードのデコード元のデータと 乱数命令の設定パラメータとシードが異なるだけで, プログラム自体に変化はありません.
実際には乱数命令によって得られた乱数を,デコード元のデータにxorで 掛け合わせているだけです.

なので,乱数アルゴリズムが推定できれば,静的解析によってパスワードを 取得することは,難しくはありません.

しかしそのようにしてパスワードの取得とチームフラグのコミットを 繰り返し行っていると,runme.xの内容が徐々に変わってきます.
具体的には,decrypt()の内容が微妙に変わってきます.
もっと詳しく言うと,乱数に 0x55 を xor してからデコードする場合が 出てきます.
runme.xを逆アセンブルして,確認してみてください.

ただし必ず0x55が掛け合わせてあるというわけではなく, そうでない,元と同じ場合もあります.
このため静的解析で0x55の対応ができなかった場合には,
「runme.xが運良く元と同じプログラムだった場合にはパスワードがわかるが, そうでない場合(0x55がxorされる場合)にはパスワードがわからない」
という状態になります.

つまり運によってパスワードがわかったりわからなかったりするように なるわけです. (もちろん0x55の対応ができれば,その場合にもパスワードは取得できます)

で,引続きチームフラグのコミットを続けると,今度はたまに そもそも bfin-elf-run では実行できないrunme.xが出てきます.

※ コミットが面倒だったら,サーバの /home/user/tmp/count.txt に コミットの成功数がカウントされているので,これを手動で増やしてください. count.txtの値に応じてrunme.xの変移が大きくなります. 詳しくは exec-sim の make.sh と,そこから呼び出されるスクリプトを参照
※ 標準ではrunme.xは5分で更新されます.速めたい場合には,/home/user/tmp/interval.txt を調整してください

アーキテクチャを調べてみます.

$ file runme.x
msp430-elf.x: ELF 32-bit LSB executable, TI msp430, version 1 (embedded), statically linked, not stripped
MSP430というアーキテクチャが出てきています.
アーキテクチャはBlackfinだけでなく他にも複数ありました.
つまりMSP430についても乱数命令の仕様を解析して, Blackfinと同様の対応をすればいいわけです.
そうすれば,runme.xがMSP430用だった場合にも,パスワードを取得して チームフラグをコミットできるようになるわけです.

その先は,runme.xが以下のように変化してきます.

こうなると最終的には,GDBサーバ側で実装されている乱数命令と 同等の実装を手もとでエミュレータに行い,そこでrunme.xを実行する 必要が出てきます.
これが,この競技の最終段階です.

■ おしまい