
RustベースのPoC。Windowsファイバーを使用してメモリ内のコードをステルス実行し、制御ファイバーとペイロードファイバーを切り替えることでカーネルコールバックを介さずにEDRからペイロードスタックを隠蔽します。
ファイバーとは、Windowsに組み込まれている優先度ベースのスケジューリング機構に依存するのではなく、アプリケーションが手動でスケジュールする必要がある実行単位です。ファイバーは、軽量スレッドと呼ばれることもよくあります。ファイバーとは何か、どのように動作するかの詳細については、公式ドキュメントを参照してください。
ファイバーを使用すると、1つのスレッド内で複数の実行フローを持つことができ、それぞれが独自のレジスタ状態とスタックを持ちます。一方、ファイバーはカーネルからは不可視であり、新しいスレッドを生成するよりもステルス性が高く(かつ安価な)インメモリコード実行方法となります。
1つのスレッドは複数のファイバーを作成でき、SwitchToFiber関数を呼び出すことで好きなタイミングでそれらを切り替えることができます。その前に、現在のスレッド自体がファイバーになる必要があります(ConvertThreadToFiberを呼び出す)。ファイバーのみが他のファイバーを作成できるからです。最後に、スケジュールされたときにインメモリコード(たとえば、リフレクティブにPEをロードした後やシェルコードなど)を実行するファイバーを作成するには、CreateFiberを呼び出すだけで済みます。
SwitchToFiber関数はこのプロセスで最も重要な部分であり、すべての魔法が起こる場所です。この関数を使用すると、あるファイバーを別のファイバーにスケジュールすることができ、すべてユーザー空間で行われます。公式ドキュメントによると、「SwitchToFiber関数は現在のファイバーの状態情報を保存し、指定されたファイバーの状態を復元します」。つまり、この関数が呼び出されると、レジスタ値とスタックが現在のファイバー状態から対象ファイバー状態に切り替わり、プロセスが完了すると現在のファイバーのスタックを「隠す」ことができます。これにより、実行が停止されたポイントから対象ファイバーの実行を継続することも可能になります(スケジューラが独自の優先度ロジックに従ってスレッドを切り替えるのと同じように)。
そして、これこそがこのシンプルなPoCが行っていることです:
run()関数を実行する新しいファイバーを作成します。このファイバーは以降ペイロードファイバーと呼ばれます。このプロセスは無限に繰り返されます。
ファイバーの使用は、一部のタイプのペイロード(C2ビーコンなど)にとって、以下のような理由で有利になる可能性があります:
JMPやCALL命令を使用せずにインメモリコードを実行できます。文字列リテラルを難読化するためにLITCRYPTプラグインを使用しているため、コードをコンパイルする前に環境変数LITCRYPT_ENCRYPT_KEYを設定する必要があります:
C:\Users\User\Desktop\Fiber> set LITCRYPT_ENCRYPT_KEY="yoursupersecretkey"
その後、ペイロードとローダーの両方をコンパイルし、最後のものを実行するだけです:
C:\Users\User\Desktop\Fiber\payload> cargo build --release
C:\Users\User\Desktop\Fiber\loader> cargo build --release
C:\Users\User\Desktop\Fiber\loader\target\release> loader.exe
このPoCの実行にはあまり神秘性はありません。必要なことは、ローダーを実行し、ProcessHackerなどのツールを使用してスレッドスタックを検査することだけです。ペイロードはスリープの前にコントロールファイバーに戻るため、ペイロードファイバーのスタックはほとんどの時間隠されたままになります。出力では、既に説明したロジックに従って2つのファイバーが連続してスケジュールされるのを見ることができます。
コードにはコメントがあり、ファイバーの使用方法、作成方法、スケジュール方法が示されています。例として提供されているローダーとペイロードの両方が無限ループに「はまって」おり、ファイバー間を無期限に切り替えて実行を継続できることに気付くでしょう。
別のペイロードをテストしたい場合は、ローダーのファイルsrc::main.rsの32行目にあるパスを変更するだけです。その場合、新しいDLLは入力パラメータとしてコントロールファイバーのアドレスを受け取るrun(PVOID)関数をエクスポートする必要があります。この関数はSleep関数を呼び出すためにコントロールファイバーに戻る必要がありますが、必要に応じてこの動作を自由に変更できます。
ランダムなペイロードでこのツールをテストする別の方法は、IATフッキングを実行して、ペイロードによるSleep関数(または他のインポートされた関数)への呼び出しをローダー上の関数にリダイレクトし、その呼び出しが発生したときにコントロールファイバーに戻れるようにすることです。お任せします。
以下のスクリーンショットでは、ファイバーを切り替えるにつれて、現在のスレッドのスタックがあるプライベートメモリ領域から別の領域に移動する様子を見ることができます:
