WOW64!フック: WOW64 サブシステムの内部構造とフック手法

Native x64 Syscall Stub news

Microsoft は下位互換性で知られています。数年前に Windows の 64 ビット バージョンを展開したとき、既存の 32 ビット アプリケーションとの互換性を提供する必要がありました。アプリケーションのビット数に関係なくシームレスな実行を提供するために、WoW (Windows on Windows) システムが考案されました。これ以降「WOW64」と呼ばれるこのレイヤーは、すべての Windows API 呼び出しを 32 ビット ユーザー空間から 64 ビット オペレーティング システム カーネルに変換する役割を果たします。このブログ投稿は 2 つのセクションに分かれています。まず、WOW64 システムを深く掘り下げることから始めます。これを行うには、32 ビットのユーザー空間からの呼び出しをトレースし、最終的にカーネルに移行するために必要な手順に従います。記事の後半では、2 つのフッキング テクニックとその有効性を評価します。このシステムがどのように機能するか、マルウェアがそれをどのように悪用するかについて説明し、すべての WoW syscall をユーザー空間からフックできるメカニズムについて詳しく説明します。ここに記載されているすべての情報は、Windows 10 バージョン 2004 の時点で正しいものであり、場合によって、以前の Windows バージョンの実装方法から変更されていることに注意してください。

認識

何よりもまず、これは複数の著者による既存の研究があるトピックです。この作業は、内部構造を効率的に調査する上で非常に重要であり、これらの著者がすばらしい作業を公開していなければ、調査にさらに時間がかかっていたでしょう。次の参考文献を引用したいと思います。

  • ( wbenny ): ARM 上の WOW64 内部の非常に詳細なビュー
  • ( ReWolf ): PoC 天国の門の実装
  • ( JustasMasiulis ): 非常にクリーンな C++ 天国の門の実装
  • ( MalwareTech ): WOW64 セグメンテーションの説明

WOW64の内部

WOW64 システムが内部でどのように機能するかを理解するために、システム DLL 内からカーネルに移行する前に、32 ビット ユーザーモードで始まる呼び出しシーケンスを調べます。これらのシステム DLL 内で、オペレーティング システムは引数をチェックし、最終的に syscall スタブと呼ばれるスタブに移行します。この syscall スタブは、カーネルで API 呼び出しを処理します。 64 ビット システムでは、図 1 に示すように、syscall スタブは syscall 命令を直接実行するので単純です。

Native x64 Syscall Stub
図 1: ネイティブ x64 Syscall スタブ

図 2 は、WOW64 で実行されている 32 ビット プロセスの syscall スタブを示しています。

WOW64 Syscall スタブ
図 2: WOW64 Syscall スタブ

WOW64 バージョンの syscall 命令の代わりに、 Wow64SystemServiceCallが呼び出されることに注意してください。 WOW64 システムでは、通常はカーネルへのエントリが代わりにユーザーモード ルーチンの呼び出しに置き換えられます。このWow64SystemServiceCallに続いて、 Wow64Transitionという名前のポインターを介して間接的な jmp をすぐに実行することが図 3 でわかります。

Wow64SystemService はポインター「Wow64Transition」を介して遷移します
図 3: ポインタ ‘Wow64Transition’ による Wow64SystemService 遷移

Wow64SystemServiceCall関数は、ntdll_77550000 というラベルの付いた ntdll 内にあることに注意してください。 WOW64 プロセスには、32 ビット モジュールと 64 ビット モジュールの 2 つの ntdll モジュールがロードされています。 WinDbg は、32 ビット バリアントの後にモジュールのアドレスを配置することで、これら 2 つを区別します。 64 ビットの ntdll は %WINDIR%System32 にあり、32 ビットは %WINDIR%SysWOW64 にあります。 PDB では、64 ビットと 32 ビットの ntdll はそれぞれ ntdll.pdb と wntdll.pdb として参照されます。逆アセンブラーにロードしてみてください。コール トレースを続けます。Wow64Transitionポインターが保持するものを見ると、その宛先がwow64cpu!KiFastSystemCallであることがわかります。余談ですが、 wow64cpu!KiFastSystemCallのアドレスは、メンバー WOW32Reserved を介して 32 ビット TEB (スレッド環境ブロック) に保持されていることに注意してください。これは、このトレースには関係ありませんが、知っておくと便利です。図 4 は、 KiFastSystemCallの本体を示しています。

KiFastSystemCall は、セグメント セレクタ 0x33 を介して x64 モードに移行します
図 4: KiFastSystemCall は、セグメント セレクター 0x33 を介して x64 モードに移行します

KiFastSystemCallは、命令の直後に 0x33 セグメント セレクターを使用してメモリ位置への jmp を実行します。この 0x33 セグメントは、(MalwareTech) で説明されているように、GDT エントリを介して CPU を 64 ビット モードに移行します。

ここまでのトレースを要約してみましょう。 ntdll、NtResumeThread の呼び出しから開始しました。この関数は、Wow64Transition を実行する Wow64SystemServiceCall 関数を呼び出します。 KiFastSystemCall は、32 ビットから 64 ビットの実行への移行を実行します。フローを図 5 に示します。

32 ビットから 64 ビットへの移行
図 5: 32 ビットから 64 ビットへの移行

CPU トランジション ジャンプの宛先は、図 6 に示す 64 ビット コードです。

KiFastSystemCall の宛先
図 6: KiFastSystemCall の宛先

図 6 は、これまでにこの呼び出しトレースで実行された最初の 64 ビット命令を示しています。これを理解するには、WOW64 システムがどのように初期化されるかを調べる必要があります。詳細については、(wbenny) を参照してください。今のところ、 wow64cpu!RunSimulatedCodeの重要な部分を見ることができます。

64 ビット レジスタは RunSimulatedCode に保存されます
図 7: 64 ビット レジスタは RunSimulatedCode に保存されます

図 7 は、スロット インデックス 1 でスレッド ローカル ストレージにアクセスするために使用される 64 ビット TEB の検索を示しています。次に、関数ポインタ テーブルをレジスタ r15 に移動します。取得された TLS データは文書化されていないデータ構造WOW64_CPURESERVEDであり、WOW64 レイヤーが 32 ビットと 64 ビットの境界を越えてレジスタを設定および復元するために使用するレジスタ データと CPU 状態情報が含まれています。この構造内にはWOW64_CONTEXT構造があり、Microsoft の Web サイトで部分的に文書化されています。この投稿の最後に両方の構造をリストしました。このコンテキスト構造がどのように使用されるかについては後で説明しますが、jmp を理解するために知っておく必要があるのは、r15 が関数ポインター テーブルであることだけです。

ここで注目すべきは、WOW64 層のアーキテクチャです。 64 ビット カーネルの観点から見ると、32 ビット (Wow64) ユーザーモード アプリケーションの実行は、基本的に大きな while ループです。ループは、プロセッサの 32 ビット実行モードで x86 命令を実行し、システム コールを処理するために時々ループを終了します。カーネルが 64 ビットであるため、プロセッサ モードが一時的に 64 ビットに切り替えられ、システム コールが処理された後、モードが元に戻り、一時停止した場所からループが続行されます。 WOW64 レイヤーはエミュレーターのように機能し、代わりに物理 CPU で命令が実行されると言えます。

図 6 の jmp 命令に戻ると、何が起きているかがわかります。命令 jmp [r15 + 0xF8] は、C コードの jmp TurboThunkDispatch[0xF8 / sizeof(uint64_t)] と同等です。このインデックスの関数ポインターを見ると、関数wow64cpu!CpupReturnFromSimulatedCodeにいることがわかります (図 8)。

TurboThunk テーブルの最後の関数ポインタ エントリは終了ルーチンです
図 8: TurboThunk テーブルの最後の関数ポインター エントリは終了ルーチンです

このルーチンは、32 ビット レジスタの状態を前述のWOW64_CONTEXT構造体に保存し、syscall の引数を取得します。ここでややこしいことが起こっているので、これを詳しく調べてみましょう。まず、スタックへのポインターが xchg を介して r14 に移動されます。この場所の値は、 Wow64SystemServiceCallが呼び出された syscall スタブからの戻りアドレスになります。次に、スタック ポインター r14 が 4 だけインクリメントされ、これらすべてのコンテキスト値を復元するときにスタックをリセットする必要がある場所へのポインターが取得されます。これら 2 つの値は、コンテキストの EIP 変数と ESP 変数にそれぞれ格納されます。 r14 スタック ポインターは、__stdcall 引数がある場所を取得するためにもう一度インクリメントされます (stdcall はすべての引数をスタックに渡すことを思い出してください)。この引数配列は後で重要になります。覚えておいてください。引数ポインタは r11 に移動されるため、C では、r11 は各スロットが引数 uint32_t r11[argCount] であるスタック スロットの配列と同等であることを意味します。その後、残りのレジスタと EFlags が保存されます。

32 ビット コンテキストが保存されると、WOW64 レイヤーは、syscall 番号の上位 16 ビットを取得して呼び出す適切な TurboThunk を計算し、そのサンクにディスパッチします。この配列の先頭には関数TurboDispatchJumpAddressEndがあることに注意してください (図 9 を参照)。これは、TurboThunks をサポートしない関数に対して呼び出されます。

TurboThunk テーブルの最初の関数ポインタ エントリはエントリ ルーチンです
図 9: TurboThunk テーブルの最初の関数ポインター エントリはエントリ ルーチンです

TurboThunks は (wbenny) によって説明されています。まだ読んでいない場合は、この時点で彼のブログ投稿を読んでください。投稿を要約すると、幅が <= sizeof(uint32_t) の単純な引数を持つ関数の場合、WOW64 レイヤーはこれらの引数をゼロまたは符号拡張を介して 64 ビットに直接拡張し、カーネルに対して直接 syscall を実行します。これはすべて、以下に詳述するより複雑なパスを実行するのではなく、wow64cpu 内で発生します。これは最適化として機能します。 TurboThunks をサポートしないより複雑な関数の場合、図 10 に示すように、 wow64!SystemServiceExにディスパッチしてシステム コールを実行するTurboDispatchJumpAddressEndスタブが使用されます。

複雑なシステム コールは Wow64SystemServiceEx を経由します
図 10: Wow64SystemServiceEx を経由する複雑なシステム コール

このルーチンについては、このブログ記事の要点であるため、すぐに説明しますが、ここでは、この呼び出しトレースを終了させてください。システム コールの実行からWow64SystemServiceExが戻ると、eax の戻り値がWOW64_CONTEXT構造体に移動され、32 ビット レジスタの状態が復元されます。これには 2 つの方法があります。一般的なケースと、 NtContinueやその他の WOW64 内部でのみ使用されるように見えるケースです。図 11 に示すように、TLS スロットから取得されたWOW64_CPURESERVED構造体の先頭にあるフラグがチェックされ、たどる復元パスが制御されます。

システムコールが完了すると、CPU の状態が復元されます。 XMM レジスタを処理する単純なパスと複雑なパスがあります
図 11: システム コールが完了すると、CPU の状態が復元されます。単純なパスと XMM レジスタを処理する複雑なパスがあります

単純なケースでは、 WOW64_CONTEXTに保存されているすべてのレジスタを復元した後、セグメント セレクタ 0x23 を使用して 32 ビット モードに遷移する jmp を作成します。より複雑なケースでは、一部のセグメント、xmm 値、およびWOW64_CONTEXT構造体に保存されたレジスタをさらに復元し、iret を実行して元に戻ります。一度構築された一般的なケースの jmp を図 12 に示します。

32 ビット モードに戻るために動的に構築された jmp
図 12: 32 ビット モードに戻るように動的に構築された jmp

この時点で、コール トレースは完了です。 WOW64 レイヤーは 32 ビット モードに戻り、最初に使用した syscall スタブのWow64SystemServiceCallの後に ret で実行を継続します。 WOW64 レイヤー自体のフローを理解したので、前に説明したWow64SystemServiceEx呼び出しを調べてみましょう。

Wow64SystemServiceExルーチンに少し触れた図 13 は、後で使用するいくつかの興味深いロジックを示しています。

システムコールのディスパッチの前後に呼び出されるロギング ルーチン
図 13: syscall のディスパッチの前後に呼び出されるロギング ルーチン

このルーチンは、渡された引数配列を通常の 64 ビット システム モジュールが期待するより広い 64 ビット型に変換するルーチンへのポインタを保持するサービス テーブルにインデックスを作成することから始まります。この引数配列は、r14 で以前に格納されたスタック スロットとまったく同じです。

LogService関数への 2 つの呼び出しが存在しますが、これらは DLL %WINDIR%system32wow64log.dll がロードされ、Wow64LogInitialize、Wow64LogSystemService、Wow64LogMessageArgList、および Wow64LogTerminate のエクスポートがある場合にのみ呼び出されます。この DLL はデフォルトでは Windows に存在しませんが、管理者権限で配置できます。

次のセクションでは、このロギング DLL を使用して、この wow64layer を通過するシステムコールをフックする方法について詳しく説明します。ログ ルーチンLogServiceは、syscall が処理される前後に呼び出されるため、引数と戻り値を検査できる、標準的な外観のインライン フック スタイルのコールバック関数を実現できます。

インライン フックのバイパス

このブログ投稿で説明されているように、Windows は 32 ビット アプリケーションが WOW64 レイヤーを使用して 64 ビット システム上で 64 ビット syscall を実行する方法を提供します。ただし、前述のセグメンテーション スイッチは手動で実行でき、64 ビット シェルコードを記述して syscall をセットアップできます。このテクニックは、一般に「ヘブンズ ゲート」と呼ばれています。 JustasMasiulis の作品call_function64を参照して、これが実際にどのように行われるかを確認できます (JustasMasiulis)。システム コールがこの方法で実行されると、WOW64 レイヤーが使用する 32 ビットの syscall スタブは、実行チェーンで完全にスキップされます。これらのスタブにあるインライン フックもバイパスされるため、これはセキュリティ製品やトレース ツールにとっては残念なことです。マルウェアの作成者はこれを知っており、場合によっては「Heaven’s Gate」をバイパス手法として利用しています。図 14 と図 15 は、WOW64 レイヤーを通過する通常のシステムコール スタブと、マルウェアが「Heaven’s Gate」を利用するフックされたシステムコール スタブの実行フローを示しています。

NtResumeThread が WOW64 レイヤーを通過する
図 14: WOW64 レイヤーを通過する NtResumeThread
WOW64 レイヤーを通過する前の NtResumeThread インライン フック
図 15: WOW64 レイヤーを通過する前の NtResumeThread インライン フック

図 15 に示すように、Heaven’s Gate 手法を使用すると、インライン フックと WOW64 レイヤーが完了したに実行が開始されます。これは効果的なバイパス手法ですが、ドライバーやハイパーバイザーなどの下位レベルから簡単に検出できます。インライン フックへの最も簡単なバイパスは、通常はディスク上のバイトから元の関数バイトを復元することです。 AgentTesla や Conti などのマルウェアは、この最後の回避手法を利用することが知られています。

インライン フックによる WOW64 のフック

マルウェア アナリストとして、サンプルが WOW64 レイヤーをバイパスしようとするタイミングを検出できることは非常に役立ちます。これを検出する明白な手法は、64 ビット syscall スタブと 32 ビット syscall スタブにインライン フックを配置することです。 64 ビット フックが 32 ビット フックを通過しなかった呼び出しを検出した場合、サンプルがヘブンズ ゲートを利用していることがわかります。この手法は、前述の回避手法の両方を検出できます。しかし、実際には、これを実装するのは非常に困難です。 64 ビットの syscall スタブをフックするために満たさなければならない要件を見ると、次のリストが表示されます。

  1. 32 ビット モジュールから 64 ビット フックをインストールする
    • 32 ビット モジュールから 64 ビット アドレス空間をどのように読み書きしますか?
  2. 32 ビット モジュールから 64 ビット コールバックを実装する
    • 通常、インライン フックは C 関数をコールバック スタブとして使用しますが、32 ビット モジュールをコンパイルしているので、必要な 64 ビット コールバックの代わりに 32 ビット コールバックを使用します。

最初の課題を解決するために、ntdll は親切にもエクスポートNtWow64ReadVirtualMemory64NtWow64WriteVirtualMemory64 、およびNtWow64QueryInformationProcess64を提供します。これらを使用して、メモリの読み取り、メモリの書き込み、および 32 ビット プロセスからの 64 ビット モジュールの PEB の取得が可能です。ただし、適切なビット数のコールバック スタブを作成するには、シェルコードまたは JIT のいずれかが必要になるため、2 番目の課題ははるかに困難です。実際には、これには ASMJIT を利用できます。ただし、これは多数の API をトレースするには非常に面倒な手法です。この技術には他にも課題があります。たとえば、最新の Windows 10 では、ntdll64 のベース アドレスは、Windows 7 のように下位 32 ビット アドレスではなく上位 64 ビット アドレスに設定されています。必要なメモリ範囲内でトランポリンを割り当てることは困難です。これは、標準の ret 命令が 64 ビットの戻りアドレスを表すのに十分なビットをスタック上に持っていないためです。

余談ですが、WOW64 レイヤーには、 NtWow64*関数を処理する際のバグと思われるものが含まれていることに注意してください。これらの API はすべて、最初の引数としてHANDLEを取り、64 ビットに符号拡張する必要があります。ただし、これはこれらの API では発生しないため、疑似ハンドル -1 を使用すると、呼び出しはSTATUS_INVALID_HANDLEで失敗します。このバグは、未知の Windows 10 バージョンで導入されました。これらの API を正常に使用するには、 OpenProcessを使用して実数の正の値のハンドルを取得する必要があります。

この投稿はすでに非常に長いため、64 ビット syscall スタブをインライン フックする方法の内部については説明しません。代わりに、フック ライブラリPolyHook2を拡張して、これらの Windows API を使用してクロスアーキテクチャ フックをサポートする方法を示し、残りは読者の演習として残します。これが機能するのは、PolyHook のトランポリンが +-2GB に制限されておらず、レジスタを台無しにしないためです。それがどのように達成されるかの内部構造は、別の投稿のトピックです。図 16 は、polyhook の C++ API をオーバーロードして、前述の WinAPI を使用してメモリを読み書きする方法を示しています。

64 ビット メモリの読み取り/書き込み/保護のためのメモリ操作のオーバーロード
図 16: 64 ビット メモリの読み取り/書き込み/保護のためのメモリ操作のオーバーロード

これらのインライン フックが 64 ビット syscall スタブに配置されると、Heaven’s Gate を利用するすべてのアプリケーションが適切に傍受されます。このフック手法は非常に侵襲的で複雑であり、サンプルが 64 ビット モジュールの syscalls スタブを使用するのではなく syscall 命令を直接実行する場合は、依然としてバイパスされる可能性があります。したがって、ドライバーまたはハイパーバイザーは、この回避手法を検出するのに適しています。代わりに、より一般的なバイト復元回避手法に焦点を当て、WOW64 レイヤー自体をフックする方法を探すことができます。これには、アセンブリの変更はまったく含まれていません。

LogService 経由で WOW64 をフックする

WOW64 レイヤーの実行フローを振り返ってみると、ロギング DLL がロードされている場合、 Wow64SystemServiceExルーチンを介して送信されるすべての呼び出しがルーチンWow64LogSystemServiceを呼び出す可能性があることがわかっています。このロギング DLL とルーチンを使用して、アセンブリを変更することなく、インライン フックとまったく同じ方法で記述できるフックを実装できます。

これを実装するための最初のステップは、ログ ルーチンが呼び出されるように、 Wow64SystemServiceExルーチンを介してすべての API 呼び出しパスを強制することです。 TurboThunks をサポートするものは、このパスを使用しないことを前に思い出してください。幸運なことに、TurboDispatchJumpAddressEnd を指すTurboThunkエントリはすべてこのパスを使用することがわかっています。したがって、TurboThunk テーブル内のすべてのエントリがそのアドレスを指すようにすることで、目的の動作が実現されます。図 17 に示すように、Windows はwow64cpu!BTCpuTurboThunkControlを介してこのパッチを実装しています。

TurboThunk テーブルへのパッチ適用が実装されました
図 17: TurboThunk テーブルへのパッチ適用が実装されました

以前の Windows バージョンでは、これをエクスポートしたモジュールとその方法が Windows 10 バージョン 2004 とは異なることに注意してください。このパッチ ルーチンを呼び出した後、WOW64 を介したすべてのシステムコール パスはWow64SystemServiceExを通過し、マンインするロギング DLL の作成に集中できます。 -the-middles (MITM) すべての呼び出し。ここで考慮すべき課題がいくつかあります。

  1. ログ DLL から現在発生しているシステム コールを特定するにはどうすればよいでしょうか。
  2. コールバックはどのように記述されますか? Wow64log は 64 ビットの DLL です。32 ビットのコールバックが必要です。
    • シェルコードが必要ですか、それとも素敵な C スタイルの関数コールバックを作成できますか?
  3. どの API を呼び出すことができますか?読み込まれるのは 64 ビットの ntdll だけです。

最初の懸念事項はかなり簡単です。wow64log DLL 内から、syscall スタブから syscall 番号を読み取って、番号から名前へのマップを作成できます。これが可能なのは、syscall スタブが常に同じアセンブリで開始され、syscall 番号が 0x4 の静的オフセットにあるためです。図 18 は、このマップの値を、 Wow64LogSystemServiceのパラメーター構造WOW64_LOG_SERVICEに渡されたシステムコール番号と比較する方法を示しています。

typedef uint32_t* WOW64_ARGUMENTS;
構造体 WOW64_LOG_SERVICE
{
uint64_t BtLdrEntry;
WOW64_ARGUMENTS 引数。
ULONG ServiceTable;
ULONG サービス番号;
NTSTATUS ステータス。
BOOLEAN PostCall;
};

EXTERN_C
__declspec(dllexport)
NTSTATUS
NTAPI
Wow64LogSystemService(WOW64_LOG_SERVICE*サービス)
{
for (uint32_t i = 0; i < LAST_SYSCALL_ID; i++) {
const char* sysname = SysCallMap[i].name;
uint32_t syscallNum = SysCallMap[i].SystemCallNumber;
if (ServiceParameters->ServiceNumber != syscallNum)
継続する;
//ログ システム名
}
}

図 18: どの syscall が発生しているかを判断する最小限の例 — 実際には、サービス テーブルもチェックする必要があります

コールバックを書くのは少し難しいです。 wow64log DLL は 64 ビット モードで実行されています。追加の 32 ビット モジュールを WOW64 プロセスにロードするのは非常に簡単なので、32 ビット モードでコールバックを記述できるようにしたいと考えています。これを処理する最善の方法は、32 ビット モードに戻ることができるシェルコードを記述し、コールバックを実行してから、64 ビット モードに戻って wow64log DLL で実行を継続することです。この時点では、セグメント遷移自体はかなり簡単です。ジャンプするときに 0x23 または 0x33 セグメント セレクターを使用するだけでよいことがわかっています。しかし、64 ビットと 32 ビットの呼び出し規約の違いにも対処する必要があります。したがって、シェルコードは、64 ビット引数のレジスタ/スタック スロットを 32 ビット引数レジスタ/スタック スロットに移動する役割を果たします。すべての引数がスタック上にあり、シェルコードがスタックのレイアウトとクリーンアップを完全に制御できるため、32 ビットのコールバックを __cdecl のみにするように強制すると、これが容易になります。図 19 は、各呼び出し規約の引数の場所を示しています。最初の 4 つの引数が再配置されると、それ以降のすべての引数をループ内で移動できます。これは、単にスタック値を下のスロットに移動するだけだからです。これは、MSVC の外部 masm ファイルを使用して比較的簡単に実装できます。アーキテクチャが混在しているため、アセンブラを使用するのではなく、生のバイトをポイントで発行する必要があります。あるいは、GCC または Clang インライン アセンブリを使用することもできます。 ReWolf の作業は、32 ビット -> 64 ビットの反対方向を達成し、msvc インライン asm を介してシェルコードを実装します。 X64 MSVC はこれをサポートしておらず、その方法を使用すると REX プレフィックスが複雑になります。外部の masm ファイルを使用し、リンカーに依存してこのシェルコードを実装する方が適切です。

引数番号

Cdecl の場所

ファストコールの場所

特別なケース?

0

[ebp + 8]

rcx

はい

1

[ebp + 12]

rdx

はい

2

[ebp + 16]

r8d

はい

3

[ebp + 20]

r9d

はい

4

[ebp + 24]

[rbp + 32 + 8]

いいえ

5

[ebp + 28]

[rbp + 32 + 16]

いいえ

6

[ebp + 32]

[rbp + 32 + 24]

いいえ

図 19: Cdecl と Fastcall の引数の位置

このシェルコードを記述して適切な C++ 関数にラップすると、wow64log DLL は単純な C スタイルの関数ポインター呼び出しを介してコールバックを呼び出すことができます (図 20 参照)。

call_function32 はシェルコードを呼び出して、64 ビット ロギング DLL から 32 ビット コールバックを呼び出します。
図 20: call_function32 はシェルコードを呼び出して、64 ビット ログ DLL から 32 ビット コールバックを呼び出します。

32 ビット コールバック内から、任意の MITM 操作を実行できますが、呼び出し可能な API には制限があります。 WOW64 レイヤーが実行するコンテキストの保存により、WOW64 レイヤーに再び入る 32 ビット API は、コンテキスト値が破損するため、呼び出されない場合があります。したがって、64 ビット ntdll からエクスポートされたものである WOW64 に再入力しない API のみに限定されます。 NtWriteFileエクスポートを使用すると、stdout またはファイルに簡単に書き込むことができますが、64 ビット実行モードに再度入り、以前と同様に逆の引数マッピングを行う必要があります。このロギング ルーチンは、32 ビット コールバック内から呼び出すことができ、図 21 と図 22 に示されています。

call_function64 はシェルコードを呼び出して、32 ビットのコールバックで 64 ビットの WriteFile を呼び出します。
図 21: call_function64 はシェルコードを呼び出して、32 ビット コールバックで 64 ビット WriteFile を呼び出します。
32 ビットのコールバックは、再入不可の WOW64 API のみを呼び出すルーチンを介してログに記録する必要があります
図 22: 32 ビット コールバックは、再入不可の WOW64 API のみを呼び出すルーチンを介してログに記録する必要があります

その結果、インライン フックとまったく同じように機能するクリーンなコールバック スタブが作成されますが、アセンブリの変更は必要ありません。引数も簡単に操作できますが、スタック ウォーク ハッカーを少し実装しない限り、戻りステータスを変更できない場合があります。その他の唯一の考慮事項は、wow64log DLL 自体が CRT メカニズムでビルドされないように慎重に作成する必要があることです。必要なフラグは次のとおりです。

  • /NODEFAULT LIB で CRT を無効にし (すべての C API が使用できなくなりました)、新しいエントリ ポイント名を CRT NtDllMain を初期化しないように設定します。
  • すべての CRT セキュリティ ルーチンを無効にする /GS-
  • C++ 例外を無効にする
  • デフォルトのリンカー ライブラリを削除し、ntdll.lib のみをリンクします
  • extern “C” __declspec(dllimport) <typedef> を使用して、正しい NtApis にリンクします

wow64log インライン フックを介して独自のシステム コールをフックするプログラムの例を図 23 に示します。

インライン フックの動作のデモンストレーション
図 23: 動作中のインライン フックのデモンストレーション

結論

インライン WOW64 フック、wow64log フック、およびカーネル/ハイパーバイザー フックを使用すると、ユーザーモード フック回避のすべての手法を簡単かつ自動的に特定できます。フックのどのレイヤーがスキップまたはバイパスされているかを検出すると、どの回避手法が使用されているかがわかります。識別テーブルは次のとおりです。

回避モード

32ビットインライン

wow64ログ

64ビットインライン

カーネル/ハイパーバイザー

プロローグ リストア

ヘブンズゲート sys-stub

ヘブンズゲートダイレクトシスコール

構成 付録

struct _WOW64_CPURESERVED
{
USHORT フラグ。
USHORT MachineType;
WOW64_CONTEXT コンテキスト。
char ContextEx[1024];
};

typedef ULONG *WOW64_LOG_ARGUMENTS;
構造体 _WOW64_SYSTEM_SERVICE
{
unsigned __int32 SystemCallNumber : 12;
unsigned __int32 ServiceTableIndex : 4;
unsigned __int32 TurboThunkNumber : 5;
unsigned __int32 AlwaysZero : 11;
};
#pragma pack(プッシュ、1)
構造体 _WOW64_FLOATING_SAVE_AREA
{
DWORD ControlWord;
DWORD StatusWord;
DWORD タグワード;
DWORD エラーオフセット;
DWORD エラーセレクター;
DWORD データオフセット;
DWORD データセレクター;
BYTE RegisterArea[80];
DWORD Cr0NpxState;
};
#pragma pack(ポップ)

#pragma pack(プッシュ、1)
構造体 _WOW64_CONTEXT
{
DWORD ContextFlags;
DWORD Dr0;
DWORD Dr1;
DWORD Dr2;
DWORD Dr3;
DWORD Dr6;
DWORD Dr7;
WOW64_FLOATING_SAVE_AREA FloatSave;
DWORD SegG;
DWORD SegF;
DWORD SegE;
DWORD SegD;
DWORD エディ;
DWORD Esi;
DWORD Ebx;
DWORD Edx;
DWORD Ex;
DWORD Eax;
DWORD Ebp;
DWORD Eip;
DWORD SegC;
DWORD EFlags;
DWORD Esp;
DWORD SegS;
BYTE ExtendedRegistersUnk[160];
M128A Xmm0;
M128A Xmm1;
M128A Xmm2;
M128A Xmm3;
M128A Xmm4;
M128A Xmm5;
M128A Xmm6;
M128A Xmm7;
M128A Xmm8;
M128A Xmm9;
M128A Xmm10;
M128A Xmm11;
M128A Xmm12;
M128A Xmm13;
M128A Xmm14;
M128A Xmm15;
};
#pragma pack(ポップ)

参照: https://www.mandiant.com/resources/blog/wow64-subsystem-internals-and-hooking-techniques

Comments

タイトルとURLをコピーしました