この記事は、CTF Advent Calendar 2021の4日目です。
CTFで出題されるreversingの問題のひとつに、独自VMと呼ばれる、独自に実装されたVirtual Machineの上で動くバイトコード(プログラム)の解析を行うものがあります。
この問題ではVM自体のバイナリを読み、どのような命令があるのか、どのような挙動なのかを把握するのはもちろん、最終的にはそのVM上で動くバイトコード自体を読み解く必要があり、かなり根気がいります。
VMのバイナリはいつも通りIDA Proで解析すればよいのですが、その後のバイトコードの解析にはもちろんIDA Proが使えず、簡易的なディスアセンブラを実装し、テキストエディタでメモを書きながら読んでいく必要があります。関数呼び出しや分岐があればあるほど読みにくくなっていき、解析にかなりの時間を費すことになります。
そこでIDA Proのプラグインを自分で書いて、独自VM問のバイトコードをIDA Proで読めるようにしてしまおうというのがこの記事での本題です。
IDA Proのprocessor moduleを書く
IDA Proではprocessor moduleを書くことで独自のアーキテクチャを定義し読み込むことができます。
IDA Pro本体に同梱されているprocessor moduleのほとんどはC++で実装されていますが、CTFで対象となるのは小規模なバイナリなので書きやすさを優先して今回はPythonで実装しました。 独自のprocessor moduleを書く際のテンプレートや、Pythonで書かれたprocessor moduleもいくつか同梱されているので、それらを参考に書いていきます。
- IDA SDKの
module/script/proctemplate.py
を元に書いていくmodule/script/ebc.py
,module/script/msp430.py
がPythonで書かれたprocessor module- IDA SDKのドキュメントはこれ https://hex-rays.com/products/ida/support/sdkdoc/index.html
- idabinの
python/3
以下にidapythonのモジュールがあるので補完が効くようにIDEに読み込ませておく
- 最後にスクリプトをidabinの
procs
以下に置くことで、ファイル読み込み時にprocessor typeとして選択できる
次に過去に出題された独自VM問のために作ったprocessor moduleを紹介します。
例題1: baby-a-fallen-lap-ray - DEFCON 2021 Quals
よくわからないマシン(エミュレータ)上で動くVM上で動くバイトコードのreversingです。解析自体はかなりつらいです。
作ったprocessor module: https://github.com/akiym/ida-ctf-vm-chall-reversing/blob/0a499f8427eb09241f7c4b861314aa87c17e6230/procs/fallen-lap-ray.py
- 命令の定義
- ジャンプする命令は
CF_JUMP
をつけておき、ev_emu_insn
で判別に使う - 実際に使っているのは
CF_JUMP
とCF_STOP
だけなので、使っていないなら適当でも動くはず - https://github.com/akiym/ida-ctf-vm-chall-reversing/blob/0a499f8427eb09241f7c4b861314aa87c17e6230/procs/fallen-lap-ray.py#L70-L101
- ジャンプする命令は
ev_ana_insn
ev_
系の関数はIDA自体から呼ばれるハンドラのようなもので、これは実際にバイトコードを読んで対応する命令を定義していくところ- https://github.com/akiym/ida-ctf-vm-chall-reversing/blob/0a499f8427eb09241f7c4b861314aa87c17e6230/procs/fallen-lap-ray.py#L505-L598
- 人間が読みやすくしたいのでaliasに変換する
- スタックの操作はpush, popのような命令で読むことに慣れているのでそうしておく
- https://github.com/akiym/ida-ctf-vm-chall-reversing/blob/0a499f8427eb09241f7c4b861314aa87c17e6230/procs/fallen-lap-ray.py#L476-L503
ev_emu_insn
- 命令から対応する操作や意味を定義していくところ
- jmp命令で飛ぶ先やimm命令で読もうとしているデータへのcross referenceを追加して、まさにgraph viewで見えるような命令同士の繋がりを定義する
- https://github.com/akiym/ida-ctf-vm-chall-reversing/blob/0a499f8427eb09241f7c4b861314aa87c17e6230/procs/fallen-lap-ray.py#L361-L433
- スタックポインタのトレース
- push, pop命令や$sp自体への加算をトレースして、Options -> General -> Display dissassembly line parts (Stack pointer)から表示切り替えできるスタックポインタに反映させる
- https://github.com/akiym/ida-ctf-vm-chall-reversing/blob/0a499f8427eb09241f7c4b861314aa87c17e6230/procs/fallen-lap-ray.py#L328-L359
実のところ、IDA Proでバイトコードを読むというアイディアは以下のwriteupからいただきました(元記事ではBinary Ninjaを使っています)。ありがとうございます。
例題2: EmojiVM - HITCON CTF 2019
この問題はスタックマシン型のVMなのですが、自分の中で解析方法が定まっておらずprocessor moduleでスタックの状態をエミュレーションしながら、その結果をコメントに追記していく形にしました。あまりIDA Proでの解析の恩恵を得られなかった例です。
作ったprocessor module: https://github.com/akiym/ida-ctf-vm-chall-reversing/blob/0a499f8427eb09241f7c4b861314aa87c17e6230/procs/emojivm.py
まとめ
IDA Proを使って独自VM問のバイトコードの解析ができるようprocessor moduleを書いてみました。今後出題される問題でも今回書いたスクリプトを少し書き換えれば応用可能なので、また使う機会があるかもしれません。
実際のところ、IDA Proで読めたからといってそこで終わりではなく、ここからまた時間をかけて人間が読む作業は残っています。 2020 Plug-In Contest – Hex Rays にbfというbrainfuckをHex-Rays decompilerでデコンパイルするというプラグインがあったので、解析補助のためにデコンパイラを実装できなくはないのかもしれませんが、1つのVM問に対する実装量がかなり多くなるはずで、現実的には人間が読むほうが早いということになりそうではあります。
今後も問題を解く上で書いたprocessor moduleは以下のリポジトリに追加していく予定です。面白かった独自VM問の過去問がありましたらIDA Proで読もうと思いますので是非 @akiym まで教えてください。