前回は、リバースエンジニアリングツールである Ghidra を使って、STM32 の ELFファイルを解析してみました。
今回も、書籍「リバースエンジニアリングツールGhidra実践ガイド (Compass Booksシリーズ)」を読みながら、さらに、STM32 の ELFファイルの解析を進めていきます。
それでは、やっていきます。
参考文献
はじめに
「セキュリティ」の記事一覧です。良かったら参考にしてください。
セキュリティの記事一覧
まず、書籍「リバースエンジニアリングツールGhidra実践ガイド (Compass Booksシリーズ)」のサポートサイトは以下です。
ここからサンプルプログラムがダウンロードできます。
book.mynavi.jp
今回は、前回に引き続き、STM32 の ELFファイルの解析を進めていきます。
Ghidraの設定
Ghidra の設定、というか、使いやすいようにカスタマイズする方法が、書籍「リバースエンジニアリングツールGhidra実践ガイド (Compass Booksシリーズ)」に書かれています。その中で、私が設定した内容を書いておきます。
ハイライトの設定
Edit → Tool Options を開きます。
Listing Fields の Cursor Text Hightlight を開きます。
対象とする文字列を、マウスの真ん中ボタンを押すと、それと同じ文字列がハイライトする機能です。真ん中ボタンだと使いにくいので、左クリックに変更します。
Mouse Button To Active を MIDDLE から LEFT に変更して、Apply をクリックします。
キーバインドの設定
Edit → Tool Options を開きます。
Key Bindings を開きます。
以下に、便利なキーバインドも書いておきます。
- Previous Location in History(ALT-LEFT):関数の中に入った後、元の位置に戻りたいときがありますが、そのときに使える機能です、Altキーを押しながら←と→で行き来できます、今のところ、キーバインドは変更してません
- Find References To(Ctrl-Shift-F):関数名や、アドレスなどにカーソルを置いた状態で、このキーバインドを押すと、そのアドレスを参照している箇所が一覧で表示されます。関数の先頭アドレスにカーソルを置いて、このキーバインドを押すと、関数の呼び出し元の一覧が表示される機能です、Ctrl+Shift+Fキーで起動できます。書籍で勧めているように、xキーに割り当てました
- Find References to Symbol(Ctrl-Shift-F):上のFind References Toと同じ機能で、デコンパイルウィンドウで同じ機能が使えるようにする機能です。デフォルトではキーが割り当てられていませんので、Find References Toと同じ、xキーを割り当てました
使っていくうちに、また必要なキーバインドがあれば、追記していきます。
Ghidraの機能
Function Call Graph
Window → Function Call Graph で起動します。
現在のカーソル位置にある関数を起点に Function Call Graph が起動します。
各関数をダブルクリックすると、その関数の中に入っていきます。
ちなみに、マウスのホイールを回すと、ズームイン、ズームアウトします。Ctrlキーを押しながらホイールを回すと、移動できます。
Function Graph
Window → Function Graph で起動します。
現在のカーソル位置にある関数の分岐が表示されます。
マウスのホイールの動きは、Function Call Graph と同じです。
矢印にマウスを置くと、その分岐のコードが表示されます。関数の構造が可視化されて、理解が進みます。
デバッグ情報の無いファイルを使う
これまで使ってきた、STM32 の通常の ELFファイルは、デバッグ情報を含んでいるので、ソースコードが無くても、それなりに解析が可能でした。
一方、デバッグ情報の無いファイルを試してみます。
以前(STM32(ARM Cortex-M)のELFファイル⇔バイナリの変換を行う - 土日の勉強ノート)、以下のコマンドで、ELFファイルをバイナリファイルに変換しました。
$ arm-none-eabi-objcopy -O binary stm32f4discovery_sample.elf stm32f4discovery_sample_objcopy.bin
このバイナリファイルを読み込ませてみます。
手順で異なるところだけ示します。
Import File... で、stm32f4discovery_sample_objcopy.bin を選択すると、Language を指定してください、と出ます。
STM32F4 は、Arm v7-M のはずです。ARM v7 32bit little endian は2つあります。Compiler が default と Visual Studio です。default を選んでおきます。
そういえば、前回は自動認識されましたが、v7 ではなく、v8 になっていました。
解析させて、Code Browser を開きます。
うーん、undefined がいっぱい出てます。命令が認識できていません。
前回にならって、v8 を選択してみます。ダメでした。v8-m や、v8T なども選んでみましたが、うまくいきません。
仕方ないので、以前(STM32(ARM Cortex-M)のELFファイル⇔バイナリの変換を行う - 土日の勉強ノート)、bin2elf.sh を使って、バイナリファイルから ELFファイルを再構築したもの(stm32f4discovery_sample_bin2elf_entry.elf)を使ってみます。
通常の ELFファイルと同様に、自動でファイルを認識してくれました。解析が完了すると、通常の ELFファイルと同じように認識しました。成功です。
デバッグ情報が無いと、プログラムの構造を理解するだけでも大変そうです。
今回はここまでにします。
使い方のメモ
ここからは、これどうやるのかな?という内容を個別にメモしておきます。
プログラムのエクスポート
Ghidra に表示されるデコンパイルされた C言語の内容をまとめてエクスポートすることが出来ます。
File → Export Program... を開き、Format で C/C++ を選択して、ファイルパスを指定して OK をクリックすると、1ファイルにデコンパイルした C/C++ のソースコードをエクスポートできます。
個別のウィンドウを別ウィンドウにした場合に元に戻す方法
これは、かなり困りました。こういう操作系の悩みってググっても出ないし、ChatGPT はそれらしいこと言ってくるだけで全然ダメだし。。というわけで、ここに書いておきます。
分かってしまえば簡単です。いったん、以下のように、フローティングウィンドウにしたとします。
これを、もとのメインウィンドウにドッキングさせる方法です。フローティングにしたウィンドウのタイトルバーをクリックしてドラッグするのではなく、下図のように、フローティングにしたウィンドウの中の Console のタイトルバー(下図の赤枠)をクリックして、メインウィンドウにドラッグすると元のようにドッキングした状態に戻せます。
気づかないときは、一生気づかない難問でした(笑)。
Symbol Tree
Ghidra をしばらく使っていると、この Symbol Tree のウィンドウはよく使うようになりました。
Exports
対象のバイナリに対して、最初は、main関数か、エントリポイントを探す場合が多いと思います。
strip されていなければ、Functions の main をクリックすれば、main関数が見つかります。しかし、strip されたバイナリの場合、main関数が見つからない場合があります。
そのときは、Symbol Tree の Exports を見ます。この中に、entry が入っているので、それをダブルクリックすると、main関数が表示されます。もしくは、__libc_start_main()
が表示された場合、__libc_start_main()
の第1引数の関数をダブルクリックすると、main関数が表示されます。
Exports は、外部公開しているシンボルのことだと思います。API関数を探す場合にも使えると思います。
Imports
小さいプログラムの場合は、main関数から順番に見ていけばいいのですが、規模が大きいプログラムの場合は、見たいところから見ていくという方法の方が効率がいいと思います。
そのときは、Imports の <EXTERNAL> を見ます。ここには、使用している外部ライブラリのシンボルが格納されています。
例えば、libc の strcpy関数があるかどうかを確認できますし、system関数があるか、なども確認できます。また、通信系のプログラムの場合、send、recv などが格納されていると思うので、そこからデータの入出力を見ていく、という方法が効率的だったりすると思います。
もちろん、C++ のプログラムの場合は、Symbol Tree の Classes や、Namespaces を使うと効率がいいと思います。
Function Call Trees
デフォルトでは表示されてないですが、Function Call Trees も便利です。
下図のように、ツールバーの矢印のアイコンをクリックすると、Function Call Trees のウィンドウが表示されます。現在の関数の呼び出し元をたどっていけますし、呼び出し先もたどっていきます。とても便利です。
GhidraのInternal Decompiler Functions(内部関数)
逆コンパイルで表示される C言語には、SUB41()
、CONCAT31()
など、見慣れない関数が出てきます。これらは、Ghidra の内部関数のようで、ヘルプにその機能について説明されています。ヘルプは英語なので、ここで簡単に説明を書いておきます。
SUB()
切り捨て操作関数で、SUBPIECE の SUB のようです。
実際は、SUB41(x, c)
のようになっていて、4 と 1 は別の値になったりします。それぞれの意味を説明します。
- 4:入力 x のバイト数を示します
- 1:出力のバイト数を示します
- x:入力値です
- c:最下位バイトから切り捨てられるバイト数です
例が掲載されています。
SUB42(0xaabbccdd,1) = 0xbbcc
は、入力が 4byte の 0xaabbccdd
で、第2引数が 1 なので、最下位バイトの 0xdd
は捨てられます。出力バイト数は 2 なので、結果は、真ん中 2byte の 0xbbcc
になります。
いくつか例を示しておきます。
SUB41(0xaabbccdd, 0) = 0xdd
SUB42(0xaabbccdd, 0) = 0xccdd
SUB84(0xaabbccddeeffgghh, 4) = 0xaabbccdd
CONCAT()
連結操作関数です。
実際は、CONCAT31(x, y)
のようになっていて、3 と 1 は別の値になったりします。それぞれの意味を説明します。
- 3:入力 x のバイト数を示します
- 1:入力 y のバイト数を示します
- x:入力値です
- y:入力値です
例が掲載されています。
CONCAT31(0xaabbcc,0xdd) = 0xaabbccdd
は、入力が 3byte の 0xaabbcc
と 1byte の 0xdd
で、これを連結すると、結果は、0xaabbccdd
になります。
ZEXT()
ゼロ拡張関数です。
実際は、ZEXT14(x)
のようになっていて、1 と 4 は別の値になったりします。それぞれの意味を説明します。
- 1:入力 x のバイト数を示します
- 4:出力バイト数を示します
- x:入力値です
例が掲載されています。
ZEXT24(0xaabb) = 0x0000aabb
は、入力が 2byte の 0xaabb
で、これを 4byte にゼロ拡張すると、結果は、0x0000aabb
になります。
SEXT()
符号拡張関数です。
実際は、SEXT14(x)
のようになっていて、1 と 4 は別の値になったりします。それぞれの意味を説明します。
- 1:入力 x のバイト数を示します
- 4:出力バイト数を示します
- x:入力値です
例が掲載されています。
SEXT48(0xaabbccdd) = 0xffffffffaabbccdd
は、入力が 4byte の 0xaabbccdd
で、これは最上位ビットがセットされてるので、8byte に符号拡張すると、結果は、0xffffffffaabbccdd
になります。
SBORROW()
符号付き借用演算子のテスト関数です。
実際は、SBORROW4(x,y)
のようになっていて、4 は別の値になったりします。それぞれの意味を説明します。
- 4:入力 x、y のバイト数を示します
- x:入力値です
- y:入力値です
符号付き整数として「x」から「y」を減算したときに算術オーバーフローが発生する場合は true を返します。
CARRY()
符号なしオーバーフロー演算子のテスト関数です。
実際は、CARRY4(x,y)
のようになっていて、4 は別の値になったりします。それぞれの意味を説明します。
- 4:入力 x、y のバイト数を示します
- x:入力値です
- y:入力値です
符号なし整数として x
と y
を加算したときに算術オーバーフローが発生する場合は true を返します。
SCARRY()
符号付きオーバーフロー演算子のテスト関数です。
実際は、SCARRY4(x,y)
のようになっていて、4 は別の値になったりします。それぞれの意味を説明します。
- 4:入力 x、y のバイト数を示します
- x:入力値です
- y:入力値です
x
と y
を符号付き整数として加算したときに算術オーバーフローが発生する場合は true を返します。
Ghidraのコメント機能
Ghidra には、コメントを追加する機能があります。単純にコメントを付けたいところで、右クリック → Comment にいくつかありますが、いずれをクリックしても同じダイアログが出ます。
追加できるコメントの種類は、EOL Comment、Pre Comment、Post Comment、Plate Comment、Repeatable Comment の5種類です。いずれも、アセンブラの方には、いい感じのコメントが作られるんですが、逆コンパイラした C言語のソースの方は、デフォルトでは、Pre Comment しか表示されないようです。
右クリックして、Properties を選ぶと、以下のように、逆コンパイラウィンドウに表示できるコメントを増やすことが出来ます。Display EOL comments、Display Plate comments、Display POST comments に追加でチェックを入れました。
これで、逆コンパイラの方にも、EOL、Plate、POST のコメントも表示されるようになりますが、表示される位置は、全て、Pre Comment と同じです。できれば、コメントを付けたい文の前にだけ、コメントを追加されるのではなく、文の後ろにコメントが表示されてほしかったです。
おわりに
今回は、Ghidra の具体的な使い方と、機能について理解しました。
次回からは、書籍「リバースエンジニアリングツールGhidra実践ガイド (Compass Booksシリーズ)」に沿って、リバースエンジニアリングを理解していきたいと思います。
最後になりましたが、エンジニアグループのランキングに参加中です。
気楽にポチッとよろしくお願いいたします🙇
今回は以上です!
最後までお読みいただき、ありがとうございました。