ナビゲーションをスキップする
BlackBerry ThreatVector ブログ

動的バイナリ計装フレームワークによるマルウェア解析

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

はじめに

マルウェアのリバースエンジニアは、実行中のプログラムを調査するために動的コード解析を実施しますが、その方法は、デバッガを使用して疑わしいプロセスを監視するやり方が一般的です。この補完的なアプローチとして、動的バイナリ計装(DBI:Dynamic Binary Instrumentation)フレームワークを使用して、実行中のプロセスを調査する方法があります。デバッガの場合はプロセスにアタッチできますが、DBI手法を使用することで、プロセス内にコードを注入して実行し、その内部を調べることができます。

有名な DBI フレームワークには Intel の Pin、DynamoRIO、Frida などがあり、これらは、独自開発プログラムの評価やプログラムの性能評価に使われるのが一般的ですが、マルウェア解析の高速化にも役立ちます。アナリストは、DBI フレームワークを使用して、関数をフックして API 呼び出しを監視し、その入出力を評価し、さらに実行中の命令やデータに変更を加えられるようになります。これらの DBI フレームワークはデスクトップとモバイル両方のオペレーティングシステム(Windows、macOS、GNU/Linux、iOS、Android™、QNXなど)を対象としており、ツール開発支援のための詳細な API ドキュメントも提供されています。

このブログでは、Frida を使用してリバースエンジニアリングワークフローを自動化する方法を紹介します。具体的には、難読化された実行可能コンテンツを Frida を使用して特定およびダンプする方法を紹介し、Frida の主な機能と、Frida Python スクリプトのコアコンポーネントを解説します。アナリストはこれらの知識を活用して、バイナリ解析のためのカスタムツールを迅速に構築できます。

Frida の概要

Frida は、Ole André V. Ravnås 氏が開発した無料のオープンソースソフトウェアです。アナリストは、Frida を使用してプログラムに JavaScript を注入し、実行中の関数呼び出しの入出力を監視、傍受、改変することができます。また、デスクトップとモバイルのさまざまなオペレーティングシステム上で動作します。そのメリットをすぐに実現するには、Frida が提供するコマンドラインツールが便利ですが、Frida フレームワークの機能性と柔軟性を最大化するには、提供されている Python バインディングを利用するのが最適です。

Frida を利用するには、Windows、macOS、GNU/Linux のいずれかのオペレーティングシステムに Python 3 をインストールする必要があります。この記事は Windows のマルウェアを対象とするため、解析には Windows 環境を使用します。

Frida をインストールするには、インターネットに接続されたマシンで、以下のコマンドを実行します。

pip install frida-tools

frida-trace による API トレース

Frida をインストールするには、インターネットに接続されたマシンで、以下のコマンドを実行します。

frida-trace の使い方を簡単に見てみるため、一般公開されている 64 ビット Windows 実行ファイルを調べてみましょう。解析用の名前として、このファイルを sample.exe と名付けます。プロセスを生成し、関数呼び出しのトレースを開始するには、以下のコマンドライン形式を使用します。

frida-trace -f <プログラム名> -i <監視対象の関数(複数可)>

マルウェア解析でトレース対象にするべき API は数多くあります。多くの場合に役立つのが、実行中に作成されるファイルや開かれるファイルを監視することです。文字列「CreateFile」を含むすべての API を監視するには、以下のコマンドを実行します。

frida-trace -f sample.exe -i *CreateFile*

このコマンドにより複数のタイプのCreateFileを包括的にカバーできますが、出力の数は到底確認できないほどになります(ここには表示されていません)。このコマンドをさらに改良するために、kernel32.dll にある CreateFileA だけをトレースすることにします。この API を選択するのは、この API がプログラムのインポートアドレステーブル(IAT: Import Address Table)に表示されているためです。この API を対象とした以下のコマンドでは、図 1 のような出力が得られます。


図 1:CreateFileA の初回の出力
 

この出力には指定した API の呼び出しが 1回示されていますが、関連情報は得られていません。これは出発点としては合格ですが、この関数呼び出しが参照する具体的なファイルや I/O デバイスなど、より詳細な出力が必要です。

Frida の出力は、CreateFileA.js というハンドラを参照しています。このフレームワーク内では、関数が呼び出されたときと、値を返したときに発生するアクションをハンドラファイルが定義します。CreateFileA.js には、デフォルトで以下のような内容が含まれています。


図 2:CreateFileA のデフォルトのハンドラ

ここにはコンテキストの解析に役立つ文字列がかなり含まれています。onEnter と onLeave に対する参照に注目します。これらは強調するためにオレンジ色で表示されています。onEnter 関数は、CreateFileA が呼び出されたときに実行するコードを指定します。デフォルトでは、この関数が呼び出されたことを明確にするために、API 名を表示する log ステートメントが含まれています(図 1 を参照)。

コメントでも説明されているように、args パラメータは関数に渡される引数の配列です。たとえば、第 1 引数には args[0]、第 2 引数には args[1] でアクセスします。

onLeaveは,関数が値を返したときに実行するコードを指定します。デフォルトでは,ここにはコードはありません。戻り値へのポインタである retval パラメータは、後ほど利用します。

CreateFileA に関する Microsoft のドキュメントでは、第 1 引数は、作成またはオープンするファイルまたはデバイスを指すと説明されています。以下のように onEnter を修正することで、Frida にこの引数を出力させることができます。


 
図 3:変更された CreateFileA ハンドラ
 
この変更されたハンドラで frida-trace コマンドを実行すると、以下のような出力が得られます。
図 4:修正された frida-trace の出力

新しい出力では、対象となるファイルやデバイスが参照されています。この場合は、プロセス間の通信に使用される名前付きパイプが表示されています。この名前付きパイプの特定のフォーマットは、Cobalt Strike のバイナリ解析でよく発見されるもので、このサンプルがレッドチームのツールセットに関連していることを示唆しています。

Cobalt Strike は、レッドチーム、侵入テスト担当者、攻撃者グループがよく使用するツールで、インシデント対応で Cobalt Strike のローダーがディスク上で発見されることがよくあります。このローダーが、主要ペイロードである「Beacon」を起動することで、ターゲットマシンを制御できるようになります。ローダーには、悪意のあるサーバーから Beacon をダウンロードして実行する「ステージ型」と、Beacon のペイロードを難読化し、メインの実行可能ファイルに埋め込む「ステージレス型」があります。

通常、Cobalt Strike のバイナリを解析するには、難読化と実行の複数の段階を経る必要があり、その過程でメモリ内のシェルコードや追加の Windows 実行可能ファイルが明らかになります。ここでは、最初にプログラムが実行する難読化を調査し、次に Frida を使ってそのプロセスを自動化していくことにします。

 

監視対象の API を選択する

通常、Cobalt Strike の実行可能ファイルは、実行中にコンテンツを難読化して内蔵するコードを起動します。このプロセスは以下のような段階で構成されます。

  • メモリを割り当てる。
  • データをコードにデコードする。
  • 実行可能コンテンツを新たに割り当てられたメモリにコピーする。
  • 実行をコードに移行する。

メモリ領域の割り当てや操作に使用される Windows API は複数ありますが、ここでは VirtualAlloc と VirtualProtect に注目します。

VirtualAlloc は、現在のプロセスにメモリを割り当てるために使用されます。Microsoft の Web サイトで説明されているように、この API の構文は以下のようになります。

 

LPVOID VirtualAlloc(

  LPVOID lpAddress,

  SIZE_T dwSize,

  DWORD  flAllocationType,

  DWORD  flProtect

);

これら 4 つのパラメータのうち 2 つ目のパラメータは、割り当てられるメモリ領域のサイズ(バイト)を指定します。VirtualAlloc が正常に実行された場合、戻り値には割り当てられたメモリ領域の開始アドレスが指定されます。また、使用に備えるためメモリ領域はゼロになります。

VirtualProtect は,メモリ領域の権限(読み取り,書き込み,実行など)を変更するために使用されます。Microsoft の Web サイトで説明されているように、この API の構文は以下のようになります。

BOOL VirtualProtect(

  LPVOID lpAddress,

  SIZE_T dwSize,

  DWORD  flNewProtect,

  PDWORD lpflOldProtect

);

4つのパラメータのうち 1 つ目のパラメータは、権限が変更されるメモリ領域の開始アドレスを指定します。3 つ目のパラメータは、新たに適用される権限を指定します(Microsoftではこれを「メモリ保護定数」と呼んでいます。オプションの一覧はこちらを参照)。

sample.exe をデバッガに読み込ませることで、VirtualAlloc と VirtualProtect にブレークポイントを設定し、これらの API に対する呼び出しを監視して、割り当てられたメモリ領域が実行中にどのように変化するかを確認できるようになります。今回の場合、Frida を使用してこれらの API 参照を詳しく調査します。

frida-python でスクリプトを作成する

frida-trace は、このフレームワークのメリットを初めて体験する手法としては最適ですが、Frida の Python バインディングを利用して独自のスクリプトを記述することで、API 監視の統制力と柔軟性をさらに高めることができます。このセクションでは、実行可能コンテンツのメモリ領域を監視する、Frida の機能スクリプトの主要コンポーネントを紹介します。ここでは、このスクリプトを script.py と名付け、以下の内容を実行します。

  • ターゲットの実行可能ファイルをコマンドラインで受け入れる。
  • プログラムを実行する。
  • 生成されたプロセスにアタッチする。
  • 短時間スリープし、この時間に Frida がプロセスにアタッチして実行を一時停止する。
  • ターゲットプロセスに注入する JavaScript コードを指定する。これにより VirtualAlloc と VirtualProtect がフックされ、ターゲットプロセス内から Python プロセスにメッセージを送信するためのコードが追加されます。
  •  JavaScript コードをターゲットプロセスに注入する。
  • プロセスを再開する。
  • 待機して、ターゲットプロセスからのメッセージをすべて受信する。
図 5:初回の Python スクリプト

このスクリプトに含まれている(黄色でハイライトされている)いくつかの関数について、詳しく説明します。

  • Session.create_script (Python): ターゲットプロセスに注入される JavaScript コードを指定します。
  • Module.getExportByName (JavaScript): 呼び出しの傍受に必要な、指定された API のアドレスを返す関数です。
  • Interceptor.attach (JavaScript): (Module.getExportByName が返す)指定されたアドレスの呼び出しを傍受し、前述の onEnter および onLeave 関数を指定します。
  • Console.log (JavaScript): ターゲットプロセスに注入された JavaScript コードからのメッセージを Python プロセスに送信します。

詳しくは Frida の JavaScript API ドキュメントを参照してください。

ここでスクリプトを実行し、出力の最初の数行を見てみます。

図 6:Python スクリプトの出力


この出力によると、VirtualAlloc はアドレス 0x2d00000 に 260,608 バイトを割り当て、保護値は 0x4 (PAGE_READWRITE) だったことがわかります。Microsoft のドキュメントによると、この値は読み取り/書き込み権限を表しています。

その後、同じメモリ領域に対して VirtualProtect が呼び出され、その保護値が読み取り/実行権限を表す0x20 (PAGE_EXECUTE_READ) に変更されます。これは、難読化でよく確認される呼び出しと引数のシーケンスです。VirtualAlloc が最初に書き込み可能なメモリ領域を確保し、次にVirtualProtect がそのメモリ領域に対する保護を変更して実行可能にします。多くの場合、これらの呼び出しの間に実行可能コンテンツが難読化され、このメモリ領域に配置されます。

この仮説を検証するために、VirtualProtect が権限を更新した時点での 0x2d00000 の内容を確認することができます。これには、VirtualProtect の onEnter 関数を変更して、メモリ領域の 16 進ダンプを生成する必要があります。


図 7:16 進ダンプによる VirtualProtect のインターセプター
 
スクリプトを再実行すると以下が生成されます。
図 8:16 進ダンプにより修正された出力
 

割り当てられたメモリ領域に MZ ヘッダーが存在していることがわかります。これは、難読化された Windows 実行可能ファイルである可能性があります。ここで、メモリ領域の先頭に「MZ」があることを確認した場合に、そのメモリ領域をダンプするコードを追加できます。
 

図 9:MZ を特定し領域をダンプするコードが追加された VirtualProtect インターセプター
 

このコードは、Frida の readAnsiString() 関数を使ってメモリ領域の先頭 2 バイトを読み取り、それらが「MZ」と一致するかどうかをチェックします。一致した場合、readByteArray() 関数を使ってメモリの内容を読み取り、ファイルをディスクにダンプします。変更したスクリプトを実行すると、以下が生成されます。


図 10:MZ ヘッダーを持つコンテンツをダンプするよう修正された出力
 

ダンプされたファイルは 64 ビットの DLL で、ReflectiveLoader という名前のエクスポートが1つと、imphash 値 253Ad4e3ba1e8984c7a31117a5643de9ed85 が含まれています。これらの値を調査し、さらにいくつかの解析を行った結果、これが Cobalt Strike Beacon の DLLであることが確認されました。この DLL の詳細な解析は、この記事では行いませんが、さまざまなオープンソースを少し調べただけでも、構成ファイルの抽出を含む Beacon DLL 解析のさまざまな手法やツールを入手できます。

前述の「MZ」のマッチングに関連する、いくつかの注意点について説明します。まず、この実行可能ファイルに対するチェックはシンプルなものであるため、不完全または無効な実行可能ファイルが特定される可能性があります。2 つ目に、この方法の場合、メモリに読み込まれたターゲット実行可能ファイルを表す、マップされた実行可能ファイルが特定される可能性があります。マップされた実行可能ファイルはディスク上の実行可能ファイルに似ていますが、メモリ内のファイルとディスク上のファイルという違いにより、ダンプされた実行可能コンテンツを実行可能にするための修正が必要になる可能性があります(この修正は、BlackBerry の PE Tree で行うことができます)。

たとえば、メモリ内の PE ファイルの各セクションの配置は、ディスク上のファイルでの配置とは異なります。通常、ディスク上の最初のセクションは、メモリ内のファイルオフセット 1024 から始まりますが、最初のセクションは通常オフセット 4096 で始まります。ここではこれ以上詳しく説明しませんが、この違いを、メモリ内にマップされた実行ファイルとマップされていない実行ファイルを区別する方法として利用することが可能です。ここで紹介するコードはあくまで出発点に過ぎません。その他のさまざまなシナリオについては、皆さん自身で考えてみてください。

 

シェルコードを特定・抽出する

では、別の Cobalt Strike 実行可能ファイルを見てみましょう。これは Beacon ペイロードをメモリにダウンロードして実行することを目的とした、一般に公開されているステージ型ファイルです。1 つ目の例と同様、このプログラムも実行中にコードを難読化します。この 32 ビット Windows 実行ファイルを sample2.exe と呼ぶことにしましょう。sample2.exe をデバッガに読み込ませることで、VirtualAlloc と VirtualProtect にブレークポイントを設定し、このプログラムが参照するすべてのメモリ領域を監視できます。割り当てられた領域と参照される領域をそれぞれ監視することで、メモリ内で難読化されたシェルコードのインスタンスを 2 つ発見できます。

Python スクリプトの最初のバージョン(図 5 を参照)を sample2.exeに実行することで、VirtualAlloc と VirtualProtect の呼び出しを監視できます。以下がその詳細な内容です。


 
図 11:sample2.exe に実行したスクリプトの出力

以下の異なる 4 つのアドレスが参照されていることがわかります。

  • 0x400000: このアドレスは、メモリ内のメインの実行可能ファイル(マップされた実行可能ファイル)の位置で、最初の VirtualProtect 参照に渡されます。興味深いのは、この呼び出しが、実行可能コンテンツが存在する .text セクションをカバーする領域に、保護値 0x40 (PAGE_EXECUTE_READWRITE) を割り当てている点です。通常このセクションが書き込み可能とマークされることはありません。
  • 0x870000: VirtualAlloc に対する最初の呼び出しは、この位置にメモリを割り当て、保護値 0x40 (PAGE_EXECUTE_READWRITE) を割り当てます。実行可能権限が割り当てられているため、この領域は監視対象として有用ですが、このアドレスが今回の出力で再度参照されることはありません。このメモリ領域については後ほど説明します。
  • 0x24d0000: VirtualAlloc に対する 2 回目の呼び出しは、読み取り/書き込み権限と共にこの位置にメモリを割り当てます。出力の末尾から、VirtualProtect に対する最後の呼び出しが、この領域の先頭 800 バイトの権限を 0x20 (PAGE_EXECUTE_READ) に変更していることがわかります。この実行可能ファイルの権限から判断すると、VirtualProtect が呼び出される前に、この位置にコードが配置されている可能性があります。
  • 0x24f0000: このアドレスにメモリが割り当てられていますが、初期段階の保護には実行可能権限が含まれていません。この保護レベルに対する変更はないため、今回はこのメモリ領域を無視します。

最初に 0x24d0000 のメモリの内容を調べます。VirtualProtect に対する最後の呼び出しで、図 7 での説明と同じ 16 進ダンプコードを使用できます。この 1 行を追加して 16 進ダンプを作成することで、VirtualProtect への最後の呼び出しに関する詳細情報を入手できます(その他の出力は簡略化のために省略しています)。

図 12:16 進ダンプにより修正された VirtualProtect の出力(抜粋)
 

先頭バイトの FC E8 は、Cobalt Strike や Metasploit で使用される実行可能コンテンツを含む、シェルコードの先頭によく見られます。前述の例と同様に、共通のバイトシーケンスを自動的に検索することで、潜在的なシェルコードを特定できます。シェルコードの特定で多く発見されるバイトシーケンスには以下のようなものがあります。

  • FC E8: CLD(CLear Direction flag)および CALL opcode 命令に変換されます。
  • 55 8B EC: x86 の関数の先頭(関数プロローグ)によく見られる、push ebp および mov ebp,esp 命令に変換されます。
  • EB: 相対ジャンプ命令の opcode です。
  • E8: CALL 命令の opcode です。

これはすべてを網羅したリストではありません。これらの短いバイトシーケンスに基づいてコードを特定することで誤検知が発生する可能性があります。今回の目的は、実行可能コンテンツを含む可能性の高い領域を特定することですが、追加調査が必要になる可能性もあります。

シェルコードを自動的に特定してダンプするには、VirtualProtect の OnEnter 関数を変更します。

図 13:疑わしいシェルコードをダンプするよう更新された VirtualProtect の OnEntry

コードのハイライトされた部分は、それぞれ以下を実行します。

  • :シェルコードの一般的なバイトシーケンスを各要素に持つ配列を作成します。
  • 青: VirtualProtect に渡された第 1 引数のアドレスで 4 バイトを読み込みます。これにより配列バッファが返されますが、このままでは作業が難しくなる場合があるため、このブロックの残りのコードで、配列バッファを解析しやすい文字列に変更します。
  • 緑: 指定されたシェルコードの opcode について反復処理を実行し、先に読み込まれた先頭バイトとそれぞれを比較します。一致がある場合は、さらなる評価のために疑わしいシェルコードをディスクにダンプします。

このスクリプトを script2.py と名付け、実行すると、以下が生成されます(出力の一部は簡略化のために省略しています)。

図 14:シェルコードをダンプするコードが追加されたスクリプトの出力
 
このシェルコードの機能は、いくつかの選択肢を通して理解することができます。Frida は生のシェルコードは実行できないものの、いくつかのエミュレータを利用することができます。たとえば scdbg を使用して 0x24d0000_sc.bin を実行すると、以下が生成されます。
図 15:Scdbg の出力
 
この出力により、指定された IP アドレスとポートへの接続をシェルコードが試みていることがわかります。あるいは、Windows エミュレーションフレームワーク Speakeasy でシェルコードを直接実行すると、以下が生成されます (簡略化のために一部省略しています)。
図 16:Speakeasy の出力
 

この出力では、scdbg よりも詳しい情報を入手できます。InternetConnectA の呼び出しで IP アドレスとポートを確認でき(第 3 引数の 0x50 は 10 進法で 80)、HttpOpenRequestA の呼び出しではリクエストターゲット「/M7ph」が特定され、HttpSendRequestA の呼び出しではユーザーエージェント文字列が指定されています。このサンプルについてはこれ以上詳しく調査しませんが、この目的が Beacon DLL ペイロードのダウンロードであることは明らかです。

 

その他のコード特定アプローチ

今回 MZ ヘッダーとシェルコードを特定したのは、実行アクセスを含むように権限を変更する VirtualProtect の呼び出しがきっかけでした。しかし、VirtualProtectの呼び出しはコードの実行には必要ありません。たとえば前述のシェルコードの例では、最初のメモリ領域は、VirtualAlloc によって読み取り/書き込み/実行権限で割り当てられていました。この関数呼び出しが完了すると、この領域にコードをコピーして、保護属性に変更を加えることなく実行することができます。メモリ領域の実行可能コードを監視するという目的を徹底するには、さらに 2 つのオプションを検討する必要があります。

  1. 注視すべき追加の API を特定する。
  2. 割り当てられたメモリ領域の変更を追跡する。

注視すべき追加の API を特定する

sample2.exe を解析し、0x870000 のメモリ領域(図 11 で最初に割り当てられた領域)に実行可能コンテンツがどのようにコピーされるかを把握することで、監視対象となる追加の API が見つかる可能性があります。sample2.exe を x32dbg に読み込み、最初の VirtualAlloc の呼び出しが値を返した後のコードを確認すると、以下のようになります。
 
図 17:割り当てられた領域へのコンテンツのコピーに memcpy が使用されている
 

このコードの重要な活動は、以下の位置で発生しています。

  • 0x406403: VirtualAlloc が呼び出され、新たに割り当てられたメモリ領域を戻り値がポイントします。
  • 0x406406: EAX に格納された VirtualAlloc の戻り値がローカル変数にコピーされます。
  • 0x406419: ローカル変数に格納された VirtualAlloc の戻り値が ECX に配置されます。
  • 0x40641C: ECX 内のアドレスが、メモリ内の位置の間でバイトをコピーする memcpy の第 1 引数として、スタックにプッシュされます(Microsoft のドキュメントを参照)。第 1 引数は、コピー先のアドレスです。
  • 0x40641D: memcpy が呼び出されます。第 1 引数については上記の説明のとおりです。第 2 引数は、0x406418 の push eax でスタックにプッシュされたコピー元コンテンツのアドレスです。第 3 引数はコンテンツのサイズを指定します。この値は 0x406409 でスタックにプッシュされます。

このコードにより、新たに割り当てられたメモリ領域へのコンテンツのコピーに memcpy が使用されていることがわかります。第 2 引数で指定されたメモリ領域を x32dbg で調べると、以下のようになります。


図 18:memcpy のコピー元コンテンツが実行可能ファイルである可能性が高い
 

先頭バイトの 55 8B EC は、前述のとおり x86 の関数プロローグに共通する opcode です。これにより、割り当てられたメモリ領域にシェルコードがコピーされているという疑念が裏付けられ、使用された API が memcpy であることが明らかになりました。

このプロセスを自動化するには、memcpy の呼び出しを傍受して、2 つ目(コピー元)の operand を評価して、シェルコードに共通する opcode を調べます。このアプローチで検証を重ねると、実行中に memcpy の呼び出しが多数行われ、その多くが数バイトだけしかコピーしていないことがわかります。この場合、コピー元アドレスの評価を限定することで、このノイズを削減できます。具体的には、バイトの最小しきい値を設定し、その値以上をコピーしている memcpy 呼び出しだけに限定します。この例では、最小しきい値を 500 バイトに設定します。その結果、コードは VirtualProtect のインターセプターとほぼ同じになります。これがスクリプトを最適化する機会であることは明らかですが、完全を期すため、このコードを以下に示します。

図 19:memcpy の OnEntry コードによるコピー元アドレスの評価
 
このスクリプトを実行すると、初回の出力は以下のような結果となります(抜粋)。
図 20:memcpy コードを含むスクリプトの出力
 
ダンプされたファイルを scdbg 内で実行すると、以下の出力が生成されます。
図 21:エラーを含む scdbg の出力

この出力は、シェルコードの実行をエミュレートした前述の作業ほどは参考にならず、シナリオがより複雑であることを示しています。プログラムでエラーが発生しています。これは、シェルコードがメモリ内の別の位置(40136e で始まる行の VirtualAlloc で割り当てられたメモリ領域)にアクセスしたためです。この試みは、エミュレーションの制約が原因で失敗しました。このような場合の多くは、手作業の割合がより大きいデバッグが必要になりますが、それについては今回の記事では詳しく触れません。詳しく確認すると、コードがメモリ内の領域にジャンプして実行を継続していますが、追加のコードは、既に抽出済みのシェルコードの最終段階をデコードしているだけです。

その他の API についてはここでは取り上げませんが、フックすることを検討すべきその他の関数には、VirtualAllocEx、VirtualProtectEx、HeapAlloc、WriteProcessMemory、NtWriteVirtualMemory などがあります。

 

割り当てられたメモリ領域を追跡する

監視対象の API を調査することに加えて、割り当てられたメモリ領域を綿密に追跡することで、実行可能コンテンツの検出精度を高めることができます。推奨されるのは、Frida フレームワークに永続的なメモリブレークポイントと同等のものを設定し、割り当てられた複数のメモリ領域から実行可能コンテンツを特定することです。これにより、あるメモリ領域のコンテンツが実行された時点で、そのことを把握できます。これに似た機能を提供するのがメモリアクセス監視 API です。この API は 1 つ以上のメモリ領域へのアクセスを監視し、アクセスが発生した場合に指定の関数を実行します。ただし残念ながら、初回のアクセス時にしか通知を行わない(ワンショット)ため、永続的なソリューションではありません。

この制約を回避するために、メモリ領域の配列を作成して、新しいメモリ領域の割り当てや参照を常時監視する方法があります。ここで VirtualAlloc や VirtualProtect などの API の呼び出しが発生した場合、このアドレスのリストを反復処理して、実行可能コンテンツの存在をチェックできます。これにより、実行中にメモリ内の複数の領域を監視するという、マルウェア解析の一般的なワークフローを自動化することができます。

以下のコードは、メモリ領域の配列を定義し、VirtualAlloc によって新しい領域が割り当てられた場合に配列への追加を実行するようなスクリプトの更新を示しています。

図 22:メモリ領域を追跡するオブジェクトの配列で更新された傍受コード
 

onEnter で「this」を使用することで、onLeave から引数へのアクセスを提供している点に注意してください。メモリ領域の配列の各オブジェクトがアドレスとサイズを必要とし、領域のサイズは引数内で VirtualAlloc に渡されます。

メモリ領域を追跡する配列の完全な実装は皆さんで考えてみてください。このアプローチを利用する場合、追加のコードで以下を実行する必要があります。

  • 追加する前に、配列内にメモリ領域が既に存在しているかどうかをチェックする。
  • 今後のアクセス違反を回避するため、VirtualFreeで解放されたメモリ領域を削除する。
  • 不要なメモリの評価やダンプの重複を防止するため、実行可能コンテンツの特定とダンプを正常に完了した後に、配列から要素を削除する。

 

おわりに

この記事では、動的バイナリ計装(DBI)フレームワーク Frida を使用したマルウェア解析の方法を紹介しました。組み込みツールの 1 つである frida-trace は、API 呼び出しの傍受と調査を実施する出発点として非常に適しています。さらに強力なソリューションを実現する場合は、Frida の Python バインディングを利用できます。今回はこれらを使用して複数のバイナリを解析し、メモリ内の実行可能コードを特定し、そのコンテンツをディスクにダンプしました。DBI フレームワークを使用することで、解析を容易に自動化し、アナリストによるカスタムツールのプロトタイピングと開発の期間を短縮することができます。

 

・お問い合わせ:https://www.blackberry.com/ja/jp/forms/enterprise/contact-us

・イベント/セミナー情報:https://www.blackberry.com/ja/jp/events/jp-events-tradeshows

・BlackBerry Japan:https://www.blackberry.com/ja/jp

The BlackBerry Research and Intelligence Team

About The BlackBerry Research and Intelligence Team

BlackBerry の Research and Intelligence Team は、新たに生じている脅威と持続的な脅威を検証し、セキュリティ担当者とその所属企業のために、インテリジェンス解析を提供しています。