Ghidraで始めるリバースエンジニアリング(使い方編) - 土日の勉強ノート

土日の勉強ノート

AI、機械学習、最適化、Pythonなどについて、技術調査、技術書の理解した内容、ソフトウェア/ツール作成について書いていきます

Ghidraで始めるリバースエンジニアリング(使い方編)

前回は、リバースエンジニアリングツールである Ghidra を使って、STM32 の ELFファイルを解析してみました。

今回も、書籍「リバースエンジニアリングツールGhidra実践ガイド (Compass Booksシリーズ)」を読みながら、さらに、STM32 の ELFファイルの解析を進めていきます。

それでは、やっていきます。

参考文献

はじめに

「セキュリティ」の記事一覧です。良かったら参考にしてください。

セキュリティの記事一覧
・第1回:Ghidraで始めるリバースエンジニアリング(環境構築編)
・第2回: Ghidraで始めるリバースエンジニアリング(使い方編) ← 今回
・第3回:VirtualBoxにParrotOS(OVA)をインストールする
・第4回:HTTPを題材にtcpdumpの出力を理解する
・第5回:nginx(エンジンエックス)を理解する
・第6回:Python+Flask(WSGI+Werkzeug+Jinja2)を動かしてみる
・第7回:Python+FlaskのファイルをCython化してみる
・第8回:shadowファイルを理解してパスワードを解読してみる
・第9回:安全なWebアプリケーションの作り方(徳丸本)の環境構築
・第10回:Vue.jsの2.xと3.xをVue CLIを使って動かしてみる(ビルドも行う)
・第11回:Vue.jsのソースコードを確認する(ビルド後のソースも見てみる)
・第12回:徳丸本:OWASP ZAPの自動脆弱性スキャンをやってみる
・第13回:徳丸本:セッション管理を理解してセッションID漏洩で成りすましを試す
・第14回:OWASP ZAPの自動スキャン結果の分析と対策:パストラバーサル
・第15回:OWASP ZAPの自動スキャン結果の分析と対策:クロスサイトスクリプティング(XSS)
・第16回:OWASP ZAPの自動スキャン結果の分析と対策:SQLインジェクション
・第17回:OWASP ZAPの自動スキャン結果の分析と対策:オープンリダイレクト
・第18回:OWASP ZAPの自動スキャン結果の分析と対策:リスク中すべて
・第19回:CTF初心者向けのCpawCTFをやってみた
・第20回:hashcatの使い方とGPUで実行したときの時間を見積もってみる
・第21回:Scapyの環境構築とネットワークプログラミング
・第22回:CpawCTF2にチャレンジします(この記事はクリア状況を随時更新します)
・第23回:K&Rのmalloc関数とfree関数を理解する
・第24回:C言語、アセンブラでシェルを起動するプログラムを作る(ARM64)
・第25回:機械語でシェルを起動するプログラムを作る(ARM64)
・第26回:入門セキュリティコンテスト(CTFを解きながら学ぶ実践技術)を読んだ

まず、書籍「リバースエンジニアリングツール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 が起動します。

各関数をダブルクリックすると、その関数の中に入っていきます。

Function Call Graph
Function Call Graph

ちなみに、マウスのホイールを回すと、ズームイン、ズームアウトします。Ctrlキーを押しながらホイールを回すと、移動できます。

Function Graph

Window → Function Graph で起動します。

現在のカーソル位置にある関数の分岐が表示されます。

マウスのホイールの動きは、Function Call Graph と同じです。

矢印にマウスを置くと、その分岐のコードが表示されます。関数の構造が可視化されて、理解が進みます。

Function Graph
Function 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 を選んでおきます。

Languageの選択画面
Languageの選択画面

そういえば、前回は自動認識されましたが、v7 ではなく、v8 になっていました。

解析させて、Code Browser を開きます。

うーん、undefined がいっぱい出てます。命令が認識できていません。

v7で認識させて結果
v7で認識させて結果

前回にならって、v8 を選択してみます。ダメでした。v8-m や、v8T なども選んでみましたが、うまくいきません。

仕方ないので、以前(STM32(ARM Cortex-M)のELFファイル⇔バイナリの変換を行う - 土日の勉強ノート)、bin2elf.sh を使って、バイナリファイルから ELFファイルを再構築したもの(stm32f4discovery_sample_bin2elf_entry.elf)を使ってみます。

通常の ELFファイルと同様に、自動でファイルを認識してくれました。解析が完了すると、通常の ELFファイルと同じように認識しました。成功です。

bin2elf.shを使って再構築したELFファイルの解析結果
bin2elf.shを使って再構築したELFファイルの解析結果

デバッグ情報が無いと、プログラムの構造を理解するだけでも大変そうです。

今回はここまでにします。

使い方のメモ

ここからは、これどうやるのかな?という内容を個別にメモしておきます。

プログラムのエクスポート

Ghidra に表示されるデコンパイルされた C言語の内容をまとめてエクスポートすることが出来ます。

File → Export Program... を開き、Format で C/C++ を選択して、ファイルパスを指定して OK をクリックすると、1ファイルにデコンパイルした C/C++ のソースコードをエクスポートできます。

デコンパイルしたソースコードをまとめてエクスポート
デコンパイルしたソースコードをまとめてエクスポート

個別のウィンドウを別ウィンドウにした場合に元に戻す方法

これは、かなり困りました。こういう操作系の悩みってググっても出ないし、ChatGPT はそれらしいこと言ってくるだけで全然ダメだし。。というわけで、ここに書いておきます。

分かってしまえば簡単です。いったん、以下のように、フローティングウィンドウにしたとします。

Consoleウィンドウをフローティングにした場合
Consoleウィンドウをフローティングにした場合

これを、もとのメインウィンドウにドッキングさせる方法です。フローティングにしたウィンドウのタイトルバーをクリックしてドラッグするのではなく、下図のように、フローティングにしたウィンドウの中の 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 のウィンドウが表示されます。現在の関数の呼び出し元をたどっていけますし、呼び出し先もたどっていきます。とても便利です。

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:入力値です

符号なし整数として xy を加算したときに算術オーバーフローが発生する場合は true を返します。

SCARRY()

符号付きオーバーフロー演算子のテスト関数です。

実際は、SCARRY4(x,y) のようになっていて、4 は別の値になったりします。それぞれの意味を説明します。

  • 4:入力 x、y のバイト数を示します
  • x:入力値です
  • y:入力値です

xy を符号付き整数として加算したときに算術オーバーフローが発生する場合は 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シリーズ)」に沿って、リバースエンジニアリングを理解していきたいと思います。

最後になりましたが、エンジニアグループのランキングに参加中です。

気楽にポチッとよろしくお願いいたします🙇

今回は以上です!

最後までお読みいただき、ありがとうございました。