株式会社Ninjastars 技術系ブログ

「本質的安全を提供し、デジタル社会を進化させる!!!」

Ghidra Tutorial + CTF・脆弱性診断 超入門 Reversing②

株式会社Ninastars
取締役:齊藤和輝

前回に引き続きReversing編ではCTFや脆弱性診断においてのリバーシング技術の基本をお教えします。
今回の記事ではどういった思考でアセンブリを読んでいくのかということを詳細に書いていきます。
またGhidraのチュートリアルとしても利用できるようにしています。

参考問題集

今回はeagle0wl氏のcrackmeシリーズのcrkme04という問題を使用します。
リンク先からeagle0wl's crackme VOL.01 ver1.02をクリックしてダウンロードしてください。

www.mysys.org

参考サイト

アセンブリ言語の基本知識が掲載されている。
programmer.main.jp

使用ツール

Ghidra

NSA(米国国家安全保障局)が公開した高機能なバイナリ解析ツール。
ninjastars.hatenablog.com

crkme04

起動すると以下の画像のようなウインドウが立ち上がります。

f:id:Ninjastars:20190308165056p:plain
crkme04ウインドウ
テキストボックスに何かしらの文字列を入力し、登録のボタンを押しても不正解の場合何も表示されません。
しかしながら、内部では正解と不正解を判定するためのプログラムが動いているだろうと予測できます。
そこで静的解析を行い、答えとなる文字列を取得したいと思います。

まずは大まかでいいので道筋を立てていきましょう。
ステップ1.静的解析ツール「Ghidra」を使用し、crkme04の逆アセンブルを行う。
ステップ2.正解と不正解を判定する際に必ず必要な関数や我々の目に見える情報の中からキーワードとなる文字列を予想しながら検索する。
ステップ3.ステップ2で検索した結果の前後から比較用オペコードを探す。
ステップ4.どのように条件分岐しているのかを把握する。
ステップ5.これ以降はプログラムによって様々な道筋が変わるので考えないことにする.....

では実際に解析をしていきたいと思います!
ステップ1
Ghidraを用いてcrkme04.exeを解析してみましょう。
前回と同様にcrkme04.exeのファイルをCodeBrowser画面にドラッグ&ドロップして逆アセンブルしてもらいます。
以下のような画面になると思います。

f:id:Ninjastars:20190307161251p:plain
Ghidraによる逆アセンブル

ステップ2
今回は正解と不正解を判定する際に必ず必要な関数を予想して検索していきたいと思います。
まずはCodeBrowser上部のWindow->Defined Stringsをクリックして文字列として可読なものを全て列挙してもらいます。
(当然ながらここには答えとなる文字列は存在しないでしょう...)
ここで気になるのは"GetWindowTextA"です。
これはC系言語におけるテキストボックスに入力された文字列を取得する関数です。
どこかのタイミングで必ずテキストボックスに入力された文字列と比較をしているのでこれを手がかりにしていきたいと思います。
次にCodeBrowser上部のSearch->ProgramTextで指定した文字列をプログラム中のテキストから検索してくれます。
この検索機能ではSearch TypeやFields、Memory Block Types等を指定できます。
今回は以下の画像のように検索していきます。

f:id:Ninjastars:20190308112357p:plain
Searchウインドウ
Search Allで指定した範囲で全探索して候補を挙げてくれます。
今回は一個しかないのでそれをダブルクリックすると実際のアドレスまで飛んでくれます。
Listingウインドウに戻るとGetWindowTextAの文字があります。
文字を右クリックして一番下のReferences->Show References to GetWindowTextAを選択するとReferencesウインドウが現れます。
ReferencesウインドウにてGetWindowTextAをダブルクリック。するとListingウインドウがGetWindowTextAをcallしているアドレスに飛びます。
f:id:Ninjastars:20190308122950p:plain
Listingを見るとGetWindowTextAをcallしていることがわかる。

ステップ3
このままだと命令群の全体図が見づらいのでGhidraの機能である"Display Function Graph"を使いたいと思います。
CodeBrowserの上部ツールアイコンの中から"Display Function Graph"をダブルクリックすると以下の画像のようにフローチャートとして図式化してくれます。

f:id:Ninjastars:20190308123503p:plain
便利なフローチャートを作成してくれる。
黄色で示されている部分にGetWindowTextAをcallしている列があります。
拡大した画像を載せておきます。
f:id:Ninjastars:20190308123900p:plain
CALL GetWindowTextAという演算が見える
拡大した画像を見てみるとCALL GetWindowTextAの周囲に"CMP EAX,0x2c2e60f9"や"JNZ LAB_0040121f"と緑、赤の矢印があるのがわかります。
CMPは比較用のオペコードでJNZは条件分岐用のオペコードです。
このステップでの目的は達成したので次のステップにいきます。

ステップ4
どのように条件分岐しているのかはフローチャートから把握できます。
ここではより詳細な部分を解説してきたいと思います。
比較の対象と条件を見ていきましょう。
"CMP EAX,0x2c2e60f9"は「0x2c2e60f9とEAXを比較する」という意味です。
"JNZ LAB_0040121f"は「(比較結果が)0ではないときアドレス004121fへ移行する」という意味です。
つまりこの2つの命令によって条件分岐しているのではないかと予想できます。
フローチャート内のアドレス004121fを見ていきましょう。
ここでは"JMP LAB_0040109e"しかないのでただ単に他のアドレスへ移行しているだけです。あまり意味がありません。
では"JNZ LAB_0040121f"へ戻ってみましょう。
条件分岐には通常2つの行き先があります。もう1つの行き先を見ていきましょう。
PUSH 0x0
PUSH DAT_004030ed
PUSH DAT_004030f6
PUSH dword ptr[EBP+param_1]
CALL MessageBoxA
という命令群があります。わざわざMessageBoxAが呼び出されているのでここが正解の文字を表示している部分なのでしょう。
(実際ここで正解の文字を表示しています。)
正解の処理へと移行している部分が判明しました。
ここで分かったことは、"CMP EAX,0x2c2e60f9"の比較結果が0となれば正解の処理に移行するということです。
次のステップではEAXの実態を調べていきたいと思います。

ステップ5
"CALL GetWindowTextA"から"JNZ LAB_0040121f"の間にEAXは一箇所しかありません。
ここで注目するのは"CALL FUN_00401224"です。
一般に関数の引数と戻り値はStackとEAXでやり取りされています。
CALLは関数を呼び出しているオペコードであり、そのオペランドが呼び出している関数です。
Function Graphウインドウの"FUN_00401224"をダブルクリックすると関数のフローチャートに移行します。

f:id:Ninjastars:20190308144138p:plain
関数FUN_00401224のフローチャート
関数を紐解くには常にプログラムを逆から読んでいきます。
フローチャート内でRETを探しましょう。RETは関数から本流のプログラムに戻るためのオペコードです。
MOV EAX,EBX
LEA...
RET 0x4
という命令群を見てみると"MOV EAX,EBX"があります。
"MOV EAX,EBX"は「EBXの値をEAXに格納する」という意味です。
これからはEBXの値に注目して読んでいきましょう。
フローチャート内にEBXは7箇所ありますが、"XOR EBX,EBX"は「EBXの値を0にする」だけの命令です。
今回EBXの値が"0x2c2e60f9"にしたいので"XOR EBX,EBX"がEBXの値を弄っている最後の命令としないように考えます。
00401249 XOR EBX,EBXから赤い矢印を通ってRETを含む命令群に移行しているので、ここを通らないようにします。
2つの矢印でこのルートを通るので2つの命令群を見ます。
どちらもEAXの値を比較して条件分岐しています。
結果としてEAXの値が0x30以上0x39以下であれば"XOR EBX,EBX"の命令を通らないことが分かります。
ASCIIコード表と照らし合わせてみると0x30以上0x39以下は0から9までの数字の文字列になっています。
ここでわかった情報は答えの文字列は全て数字で構成されているということです。
かなり答えに近づいて来ました。
次に残ったEBXの候補からさらなる情報を掴んでいきましょう。
残ったEBXは"MOV EAX,EBX"を含め3箇所です。
0040123cから始まる命令群に着目して読んでみます。
SUB EAX,0x30
XCHG EAX,EBX
IMUL EAX,EAX,0xa
ADD EBX,EAX
XOR EAX,EAX
JMP LAB_0040122c
この命令群の処理を紐解くと
EBX=EBX+(EAX-0x30)×(処理のループ数)×0xa
という数式で表せます。
日本語で表現すると「入力した数字の列を即値に直してEBXに格納する」という意味になります。
これで答えを発見するための情報が出揃いました。
・答えの文字列は全て数字で構成されている。
・即値として"0x2c2e60f9"と等しくなればよい。

Pythonを使って"0x2c2e60f9"の値を出してみましょう。
GhidraにはPythonインタラクティブモードを使用できる機能があります。
CodeBrowser上部のツールバーからWindow->Pythonで起動します。
f:id:Ninjastars:20190308161326p:plain
0x2c2e60f9の10進数表記
答えが741236985とわかりました!
実際にcrkme04.exeを起動して打ち込んでみると...
f:id:Ninjastars:20190308162046p:plain
正解しました!

注意事項

本レポートに記載されている内容を許可されていないソフトウェアで行うと、場合によっては犯罪行為となる可能性があります。そのため、記事の内容を試す際には許可されたソフトウェアに対してのみ実施するようにしてください。

本レポートについて
お問い合せ
E-mail:saito@ninjastars-net.com

株式会社Ninjastars
取締役:齊藤和輝