196の日記

セキュリティ系の勉強、その他開発メモとか雑談、忘れそうな計算式などを書き溜める場所になっています. githhubはUnity触っていた頃ものがメイン https://github.com/196kakinuma

アナライジングマルウェア 3動的解析を妨害するマルウェアの解析 メモ

動的解析(ブラックボックス解析)はマルウェアの挙動を容易に把握することができるが、マルウェアはこうした動的解析を妨げるアンチデバッグ機能を持っている。

動的解析のソフトウェア

仮想化ソフトウェア
システムリソース監視ツール

Process Monitor, Process Explorer

デバッガ

動的解析ツールの検出

動的解析する際はシステムリソース(ファイル、レジストリ、ネットワークなど)を監視するツールやデバッガを利用するが、マルウェアはこうした監視ツールのプロセスを検出することで、それらを停止させるまたは感染動作を止めてしまう等を行う。

  1. プロセス一覧から動的解析用ツールを探し出す方法

マルウェアはWin32API等のAPIを用いてデバッガや監視ツールのプロセスを探そうとする。
例えば、CreateToolhelp32Snapshotを呼び出してプロセスの一覧を取得し、Process32FirstとProcess32Nextを呼び出して動作中のプロセスを見ていく。
【回避策】
・実行ファイル名を変更する→マルウェアがプロセスのszExeFileのみを確認している場合に有効
・Process32FirstでFalse(0)を返す→デバッガよりモジュールを書き換える。kernel32内のProcess32Firstをたどってバイナリ等の返り値変更する。

  1. ウィンドウを検出する方法

多くの監視ツールやデバッガはGUIを実装するためウィンドウを備えているためこれを検出する。EnumWindows、FindWindow等の関数でウィンドウを列挙可能。ここからGetClassName等で名前を取得しブロックする。
【回避策】
・上と同じ方法でGetClassNameの返り値を変更する。
・常にFalseが返るようにGetClassNameのエントリポイントを以下の機械語命令列に変更する。

XOR EAX, EAX //EAXを0にする
RETN 0C //MS stdcall規則に従うため引数のサイズぶん(12バイト)巻き戻す必要がある
  1. 動的解析ツールが利用するデバイスファイルを検出する方法

カーネルモードで動作する監視ツールやデバッガには各ツール固有のデバイスファイルを生成するものがある。このファイルの存在を確認することで監視ツールを検出できる。例えばProcess Monitorは ¥¥.¥Global¥ProcmonDebugLoggerという名前のデバイスファイルを作成する。CreateFileというAPIを使用すると確認可能。
【回避策】
CreateFileはよく呼ばれる関数なので常にFalseを返すようになると、不都合。
CreateFileにデバッガから条件付きブレークポイントをうって、必要な場合のみレジスタの値を変更する。


  1. タイマーによるチェック

実行時間を計測することで、ステップ実行などが行われていることを検知する。Win32APIでは、GetTickCountを使用することで時間を取得可能。
【回避策】
例えば条件付きブレークポイントの処理を自動化することで時間を短縮する。
時刻を計測するWin32APIは多数あるため偽ると帳尻が合わなくなる可能性がある。

  1. RDTSC命令

Read Time Stamp Counter命令は、Intel Architecture32(IA-32)における機械語命令で現在の計画ロックサイクルを64ビットの値としてEDXならびにEAXレジスタに格納する。
【回避策】
ユーザモードでRDTSC命令を実行できるがCR4レジスタのTSD(Time Stamp Disable)フラグをセットするとで、この命令の実行を特権レベル0だけに限定できる。これによりユーザモードで呼ばれた場合に、GeneralProtection(GP) exception例外を発生するようになる。この例外を捕まえることで効率的に命令の挙動を変化させることができる。「Fake RDTSC」はこれを手助けしてくれるツール。http://deroko.phearless.org/ring0.html (2018現在ダウンロードできた)


  1. マルウェアの再起動

デバッガの追跡を振り切る。新しくプロセスを生成されるとデバッグすることができない。
余談としてマルウェアが感染したばかりの時によく観測される挙動がシステムディレクトリへのインストール(自分自身のコピー)と再起動。Windowsのシステムディレクトリには多くのexeファイルがあったりPathが通っていたりと身を隠すには便利な場所。システムディレクトリへのインストール等が目的で再起動を起こしている可能性もある。

【回避策】
マルウェアがどのように「自分が再起動した」かを判断しているかを確認して対処する。



  1. デバッガの検出

IsDebuggerPresentという自身がデバッグされているかチェックできるAPIがある。
【回避策】
・返り値を書き換える。
・この関数はThread Environment Block(TEB)構造体のオフセット0x30にあるProcess Environment Block(PEB)構造体のオフセット0x2を取得しそれを戻り値としている。具体的にはBeingDebuggedという値なのでその値を0にすればok. ここはデバッグされているかどうかのフラグが格納されている。中にはこの構造体を直接見るマルウェアもあるためここを書き換えるが吉。


PEB!NtGlobalFlagsには様々なAPIがどのように振る舞うかを決めるためのフラグが用意されている。デバッグされていない状態では0、されている状態では書籍では0x70が格納されていた。
【回避策】
上に同じ。



CheckRemoteDebuggerPresentという指定したプロセスがデバッグされているかどうかを問い合わせるためのAPIWindowsには用意されている。内部でNtQueryInformationProcessを呼び出し、カーネルに対象がデバッグされているかを問い合わせる。
【回避策】
NtQueryInformationProcessに対して第2引数が7(ProcessDebugPort)の場合に条件付きブレークポイントを設定して、NtQueryInformationProcessから戻る時に第3引数が指す4バイトのデータを0にすればok.




  1. 仮想化ソフトウェア上での動作の検出

VMWareバックドアI/Oポート
VMWare等にはゲストとホストOSとの間でやり取りをするためにバックドアI/Oポートという仕組みがある。この仕組みがあるかを確認することで、仮想上で実行されているか確認ができる。ある命令を実行すると、VMWareバージョン等が取得できるのでそれを基準に判断する。具体的にはEAXレジスタに'VMXh', ECXにコマンド番号、EBXにコマンド引数を指定し、IN命令を実行するとEAXとECXにバージョン情報、EBXに'VMXh'を格納して制御を返してくれる。

__asm {
    push edx
    push ecx
    push ebx
    mov eax, 'VMXh'
    mov ebx, 0        //コマンド引数がないため0
    mov ecx, 10  //コマンド番号
    mov edx, 'VX'    //ポート番号
    in eax, dx           //read port
    cmp ebx, 'VMXh' 
    setz [rslt]           //return valueのセット
    pop ebx
    pop ecx
    pop edx 
}

また、ユーザモードでIN命令を実行しようとすると例外が発生するため、これをキャッチできるかで仮想環上かを確認する手法もある。
【回避策】
値を見て判断している場合、ホストOSの仮想マシンイメージ保存先にあるゲストの名前.vmxファイルに以下の行を追加する。

isolation.tools.getVersion.disable = "TRUE"

上の対策では例外を再現できないため別の手法を用いる。
VMWareでは1つのゲストOSにつき1つのvmware-vmx.exeプロセスがホスト側で動作する。このプロセス内には特定の値がシグネチャ'VMXh'かどうかをチェックし、条件分岐するコードが存在する。

cmp large dword ptr ds:0D018h, 'VMXh'
jz  loc_7CF2FF

このjz命令をNOP命令に変更することで上の2つに対応が可能。ホスト側のImmunity Debugger等からvmware-vmx.exeにアタッチする。



IDT,LDTアドレスの確認
IA-32アーキテクチャではInterrupt Descript Table(IDT)と呼ばれる割り込みや例外発生時に呼び出す関数のテーブルがあるが、これは1CPU(1コア)に一つしか存在しない。VMWareではアクセラレーション機能が有効な場合実PCとは違った値になるらしい。これを確認することで(Red Pillというjoanna氏のコードがある)判断ができるが、IDTはプロセッサごとに割り当てられるため、マルチプロセッサの現在では識別に使用するのば難しい。
代用策としてLocal Descriptor Table(LDT)がある。実PCでは0が格納されているが、仮想マシン上では0以外の値になっている。
【回避策】
VMWareアクセラレーション機能を無効にするか、Intel VTやAMD-Vといったハードウェア仮想化機能を有効にすることで解消可能。以下の行を.vmxに追加。

monitor.virtual_exec = "hardware"
  1. 様々な例外発生への対処

例外を補足するコードを用意しておき意図的に例外を発生させることで例外を補足するコードに制御がうつったかを確認するものがある。try exceptで例外をキャッチしそれをデバッガで補足した場合、except内に書かれているコードは実行されないため、except内のコードが実行されたか(フラグが変化したか等)を確認することでデバッグされているかを判断



  1. APIに設定されたブレークポイント(INT3)の検出

ソフトウェアブレークポイントではプログラムの処理を中断したい箇所にINT3命令(0xCC)を設定する。これを実行しようとすると例外が発生し、それを補足した後で元の値を復元、ステップ実行させて再度INT3を設定する。Win32APIを呼び出す前に、その命令の先頭がINT3命令かをチェックすることでデバッグされているか判断する。
【回避策】
先頭より後ろにINT3命令を設定する。
デバッガの代わりにINT3命令の挙動をINT3以外の命令で実装する。