John Uhlmann

Pilas de llamadas: No más pases libres para el malware

Exploramos el inmenso valor que las pilas de llamadas aportan a la detección de malware y por qué Elastic las considera una telemetría vital para los puntos finales de Windows a pesar de las limitaciones arquitectónicas.

Pilas de llamadas: No más pases libres para el malware

Las pilas de llamadas proporcionan el quién

Uno de los diferenciadores clave de la telemetría de puntos finales de Windows de Elastic son las pilas de llamadas.

La mayoría de las detecciones dependen de lo que está sucediendo, y esto a menudo es insuficiente ya que la mayoría de los comportamientos tienen un doble propósito. Con las pilas de llamadas, agregamos la capacidad detallada de determinar también quién está realizando la actividad. Esta combinación nos brinda una capacidad incomparable para descubrir actividad maliciosa. Al proporcionar esta telemetría profunda al motor de reglas en el host de Elastic Defend , podemos responder rápidamente a las amenazas emergentes.

Las pilas de llamadas son una hermosa mentira

En informática, una pila es una estructura de datos donde “último en entrar, primero en salir”. De manera similar a una pila de elementos físicos, solo es posible agregar o quitar el elemento superior. Una pila de llamadas es una pila que contiene información sobre las llamadas de subrutinas activas actualmente.

En los hosts x64, esta pila de llamadas solo se puede generar con precisión empleando funciones de seguimiento de ejecución en la CPU, como Intel LBR, Intel BTS, Intel AET, Intel IPT y x64 Architectural LBR. Estas funciones de seguimiento fueron diseñadas para fines de creación de perfiles de rendimiento y depuración, pero también se pueden emplear en algunos escenarios de seguridad. Sin embargo, lo que está disponible de manera más general es una pila de llamadas aproximada que se recupera de la pila de datos de un hilo a través de un mecanismo llamado recorrido de pila.

En la arquitectura x64, el “registro de puntero de pila” (rsp) como era de esperar apunta a una estructura de datos de pila, y hay instrucciones eficientes para leer y escribir los datos en esta pila. Además, la instrucción call transfiere el control a una nueva subrutina pero también almacena una dirección de retorno en la dirección de memoria referenciada por el puntero de la pila. Posteriormente, una instrucción ret recuperará esta dirección almacenada para que la ejecución pueda regresar al punto donde se detuvo. Las funciones en la mayoría de los lenguajes de programación normalmente se implementan empleando estas dos instrucciones, y tanto los parámetros de función como las variables de función locales normalmente se asignarán en esta pila para el rendimiento. La parte de la pila relacionada con una sola función se denomina marco de pila.

El recorrido de pila es la recuperación únicamente de las direcciones de retorno de los datos heterogéneos almacenados en la pila de subprocesos. Las direcciones de retorno se deben almacenar en algún lugar para controlar el flujo, por lo que el recorrido de pila adopta estos datos existentes para aproximar a una pila de llamadas. Esto es totalmente adecuado para la mayoría de los escenarios de depuración y creación de perfiles de rendimiento, pero un poco menos útil para la auditoría de seguridad. El problema principal es que no se puede desmontar hacia atrás. Siempre puedes determinar la dirección de retorno para un sitio de llamada determinado, pero no a la inversa. El mejor enfoque que puede adoptar es verificar cada una de las 15 longitudes de instrucciones anteriores posibles y ver cuál se desensamblará en exactamente una instrucción de llamada. Incluso entonces, todo lo que recuperaste es un sitio de llamada anterior , no necesariamente el sitio de llamada anterior exacto. Esto se debe a que la mayoría de los compiladores emplean la optimización de llamadas finales para omitir marcos de pila innecesarios. Esto crea escenarios molestos para la seguridad como que no hay garantía de que la función Win32StartAddress esté en la pila aunque fue llamada.

Entonces, lo que usualmente llamamos una pila de llamadas es en realidad una pila de direcciones de retorno.

Los autores de malware aprovechan esta ambigüedad para mentir. O bien crean marcos de pila de trampolín a través de módulos legítimos para ocultar llamadas que se originan a partir de código malicioso, o bien fuerzan el recorrido de la pila para predecir direcciones de retorno diferentes a las que ejecutará la CPU. Por supuesto, el malware siempre fue simplemente un intento de mentir, y el antimalware es simplemente el proceso de exponer esa mentira.

“...pero con el tiempo la verdad saldrá a la luz.”

  • William Shakespeare, El mercader de Venecia, Acto 2, Escena 2

Haciendo que las pilas de llamadas sean hermosas

Hasta ahora, un recorrido de pila es simplemente una lista de direcciones de memoria numérica. Para que sean útiles para el análisis necesitamos enriquecerlos con contexto. (Nota: actualmente no incluimos marcos de pila de kernel).

El enriquecimiento útil mínimo es convertir estas direcciones en desplazamientos dentro de los módulos (por ejemplo, ntdll.dll+0x15c9c4). Aunque esto sólo detectaría el malware más notorio, podemos profundizar más. Los módulos más importantes de Windows son aquellos que implementan las API nativas y Win32. La interfaz binaria de la aplicación para estas API requiere que el nombre de cada función se incluya en el directorio de exportación del módulo contenedor. Esta es la información que Elastic emplea actualmente para enriquecer sus pilas de llamadas de puntos finales.

Se podría lograr un enriquecimiento más preciso empleando los símbolos públicos (si están disponibles) alojados en la infraestructura del proveedor (especialmente Microsoft). Si bien este método ofrece mayor fidelidad, implica costos operativos más elevados y no es factible para nuestros clientes aislados.

Una regla general para el kernel de Microsoft y los símbolos nativos es que la interfaz exportada de cada componente tiene un prefijo en mayúscula, como Ldr, Tp o Rtl. Las funciones privadas extienden este prefijo con una p. De forma predeterminada, las funciones privadas con enlace externo se incluyen en la tabla de símbolos públicos. Un desplazamiento muy grande podría indicar una función muy grande, pero también podría indicar simplemente una función sin nombre para la que no tiene símbolos. Una pauta general sería considerar que cualquier desplazamiento de tres dígitos o más en una función exportada probablemente pertenezca a otra función.

Pila de llamadasPaseo por la pilaMódulos de recorrido de pilaExportaciones de Stack Walk (enfoque elástico)Símbolos públicos de Stack Walk
0x7ffb8eb9c9c2 0x12d383f0046 0x7ffb8eb1a9d8 0x7ffb8eb1aaf4 0x7ffb8ea535ff 0x7ffb8da5e8cf 0x7ffb8eaf14eb0x7ffb8eb9c9c4 0x7ffb8c3c71d6 0x7ffb8eb1a9ed 0x7ffb8eb1aaf9 0x7ffb8ea53604 0x7ffb8da5e8d4 0x7ffb8eaf14f1ntdll.dll+0x15c9c4 kernelbase.dll+0xc71d6 ntdll.dll+0xda9ed ntdll.dll+0xdaaf9 ntdll.dll+0x13604 kernel32.dll+0x2e8d4 ntdll.dll+0xb14f1ntdll.dll!NtProtectVirtualMemory+0x14 kernelbase.dll!VirtualProtect+0x36 ntdll.dll!RtlAddRefActivationContext+0x40d ntdll.dll!RtlAddRefActivationContext+0x519 ntdll.dll!RtlAcquireSRWLockExclusive+0x974 kernel32.dll!BaseThreadInitThunk+0x14 ntdll.dll!RtlUserThreadStart+0x21ntdll.dll!NtProtectVirtualMemory+0x14 kernelbase.dll!VirtualProtect+0x36 ntdll.dll!RtlTpTimerCallback+0x7d ntdll.dll!TppTimerpExecuteCallback+0xa9 ntdll.dll!TppWorkerThread+0x644 kernel32.dll!BaseThreadInitThunk+0x14 ntdll.dll!RtlUserThreadStart+0x21

Comparación de los niveles de enriquecimiento de la pila de llamadas

En el ejemplo anterior, el shellcode en 0x12d383f0000 empleó deliberadamente una llamada de cola para que su dirección no apareciera en el recorrido de la pila. Esta mentira por omisión es evidente incluso cuando se camina al acecho. Elastic informa esto con la heurística proxy_call ya que el malware registró una función de devolución de llamada del temporizador para redirigir la llamada a VirtualProtect desde un hilo diferente.

Cómo hacer que las pilas de llamadas sean poderosas

Las pilas de llamadas de las llamadas del sistema que monitoreamos con Event Tracing for Windows (ETW) tienen una estructura esperada. En la parte inferior de la pila se encuentra el hilo StartAddress, normalmente ntdll.dll!RtlUserThreadStart. A esto le sigue la entrada del hilo de la API Win32: kernel32.dll!BaseThreadInitThunk y luego el primer módulo de usuario. Un módulo de usuario es un código de aplicación que no es parte de la API Win32 (o nativa). Este primer módulo de usuario debe coincidir con la Win32StartAddress del hilo (a menos que esa función emplee una llamada final). Seguirán apareciendo más módulos de usuario hasta que el módulo de usuario final realice una llamada a una API Win32 que a su vez realiza una llamada a una API nativa, lo que finalmente da como resultado una llamada del sistema al kernel.

Desde el punto de vista de detección, el módulo más importante en esta pila de llamadas es el módulo de usuario final. Elastic muestra este módulo, incluido su hash y cualquier firma de código. Estos detalles ayudan en la clasificación de alertas, pero lo que es más importante, mejoran significativamente el detalle con la que podemos establecer una base para los comportamientos del software legítimo que a veces se comporta como malware. Cuanto más exactamente podamos establecer la línea base normal, más difícil será para el malware infiltrar.

{
  "process.thread.Ext": {
    "call_stack_summary": "ntdll.dll|kernelbase.dll|file.dll|rundll32.exe|kernel32.dll|ntdll.dll",
    "call_stack": [
      { "symbol_info": "c:\\windows\\system32\\ntdll.dll!NtAllocateVirtualMemory+0x14" }, /* Native API */
      { "symbol_info": "c:\\windows\\system32\\kernelbase.dll!VirtualAllocExNuma+0x62" }, /* Win32 API */
      { "symbol_info": "c:\\windows\\system32\\kernelbase.dll!VirtualAllocEx+0x16" }, /* Win32 API */
      {
        "symbol_info": "c:\\users\\user\\desktop\\file.dll+0x160d8b", /* final user module */
        "callsite_trailing_bytes": "488bf0488d4d88e8197ee2ff488bc64883c4685b5e5f415c415d415e415f5dc390909090905541574156415541545756534883ec58488dac2490000000488b71",
        "callsite_leading_bytes": "088b4d38894c2420488bca48894db8498bd0488955b0458bc1448945c4448b4d3044894dc0488d4d88e8e77de2ff488b4db8488b55b0448b45c4448b4dc0ffd6"
      },
      { "symbol_info": "c:\\users\\user\\desktop\\file.dll+0x7b429" },
      { "symbol_info": "c:\\users\\user\\desktop\\file.dll+0x44a9" },
      { "symbol_info": "c:\\users\\user\\desktop\\file.dll+0x5f58" },
      { "symbol_info": "c:\\windows\\system32\\rundll32.exe+0x3bcf" },
      { "symbol_info": "c:\\windows\\system32\\rundll32.exe+0x6309" }, /* first user module - typically the ETHREAD.Win32StartAddress module */
      { "symbol_info": "c:\\windows\\system32\\kernel32.dll!BaseThreadInitThunk+0x14" }, /* Win32 API */
      { "symbol_info": "c:\\windows\\system32\\ntdll.dll!RtlUserThreadStart+0x21" /* Native API - the ETHREAD.StartAddress module */
      }
    ],
    "call_stack_final_user_module": {
      "path": "c:\\users\\user\\desktop\\file.dll",
      "code_signature": [ { "exists": false } ],
      "name": "file.dll",
      "hash": { "sha256": "0240cc89d4a76bafa9dcdccd831a263bf715af53e46cac0b0abca8116122d242" }
    }
  }
}

Ejemplo de pila de llamadas enriquecida

Enriquecimientos del módulo de usuario final de la pila de llamadas:

nameEl nombre del archivo del call_stack_final_user_module. También puede ser "Sin respaldo", lo que indica memoria ejecutable privada, o "Indeterminado", lo que indica una pila de llamadas sospechosa.
caminoLa ruta del archivo del call_stack_final_user_module.
hash.sha256El sha256 del call_stack_final_user_module, o del módulo protection_provenance si lo hay.
firma_de_códigoFirma de código del call_stack_final_user_module, o del módulo protection_provenance si lo hay.
asignación_de_bytes_privadosLa cantidad de bytes en esta región de memoria que son +X y no compartibles. Los valores distintos de cero pueden indicar enganches, parches o vaciados del código.
protectionLa protección de memoria para la región de actuación de las páginas está incluida si no es RX. Corresponde a MEMORY_BASIC_INFORMATION.Protect.
protection_provenanceEl nombre de la región de memoria que provocó la última modificación de la protección de esta página. "Sin respaldo" puede indicar shellcode.
ruta de procedencia de protecciónLa ruta del módulo que provocó la última modificación de la protección de esta página.
razónEl call_stack_summary anómalo que condujo a una procedencia de protección "Indeterminada".

Un glosario rápido de pilas de llamadas

Al examinar las pilas de llamadas, hay algunas funciones de API nativas que conviene conocer. Ken Johnson, ahora de Microsoft, nos proporcionó un catálogo de devoluciones de llamadas del modo kernel NTDLL al modo usuario para ayudarnos a comenzar. En serio, deberías hacer una pausa aquí y leer eso primero.

Conocimos RtlUserThreadStart anteriormente. Tanto este como su hermano RtlUserFiberStart solo deberían aparecer en la parte inferior de una pila de llamadas. Estos son los puntos de entrada para los hilos y fibras del usuario, respectivamente. Sin embargo, la primera instrucción en cada hilo es en realidad LdrInitializeThunk. Luego de ejecutar el componente de modo usuario de la inicialización del hilo (y el proceso, si es necesario), esta función transfiere el control al punto de entrada a través de NtContinue, que actualiza el puntero de instrucción directamente. Esto significa que no aparecerá en ningún recorrido de pila futuro.

Entonces, si ve una pila de llamadas que incluye LdrInitializeThunk, significa que está en el comienzo de la ejecución de un hilo. Aquí es donde opera el motor Shim de compatibilidad de aplicaciones, donde los productos de seguridad basados en ganchos prefieren instalar y donde el malware intenta ejecutar antes que esos otros productos de seguridad. Marcus Hutchins y Guido Miggelenbrink escribieron excelentes blogs sobre este tema. Esta carrera de startups no existe para productos de seguridad que emplean el kernel ETW para telemetría.

{
  "process.thread.Ext": {
    "call_stack_summary": "ntdll.dll|file.exe|ntdll.dll",
    "call_stack": [
      { "symbol_info": "c:\\windows\\system32\\ntdll.dll!ZwProtectVirtualMemory+0x14" },
      { "symbol_info": "c:\\users\\user\\desktop\\file.exe+0x1bac8" },
      { "symbol_info": "c:\\windows\\system32\\ntdll.dll!RtlAnsiStringToUnicodeString+0x3cb" },
      { "symbol_info": "c:\\windows\\system32\\ntdll.dll!LdrInitShimEngineDynamic+0x394d" },
      { "symbol_info": "c:\\windows\\system32\\ntdll.dll!LdrInitializeThunk+0x1db" },
      { "symbol_info": "c:\\windows\\system32\\ntdll.dll!LdrInitializeThunk+0x63" },
      { "symbol_info": "c:\\windows\\system32\\ntdll.dll!LdrInitializeThunk+0xe" }
    ],
    "call_stack_final_user_module": {
      "path": "c:\\users\\user\\desktop\\file.exe",
      "code_signature": [ { "exists": false } ],
      "name": "file.exe",
      "hash": { "sha256": "a59a7b56f695845ce185ddc5210bcabce1fff909bac3842c2fb325c60db15df7" }
    }
  }
}

Ejemplo de ejecución previa al punto de entrada

El siguiente par es KiUserExceptionDispatcher y KiRaiseUserExceptionDispatcher. El núcleo emplea el primero para pasar la ejecución a un controlador de excepciones estructurado de modo de usuario registrado después de que se produjo una condición de excepción de modo de usuario. Este último también genera una excepción, pero en nombre del núcleo. Esta segunda variante generalmente solo la detectan los depuradores, incluido Application Verifier, y ayuda a identificar cuándo el código de modo de usuario no está verificando de manera suficiente los códigos de retorno de las llamadas al sistema. Estas funciones generalmente se ven en pilas de llamadas relacionadas con el manejo de fallas específicas de la aplicación o en los reportes de errores de Windows. Sin embargo, a veces el malware lo usará como un pseudo-punto de interrupción; por ejemplo, si desean fluctuar las protecciones de memoria para volver a ocultar su shellcode inmediatamente luego de realizar una llamada al sistema.

{
  "process.thread.Ext": {
    "call_stack_summary": "ntdll.dll|file.exe|ntdll.dll|file.exe|kernel32.dll|ntdll.dll",
    "call_stack": [
      {
        "symbol_info": "c:\\windows\\system32\\ntdll.dll!ZwProtectVirtualMemory+0x14",
        "protection_provenance": "file.exe", /* another vendor's hooks were unhooked */
        "allocation_private_bytes": 8192
      },
      { "symbol_info": "c:\\users\\user\\desktop\\file.exe+0xd99c" },
      { "symbol_info": "c:\\windows\\system32\\ntdll.dll!RtlInitializeCriticalSectionAndSpinCount+0x1c6" },
      { "symbol_info": "c:\\windows\\system32\\ntdll.dll!RtlWalkFrameChain+0x1119" },
      { "symbol_info": "c:\\windows\\system32\\ntdll.dll!KiUserExceptionDispatcher+0x2e" },
      { "symbol_info": "c:\\users\\user\\desktop\\file.exe+0x12612" },
      { "symbol_info": "c:\\windows\\system32\\kernel32.dll!BaseThreadInitThunk+0x14" },
      { "symbol_info": "c:\\windows\\system32\\ntdll.dll!RtlUserThreadStart+0x21" }
    ],
    "call_stack_final_user_module": {
      "name": "file.exe",
      "path": "c:\\users\\user\\desktop\\file.exe",
      "code_signature": [ { "exists": false }],
      "hash":   { "sha256": "0e5a62c0bd9f4596501032700bb528646d6810b16d785498f23ef81c18683c74" }
    }
  }
}

Fluctuación de protección mediante un ejemplo de controlador de excepciones

El siguiente es KiUserApcDispatcher, que se emplea para entregar APC de usuario. Estas son una de las herramientas favoritas de los autores de malware, ya que Microsoft sólo proporciona una visibilidad limitada de su uso.

{
  "process.thread.Ext": {
    "call_stack_summary": "ntdll.dll|kernelbase.dll|ntdll.dll|kernelbase.dll|cronos.exe",
    "call_stack": [
      { "symbol_info": "c:\\windows\\system32\\ntdll.dll!NtProtectVirtualMemory+0x14" },
      { "symbol_info": "c:\\windows\\system32\\kernelbase.dll!VirtualProtect+0x36" }, /* tail call */
      { "symbol_info": "c:\\windows\\system32\\ntdll.dll!KiUserApcDispatcher+0x2e" },
      { "symbol_info": "c:\\windows\\system32\\ntdll.dll!ZwDelayExecution+0x14" },
      { "symbol_info": "c:\\windows\\system32\\kernelbase.dll!SleepEx+0x9e" },
      {
        "symbol_info": "c:\\users\\user\\desktop\\file.exe+0x107d",
        "allocation_private_bytes": 147456, /* stomped */
        "protection": "RW-", /* fluctuation */
        "protection_provenance": "Undetermined", /* proxied call */
        "callsite_leading_bytes": "010000004152524c8d520141524883ec284150415141baffffffff41525141ba010000004152524c8d520141524883ec284150b9ffffffffba0100000041ffe1",
        "callsite_trailing_bytes": "4883c428c3cccccccccccccccccccccccccccc894c240857b820190000e8a10c0000482be0488b052fd101004833c44889842410190000488d84243014000048"
      }
    ],
    "call_stack_final_user_module": {
      "name": "Undetermined",
      "reason": "ntdll.dll|kernelbase.dll|ntdll.dll|kernelbase.dll|file.exe"
    }
  }
}

Fluctuación de protección mediante el ejemplo de APC

El administrador de ventanas de Windows se implementa en un controlador de dispositivo en modo kernel (win32k.sys). Principalmente. A veces, el administrador de ventanas necesita hacer algo desde el modo usuario, y KiUserCallbackDispatcher es el mecanismo para lograrlo. Es básicamente una llamada al sistema inversa que apunta a las funciones user32.dll. Sobreescribir una entrada en KernelCallbackTable de un proceso es una forma fácil de secuestrar un hilo GUI, por lo que cualquier otro módulo que siga a esta llamada es sospechoso.

El conocimiento del propósito de cada uno de estos puntos de entrada del modo kernel al modo usuario ayuda en gran medida a determinar si una pila de llamadas determinada es natural o si fue mal empleada para lograr objetivos alternativos.

Hacer que las pilas de llamadas sean comprensibles

Para facilitar la comprensión, también etiquetamos el evento con varios process.Ext.api.behaviors que identificamos. Estos comportamientos no son necesariamente maliciosos, pero resaltan aspectos que son relevantes para la clasificación de alertas o la búsqueda de amenazas. Para las pilas de llamadas, estos incluyen:

API nativaSe realizó una llamada directamente a la API nativa en lugar de a la API Win32.
llamada al sistema directaA syscall instruction originated outside of the Native API layer.
llamada proxyLa pila de llamadas puede indicar una llamada API proxy para enmascarar la fuente real.
ShellcodeMemoria no imagen ejecutable de segunda generación denominada API sensible.
llamada indirecta a la imagenAn entry in the call stack was preceded by a call to a dynamically resolved function.
imagen_ropNinguna instrucción de llamada precedió a una entrada en la pila de llamadas.
imagen_rwxUna entrada en la pila de llamadas se puede escribir. El código debe ser de sólo lectura.
sin respaldo_rwxUna entrada en la pila de llamadas no es una imagen y es escribible. Incluso el código JIT debe ser de sólo lectura.
pila truncadaThe call stack seems to be unexpectedly truncated. This may be due to malicious tampering.

En algunos contextos, estos comportamientos por sí solos pueden ser suficientes para detectar malware.

Suplantación de identidad: ¿evasión o responsabilidad?

La suplantación de direcciones de retorno fue una técnica básica de piratería de juegos y malware durante muchos, muchos años. Este simple truco permite que el código inyectado tome prestada la reputación de un módulo legítimo con pocas consecuencias. El objetivo de la inspección profunda de la pila de llamadas y de las líneas de base del comportamiento es impedir que el malware tenga vía libre.

Los investigadores ofensivos estuvieron colaborando en este esfuerzo buscando maneras de suplantar la pila de llamadas completa. Lo más destacable:

SilentMoonwalk, además de ser una magnífica investigación ofensiva, es un magnífico ejemplo de cómo mentir puede meterte en el doble de problemas, pero sólo si te atrapan. Muchas técnicas de evasión de defensa se basan en la seguridad por oscuridad y, una vez expuestas por los investigadores, pueden convertir en una responsabilidad. En este caso, la investigación incluyó asesoramiento sobre las oportunidades de detección que introduce el intento de evasión.

{
  "process.thread.Ext": {
    "call_stack_summary": "ntdll.dll|kernelbase.dll|kernel32.dll|ntdll.dll",
    "call_stack": [
      { "symbol_info": "c:\\windows\\system32\\ntdll.dll!NtAllocateVirtualMemory+0x14" },
      { "symbol_info": "c:\\windows\\system32\\kernelbase.dll!VirtualAlloc+0x48" },
      {
        "symbol_info": "c:\\windows\\system32\\kernelbase.dll!CreatePrivateObjectSecurity+0x31",
        /* 4883c438 stack desync gadget - add rsp 0x38 */
        "callsite_trailing_bytes": "4883c438c3cccccccccccccccccccc48895c241057498bd8448bd2488bf94885c90f84660609004885db0f845d060900418bd14585c97411418bc14803c383ea",
        "callsite_leading_bytes": "cccccccccccccccccccccccccccccc4883ec38488b4424684889442428488b442460488944242048ff15d9b21b000f1f44000085c00f8830300900b801000000"
      },
      { "symbol_info": "c:\\windows\\system32\\kernelbase.dll!Internal_EnumSystemLocales+0x406" },
      { "symbol_info": "c:\\windows\\system32\\kernelbase.dll!SystemTimeToTzSpecificLocalTimeEx+0x2d1" },
      { "symbol_info": "c:\\windows\\system32\\kernelbase.dll!WaitForMultipleObjectsEx+0x982" },
      { "symbol_info": "c:\\windows\\system32\\kernel32.dll!BaseThreadInitThunk+0x14" },
      { "symbol_info": "c:\\windows\\system32\\ntdll.dll!RtlUserThreadStart+0x21" }
    ],
    "call_stack_final_user_module": {
      "name": "Undetermined", /* gadget module resulted in suspicious call stack */
      "reason": "ntdll.dll|kernelbase.dll|kernel32.dll|ntdll.dll"
    }
  }
}

Ejemplo de pila de llamadas de SilentMoonwalk

Una técnica estándar para descubrir artefactos ocultos es enumerarlos empleando múltiples técnicas y comparar los resultados para detectar discrepancias. Así es como funciona RootkitRevealer. Este enfoque también se empleó en Get-InjectedThreadEx.exe, que sube por la pila de subprocesos y también la baja.

En determinadas circunstancias, es posible que podamos recuperar una pila de llamadas de dos maneras. Si hay discrepancias, verá la pila de llamadas menos confiable emitida como call_stack_summary_original.

{
  "process.thread.Ext": {
    "call_stack_summary": "ntdll.dll",
    "call_stack_summary_original": "ntdll.dll|kernelbase.dll|version.dll|kernel32.dll|ntdll.dll",
    "call_stack": [
      { "symbol_info": "c:\\windows\\system32\\ntdll.dll!NtContinue+0x12" },
      { "symbol_info": "c:\\windows\\system32\\ntdll.dll!LdrInitializeThunk+0x13" }
    ],
    "call_stack_final_user_module": {
      "name": "Undetermined",
      "reason": "ntdll.dll"
    }
  }
}

Ejemplo original de resumen de pila de llamadas

Las pilas de llamadas son para todos

De forma predeterminada, solo encontrará pilas de llamadas en nuestras alertas, pero esto se puede configurar a través de una política avanzada.

eventos.pilasdellamadas.emitir_en_eventosSi se configura, las pilas de llamadas se incluirán en los eventos regulares donde se recopilan. De lo contrario, solo se incluyen en eventos que activan reglas de protección del comportamiento. Tenga en cuenta que configurar esto puede aumentar significativamente los volúmenes de datos. Predeterminado: falso

Hay más información sobre las pilas de llamadas de Windows disponible en los siguientes artículos de Elastic Security Labs: