Vous avez besoin du Cloud Computing ? Commencez dès maintenant

Capacités défensives dans Windows pour RPC

Stiv Kupchik

écrit par

Stiv Kupchik

August 10, 2023

Stiv Kupchik

écrit par

Stiv Kupchik

Stiv Kupchik est Senior Security Researcher et est basé à Tel Aviv, en Israël.

Dans cet article, nous verrons comment consommer et analyser les événements du fournisseur ETW RPC, et comment analyser les événements à la recherche d'activités potentiellement malveillantes.

Table des matières

Le contenu de cet article a été présenté à l'origine à BlackHat USA 2023.

MS-RPC : Effilochage du protocole pour détecter les attaques

Nous poursuivons notre plongée dans MS-RPCavec un nouvel article de blog. Mais cette fois, au lieu de nous concentrer sur l'aspect offensif et la recherche de vulnérabilités, nous allons discuter de certaines des capacités défensives intégrées de Windows. Nous verrons comment les utiliser pour avoir un aperçu de ce qui se passe sous le capot de RPC, en espérant détecter des activités néfastes au passage.

MS-RPC fait partie intégrante du système d'exploitation Windows, ce qui signifie que de nombreuses voies de déplacement latéral passent par lui. Des attaques comme PSExec, Remote Scheduled TaskDCSyncet PetitPotam sont toutes exécutées via des protocoles MS-RPC, et il peut être difficile de distinguer le trafic réseau bénin du trafic malveillant, en ne disposant que des métadonnées réseau traditionnelles (port et adresse IP de la source et de la destination ; parfois aussi des informations sur le processus).

Nous renforcerons notre visibilité grâce à Event Tracing for Windows (ETW), un mécanisme de suivi et de surveillance intégré au système d'exploitation Windows. ETW nous fournit un grand nombre d'informations, notamment pour RPC, surtout si on les compare aux métadonnées traditionnelles du réseau.

Le concept de suivi des événements RPC en utilisant son fournisseur ETW n'est pas nouveau, et il existe déjà de nombreuses ressources et outils de qualité pour cela (nous en citons quelques-uns dans notre section Références  ; n'hésitez pas à nous en indiquer d'autres). La plupart des recherches existantes se concentrent sur les événements côté client, que les attaquants peuvent altérer ou dont ils peuvent modifier le programme pour contourner complètement la journalisation. Nous allons plutôt nous concentrer sur la détection d'attaques utilisant des événements côté serveur, qui sont hors de portée des attaquants, et examiner les éléments uniques dont nous avons besoin pour les analyser.

Dans cet article, nous verrons comment consommer et analyser les événements du fournisseur ETW RPC, et comment analyser les événements à la recherche d'activités potentiellement malveillantes. En utilisant le fournisseur ETW, nous pouvons également connaître l'opération exacte qui est demandée, ce qui nous permet de détecter les attaques avec une granularité beaucoup plus fine. Nous verrons exactement comment cela fonctionne très prochainement.

Rappel : Qu'est-ce que RPC ?

RPC est l'abréviation de Remote Procedure Call (appel de procédure à distance). Il s'agit d'une forme de communication interprocessus (IPC). Plus précisément, ce protocole est conçu pour permettre l'invocation de fonctions à distance entre différents processus. Dans notre cas, nous nous concentrerons sur l'implémentation de Microsoft, MS-RPC.

MS-RPC est conçu comme un modèle client-serveur. Le serveur définit l'interface qu'il exposera à l'aide du langage IDL (interface Definition Language). L'IDL contient un identifiant universel unique (UUID) pour l'interface, ainsi que les définitions des fonctions qu'elle expose (Figure 1).

  [
    uuid(12345678-4000-2006-0000-20000000001a)
]

interface Test
{
    void Foo([in] int number,  [in] char *message);
    void Bar([out] int * result);
}

Figure 1 : Exemple de définition d'interface IDL

La communication s'effectue par le biais de certains protocoles de transport, et chaque transport a son propre type de point de terminaison (Figure 2). La meilleure façon d'expliquer cela est de donner un exemple : La communication RPC peut s'effectuer via TCP, auquel cas le transport est TCP et le point de terminaison est le socket TCP, identifié par un numéro de port.

Transports

Points de terminaison

TCP

Canal nommé

UDP

ALPC

HTTP

Socket Hyper-V

<numéro de port>

<nom du canal>

<numéro de port>

<port ALPC>

<Nom d'hôte>

<UUID>

Figure 2 : Les protocoles de transport courants et leurs types de points de terminaison respectifs

Il est important de noter que, bien que les fonctions aient un nom lisible dans le fichier IDL, elles sont identifiées différemment sur le réseau. Au lieu d'un nom sous forme de chaîne, elles sont identifiées par un nombre entier, appelé opnum (abréviation pour numéro d'opération). Il est généralement attribué en fonction de l'ordre d'apparition des fonctions dans la définition de l'interface IDL (ainsi, dans l'exemple de la Figure 1, Foo sera identifié par l'opnum 0, tandis que Bar sera l'opnum 1). Ceci est important pour la suite, lorsque nous devrons connaître l'opnum des fonctions pertinentes pour les identifier dans les données que nous verrons.

Pour un aperçu plus long et plus approfondi de MS-RPC, vous pouvez vous référer à notre article précédentou à l'une de nos présentations à la conférence HexaCon ou DEF CON sur le sujet.

#define ETW

Event Tracing for Windows (ETW) est un mécanisme intégré de suivi et de journalisation, mis en œuvre au sein du noyau Windows. Il fonctionne selon un modèle fournisseur-utilisateur ; les fournisseurs envoient des événements au noyau, qui les réachemine vers les programmes utilisateurs. Les fournisseurs et utilisateurs doivent s'enregistrer au préalable auprès du noyau (Figure 3).

De plus, comme le routage des événements est géré par le noyau, si des événements sont envoyés par des fournisseurs, mais qu'il n'y a pas d'utilisateur pour eux, les événements sont simplement rejetés et envoyés au vide.

Flux de rapports d'événements ETW Figure 3 : Illustration du flux de rapports d'événements ETW

Microsoft-Windows-RPC

Le fournisseur ETW RPC est implémenté dans le runtime RPC, rpcrt4.dll. Il affiche 13 événements différents, mais quatre d'entre eux nous intéressent particulièrement : les événements 5 et 7 pour le démarrage et l'arrêt de l'appel du client (respectivement), et les événements 6 et 8 pour le démarrage et l'arrêt de l'appel du serveur. Nous nous concentrerons sur les événements de début d'appel (Figure 4), car ce sont eux qui fournissent le plus d'informations. (Les événements d'arrêt d'appel indiquent simplement l'état de retour RPC.) Les événements client et serveur partagent le même format.

  <template tid="RpcServerCallStartArgs_V1">
     <data name="InterfaceUuid" inType="win:GUID"/>
     <data name="ProcNum" inType="win:UInt32"/>
     <data name="Protocol" inType="win:UInt32"/>
     <data name="NetworkAddress" inType="win:UnicodeString"/>
     <data name="Endpoint" inType="win:UnicodeString"/>
     <data name="Options" inType="win:UnicodeString"/>
     <data name="AuthenticationLevel" inType="win:UInt32"/>
     <data name="AuthenticationService" inType="win:UInt32"/>
     <data name="ImpersonationLevel" inType="win:UInt32"/>
  </template>

Figure 4 : Schéma d'événement de début d'appel ETW RPC

En théorie, les données contenues dans l'événement devraient nous fournir toutes les métadonnées dont nous avons besoin pour mieux comprendre le trafic RPC. Nous savons maintenant ce qui est demandé grâce à l'UUID et à l'opnum de l'interface, et nous connaissons également l'adresse de la source ou de la destination (selon que nous examinons les événements client ou serveur), grâce au champ NetworkAddress (adresse réseau). Ça ne peut pas être aussi simple, n'est-ce pas ?

En effet. Il s'avère que le runtime RPC ne remplit pas le champ NetworkAddress lorsqu'il traite les événements d'appel de serveur. Nous devrons donc trouver un autre moyen de trouver ces données si nous voulons avoir les métadonnées de connexion. Ce champ est rempli lors des événements clients.

Extrait d'écran du runtime RPC. À l'intérieur d'un contrôle if qui vérifie la variable globale Microsoft_Windows_RPCEnableBits, il y a un appel à la fonction EtwEventWriteTransfer. Parmi les nombreux arguments que la fonction reçoit, celui de l'adresse réseau est envoyé avec la valeur 0. Figure 5 : Extrait d'écran du runtime RPC ; en surbrillance, le champ de l'adresse réseau, dont on voit qu'elle est égale à 0

Cela soulève la question suivante : pouvons-nous ignorer les événements du serveur et nous fier uniquement au côté client ? La réponse est non. Puisque nous essayons de trouver un comportement malveillant, qui proviendra de la machine contrôlée par l'attaquant, nous ne pouvons pas être sûrs qu'il ne modifiera pas le fournisseur ETW côté client (et ne le désactivera pas, ou ne bloquera pas ses événements), ni même qu'il ne passera pas par le runtime RPC.

La bibliothèque python populaire Impacket, souvent utilisée dans les validations de principe (PoC) et les outils d'attaque (et qui contient des implémentations d'attaques réseau, comme PSExec), implémente le trafic RPC en son sein, de sorte que tout attaquant l'utilisant contournera le runtime RPC et ne sera pas enregistré dans le fournisseur ETW. Les événements de serveur échappant au contrôle des attaquants, il est plus prudent de s'y fier.C'est pourquoi nous allons maintenant nous concentrer sur la manière d'obtenir les données réseau d'ailleurs et de les faire correspondre à l'événement RPC.

Correspondance entre les événements RPC et les flux réseau

Faisons une pause dans notre étude du fournisseur ETW RPC pour nous intéresser à d'autres fournisseurs ETW, à savoir les fournisseurs TCP et SMB . Ces deux protocoles sont les protocoles de transport les plus courants pour le trafic RPC. Puisque nous avons dit que le type de point de terminaison RPC dépend du protocole de transport, nous pouvons faire correspondre le point de terminaison (numéro de port, nom du canal, etc.) tel que nous le recevons du fournisseur RPC à la valeur correspondante dans le fournisseur ETW de transport.

Pour TCP, c'est assez simple. Examinons l'événement ID 1017, appelé TcpAcceptListenerComplete, qui se déclenche une fois la connexion TCP en trois temps terminée.

Il comporte deux champs (essentiellement) : LocalAddress et RemoteAddress (bien que, dans notre cas, puisque nous nous intéressons aux événements du serveur, local se réfère au serveur tandis que distant se réfère au client). Les valeurs des champs d'adresse sont binaires et contiennent la famille d'adresses, l'adresse IP et le numéro de port (Figure 6).

0

1

2

3

4

5

6

7

Famille d'adresses

Numéro de port

Valeur d'adresse (IP si AF_INET)

Figure 6 : Format du champ binaire d'adresse

Tout ce que nous avons à faire est d'extraire l'adresse IP (du client) du champ RemoteAddress et le suivre avec le port (de destination) dans le champ LocalAddress . Maintenant, dès que nous recevons un événement RPC sur notre serveur, sur le même numéro de port TCP, nous pouvons savoir d'où il vient sur la base de la correspondance port-IP que nous avons extraite du fournisseur TCP (Figure 7).

Résumé graphique illustrant la manière dont nous faisons correspondre les événements RPC et TCP. Les deux événements sont représentés comme des pièces de puzzle correspondantes. L'événement RPC contient le champ Endpoint, qui correspond au port TCP local. L'événement TCP possède le champ LocalAddress, qui contient également le port TCP local. Il contient aussi le champ RemoteAddress, qui contient l'adresse IP du client Figure 7 : Correspondance entre les événements TCP et RPC

Avec SMB, la situation est un peu plus compliquée, car différents éléments d'information apparaissent dans différents événements ETW. Le point de terminaison est un canal nommé, identique à un fichier, mais qui permet d'accéder à plusieurs fichiers au cours d'une même session SMB. Ainsi, dans ce cas, pour faire correspondre le point de terminaison avec sa source réseau, nous devons suivre deux événements : un pour la connexion proprement dite et un pour la demande de fichier (Figure 8).

Pour l'événement de connexion, nous avons l'ID d'événement 500, Smb2ConnectionAcceptStart, qui est déclenché lorsque la connexion SMB est établie. Nous en obtenons l'IP source et un UUID de connexion. Nous recherchons ensuite l'événement ID 8, Smb2RequestCreate_V2, qui contient le nom de fichier demandé et le même champ UUID de connexion. Il ne nous reste plus qu'à croiser les deux événements via l'UUID de connexion pour faire correspondre le nom du canal à l'IP qui l'a demandé (et plus tard, nous devrons faire correspondre le nom du canal au point de terminaison de l'appel RPC).

Diagramme indiquant le processus permettant de faire correspondre les événements SMB aux événements RPC. À partir de l'événement Smb2ConnectionAcceptStart, nous enregistrons l'adresse IP et utilisons le GUID de connexion pour établir une correspondance avec l'événement Smb2RequestCreate_V2 qui suit. Dans Smb2RequestCreate_V2, nous pouvons prendre le nom du canal, qui est ensuite mis en correspondance avec le champ endpoint dans l'événement RpcServerCallStart_V1, issu du fournisseur RPC. Nous conservons les champs Opnum et Interface, qui, avec le champ IP enregistré précédemment, nous renseignent sur l'ensemble du processus Figure 8 : Utilisation des événements SMB ETW pour faire correspondre l'adresse IP source aux événements ETW RPC

Pour vous éviter d'avoir à effectuer cette correspondance vous-même, nous avons implémenté cet algorithme. Vous pouvez trouver l'outil RPC Visibility dans notre référentiel GitHub. Cet outil est écrit en Python et sauvegarde le trafic réseau RPC enregistré dans une base de données Neo4j pour une visualisation facile.

Détection de l'attaque

Maintenant que nous disposons de toutes les informations nécessaires, nous pouvons enfin nous concentrer sur la détection des flux RPC malveillants. Le processus général est simple : Nous détectons une attaque passant par RPC, nous effectuons une validation de principe de celle-ci et voyons quelle interface RPC elle utilise, ainsi que l'opération demandée. Il suffit ensuite de créer une signature correspondant à cette opération. Nous avons inclus des requêtes de signature fonctionnant avec l'implémentation de la base de données Neo4j dans notre version, mais continuez votre lecture pour la logique générale et les conditions.

PSExec

PSExec est à la fois le nom général d'une technique d'attaque et un outil Sysinternals (celui qui a donné son nom à la technique). Fondamentalement, l'outil copie un binaire de service dans le partage ADMIN$ de la cible distante (dossier d'installation de Windows) et communique ensuite avec le gestionnaire de service via son interface RPC (MS-SCMR) pour créer un service pour le binaire et l'exécuter (Figure 9).

Le flux d'attaque psexec. Il y a un hacker, un PC reliés par deux flèches. La première flèche est étiquetée 1. SMB, et contient le texte « cp psexecsvc.exe \\VICTIM\ADMIN$\psexesvc.exe », pour indiquer la commande permettant de copier le binaire de service sur la machine victime. La deuxième flèche est étiquetée « 2. RPC, MS-SCMR » et contient le texte « RCreateServiceW(\\Victim, C:\Windows\psexesvc.exe) », pour indiquer que la fonction SCMR est appelée pour démarrer le service à distance Figure 9 : Flux de l'attaque PsExec

Il existe de nombreuses raisons légitimes de contacter des machines distantes par l'intermédiaire de SCMR ; les surveillances interrogeant l'état des services distants, par exemple. Il y a beaucoup moins de raisons de créer de nouveaux services à distance. Par conséquent, nous ne voulons pas déclencher d'alertes sur n'importe quelle connexion via SCMR (que nous pouvons détecter même sans l'ETW RPC, simplement en faisant correspondre une connexion réseau entrante au processus du gestionnaire de services services.exe), mais uniquement sur les connexions créant des services distants.

Notre signature doit donc être (en termes généraux, non spécifiques à notre implémentation de la visibilité RPC) :

interface_uuid ==  “367ABB81-9844-35F1-AD32-98F038001003” AND (opnum == 0xC OR opnum == 0x18)

où 0xC est l'opnum de RCreateServiceW et 0x18 est pour RCreateServiceA.

Planificateur de tâches à distance

Tout comme PSExec, le planificateur de tâches peut être utilisé pour lancer un binaire distant et réaliser ainsi un mouvement latéral. Il n'est même pas nécessaire de lancer un nouveau binaire, puisqu'il peut tout aussi bien lancer une console cmd ou PowerShell, et télécharger un binaire hébergé en ligne.

De même, comme pour PSExec, nous ne voulons pas détecter n'importe quel accès au service Planificateur de tâches, mais nous nous intéressons surtout aux appels RPC à SchRpcRegisterTask.

interface_uuid ==  “86D35949-83C9-4044-B424-DB363231FD0C” AND opnum == 0x1

DCSync

DCSync est une autre attaque basée sur RPC, mais qui vise les contrôleurs de domaine. Dans ce cas, l'attaquant se connecte au contrôleur de domaine réel, en prétendant être un nouveau contrôleur de domaine. Il demande ensuite à répliquer la base de données d'informations d'identification du contrôleur de domaine, afin d'accéder aux hachages de mots de passe KRBTGT.

La demande de réplication s'effectue par l'intermédiaire de l'interface RPC MS-DRSR et utilise la fonction spécifique DRSGetNCChanges (opnum 3).

interface_uuid ==  “e3514235-4b06-11d1-ab04-00c04fc2dcd2” AND opnum == 0x3

PetitPotam

PetitPotam est une attaque par coercition d'authentification sur le service EFS (Encrypted File System). Les attaquants peuvent se connecter à l'interface RPC EFS (MS-EFSR) et lui demander d'ouvrir un fichier distant spécifié par un chemin UNC. Cela déclenche alors une connexion SMB sortante avec authentification que l'attaquant peut ensuite relayer.

Lorsque la validation de principe d'attaque a été publiée, elle a utilisé EfsRpcOpenFileRaw (opnum 0), qui a depuis été corrigé. Topotam, le chercheur qui a trouvé la vulnérabilité, a également découvert que EfsRpcEncryptFileSrv (opnum 4) présentait le même défaut.

interface_uuid ==  “c681d488-d850-11d0-8c52-00c04fd90f7e” AND (opnum == 0x0 OR opnum == 0x4)

Inconvénients

Bien qu'il y ait beaucoup d'informations à tirer du fournisseur ETW RPC, il ne s'agit pas d'un guichet unique pour tous nos besoins en matière de sécurité. Si connaître l'opération demandée dans chaque flux réseau est une donnée précieuse, l'information la plus cruciale, à savoir les données transmises dans chaque requête, n'est pas enregistrée. Cela signifie que la détection des mouvements latéraux via le fournisseur ETW n'est encore qu'une approche heuristique, bien qu'avec beaucoup plus de contexte que les quadruplets de réseau traditionnels.

Il s'agit également d'une méthode de détection pure, qui ne peut pas être utilisée pour arrêter les attaques ou y répondre. Microsoft nous fournit un autre mécanisme de défense intégré pour RPC, le filtre RPC du pare-feu Windows. Vous pouvez en savoir plus sur ce mécanisme de filtrage et apprendre à l'utiliser dans notre article Guide définitif sur le filtre RPC (Remote Procedure Call).

Synthèse

Le fournisseur ETW RPC n'est pas un nouvel ajout à Windows, mais il a été largement négligé en ce qui concerne la défense du réseau. Il existe quelques outils qui interagissent et consomment des événements avec celui-ci, mais ils sont principalement destinés aux chercheurs en sécurité, et moins axés sur le côté réseau.

Dans cet article, nous avons discuté de la façon dont nous pouvons utiliser le fournisseur ETW RPC, couplé aux fournisseurs TCP et SMB, pour gagner en visibilité sur les opérations RPC demandées provenant du réseau. Nous disposons ainsi d'une approche heuristique que nous pouvons utiliser pour détecter d'éventuelles requêtes malveillantes susceptibles d'être utilisées pour un mouvement latéral.

Références



Stiv Kupchik

écrit par

Stiv Kupchik

August 10, 2023

Stiv Kupchik

écrit par

Stiv Kupchik

Stiv Kupchik est Senior Security Researcher et est basé à Tel Aviv, en Israël.