脅威のスポットライト:Ghidraによるコード解析入門

By The Cylance Threat Research Team

本ブログ記事は、2019年7月31日に米国で公開された抄訳版です。原文はこちらからご覧頂けます。

はじめに

この記事では、Ghidraを使用して悪意のあるコードを解析する場合のアプローチについて説明します。Ghidraは、米国国家安全保障局(NSA)が開発したソフトウェアリバースエンジニアリング(SRE)フレームワークであり、無料で利用できます。この強力なリバースエンジニアリングツールは、2019年3月にオープンソースソフトウェアとしてリリースされて、予算の多寡に関係なく誰でも利用できるようになりました。

NSAの開発者は、GitHubでGhidra用のページを管理して、数百件のバグ、質問、および拡張リクエストに対処しています。Ghidraがリリースされてから、その寄稿者コミュニティは拡大し続けており、4つのマイナーリリースが公開されています。以前のブログ記事では、Ghidraのリリースを取り上げて、その主な機能について説明しています。

Ghidraによるファイルの解析

実行可能ファイルのロード

Ghidraの最新リリースは、こちらから入手できます。 インストール手順は簡単で、zipアーカイブをダウンロードして解凍するだけです。最初に、Ghidraバッチファイルを起動します。次に、「File」>>「New Project」を選択して、プロジェクトを作成します。 リバースエンジニアリングの作業対象はマルチコンポーネントマルウェアサンプルであることが多いことを反映して、Ghidraは、個々のファイルではなく、プロジェクトに重点を置いています。

プロジェクトには、シングルユーザーで解析する非共有オプションおよびコラボレーションで解析する共有オプションがあります。共有の場合、サーバー上のプロジェクトリポジトリに複数のユーザーがアクセスできます。このブログ記事ではシングルユーザー解析に重点を置いていますが、共有機能はチームでリバースエンジニアリングする場合に重要であり、試してみる価値があります。プロジェクトオプションを選択したら、次はプロジェクトの名前を指定します。

図1:プロジェクトの新規作成

この図の例では、Remcos サンプルの解析に重点を置きます。Remcosは、Breaking-Security.netで販売されているリモート管理ツール(RAT)であり、感染したマシンをさまざまな方法で監視および制御できます。

次に、ファイルサンプルをプロジェクトウィンドウにドラッグアンドドロップします。これにより、ダイアログボックスが開きます。ここでは、デフォルトをそのまま使用できます。「OK」をクリックして、次に進みます。

図2:ファイルのインポート

インポートが完了すると、サマリー情報が表示されます。

図3:「Import Results Summary」

プロジェクトウィンドウでインポートしたファイルをダブルクリックすると、CodeBrowserが起動されて、ファイルを解析するかどうかを確認するプロンプトが表示されます。ここで解析オプションを変更して、「WindowsPE x86 Propagate External Parameters」を追加できます。このオプションを選択すると、関数の引数がコメントに追加されます。次に、「Analyze」ボタンをクリックします。

図4:ファイル解析の開始

表示要素の変更

CodeBrowserでターゲットファイルをレビューしている間に、バックグラウンドでGhidraが解析を実行します。「Listing」ウィンドウ(アセンブリコードが表示されます)を少し変更して見やすくすると、便利な場合があります。CodeBrowserのオプションにアクセスするには、「Edit」 >> 「Tool Options」:を選択します。

図5:CodeBrowserのオプションの表示

環境に関する以下のオプションを変更することをお勧めします(この記事のスクリーンショットにはこれらの変更が反映されています)。

  • Listing Display: 読みやすくするために、フォントサイズを大きくして、太字の書式を有効にします。
  • Listing Fields >> Bytes Field: 「Maximum Lines to Display」を1に変更して、アセンブリコードの行間隔を単純にします。
  • Listing Fields >> Cursor Text Highlight: 「Mouse Button to Activate」を「LEFT」に変更します。左マウスボタンをクリックすると、選択されているテキスト全体が強調表示されます(他の逆アセンブラと同様)。 
  • Listing Fields >> EOL Comments Field: 「Show Semicolon at Start of Each Line」をオンにすると、アセンブリテキストと挿入されたコメントが分離されて見やすくなります。
  • Listing Fields >> Operands Field: 「Add Space After Separator」をオンにすると、テキストが読みやすくなります。

API参照の調査

興味のあるコードは、Windows APIの参照を手掛かりにして調査するのが一般的なやり方です。Ghidraのインターフェイスでは、さまざまな方法でAPI参照を表示できます。ここでは、「Window」 >>「Symbol References」を使用して参照を表示します(このリストには 「Imports」よりも多くの参照が表示されます)。

興味のあるAPIを選択して、その関数の呼び出し元を特定します。たとえば、the CreateProcessA APIは、このプログラムからどんなプロセスが生成されるのかを理解するために調査する価値があります。API名をクリックして右側で参照を選択するだけで、このAPIを参照している場所を特定できます。以下の図では、2番目のCreateProcessAの参照をクリックしています。

図6:CreateProcessA API参照の特定

参照をクリックすると、該当する逆アセンブリが「Listing」ビューに表示されます。

図7:CreateProcessA API参照の追跡

仮想アドレス0x40A0DAのCALLで、CreateProcessAを参照していることがわかります。CALLの前にある複数のPUSH命令は、CreateProcessAの引数を参照しています。右側に、この関数のパラメータを示す自動生成コメントが表示されます(MicrosoftのWebサイトを参照)。dwCreationFlagsパラメータは、プロセス作成フラグを指定します。このフラグが生成されるプロセスの特性になります。全フラグのリストは、こちらを参照してください。

値0x8000000は、記号定数を表します。これをもっと人間が読みやすい表現に変換するために、この値を右クリックして「Set Equate…」を選択します。ウィンドウが開いて、選択されている16進数値に対する複数のオプションが表示されます。このAPIに適した選択肢を指定するにあたって、MicrosoftのWebサイトではプロセス作成フラグのオプションのほとんどが「CREATE_」で始まっていることを思い出してください。これを「Equate String」フィールドに入力すると、CREATE_NO_WINDOWというオプションが1つだけ表示されます。このフラグは、生成されたプロセスはコンソールウィンドウを起動してはいけないことを指定するものであり、正しい選択肢であると考えられます。この値を選択して、「OK」をクリックします。

図8:16進数値を記号定数に変換

これで、アセンブリには、作成フラグが16進数値ではなくテキスト表現で表示されます。

図9:作成フラグのテキスト表現の表示

逆コンパイラ出力の表示

Ghidraで非常に役立つのが、逆コンパイラ出力という組み込み機能です。これは、アセンブリコードの上位レベルのC言語表現を表示する機能です。CreateProcessAの呼び出しを選択すると、「Decompile」ウィンドウが開きます(右端に表示されます)。アセンブリに慣れているアナリストなら図9のコードを解読できますが、図10の逆コンパイラ出力のほうが明らかにこの関数呼び出しを短時間で解析できます。

逆コンパイラ出力は、「Listing」ビューで行われる変更との同期を維持していることに注意してください。C言語表現でCreateProcessAの引数としてCREATE_NO_WINDOWが表示されているのはそのためです。同様に、「Listing」ビューまたは「Decompile」ビューの一方で関数、変数、または引数の名前を変更すると、もう一方のウィンドウが更新されます。

図10:CreateProcessA APIの呼び出しの逆コンパイル

このコードを簡単にレビューしてみると、レジストリキーHKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\SystemのEnableLUA値を変更することによって、ユーザーアカウント制御(UAC)を無効にしようとしていることがわかります。

文字列参照の調査

悪意のあるコードを特定するもう1つのアプローチとして、疑わしい文字列の参照を追跡する方法があります。ターゲットファイル内に埋め込まれている文字列を確認するには、「Window」>>「Defined Strings」に移動します。このサンプルは豊富な機能を備えており、難読化されているので、調査する価値のある文字列が多数存在します。たとえば、「[Following text has been copied to clipboard]」という文字列が見つかっています(図11)。これは、マルウェアがその監視アクティビティの一部としてクリップボードのテキストをコピーしていることを示しているのでしょうか。それを確認するために、コード内でこの文字列の参照を探してみましょう。 

図11:文字列を表示

この文字列が表示されている行をクリックすると、「Listing」ウィンドウにアドレス0x414CCCのデータが表示されます。この文字列の参照を特定するには、青い領域を右クリックして、「References」>>「Show References to Address」を選択します。

図12:文字列の参照の検索

表示されるウィンドウには、この文字列の参照が1つだけ表示されています。

図13:文字列参照の表示

この参照をクリックすると、0x406AF8に移動します。これは、FUN_00406a6fというラベルが付いている関数の内部です。上下にスクロールしても、クリップボードデータを収集するコードがあるのかどうか、あるのならそれはどのコードなのかはすぐにはわかりません。呼び出される関数を1つ1つ手作業で調べる代わりに、ここでは「Window」>>「Function Call Graph」を使用できます。関数呼び出しグラフには関数間の関係が表示されるので、関数呼び出しの概要を大まかに把握できます。このインターフェイスの移動によって、FUN_00406a29の呼び出しによって複数のWindows APIが実行されて、それによってマルウェアがクリップボードデータを収集できることがわかりました。具体的には、OpenClipboard、GetClipboardData、およびCloseClipboardが実行されます。

図14:関数呼び出しグラフの表示

関数FUN_00406a29の内部を調べるには、その名前の関数ラベルをクリックして、「Listing」ウィンドウまたは「Decompile」ウィンドウを表示します。あるいは、「Listing」ビューまたは「Decompile」ビューをクリックして「g」キーを押した後、関数のラベル名またはアドレスを入力して「OK」をクリックすると、そのコードにジャンプします。逆アセンブリテキストを単純にレビューする以外に、関数内の判断ポイントを視覚的に表示すると、役に立つことがよくあります。「Window」>> 「Function Graph」に移動して、関数の個々のコードブロックおよび条件/無条件分岐を表す矢印を表示します。

図15:関数グラフの表示

最初のコードブロックには、OpenClipboardの呼び出しと、それに続くTEST命令とJZ命令(0の場合にジャンプ)があります。これらは、EAXに格納されている戻り値を評価します。JZ命令は、EAXが0の場合にコードの実行が0x406A55にジャンプすることを示します。戻り値0は、OpenClipboardの呼び出しが失敗したことを意味します。EAXが0以外の場合、OpenClipboardの呼び出しは成功しています。この場合、ジャンプは実行されません。実行は右側のコードブロックに移り、そこでクリップボードデータを収集するための別のAPI呼び出しが行われます。言い換えると、クリップボードを正常に開くことができた場合はそのデータを収集し、できなかった場合はクリップボード情報を収集するコードを実行しません。実行は、条件(EAXが0)が真の場合は緑の矢印に従い、偽の場合は赤の矢印に従うことに注意してください。

スクリプティング

Ghidraは、解析を自動化するためのJavaスクリプトとPythonスクリプト(Jython経由)の作成機能をサポートしています。組み込みスクリプトを表示するには、「Window」>>「Script Manager」を選択します。これにより200を超えるスクリプトが表示されますが、このほかにもオンラインでさらに多くのスクリプトが公開されています。

たとえば、特定の関数内のすべてのCALL命令を出力する単純なPythonスクリプトを作成します。このスクリプトを作成するには、Script Managerの右上で「Create New Script」を選択します。スクリプトの種類として「Python」を選択して、スクリプトの名前を指定します。

図16:Pythonスクリプトの作成を選択

スクリプトエディタ内で、以下のコードを追加します。


#description: Print all CALLs within a function.
#@author: Analyst
#@category _NEW_

fn = getFunctionAt(currentAddress)
i = getInstructionAt(currentAddress)
while getFunctionContaining(i.getAddress()) == fn:
    nem = i.getMnemonicString()
    if nem == "CALL":
     target_address = i.getOpObjects(0)[0]
     print(nem + " " +  str(getSymbolAt(target_address)))
    i = i.getNext()


どのスクリプトでも同じですが、同じ目標を達成するにもさまざまな方法があります。ここでは、Pythonスクリプトで関数、命令、およびオペランドにアクセスする方法の一端を示しています。

このスクリプトをテストするために、クリップボードAPIを呼び出していた関数からスクリプトを実行します。それには、以下のアクションを実行します。

  • 「Listing」ウィンドウで「g」キーを押して、0x00406A29にジャンプする。
  • Script Managerに戻る。
  • 新しいスクリプトをダブルクリックする。

コンソールウィンドウが開いて、クリップボードAPIの呼び出しを示す出力が表示されます。

図17:テストスクリプトの実行

最後に

Ghidraのリリースによって、多くの人が無償で強力なリバースエンジニアリング機能を利用できるようになりました。マルウェア解析の初心者にとっては、リバースエンジニアリングを試してみる絶好のツールです。経験を積んだアナリストは、代替的なこのフレームワークを見てびっくりするかもしれません。うまくいけば、REによる対応力を高める新しいツールと手法が手に入ります。最悪のケースでも、現在使用しているツールを愛用する理由が増えるだけです。

Ghidraについて詳しく学ぶには、公式のzipファイルに含まれているトレーニング資料を読んでください。Twitterで#Ghidraで検索すると、GhidraのTwitter公式アカウント@GHIDRA_RE (注:このアカウントは、NSAとの関連はありません) など、他の役立つリソースが見つかります。GhidraがGithubで管理している issuesページも優れたリソースです。

Tags: