Attaques de manipulation directe d’objets du noyau (DKOM) sur les fournisseurs ETW.

Portrait en vue latérale d’un homme regardant l’écran d’un ordinateur alors qu’il travaille tard le soir

Auteur

Ruben Boonen

CNE Capability Lead, Adversary Services

IBM X-Force

Dans ce billet, les hackers offensifs d’IBM Security X-Force Red analysent la manière dont les attaquants, dotés de privilèges élevés, peuvent utiliser leur accès pour mettre en place les fonctionnalités de post-exploitation de noyau Windows. Au cours des dernières années, les comptes publics ont de plus en plus montré que des attaquants moins sophistiqués utilisaient cette technique pour atteindre leurs objectifs. Il est donc important de mettre en lumière cette capacité et d’en apprendre plus sur son impact potentiel. Plus précisément, dans cet article, nous évaluerons comment la post-exploitation d’un noyau peut être utilisée pour aveugler les détecteurs ETW et les relier à des échantillons de logiciels malveillants identifiés l’année dernière.

Introduction

Au fil du temps, les mesures de sécurité et la télémétrie de détection sur Windows se sont considérablement améliorées. Lorsque ces capacités sont combinées à des solutions de détection et de réponse des terminaux (EDR) bien configurées, elles peuvent représenter une barrière non triviale à la post-exploitation. Les attaquants sont confrontés à un coût constant de développement et d’itération des tactiques, techniques et procédures (TTP) afin d’éviter les heuristiques de détection. L’équipe chargée de la simulation d’adversaires chez IBM Security X-Force est confrontée au même problème. Notre équipe a pour mission de simuler des capacités de menace avancées dans certains des environnements les plus grands et les plus robustes. La combinaison de solutions de sécurité complexes et bien réglées et d’équipes de centres d’opérations de sécurité (SOC) bien formées peut s’avérer très exigeante sur le plan technique. Dans certains cas, l’utilisation d’une TTP spécifique devient complètement obsolète en l’espace de trois à quatre mois (généralement en raison de technologies spécifiques).

Les attaquants peuvent choisir de tirer parti de l’exécution de code dans le noyau Windows pour altérer certaines de ces protections ou pour éviter complètement un certain nombre de capteurs au niveau de l’utilisateur. La première démonstration publiée d’une telle capacité a eu lieu en 1999 dans le Phrack Magazine. Au cours des années suivantes, plusieurs cas ont été signalés où des acteurs de la menace ont utilisé des rootkits de noyau pour la post-exploitation. Parmi les exemples les plus anciens, citons la famille Derusbi et la boîte à outils Lamberts.

Traditionnellement, ces types de capacités sont principalement limités aux acteurs malveillants avancés. Ces dernières années, toutefois, de plus en plus d’attaquants utilisent les primitives d’exploitation Bring Your Own Vulnerable Driver (BYOVD) pour faciliter les actions sur le point de terminaison. Dans certains cas, ces techniques ont été assez primitives, limitées à des tâches simples, mais des démonstrations plus performantes ont aussi été observées.

Fin septembre 2022, des chercheurs d’ESET ont publié un livre blanc sur une telle capacité de noyau utilisée par l’acteur malveillant Lazarus lors de plusieurs attaques contre des entités en Belgique et aux Pays-Bas dans le but d’exfiltrer des données. Ce document présente un certain nombre de primitives de manipulation directe d’objets du noyau (DKOM) que la charge utile utilise pour aveugler les télémétries OS/AV/EDR. Les recherches publiques disponibles sur ces techniques sont rares. Pour se défendre, il est indispensable d’acquérir une compréhension plus approfondie des tactiques de post-exploitation du noyau. Un argument classique et naïf souvent entendu est qu’un attaquant disposant de privilèges élevés peut faire tout ce qu’il désire, alors pourquoi devrions-nous modéliser les capacités dans ce scénario ? Il s’agit d’une position faible. Les défenseurs doivent comprendre quelles sont les capacités d’un attaquant lorsque ses privilèges sont élevés, quelles sources de données restent fiables (et lesquelles ne le sont pas), quelles options d’endiguement existent et comment les techniques avancées pourraient être détectées (même si les capacités nécessaires pour effectuer ces détections n’existent pas). Dans ce billet, je me concentrerai spécifiquement sur la correction des structures de noyau Event Tracing for Windows (ETW) afin de rendre les fournisseurs inefficaces ou inopérants. Je vais fournir quelques informations générales sur cette technique, analyser comment un attaquant peut manipuler les structures de noyau ETW et aborder certains mécanismes de recherche de ces structures. Enfin, j’examinerai comment cette technique a été implémentée par Lazarus dans sa charge utile.

Les dernières actualités technologiques, étayées par des avis d’experts

Restez au fait des tendances les plus étonnantes du secteur dans le domaine de l’IA, de l’automatisation, des données et bien d’autres avec la newsletter Think. Consultez la Déclaration de confidentialité d’IBM.

Merci ! Vous êtes abonné(e).

Vous recevrez votre abonnement en anglais. Vous trouverez un lien de désabonnement dans chaque newsletter. Vous pouvez gérer vos abonnements ou vous désabonner ici. Consultez la Déclaration de confidentialité d’IBM pour plus d’informations.

DKOM ETW

ETW est une installation de traçage rapide intégrée au système d’exploitation Windows. Elle permet la journalisation des événements et des activités système par les applications, les pilotes et le système d’exploitation, offrant une visibilité détaillée du comportement du système pour le débogage, l’analyse des performances et le diagnostic de sécurité.

Dans cette section, je vais rapidement présenter le noyau ETW et sa surface d’attaque associée. Cela permettra de mieux comprendre les mécanismes impliqués dans la manipulation des fournisseurs d’ETW et les effets associés à ces manipulations.

Surface d’attaque du noyau ETW

Des chercheurs de Binarly ont donné une présentation au BHEU 2021 sur la surface d’attaque générale d’ETW sur Windows. Vous trouverez ci-dessous un aperçu du modèle de menace.

diagramme de flux illustrant la modélisation des menaces ETW
Figure 1 – Veni, No Vidi, No Vici : Attaques sur les capteurs EDR aveugles ETW (Binarly)

Dans ce billet, nous nous concentrons sur la surface d’attaque de l’espace noyau.

un graphique montrant et décrivant les attaques contre les fournisseurs ETW en mode noyau
Figure 2 – Veni, No Vidi, No Vici : Attaques sur les capteurs EDR aveugles ETW (Binarly)

Cette publication prend uniquement en compte les attaques appartenant à la première catégorie présentée dans la « Figure 2 », où le traçage est soit désactivé, soit modifié de quelque manière que ce soit.

Il convient de rappeler que les structures opaques sous Windows sont susceptibles d’être modifiées et qu’elles changent fréquemment d’une version à l’autre de Windows. Ce point est particulièrement important lorsque vous analysez les données du noyau, car les erreurs peuvent entraîner un écran bleu de la mort (BSoD).

Initialisation

Les fournisseurs du noyau sont enregistrés à l’aide de nt!EtwRegister, une fonction exportée par ntoskrnl. Une version décompilée de la fonction est disponible ci-dessous.

capture d’écran du code de décompilation de nt!EtwRegister
Figure 3 – Décompilation de nt!EtwRegister

L’initialisation complète survient dans la fonction interne EtwpRegisterKMProvider, mais il y a deux choses à retenir ici :

  • Le ProviderId est un pointeur vers un GUID de 16 octets. Ce GUID est statique dans tous les systèmes d’exploitation, il peut donc être utilisé pour identifier le fournisseur en cours d’initialisation.
  • Le RegHandle est une adresse mémoire qui reçoit un pointeur vers une structure _ETW_REG_ENTRY lors d’un appel réussi. Cette structure de données et certaines de ses propriétés imbriquées offrent des moyens de manipuler le fournisseur ETW selon la recherche de Binarly.

Énumérons brièvement les structures que Binarly a surlignées sur sa diapositive de la Figure 2.

ETW_REG_ENTRY

Vous trouverez ci-dessous une liste complète des 64 bits de la structure _ETW_REG_ENTRY. Des détails supplémentaires sont disponibles sur le blog de Geoff Chappell, ici. Cette structure peut également être explorée plus en détail dans le projet Vergilius.

// 0x70 bytes (sizeof)
// Win11 22H2 10.0.22621.382
struct _ETW_REG_ENTRY
{
    struct _LIST_ENTRY RegList;                           //0x0
    struct _LIST_ENTRY GroupRegList;                      //0x10
    struct _ETW_GUID_ENTRY* GuidEntry;                    //0x20
    struct _ETW_GUID_ENTRY* GroupEntry;                   //0x28
    union
    {
        struct _ETW_REPLY_QUEUE* ReplyQueue;              //0x30
        struct _ETW_QUEUE_ENTRY* ReplySlot[4];            //0x30
        struct
        {
            VOID* Caller;                                 //0x30
            ULONG SessionId;                              //0x38
        };
    };
    union
    {
        struct _EPROCESS* Process;                        //0x50
        VOID* CallbackContext;                            //0x50
    };
    VOID* Callback;                                       //0x58
    USHORT Index;                                         //0x60
    union
    {
        USHORT Flags;                                     //0x62
        struct
        {
            USHORT DbgKernelRegistration:1;               //0x62
            USHORT DbgUserRegistration:1;                 //0x62
            USHORT DbgReplyRegistration:1;                //0x62
            USHORT DbgClassicRegistration:1;              //0x62
            USHORT DbgSessionSpaceRegistration:1;         //0x62
            USHORT DbgModernRegistration:1;               //0x62
            USHORT DbgClosed:1;                           //0x62
            USHORT DbgInserted:1;                         //0x62
            USHORT DbgWow64:1;                            //0x62
            USHORT DbgUseDescriptorType:1;                //0x62
            USHORT DbgDropProviderTraits:1;               //0x62
        };
    };
    UCHAR EnableMask;                                     //0x64
    UCHAR GroupEnableMask;                                //0x65
    UCHAR HostEnableMask;                                 //0x66
    UCHAR HostGroupEnableMask;                            //0x67
    struct _ETW_PROVIDER_TRAITS* Traits;                  //0x68
};

ETW_GUID_ENTRY

L’une des entrées imbriquées dans _ETW_REG_ENTRY est GuidEntry, qui est une structure _ETW_GUID_ENTRY. Plus d’informations sur cette structure non documentée sont disponibles sur le blog de Geoff Chappell ici et sur le projet Vergilius.

// 0x1a8 bytes (sizeof)
// Win11 22H2 10.0.22621.382
struct _ETW_GUID_ENTRY
{
    struct _LIST_ENTRY GuidList;                          //0x0
    struct _LIST_ENTRY SiloGuidList;                      //0x10
    volatile LONGLONG RefCount;                           //0x20
    struct _GUID Guid;                                    //0x28
    struct _LIST_ENTRY RegListHead;                       //0x38
    VOID* SecurityDescriptor;                             //0x48
    union
    {
        struct _ETW_LAST_ENABLE_INFO LastEnable;          //0x50
        ULONGLONG MatchId;                                //0x50
    };
    struct _TRACE_ENABLE_INFO ProviderEnableInfo;         //0x60
    struct _TRACE_ENABLE_INFO EnableInfo[8];              //0x80
    struct _ETW_FILTER_HEADER* FilterData;                //0x180
    struct _ETW_SILODRIVERSTATE* SiloState;               //0x188
    struct _ETW_GUID_ENTRY* HostEntry;                    //0x190
    struct _EX_PUSH_LOCK Lock;                            //0x198
    struct _ETHREAD* LockOwner;                           //0x1a0
};

TRACE_ENABLE_INFO

Enfin, l’une des entrées imbriquées dans _ETW_GUID_ENTRY est ProviderEnableInfo, qui est une structure _TRACE_ENABLE_INFO. Pour plus d’informations sur les éléments de cette structure de données, vous pouvez vous référer à la documentation officielle de Microsoft et au projet Vergilius. Les paramètres de cette structure affectent directement le fonctionnement et les capacités du fournisseur.

// 0x20 bytes (sizeof)
// Win11 22H2 10.0.22621.382
struct _TRACE_ENABLE_INFO
{
    ULONG IsEnabled;                                       //0x0
    UCHAR Level;                                           //0x4
    UCHAR Reserved1;                                       //0x5
    USHORT LoggerId;                                       //0x6
    ULONG EnableProperty;                                  //0x8
    ULONG Reserved2;                                       //0xc
    ULONGLONG MatchAnyKeyword;                             //0x10
    ULONGLONG MatchAllKeyword;                             //0x18
};

Comprendre l’utilisation du handle d’enregistrement

Bien qu’il soit bon d’avoir quelques connaissances théoriques, il est toujours préférable d’examiner des exemples concrets d’utilisation pour mieux comprendre un sujet. Prenons brièvement un exemple. Les fournisseurs de noyau ETW les plus critiques sont initialisés dans nt!EtwpInitialize, qui n’est pas exportée. L’examen de cette fonction permet de découvrir une quinzaine de fournisseurs.

Capture d’écran du code pour la décompilation partielle de nt!EtwpInitialize
Figure 4 – décompilation partielle de nt!EtwpInitialize

En prenant l’entrée Microsoft-Windows-Threat-Intelligence (EtwTi) comme exemple, nous pouvons vérifier le paramètre général ThreatIntProviderGuid pour récupérer le GUID de ce fournisseur.

Capture d’écran du code du GUID du fournisseur EtwTi
Figure 5 – GUID du fournisseur EtwTi

La recherche de ce GUID en ligne révélera immédiatement que nous avons pu récupérer la valeur correcte (f4e1897c-bb5d-5668-f1d8-040f4d8dd344).

Examinons un cas où le paramètre de handle d’enregistrement, EtwThreatIntProvRegHandle, est utilisé et analysons-le. On remarque que le handle est référencé sur nt!EtwTiLogDriverObjectUnLoad. Le nom de cette fonction indique qu’elle est destinée à générer des événements lorsqu’un objet pilote est déchargé par le noyau.

Capture d’écran du code de décompilation de nt!EtwTiLogDriverUnload
Figure 6 – Décompilation de nt!EtwTiLogDriverUnload

Les fonctions nt!EtwEventEnabled et nt!EtwProviderEnabled sont toutes deux appelées ici en passant le handle d’enregistrement comme l’un des arguments. Examinons l’une de ces sous-fonctions pour mieux comprendre ce qui se passe.

Capture d’écran du code de décompilation de nt!EtwProviderEnable
Figure 7 – Décompilation de nt!EtwProviderEnable

Il est vrai que c’est un peu difficile à suivre. Cependant, l’arithmétique des pointeurs n’est pas particulièrement importante. Concentrons-nous plutôt sur la façon dont cette fonction traite le handle d’enregistrement. Il apparaît que la fonction valide un certain nombre de propriétés de la structure _ETW_REG_ENTRY et de ses sous-structures, telles que la propriété GuidEntry.

struct _ETW_REG_ENTRY
{
    …
    struct _ETW_GUID_ENTRY* GuidEntry;                    //0x20
    …
}

Et la propriété GuidEntry->ProviderEnableInfo.

struct _ETW_GUID_ENTRY
{
    …
    struct _TRACE_ENABLE_INFO ProviderEnableInfo;         //0x60
    …
}

La fonction effectue ensuite des vérifications similaires basées sur les niveaux. Enfin, la fonction renvoie « true » ou « false » pour indiquer si un fournisseur est activé pour la journalisation des événements à un niveau et un mot de clé spécifiés. Plus de détails sont disponibles dans la documentation officielle de Microsoft.

Nous pouvons constater que lorsqu’un fournisseur est accessible via son handle d’enregistrement, l’intégrité de ces structures devient très importante pour les opérations du fournisseur. Inversement, si un attaquant était en mesure de manipuler ces structures, il pourrait influencer le flux de contrôle de l’appelant pour empêcher l’enregistrement des événements.

Attaquer les handles d’enregistrement

En revenant sur la surface d’attaque déclarée par Binary et sur la base de notre analyse, nous pouvons formuler quelques stratégies pour perturber la collecte des événements.

  • Un attaquant peut rendre NULL le pointeur _ETW_REG_ENTRY. Toute fonction faisant référence au handle d’enregistrement supposerait alors que le fournisseur n’a pas été initialisé.
  • Un attaquant peut rendre NULL le pointeur _ETW_REG_ENTRY->GuidEntry->ProviderEnableInfo. Cette action désactive les capacités de collecte du fournisseur, car ProviderEnableInfo est un pointeur vers une structure _TRACE_ENABLE_INFO qui décrit comment le fournisseur est censé fonctionner.
  • Un attaquant peut écraser les propriétés de la structure de données _ETW_REG_ENTRY->GuidEntry->ProviderEnableInfo afin de modifier la configuration du fournisseur.
    • IsEnabled : attribuez la valeur 1 pour activer la réception d’événements du fournisseur ou pour ajuster les paramètres utilisés lors de la réception d’événements du fournisseur. Configurez sur 0 pour désactiver la réception d’événements par le fournisseur.
    • Level : une valeur qui indique le niveau maximal d’événements que vous souhaitez que le fournisseur écrive. Le fournisseur écrit généralement un événement si le niveau de l’événement est inférieur ou égal à cette valeur, en plus de répondre aux critères MatchAnyKeyword et MatchAllKeyword.
    • MatchAnyKeyword : masque de mots-clés 64 bits qui détermine les catégories d’événements que vous souhaitez que le fournisseur rédige. Le fournisseur écrit généralement un événement si les bits de mots-clés de l’événement correspondent à l’un des bits définis dans cette valeur ou si l’événement n’a pas de bits de mot-clé définis, en plus de respecter les critères Level et MatchAllKeyword.
    • MatchAllKeyword : masque de mots-clés de 64 bits qui restreint les événements que vous souhaitez que le fournisseur écrive. En plus de répondre aux critères Level et MatchAnyKeyword, le fournisseur écrit généralement un événement si les bits de mot-clé de l’événement correspondent à tous les bits définis dans cette valeur ou si aucun bit de mot-clé n’est défini.

Recherche de noyau

Nous avons désormais une bonne idée de ce à quoi ressemble une attaque DKOM sur ETW. Supposons que l’attaquant dispose d’une vulnérabilité qui lui accorde une primitive de lecture/écriture du noyau, comme le fait dans ce cas le logiciel malveillant Lazarus en chargeant un pilote vulnérable. Ce qu’il manque, c’est un moyen de trouver ces handles d’enregistrement.

Je décrirai deux techniques principales pour trouver ces handles et je montrerai la variante de l’une d’entre elles qui est utilisée par Lazarus dans sa charge utile de noyau.

Contournement KASLR à niveau d’intégrité moyen (MeDIL)

Tout d’abord, il peut être prudent d’expliquer que même s’il existe un ASLR pour le noyau, il ne s’agit pas d’une limite de sécurité pour les attaquants locaux s’ils peuvent exécuter du code à MedIL ou à un niveau plus élevé. Il existe de nombreuses façons de divulguer des pointeurs de noyau qui ne sont limités que dans des scénarios de bac à sable ou LowIL. Pour en savoir plus, vous pouvez consulter I Got 99 Problems But a Kernel Pointer Ain’t One d’Alex Ionescu, dont beaucoup de techniques sont encore applicables aujourd’hui.

L’outil de choix ici est ntdll!NtQuerySystemInformation avec la classe SystemModuleInformation :

internal static UInt32 SystemModuleInformation = 0xB;

[DllImport(“ntdll.dll”)]
internal static extern UInt32 NtQuerySystemInformation(
    UInt32 SystemInformationClass,
    IntPtr SystemInformation,
    UInt32 SystemInformationLength,
    ref UInt32 ReturnLength);

Cette fonction renvoie l’adresse de base active de tous les modules chargés dans l’espace du noyau. À ce stade, il est possible d’analyser ces modules sur le disque et de convertir les offsets de fichiers bruts en adresses virtuelles relatives et vice versa.

public static UInt64 RvaToFileOffset(UInt64 rva, List<SearchTypeData.IMAGE_SECTION_HEADER> sections)
{
    foreach (SearchTypeData.IMAGE_SECTION_HEADER section in sections)
    {
        if (rva >= section.VirtualAddress && rva < section.VirtualAddress + section.VirtualSize)
        {
            return (rva – section.VirtualAddress + section.PtrToRawData);
        }
    }
    return 0;
}

public static UInt64 FileOffsetToRVA(UInt64 fileOffset, List<SearchTypeData.IMAGE_SECTION_HEADER> sections)
{
    foreach (SearchTypeData.IMAGE_SECTION_HEADER section in sections)
    {
        if (fileOffset >= section.PtrToRawData && fileOffset < (section.PtrToRawData + section.SizeOfRawData))
        {
            return (fileOffset – section.PtrToRawData) + section.VirtualAddress;
        }
    }
    return 0;
}

Un attaquant peut également charger ces modules dans son processus utilisateur à l’aide d’appels API de la bibliothèque de chargement standard (par exemple, ntdll!LdrLoadDll). Cela éviterait les complications liées à la conversion des offsets de fichiers en RVA et vice versa. Toutefois, du point de vue de la sécurité opérationnelle (OpSec), cette solution n’est pas idéale car elle peut générer davantage de télémétrie de détection.

Méthode 1 : chaînes de gadgets

Dans la mesure du possible, c’est la technique que je préfère car elle rend les fuites plus portables d’une version à l’autre, étant moins affectée par les changements de correctifs. L’inconvénient est que vous dépendez de chaînes de gadgets existantes pour l’objet que vous souhaitez divulguer.

Si on considère les handles d’enregistrement ETW, prenons l’exemple de Microsoft-Windows-Threat-Intelligence. Vous trouverez ci-dessous l’appel complet à nt!EtwRegister.

Capture d’écran du code pour le désassemblage complet du CALL nt!EtwRegister
Figure 8 – Désassemblage complet du CALL nt!EtwRegister

Ici, nous voulons faire fuir le pointeur vers le handle d’enregistrement EtwThreatIntProvRegHandle. Comme indiqué, chargé dans param_4 sur la première ligne de la Figure 8. Ce pointeur renvoie à un pointeur général dans la section .data du module noyau. Puisque cet appel se fait dans une fonction non exportée, nous ne pouvons pas divulguer son adresse directement. Nous devons plutôt regarder où ce pointeur général est référencé et voir s’il est utilisé dans une fonction dont l’adresse peut être divulguée.

capture d’écran du code pour les références nt!EtwThreatIntProvRegHandle
Figure 9 – Références nt!EtwThreatIntProvRegHandle

L’observation de certaines de ces entrées révèle rapidement un candidat dans nt!KeInsertQueueApc.

Capture d’écran du code de la décompilation partielle de nt!KeInsertQueueApc
Figure 10 – Décompilation partielle de nt!KeInsertQueueApc

C’est un excellent candidat pour plusieurs raisons :

  • nt!KeInsertQueueApc est une fonction exportée. Cela signifie que nous pouvons divulguer son adresse active en utilisant un contournement KASLR. Nous pouvons alors utiliser notre vulnérabilité du noyau pour lire les données à cette adresse.
  • Le global est utilisé au début de la fonction. C’est très utile, car cela signifie que nous n’aurons très probablement pas besoin de créer une logique d’analyse d’instructions complexe pour les trouver.

En examinant l’assemblage, vous pouvez voir la mise en page suivante.

capture d’écran du code de désassemblage partiel de nt!KeInsertQueueApc
Figure 11 – Désassemblage partiel de nt!KeInsertQueueApc

La fuite de ce handle d’enregistrement devient alors simple. Nous lisons un tableau d’octets en utilisant notre vulnérabilité, et nous recherchons la première instruction mov R10 pour calculer l’offset virtuel relatif de la variable général. Le calcul ressemblerait à ceci :

Int32 pOffset = Marshal.ReadInt32((IntPtr)(pBuff.ToInt64() + i + 3));
hEtwTi = (IntPtr)(pOffset + i + 7 + oKeInsertQueueApc.pAddress.ToInt64());

Avec le handle d’enregistrement, il est alors possible d’accéder à la structure de données _ETW_REG_ENTRY.

En général, ces chaînes de gadgets peuvent être utilisées pour divulguer une variété de structures de données de noyau. Il convient toutefois de souligner qu’il n’est pas toujours possible de trouver de telles chaînes de gadgets et que celles-ci peuvent parfois comporter plusieurs étapes complexes. Par exemple, une chaîne de gadgets susceptible de divulguer des constantes d’entrée de répertoire de pages (PDE) pourrait ressembler à ceci.

MmUnloadSystemImage -> MiUnloadSystemImage -> MiGetPdeAddress

En réalité, une analyse rapide des handles d’enregistrement ETW a révélé que la plupart d’entre eux ne disposent pas de chaînes de gadgets appropriées pouvant être utilisées de la manière décrite ci-dessus.

Méthode 2 : Analyse de la mémoire

L’autre option principale pour divulguer ces handles d’enregistrement ETW est d’utiliser l’analyse de la mémoire, soit à partir de la mémoire active du noyau, soit à partir d’un module sur le disque. N’oubliez pas que lors de l’analyse des modules sur disque, il est possible de convertir les offsets de fichiers en RVA.

Cette approche consiste à identifier des modèles d’octets uniques, à rechercher ces modèles et à effectuer certaines opérations au niveau des offsets de la correspondance des schémas. Examinons de plus près nt!EtwpInitialize pour mieux comprendre cela :

Capture d’écran du code pour la décompilation partielle de nt!EtwpInitialize
Figure 12 – Décompilation partielle de nt!EtwpInitialiser

Les quinze appels à nt!EtwRegister sont principalement regroupés dans cette fonction. La principale stratégie consiste ici à trouver un schéma unique qui apparaît avant le premier appel à nt!EtwRegister et un deuxième schéma qui apparaît après le dernier appel à nt!EtwRegister. Ce n’est pas trop complexe. Une astuce permettant d’améliorer la portabilité consiste à créer un analyseur de schémas capable de traiter les chaînes d’octets de caractères génériques. C’est une tâche laissée au lecteur.

Une fois qu’un indice de démarrage et d’arrêt a été identifié, il est possible de consulter toutes les instructions intermédiaires.

  • Les instructions CALL potentielles peuvent être identifiées sur la base du code opération de CALL, qui est 0xe8.
  • Ensuite, une lecture de la taille d’un DWORD est utilisée pour calculer l’offset relatif de l’instruction CALL potentielle.
  • Cet offset est ensuite ajouté à l’adresse relative de l’instruction CALL et incrémenté de cinq (la taille de l’instruction d’assemblage).
  • Enfin, cette nouvelle valeur peut être comparée à nt!EtwRegister pour trouver tous les emplacements CALL valides.

Une fois toutes les instructions CALL trouvées, il est possible d’effectuer une recherche en amont et d’extraire les arguments de la fonction, d’abord le GUID qui identifie le fournisseur ETW et le second, l’adresse du handle d’enregistrement. Avec ces informations en main, nous sommes en mesure de lancer des attaques DKOM informées contre les handles d’enregistrement afin d’affecter les opérations des fournisseurs identifiés.

Correctif ETW pour Lazarus

J’ai obtenu un échantillon du DLL FudModle mentionné dans le livre blanc ESET et je l’ai analysé. Ce DLL charge un pilote Dell vulnérable signé (à partir d’une ressource encodée XOR en ligne), puis guide le pilote pour corriger de nombreuses structures de noyau afin de limiter la télémétrie sur l’hôte.

Capture d’écran du code du hachage Lazarus FudModule
Figure 13 – Hash du FudModule Lazarus

Pour la dernière partie de cet article, je souhaite passer en revue la stratégie que Lazarus utilise pour trouver les handles d’enregistrement de noyau ETW. Il s’agit d’une variante de la méthode d’analyse dont nous avons parlé plus haut.

Au début de la fonction de recherche, Lazarus résout nt!EtwRegister et utilise cette adresse pour lancer l’analyse

Capture d’écran du code de la décompilation partielle de la recherche ETW sur Lazarus FudModule
Figure 14 – Décompilation partielle de la recherche ETW sur Lazarus FudModule

Cette décision est un peu étrange parce qu’elle repose sur l’endroit où cette fonction existe par rapport à l’endroit où la fonction est appelée. La position relative d’une fonction dans un module peut varier d’une version à l’autre, car un nouveau code peut être introduit, supprimé ou modifié. Toutefois, en raison de la manière dont les modules sont compilés, on s’attend à ce que les fonctions conservent un ordre relativement stable. On suppose qu’il s’agit d’une optimisation de la vitesse de recherche.

En recherchant les références à nt!EtwRegister dans ntoskrnl, il apparaît que cette technique permet de ne pas manquer beaucoup d’entrées. Lazarus peut également avoir effectué une analyse supplémentaire pour déterminer que les entrées manquantes ne sont pas importantes ou qu’il n’est pas nécessaire de les corriger. Les entrées manquantes sont mises en évidence ci-dessous. Cette stratégie permet à Lazarus de sauter les octets 0x7b1de0 lors de l’analyse, ce qui peut représenter une quantité non négligeable si l’analyseur est lent.

Capture d’écran du code pour les instances d’appels à nt!EtwRegister
Figure 15 – Instances d’appels à nt!EtwRegister

En outre, lors du lancement de l’analyse, les cinq premières correspondances sont ignorées avant de commencer à enregistrer les handles d’enregistrement. Une partie de la fonction de recherche est présentée ci-dessous.

Capture d’écran du code de la Décompilation partielle de la recherche ETW sur Lazarus FudModule
Figure 16 – Décompilation partielle de la recherche ETW sur Lazarus FudModule

Le code est légèrement obtus, mais nous obtenons le plus important de l’équation. Le code recherche les appels vers nt!EtwRegister, extrait le handle d’enregistrement, le convertit en adresse en direct à l’aide d’un contournement KASLR et stocke le pointeur dans une matrice mise de côté à cet effet dans une structure de configuration de logiciel malveillant (attribuée lors de l’initialisation).

Enfin, examinons comment Lazarus désactive ces fournisseurs.

Capture d’écran du code pour les handles d’enregistrement Lazarus FudModule NULL ETW
Figure 17 — Handles d’enregistrement Lazarus FudModule NULL ETW

Cela est globalement logique ; ce que fait Lazarus ici, c’est divulguer la variable globale que nous avons vue précédemment, puis remplacer le pointeur à cette adresse par NULL. Cela efface effectivement la référence à la structure de données _ETW_REG_ENTRY si elle existe.

Je ne suis pas entièrement satisfait de la technique présentée, et ce pour plusieurs raisons :

  • La charge utile ne capture pas le GUID du fournisseur, elle ne peut donc pas prendre de décisions intelligentes quant à savoir si elle doit ou non remplacer le handle d’enregistrement du fournisseur.
  • La décision de commencer l’analyse à un offset à l’intérieur de ntoskrnl semble discutable, parce que l’offset de l’analyse peut varier en fonction de la version de ntoskrnl.
  • Passer de manière aléatoire les 5 premiers matchs semble tout aussi discutable. Des raisons stratégiques peuvent motiver cette décision, mais une meilleure approche consiste d’abord à rassembler tous les fournisseurs, puis à utiliser une logique programmatique pour filtrer les résultats.
  • L’écrasement du pointeur vers _ETW_REG_ENTRY devrait fonctionner, mais cette technique est un peu évidente. Il serait préférable d’écraser les propriétés de _ETW_REG_ENTRY, _ETW_GUID_ENTRY ou _TRACE_ENABLE_INFO.

J’ai réimplémenté cette technique pour la science. Cependant, j’ai apporté quelques ajustements à la tâche.

  • Un algorithme de recherche optimisé pour la vitesse est utilisé pour trouver tous les octets 0xe8 dans ntoskrnl.
  • Ensuite, un certain post-traitement est effectué pour déterminer lesquelles de ces instructions CALL sont valides et leurs destinations respectives.
  • Certains appels à nt!EtwRegister ne sont pas utiles car la fonction est parfois appelée avec un argument dynamique pour le handle d’enregistrement. Pour cette raison, une logique supplémentaire est nécessaire pour filtrer les appels restants.
  • Enfin, tous les GUID sont résolus dans leur forme lisible par l’homme et les handles d’enregistrement sont énumérés.

Dans l’ensemble, après ajustements, la technique ci-dessus est clairement la meilleure façon d’effectuer ce type d’énumération. Puisque le temps de recherche est négligeable avec des algorithmes optimisés, il est logique d’analyser l’ensemble du module sur disque puis d’utiliser une logique post-analyse supplémentaire pour filtrer les résultats.

Impact de l’attaque DKOM de l’ETW

Il est prudent d’évaluer brièvement l’impact d’une telle attaque. Lorsque les données des fournisseurs sont réduites ou complètement supprimées, il y a une perte d’informations, mais tous les fournisseurs ne signalent pas les événements sensibles en matière de sécurité.

Certains sous-ensembles de ces fournisseurs sont toutefois sensibles en matière de sécurité. L’exemple le plus évident est celui de Microsoft-Windows-Threat-Intelligence (EtwTi), une source de données centrale pour Microsoft Defender Advanced Threat Intelligence (MDATP), désormais appelée Defender for Endpoint (on s’y perd). Il convient de noter que l’accès à ce fournisseur est très restreint. Seuls les pilotes Early Launch Anti Malware (ELAM) peuvent s’enregistrer auprès de ce fournisseur. De même, les processus du domaine de l’utilisateur qui reçoivent ces événements doivent avoir un statut protégé (ProtectedLight / Antimalware) et être signés avec le même certificat que le pilote ELAM.

En utilisant EtwExplorer, il est possible d’avoir une meilleure idée des types d’informations que ce fournisseur peut signaler.

Capture d’écran de la page du fichier ETW Explorer
Figure 18 – Explorateur ETW

Le manifeste XML est trop volumineux pour être inclus ici dans son intégralité, mais un événement est illustré ci-dessous pour vous donner une idée des types de données qui peuvent être supprimés à l’aide d’une attaque DKOM.

Capture d’écran du code du manifeste XML partiel EtwTi
Figure 19 – Manifeste XML partiel EtwTi

Conclusion

Le noyau a été, et continue d’être, un domaine important et contesté dans lequel Microsoft et les fournisseurs tiers doivent faire des efforts pour protéger l’intégrité du système d’exploitation. La corruption des données dans le noyau n’est pas seulement une fonctionnalité de la post-exploitation, mais aussi un composant central dans le développement d’exploits du noyau. Microsoft a déjà fait beaucoup de progrès dans ce domaine avec l’introduction de la Virtualization Based Security (VBS) et l’un de ses composants comme Kernel Data Protection (KDP).

Les utilisateurs du système d’exploitation Windows doivent quant à eux s’assurer qu’ils tirent parti de ces avancées pour imposer un coût aussi élevé que possible aux attaquants potentiels. Windows Defender Application Control (WDAC) peut être utilisé pour garantir que les protections VBS sont en place et que des stratégies existent, interdisant le chargement de pilotes potentiellement dangereux.

Ces efforts sont d’autant plus importants que nous voyons de plus en plus les attaquants standard utiliser des attaques BYOVD pour lancer une attaque DKOM dans l’espace noyau.

 

Mixture of Experts | 12 décembre, épisode 85

Décryptage de l’IA : Tour d’horizon hebdomadaire

Rejoignez notre panel d’ingénieurs, de chercheurs, de chefs de produits et autres spécialistes de premier plan pour connaître l’essentiel de l’actualité et des dernières tendances dans le domaine de l’IA.

Références supplémentaires

  • Veni, No Vidi, No Vici: Attacks on ETW Blind EDR Sensors (diapositives BHEU 2021) – ici
  • Veni, No Vidi, No Vici: Attacks on ETW Blind EDR Sensors (vidéo BHEU 2021) – ici
  • Advancing Windows Security (BlueHat Shanghai 2019) – ici
  • Exploiting a “Simple” Vulnerability – In 35 Easy Steps or Less! – ici
  • Exploiting a “Simple” Vulnerability – Part 1.5 – The Info Leak – ici
  • Introduction to Threat Intelligence ETW – ici
  • TelemetrySourcerer – ici
  • WDAC Policy Wizard – ici

Pour en savoir plus sur X-Force Red, cliquez ici. Programmez une consultation gratuite avec X-Force ici.

Solutions connexes
Solutions de sécurité d’entreprise

Transformez votre programme de sécurité avec le portefeuille de solutions le plus complet.

Découvrir les solutions de cybersécurité
Services de cybersécurité

Transformez votre entreprise et gérez les risques avec des services de conseil en cybersécurité, de cloud et de sécurité gérée.

    Découvrir les services de cybersécurité
    Cybersécurité et intelligence artificielle (IA)

    Accélérez et précisez le travail des équipes de sécurité, et rendez-les plus productives grâce à des solutions de cybersécurité cyberalimentées par l’IA.

    Découvrir AI cybersecurity
    Passez à l’étape suivante

    Que vous ayez besoin de solutions de sécurité des données, de gestion des points de terminaison ou de gestion des identités et des accès (IAM), nos experts sont prêts à travailler avec vous pour atteindre une excellente posture de sécurité. Transformez votre entreprise et maîtrisez vos risques avec un leader mondial de la cybersécurité, du cloud et des services de sécurité gérés.

    Découvrir les solutions de cybersécurité Découvrir les services de cybersécurité