PoC(概念実証)として、私はWindows Defenderを分離し、今ではコードをFlying Sandbox Monsterとしてパブリックドメインに投稿しています。 Flying Sandbox Monsterの基礎はAppJailLauncher-rs です 。これは、 信頼できないアプリケーションをAppContainersに入れるためのRustフレームワークです。 また、I / OアプリケーションをTCPサーバーに移動して、サンドボックス内のアプリケーションが完全に異なるマシンで実行されるようにすることもできます。 これは、分離の追加レベルです。
この記事では、このツールを作成するプロセスと結果を説明し、WindowsでのRustについての考えを述べます。
Flying Sandbox Monsterは、WannaCryバイナリをスキャンするために、サンドボックスでWindows Defenderを起動します
計画
Windows Defenderのシステムリソースへの妨害されないアクセスと悪意のあるファイル形式の受信により、Windows Defenderは攻撃者にとって理想的な標的になります。 MsMpEngプログラムの主要なプロセスは、SYSTEM特権を持つサービスとして機能します。 MpEngineスキャンコンポーネントは、天文学的な数のファイル形式の解析をサポートしています。 また、さまざまなアーキテクチャ用のシステムエミュレータと、さまざまな言語用のインタプリタも持っています。 これらすべてがWindowsの最高レベルの特権で実行されます。 おっと。
このすべてが私に考えさせました。 2年前にCTFコミュニティサンドボックスコンペティションで使用したのと同じ手法を使用して MpEngineを分離するのはどれほど難しいでしょうか?
Windows Defenderを分離する最初のステップは、AppContainersを起動する機能です。 AppJailLauncherをもう一度使用したいのですが、問題がありました。 オリジナルのAppJailLauncherはデモとして作成されました。 そのことがわかっていれば、メモリ管理に悩まされないように、 C ++ Coreで記述します。 過去2年間、C ++で書き直そうとしましたが、失敗しました(なぜ依存関係が常に頭痛の種なのですか?)。
しかし、それから私はインスピレーションを得ました 。 RustでAppContainerスタートアップコードを書き直してみませんか?
サンドボックスの作成
数か月後、Rustチュートリアルの簡単な研究と最初のコードの作成の後、RustでAppContainersを実行するための3つの柱、
SimpleDacl
、
Profile
および
WinFFI
あり
WinFFI
。
- SimpleDaclは、Windows用の単純な任意のACEアクセス制御エントリの追加と削除を処理する汎用クラスです。
SimpleDacl
はファイルとディレクトリの両方で機能しますが、いくつかの欠点があります。 まず、既存のACLを完全に書き換え、継承されたACEを「通常」に変換します。 さらに、解析できないACE要素を無視します(たとえば、AccessAllowedAce
およびAccessDeniedAce
を除くすべて。注: 必須および検証アクセス制御エントリはサポートしていません)。
- プロファイルは、プロファイルの作成を実装し、AppContainerを処理します。 プロファイルから、AppContainerがアクセスできるリソースのACEを作成するために使用できるSIDを取得できます。
- WinFFIは、 winapi-rsでも有用なユーティリティクラス/関数ほど実装されていない関数と構造を提供します。 Rustオブジェクト内のすべてのソース
HANDLE
とポインターをラップして、それらが機能する時間を制御することに多大な労力をHANDLE
。
次に、Windows Defenderのスキャンコンポーネントとのインターフェイスを確立する方法を理解する必要がありました。 Cでの実装例とMsMpEngのスキャンを開始する手順は、Tavis Ormandy ロードライブラリリポジトリにありました。 構造体と関数のプロトタイプをRustに移植するのは簡単ですが、最初は配列フィールドと関数ポインターを忘れてしまい、多くのさまざまな問題が発生しました。 しかし、Rustの組み込みテスト機能のおかげで、すべての移植エラーをすばやく解決しました。すぐに、EICARテストファイルをスキャンする最小限のテストケースができました 。
Flying Sandbox Monsterの基本アーキテクチャ
Flying Sandbox Monsterの例は、サンドボックスラッパーとマルウェア保護エンジン(MpEngine)で構成されています。 唯一の実行可能ファイルには、親プロセスと子プロセスの2つのモードがあります。 モードは、スキャンされたファイルの
HANDLEs
を含む環境変数の存在、およびプロセス間の通信によって決定されます。 親プロセスは、子AppContainerプロセスを作成する前に、これら2つの
HANDLEs
設定します。 これで、分離された子プロセスがウイルス対策エンジンのライブラリをロードし、受信ファイルのウイルスをスキャンします。
Malware Protection EngineはAppContainer内で初期化することを望まなかったため、これはPoCの完全な動作には不十分です。 最初はアクセス制御の問題だと思いました。 しかし、ProcMonの違い(AppContainerではなくAppContainerのパフォーマンスの違いを比較する)を注意深くチェックした後、Windowsのバージョンを決定することに問題があることに気付きました。 Tavisコードは、常にWindows XPのバージョンとして報告されています。 私のコードは、ホストシステムの実際のバージョンを報告しました。私の場合は、Windows 10です。WinDbgをチェックインすると、初期化の問題が実際にあることがわかりました。 ホストバージョンのWindowsについてMpEngineに嘘をつく必要がありました。 C / C ++では、Detoursで関数フックを使用します。 残念ながら、WindowsでのRustには、関数をフックするための同等のライブラリはありません(関数をフックするためのいくつかのライブラリは、必要以上に「重い」ことが判明しました)。 当然、 Rustの関数をフックするための単純なライブラリを実装しました(32ビットWindows PEのみ)。
AppJailLauncher-rsビュー
RustでAppJailLauncherの主要なコンポーネントを既に実装しているので、ジョブを終了してRust TCPサーバーですべてをラップしてみませんか? 私はこれをやったので、AppJailLauncherの「第2バージョン」 -AppJailLauncher-rsを紹介できてうれしいです。
AppJailLauncherは、特定のポートをリッスンし、受信したTCP接続ごとにAppContainerプロセスを開始するTCPサーバーでした。 私は車輪を再発明したくありませんでしたが、RustのコンパクトなI / Oライブラリであるmioは単に適合しませんでした。 まず、TcpClientは元の
HANDLEs
HANDLEソケットへのアクセスを提供しませんでした。 第二に、これらのソケットはAppContainerの子プロセスに継承されませんでした。 このため、appjaillauncher-rsをサポートする別の「ピラー」TcpServerを提供する必要があります。
TcpServer
は、
STDIN/STDOUT/STDERR
リダイレクトと互換性のある非同期TCPサーバーとクライアントソケットを担当し
STDIN/STDOUT/STDERR
。
socket
呼び出しによって作成された
socket
は、標準のI / Oストリームをリダイレクトできません。 適切な標準I / Oリダイレクトには、ネイティブソケット(
WSASocket
を介して作成されたソケットなど)が
WSASocket
。 リダイレクトを有効にするために、
TcpServer
はこれらの「ネイティブ」ソケットを作成し、それらからの継承を明示的に禁止しません。
Rustでの私の経験
一般的に、Rustでの私の経験は、多少の粗さにも関わらず、非常に楽しいものでした。 AppJailLauncherの開発中に私が特に気づいたこのプログラミング言語のいくつかの機能について言及させてください。
貨物 WindowsでのC ++での依存関係管理は、特にサードパーティライブラリへのリンクでは、本当に面倒で複雑な問題です。 Rustは、貨物パッケージマネージャーの助けを借りてこの問題を巧みに解決します。 引数の解析(clap-rs)、Windows FFI(winapi-rsなど)、ワイド文字列の処理など、多くの一般的な問題を解決するパッケージの大規模なセットがあります。
組み込みテスト 。 C ++アプリケーションの単体テストには、サードパーティのライブラリと多くの手作業が必要です。 AppJailLauncherの最初のバージョンのように、小さなプロジェクト用に記述されることはめったにありません。 Rustでは、単体テストは貨物システムに組み込まれ、コア機能とともに存在します。
マクロシステム 。 Rustでは、C / C ++の単純な置換エンジンとは異なり、マクロシステムは抽象構文ツリーのレベルで動作します。 ここで少し学ぶ必要がありますが、Rustマクロには、名前の競合やスコープなど、C / C ++マクロの迷惑なプロパティがまったくありません。
デバッグ WindowsでのRustのデバッグは正常に機能します。 Rustは、ソースの妨げられないデバッグを提供するWinDbg互換のデバッグシンボル(PDBファイル)を生成します。
外部関数のインターフェース 。 Windows APIはC / C ++で書かれており、これがそれにアクセスする方法であると理解されています。 Rustなどの他の言語では、外部関数インターフェイス(FFI)を使用してWindows APIにアクセスする必要があります。 Rust FFI for Windows(winapi-rs)はほとんど準備ができています。 そこには重要なAPIがありますが、アクセス制御リストを変更するためのAPIなどのそれほど頻繁には使用されないサブシステムがいくつかありません。
属性 属性の設定は次の行にのみ適用されるため、非常に面倒です。
借用チェッカー 。 所有権の概念は、Rustがメモリセキュリティを実現する方法です。 借用チェッカーの仕組みを理解しようとすると、不可解で固有のエラーが発生し、ドキュメントやチュートリアルを何時間も読む必要があります。 最後に、それは価値があります。「クリック」して概念を学んだとき、私のプログラミングスキルは劇的に進歩しました。
ベクトル C ++では、
std::vector
コンテナはサポートバッファを他のコードに公開できます。 元のベクトルは、サポートバッファーが変更されても有効です。
Vec
の場合、これはそうではありません。 ここで、
Vec
は古い
Vec
「空白」から新しいオブジェクトを作成する必要があります。
タイプオプションと結果 。 ネイティブのOptionおよびResultタイプは、エラーチェックを簡素化するはずでしたが、実際にはより詳細に見えました。 エラーがなかったふりをして、単に
unwrap
呼び出すことができますが、これは必然的に
Error
(または
None
)が出たときにランタイムの失敗につながります。
他のタイプとスライス 。 所有されている型と関連するスライス(例えば
String/str
、
PathBuf/Path
)は少し慣れる必要があります。 それらは同じような名前のペアで移動しますが、動作が異なります。 Rustでは、所有された型は、拡張可能で変更可能なオブジェクト(通常は文字列)を表します。 スライスは、不変の文字バッファー(通常は文字列)の一種です。
未来
Windows用のRustエコシステムはまだ成長しています。 Windows用のセキュリティソフトウェアの開発を簡素化する新しいRustライブラリを作成することもできます。 Windowsでのサンドボックス、PE解析、およびIATインターセプト用のいくつかのRustライブラリの初期バージョンを作成しました。 Windowsの新しいRustコミュニティに役立つことを願っています。
RustとAppJailLauncherを使用して、Microsoftの主要なウイルス対策製品であるWindows Defenderをサンドボックス化しました。 私の業績は素晴らしいものであり、少し恥ずかしいものでもあります。 素晴らしいことは、信頼できるWindowsサンドボックスエンジンがサードパーティソフトウェアで利用できることです。 マイクロソフト自体がDefenderを隔離しなかったことは残念です。 マイクロソフトは 2004年に後にWindows Defenderになるものを購入しました。 当時、そのようなバグやアーキテクチャの誤算は受け入れられませんでしたが、理解できました。 長年にわたり、Microsoftは、定期的なテストとファジングのための優れたセキュリティ組織を設立してきました。 彼女は、サンドボックスでInternet Explorerの重要な部分をサンドボックスしました。 どういうわけか、Windows Defenderは2004年に行き詰まりました。 Project Zeroメソッドを使用して、この固有の欠陥の症状を継続的に指摘する代わりに、Windows Defenderを未来に戻しましょう。