Ataques de manipulación de objetos directos del kernel (DKOM) a proveedores de ETW.

Retrato de vista lateral de un hombre mirando una pantalla de una computadora mientras trabaja tarde en la noche

Autor

Ruben Boonen

CNE Capability Lead, Adversary Services

IBM X-Force

En esta publicación, los hackers ofensivos de IBM Security X-Force Red analizan cómo los atacantes, con privilegios elevados, pueden usar su acceso para organizar las capacidades posteriores a la explotación de Windows Kernel. En los últimos años, las cuentas públicas han demostrado cada vez más que los atacantes menos sofisticados utilizan esta técnica para lograr sus objetivos. Por ello, es importante que pongamos el foco en esta capacidad y aprendamos más sobre su posible impacto. Específicamente, en esta publicación, evaluaremos cómo se puede usar la explotación posterior del Kernel para cegar los sensores de ETW y vincularlos con muestras de malware identificadas en la naturaleza el año pasado.

Introducción

Con el tiempo, las mitigaciones de seguridad y la telemetría de detección en Windows han mejorado sustancialmente. Cuando estas capacidades se combinan con soluciones de detección y respuesta de endpoints (EDR) bien configuradas, pueden representar una barrera no trivial posterior a la explotación. Los atacantes se enfrentan a un costo constante para desarrollar e iterar tácticas, técnicas y procedimientos (TTP) para evitar las heurísticas de detección. En el equipo de Adversary Simulation de IBM Security X-Force, nos enfrentamos a este mismo problema. Nuestro equipo tiene la tarea de simular capacidades avanzadas de amenazas en algunos de los entornos más grandes y reforzados. La combinación de soluciones de seguridad complejas y refinadas y equipos de centros de operaciones de seguridad (SOC) bien capacitados puede ser muy exigente para el tradecraft. En algunos casos, el uso de un TTP específico queda completamente obsoleto en un lapso de tres a cuatro meses (generalmente vinculado a pilas de tecnología específicas).

Los atacantes pueden optar por aprovechar la ejecución de código en Windows Kernel para manipular algunas de estas protecciones o evitar por completo una serie de sensores del espacio de usuario. La primera demostración publicada de tal capacidad fue en 1999 en la revista Phrack. En los años intermedios, ha habido una serie de casos informados en los que los actores de amenazas (TA) han utilizado rootkits de kernel para la explotación posterior. Algunos ejemplos más antiguos incluyen Derusbi Family y Lamberts Toolkit.

Tradicionalmente, estos tipos de capacidades se han limitado principalmente a los TA avanzados. Sin embargo, en los últimos años vimos a más atacantes comunes usar primitivas de explotación Bring Your Own Vulnerable Driver (BYOVD) para facilitar acciones en el endpoint. En algunos casos, estas técnicas fueron bastante primitivas, limitadas a tareas simples, pero también hubo demostraciones más capaces.

A finales de septiembre de 2022, investigadores de ESET publicaron un informetécnico sobre dicha capacidad de Kernel empleada por el TA Lazarus en varios ataques contra entidades en Bélgica y los Países Bajos con fines de exfiltración de datos. Este documento establece una serie de primitivas de manipulación de objetos directos del kernel (DKOM) que la carga útil utiliza para cegar la telemetría OS/AV/EDR. La investigación pública disponible sobre estas técnicas es escasa. Obtener una comprensión más profunda del tradecraft posterior a la explotación de Kernel es crítico para la defensa. Un argumento tradicional e ingenuo que se escucha a menudo es que un atacante con privilegios elevados puede hacer cualquier cosa, así que ¿por qué deberíamos modelar capacidades en ese escenario? Esta es una postura débil. Los defensores deben comprender qué capacidades tiene un atacante cuando están elevadas, qué fuentes de datos siguen siendo confiables (y cuáles no), qué opciones de contención existen y cómo se podrían detectar técnicas avanzadas (incluso si no existen capacidades para realizar esas detecciones ). En esta publicación me centraré específicamente en aplicar parches a las estructuras de Kernel Event Tracing for Windows (ETW) para que los proveedores resulten ineficaces o inoperables. Proporcionaré algunos antecedentes sobre esta técnica, analizaré cómo un atacante puede manipular las estructuras Kernel ETW y entraré en algunos de los mecanismos para encontrar estas estructuras. Por último, revisaré cómo Lazarus implementó esta técnica en su carga útil.

Las últimas noticias tecnológicas, respaldadas por los insights de expertos

Manténgase al día sobre las tendencias más importantes e intrigantes de la industria sobre IA, automatización, datos y más con el boletín Think. Consulte la Declaración de privacidad de IBM.

¡Gracias! Ya está suscrito.

Su suscripción se entregará en inglés. En cada boletín, encontrará un enlace para darse de baja. Puede gestionar sus suscripciones o darse de baja aquí. Consulte nuestra Declaración de privacidad de IBM para obtener más información.

ETW DKOM

ETW es una instalación de rastreo de alta velocidad integrada en el sistema operativo Windows. Permite el registro de eventos y actividades del sistema por aplicaciones, controladores y el sistema operativo, proporcionando visibilidad detallada del comportamiento del sistema para la depuración, el análisis de rendimiento y el diagnóstico de seguridad.

En esta sección, daré una descripción general de alto nivel de Kernel ETW y su superficie de ataque asociada. Esto será útil para comprender mejor la mecánica involucrada en la manipulación de los proveedores de ETW y los efectos asociados de esas manipulaciones.

Superficie de ataque de Kernel ETW

Investigadores de Binarly ofrecieron una charla en BHEU 2021, en la que se trató la superficie de ataque general de ETW en Windows. A continuación se muestra una descripción general del modelo de amenazas.

diagrama de flujo que muestra el modelado de amenazas ETW
Figura 1. Veni, No Vidi, No Vici: ataques a sensores EDR ciegos de ETW (Binarly)

En esta publicación, nos centramos en la superficie de ataque del espacio del Kernel.

un gráfico que muestra y describe los ataques a los proveedores de ETW en modo kernel
Figura 2. Veni, No Vidi, No Vici: ataques a sensores EDR ciegos de ETW (Binarly)

Esta publicación considera únicamente los ataques dentro de la primera categoría de ataque que se muestra en la “Figura 2”, donde el rastreo está deshabilitado o alterado de alguna manera.

Como nota de advertencia, al considerar estructuras opacas en Windows, siempre es importante recordar que están sujetas a cambios y, de hecho, cambian con frecuencia entre las versiones de Windows. Esto es especialmente importante al manipular datos del kernel, ya que los errores probablemente resulten en una pantalla azul de la muerte (BSoD). ¡Tenga cuidado!

Inicialización

Los proveedores del kernel se registran utilizando nt!EtwRegister, una función exportada por ntoskrnl. A continuación se puede ver una versión descompilada de la función.

captura de pantalla del código para la descompilación de nt!EtwRegister
Figura 3: descompilación de nt!EtwRegister

La inicialización completa ocurre dentro de la función interna EtwpRegisterKMProvider, pero hay dos conclusiones principales aquí:

  • El ProviderID es un puntero a un GUID de 16 bytes. Este GUID es estático en todos los sistemas operativos, por lo que se puede utilizar para identificar el proveedor que se está inicializando.
  • El RegHandle es una dirección de memoria que recibe un puntero a una estructura de _ETW_REG_ENTRY en una llamada exitosa. Esta estructura de datos y algunas de sus propiedades anidadas proporcionan vías para manipular el proveedor de ETW según la investigación de Binarly.

Enumeremos brevemente las estructuras que Binarly destacó en su diapositiva en la Figura 2.

ETW_REG_ENTRY

A continuación, se muestra una lista completa de 64 bits de la estructura _ETW_REG_ENTRY. Hay detalles adicionales disponibles en el blog de Geoff Chappell aquí. Esta estructura también se puede explorar más a fondo en el Vergilius Project.

// 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

Una de las entradas anidadas dentro de _ETW_REG_ENTRY es GuidEntry, que es una estructura _ETW_GUID_ENTRY. Puede encontrar más información sobre esta estructura no documentada en el blog de Geoff Chappell aquí y en el Vergilius Project.

// 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

Por último, una de las entradas anidadas dentro de _ETW_GUID_ENTRY es ProviderEnableInfo, que es una estructura _TRACE_ENABLE_INFO. Para obtener más información sobre los elementos de esta estructura de datos, puede consultar la documentación oficial de Microsoft y el Vergilius Proyect. La configuración de esta estructura afecta directamente a la operación y las capacidades del proveedor.

// 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
};

Comprender el uso del identificador de registro

Si bien algunos antecedentes teóricos son buenos, siempre es mejor observar el uso de ejemplos concretos para obtener una comprensión más profunda de un tema. Consideremos brevemente un ejemplo. La mayoría de los proveedores de ETW de kernel críticos se inicializan dentro de nt!EtwpInitialize, que no se exporta. Mirar dentro de esta función revela alrededor de quince proveedores.

Captura de pantalla del código para la descompilación parcial de nt!EtwpInitialize.
Figura 4: Descompilación parcial de nt!EtwpInitialize

Tomando como ejemplo la entrada Microsoft Windows-Threat-Intelligence (ETwTi), podemos verificar el parámetro global ThreatIntProviderGuid para recuperar el GUID de este proveedor.

Captura de pantalla del código para el GUID del proveedor de EtwTi
Figura 5: GUID del proveedor de EtwTi

La búsqueda de este GUID en línea revelará inmediatamente que pudimos recuperar el valor correcto (f4e1897c-bb5d-5668-f1d8-040f4d8dd344).

Veamos una instancia en la que se usa el parámetro de identificador de registro, EtwThreatIntProvRegHandle, y analicemos cómo se usa. Un lugar donde se hace referencia al identificador es nt!EtwTiLogDriverObjectUnLoad. Por el nombre de esta función, podemos intuir que está destinada a generar eventos cuando el Kernel descarga un objeto controlador.

Captura de pantalla del código para la descompilación de nt!EtwTiLogDriverUnload
Figura 6: Descompilación de nt!EtwTiLogDriverUnload

Las funciones nt!EtwEventEnabled y nt!EtwProviderEnabled se llaman aquí pasando el identificador de registro como uno de los argumentos. Veamos una de estas subfunciones para comprender más sobre lo que está sucediendo.

Captura de pantalla del código para la descompilación de nt!EtwProviderEnable
Figura 7: Descompilación de nt!EtwProviderEnable

Es cierto que esto es un poco difícil de seguir. Sin embargo, la aritmética de punteros no es especialmente importante. En su lugar, centrémonos en cómo esta función procesa el identificador de registro. Parece que la función valida una serie de propiedades de la estructura _ETW_REG_ENTRY y sus subestructuras, como la propiedad GuidEntry.

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

Y la propiedad GuidEntry->ProviderEnableInfo.

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

Luego, la función pasa a verificaciones similares basadas en niveles. Finalmente, la función devuelve verdadero o falso para indicar si un proveedor está habilitado para el registro de eventos en un nivel y palabra clave especificados. Hay más detalles disponibles en la documentación oficial de Microsoft.

Podemos ver que cuando se accede a un proveedor a través de su identificador de registro, la integridad de esas estructuras se vuelve muy importante para la operación del proveedor. Por el contrario, si un atacante pudiera manipular esas estructuras, podría influir en el flujo de control de la persona que llama para descartar o eliminar los eventos que se registran.

Atacar identificadores de registro

Al repasar la superficie de ataque declarada por Binarly y basándonos en nuestro análisis preliminar, podemos plantear algunas estrategias para interrumpir la recopilación de eventos.

  • Un atacante puede poner en NULL el puntero _ETW_REG_ENTRY. Cualquier función que haga referencia al identificador de registro asumirá entonces que el proveedor no se ha inicializado.
  • Un atacante puede poner en NULL el puntero _ETW_REG_ENTRY->GuidEntry->ProviderEnableInfo. Esto debería desactivar eficazmente las capacidades de recopilación del proveedor, ya que ProviderEnableInfo es un puntero a una estructura _TRACE_ENABLE_INFO que describe cómo se supone que debe funcionar el proveedor.
  • Un atacante puede sobreescribir propiedades de la estructura de datos  _ETW_REG_ENTRY->GuidEntry->ProviderEnableInfo para manipular la configuración del proveedor.
    • IsEnabled: establezca en 1 para habilitar la recepción de eventos del proveedor o para ajustar la configuración utilizada al recibir eventos del proveedor. Configure en 0 para desactivar los eventos receptores del proveedor.
    • Nivel: un valor que indica el nivel máximo de eventos que desea que escriba el proveedor. El proveedor suele escribir un evento si el nivel del evento es menor o igual a este valor, además de cumplir con los criterios MatchAnyKeyword y MatchAllKeyword.
    • MatchAnyKeyword: máscara de bits de 64 bits de palabras clave que determinan las categories de eventos que desea que escriba el proveedor. El proveedor suele escribir un evento si los bits de palabra clave del evento coinciden con cualquiera de los bits establecidos en este valor o si el evento no tiene bits de palabra clave establecidos, además de cumplir con los criterios Level y MatchAllKeyword.
    • MatchAllKeyword: máscara de bits de 64 bits de palabras clave que restringe los eventos que desea que escriba el proveedor. El proveedor suele escribir un evento si los bits de palabra clave del evento coinciden con todos los bits establecidos en este valor o si el evento no tiene bits de palabra clave establecidos, además de cumplir con los criterios Level y MatchAnyKeyword.

Tradecraft de búsqueda del kernel

Ahora tenemos una buena idea de cómo es un ataque DKOM contra ETW. Supongamos que el atacante tiene una vulnerabilidad que otorga una primitiva de lectura/escritura del kernel, como lo hace el malware Lazarus en este caso al cargar un controlador vulnerable. Lo que falta es una forma de encontrar estos identificadores de registro.

Voy a exponer dos técnicas principales para encontrar estos identificadores y mostrar la variante de uno que emplea Lazarus en su carga útil del Kernel.

Bypass KASLR de nivel de integridad medio (MedIL)

En primer lugar, puede ser prudente explicar que, si bien existe Kernel ASLR, este no es un límite de seguridad para los atacantes locales si pueden ejecutar código en MedIL o superior. Hay muchas formas de filtrar punteros de Kernel que solo están restringidas en escenarios de sandbox o LowIL. Para un poco de contexto, puede echar un vistazo a I Got 99 Problems But a Kernel Pointer Ain't One de Alex Ionescu; muchas de estas técnicas siguen siendo aplicables hoy en día.

La herramienta elegida aquí es ntdll!NtQuerySystemInformation con la clase SystemModuleInformation:

internal static UInt32 SystemModuleInformation = 0xB;

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

Esta función devuelve la dirección base activa de todos los módulos cargados en el espacio del kernel. En ese punto, es posible analizar esos módulos en disco y convertir los desplazamientos de archivos sin procesar en direcciones relativas virtuales y viceversa.

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 atacante también puede cargar estos módulos en su proceso de usuario mediante llamadas API de biblioteca de carga estándar (por ejemplo, ntdll!LdrLoadDll). Hacerlo evitaría las complicaciones de convertir las compensaciones de archivos a RVA y viceversa. Sin embargo, desde el punto de vista de la seguridad operativa (OpSec), esto no es ideal, ya que puede generar más telemetría de detección.

Método 1: Cadenas de gadgets

Cuando es posible, esta es la técnica que prefiero porque hace que las filtraciones sean más portátiles entre versiones de módulo, ya que se ven menos afectadas por los cambios en los parches. La desventaja es que depende de las cadenas de gadgets existentes para el objeto que desea filtrar.

Teniendo en cuenta los identificadores de registro ETW, tomemos Microsoft-Windows-Threat-Intelligence como ejemplo. A continuación puede ver la llamada completa a nt!EtwRegister.

Captura de pantalla del código para el desensamblado completo de nt!EtwRegister CALL
Figura 8: Desensamblado completo de CALL en nt!EtwRegister

Aquí queremos filtrar el puntero al identificador de registro, EtwThreatIntProvRegHandle. Como se ve cargado en param_4 en la primera línea de la Figura 8. Este puntero se resuelve a un global dentro de la sección .data del módulo Kernel. Dado que esta llamada ocurre en una función no exportada, no podemos filtrar su dirección directamente. En cambio, tenemos que mirar dónde se hace referencia a este global y ver si se usa en una función cuya dirección puede filtrarse.

captura de pantalla del código para las referencias de nt!EtwThreatIntProvRegHandle
Figura 9: Referencias de nt!EtwThreatIntProvRegHandle

Explorar algunas de estas entradas revela rápidamente un candidato en nt!KeInsertQueueApc.

captura de pantalla del código para la descompilación parcial de nt!KeInsertQueueApc
Figura 10: Descompilación parcial de nt!KeInsertQueueApc

Este es un gran candidato por varias razones:

  • nt!KeInsertQueueApc es una función exportada. Esto significa que podemos filtrar su dirección en vivo usando un bypass KASLR. Entonces podemos usar nuestra vulnerabilidad Kernel para leer datos en esa dirección.
  • El global se utiliza al comienzo de la función. Esto es muy útil porque significa que lo más probable es que no necesitemos construir una lógica compleja de análisis de instrucciones para encontrarlo.

Al observar el ensamblaje, se muestra el siguiente diseño.

captura de pantalla del código para el desensamblado parcial de nt!KeInsertQueueApc
Figura 11: Desensamblado parcial de nt!KeInsertQueueApc

La filtración de este identificador de registro se vuelve sencillo. Leemos una matriz de bytes usando nuestra vulnerabilidad, y buscamos la primera instrucción mov R10 para calcular el desplazamiento virtual relativo de la variable global. El cálculo sería algo así:

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

Con el identificador de registro, es posible acceder a la estructura de datos _ETW_REG_ENTRY.

En general, estas cadenas de gadgets se pueden utilizar para filtrar una variedad de estructuras de datos del Kernel. Sin embargo, vale la pena señalar que no siempre es posible encontrar tales cadenas de gadgets y, a veces, las cadenas de gadgets pueden tener múltiples etapas complejas. Por ejemplo, una posible cadena de gadgets para filtrar constantes de entrada de directorio de páginas (PDE) podría verse así.

MmUnloadSystemImage -> MiUnloadSystemImage -> MiGetPdeAddress

De hecho, un análisis superficial de los identificadores de registro de ETW reveló que la mayoría no cuentan con cadenas de gadgets adecuadas que se puedan usar como se describió anteriormente.

Método 2: Escaneo de memoria

La otra opción principal para filtrar estos identificadores de registro ETW es utilizar el escaneo de memoria, ya sea desde la memoria del kernel en vivo o desde un módulo en el disco. Recuerde que al escanear módulos en el disco es posible convertir desplazamientos de archivos en RVA.

Este enfoque consiste en identificar patrones únicos de bytes, escanear esos patrones y, finalmente, realizar algunas operaciones en los desplazamientos de la coincidencia del patrón. Echemos otro vistazo a nt! ETWPInitialize para entender mejor esto:

Captura de pantalla del código para la descompilación parcial de nt!EtwpInitialize.
Figura 12: Descompilación parcial de nt!EtwpInitialize

Las quince llamadas a nt!EtwRegister se agrupan en su mayoría en esta función. La estrategia principal aquí es encontrar un patrón único que aparezca antes de la primera llamada a nt!EtwRegister y un segundo patrón que aparezca después de la última llamada a nt!EtwRegister. Esto no es demasiado complejo. Un truco que se puede utilizar para mejorar la portabilidad es crear un escáner de patrones capaz de manejar cadenas de bytes comodín. Esta es una tarea que se deja al lector.

Una vez que se ha identificado un índice de inicio y parada, es posible ver todas las instrucciones intermedias.

  • Las posibles instrucciones CALL se pueden identificar basándose en el código de operación de CALL, que es 0xe8.
  • Posteriormente, se utiliza una lectura de tamaño DWORD para calcular el desplazamiento relativo de la posible instrucción CALL.
  • A continuación, este desplazamiento se suma a la dirección relativa de la CALL y se incrementa en cinco (el tamaño de la instrucción de ensamblaje).
  • Por último, este nuevo valor se puede comparar con nt!EtwRegister para encontrar todas las ubicaciones CALL válidas.

Una vez encontradas todas las instrucciones CALL, es posible buscar hacia atrás y extraer los argumentos de función: primero el GUID que identifica al proveedor ETW y segundo, la dirección del identificador de registro. Con esta información disponible, podemos realizar ataques DKOM informados en los identificadores de registro para afectar la operación de los proveedores identificados.

Aplicación de parches a Lazarus ETW

Obtuve una muestra del archivo DLL FudModle mencionado en el informe técnico de ESET y lo analicé. Este DLL carga un controlador Dell vulnerable firmado (desde un recurso codificado XOR en línea) y luego pone a prueba el controlador para aplicar parches a muchas estructuras del kernel con el fin de limitar la telemetría en el host.

Captura de pantalla del código para el hash Lazarus FudModule
Figura 13: Hash Lazarus FudModule

Como la parte final de esta publicación, quiero repasar la estrategia que Lazarus utiliza para encontrar los identificadores de registro de Kernel ETW. Es una variación del método de escaneo que mencionamos antes.

Al inicio de la función de búsqueda, Lazarus resuelve nt!EtwRegister y utiliza esta dirección para iniciar el escaneo

Captura de pantalla del código para la descompilación de búsqueda parcial de ETW de Lazarus FudModule
Figura 14: Descompilación de búsqueda parcial de ETW de Lazarus FudModule

Esta decisión es un poco extraña porque depende de dónde existe esa función en relación con dónde se llama a la función. La posición relativa de una función en un módulo puede variar de una versión a otra, ya que se puede introducir, eliminar o modificar código nuevo. Sin embargo, debido a la forma en que se compilan los módulos, se espera que las funciones mantengan un orden relativamente estable. Se supone que se trata de una optimización de la velocidad de búsqueda.

Al buscar referencias a nt!EtwRegister en ntoskrnl, parece que no se pierden muchas entradas utilizando esta técnica. Es posible que Lazarus también haya realizado análisis adicionales para determinar que las entradas omitidas no son importantes o que no necesitan parches. Las entradas omitidas se destacan a continuación. Emplear esta estrategia permite a Lazarus saltar 0x7b1de0 bytes mientras realiza el escaneo, lo cual puede ser una cantidad no trivial si el escáner es lento.

Captura de pantalla del código para instancias de llamadas a nt!EtwRegister
Figura 15: Instancias de llamadas a nt!EtwRegister

Además, al iniciar el escaneo, se omiten las primeras cinco coincidencias antes de comenzar a registrar los identificadores de registro. A continuación se muestra parte de la función de búsqueda.

Captura de pantalla del código para la descompilación de búsqueda parcial de ETW de Lazarus FudModule
Figura 16: Descompilación de búsqueda parcial de ETW de Lazarus FudModule

El código es un poco confuso, pero captamos los puntos destacados de la trama. El código busca llamadas a nt!EtwRegister, extrae el identificador de registro, convierte este identificador en la dirección activa mediante una omisión de KASLR y almacena el puntero en una matriz reservada para este propósito dentro de una estructura de configuración de malware (asignada en la inicialización).

Por último, echemos un vistazo a lo que hace Lazarus para deshabilitar estos proveedores.

Captura de pantalla del código para identificadores de registro de ETW NULL de Lazarus FudModule
Figura 17: Identificadores de registro de ETW NULL de Lazarus FudModule

Esto tiene sentido; lo que hace Lazarus aquí es filtrar la variable global que vimos antes y luego sobrescribir el puntero en esa dirección con NULL. Esto borra efectivamente la referencia a la estructura de datos _ETW_REG_ENTRY si existe.

No estoy completamente satisfecho con el tradecraft mostrado por varias razones:

  • La carga útil no captura los GUID del proveedor, por lo que no puede tomar ninguna decisión inteligente sobre si debe o no sobrescribir el identificador de registro del proveedor.
  • La decisión de iniciar el escaneo en un desplazamiento dentro de ntoskrnl parece cuestionable, ya que el desplazamiento del escaneo puede variar en función de la versión de ntoskrnl.
  • Omitir arbitrariamente las primeros 5 coincidencias parece igualmente cuestionable. Puede haber razones estratégicas para esta decisión, pero un mejor enfoque es recopilar primero a todos los proveedores y luego usar alguna lógica programática para filtrar los resultados.
  • Sobrescribir el puntero a _ETW_REG_ENTRY debería funcionar, pero esta técnica es un poco obvia. Sería mejor sobrescribir las propiedades de _ETW_REG_ENTRY, _ETW_GUID_ENTRY o _TRACE_ENABLE_INFO.

Reimplementé esta técnica para la ciencia; sin embargo, hice algunos ajustes al tradecraft.

  • Se utiliza un algoritmo de búsqueda optimizado para la velocidad con el fin de encontrar todos los bytes 0xe8 en ntoskrnl.
  • Luego, se realiza un posprocesamiento para determinar cuáles de ellas son instrucciones CALL válidas y sus respectivos destinos.
  • No todas las llamadas a nt!EtwRegister son útiles porque a veces se llama a la función con un argumento dinámico para el identificador de registro. Por este motivo, se necesita una lógica adicional para filtrar las llamadas restantes.
  • Finalmente, todos los GUID se resuelven en su forma legible por humanos y se enumeran los identificadores de registro.

En general, después de los ajustes, la técnica anterior es claramente la mejor manera de realizar este tipo de enumeración. Dado que el tiempo de búsqueda es insignificante con algoritmos optimizados, tiene sentido escanear todo el módulo en el disco y luego usar alguna lógica adicional posterior al escaneo para filtrar los resultados.

Impacto de ETW DKOM

Es prudente evaluar brevemente el impacto que podría tener un ataque de este tipo. Cuando los datos del proveedor se reducen o eliminan por completo, hay una pérdida de información, pero al mismo tiempo no todos los proveedores señalan eventos sensibles a la seguridad.

Sin embargo, algunos subconjuntos de estos proveedores son sensibles a la seguridad. El ejemplo más evidente de esto es Microsoft-Windows-Threat-Intelligence (EtwTi), que es una fuente de datos central para Microsoft Defender Advanced Threat Protection (MDATP), que ahora se llama Defender for Endpoint (todo es muy confuso). Cabe señalar que el acceso a este proveedor está muy restringido: solo los controladores Early Launch Anti Malware (ELAM) pueden registrar en este proveedor. Del mismo modo, los procesos del espacio de usuario que reciben estos eventos deben tener un estado protegido (ProtectedLight / Antimalware) y estar firmados con el mismo certificado que el controlador ELAM.

Con EtwExplorer, es posible hacerse una mejor idea de qué tipo de información puede enviar este proveedor.

Captura de pantalla de la página del archivo ETW Explorer
Figura 18: ETW Explorer

El manifiesto XML es demasiado grande para incluirlo aquí en su totalidad, pero a continuación se muestra un evento para dar una idea de los tipos de datos que se pueden suprimir mediante DKOM.

Captura de pantalla del código del manifiesto XML parcial de EtwTi
Figura 19: Manifiesto XML parcial de EtwTi

Conclusión

El Kernel fue y sigue siendo un área importante y controvertida donde Microsoft y proveedores externos deben hacer esfuerzos para salvaguardar la integridad del sistema operativo. La corrupción de datos en el Kernel no es solo una característica posterior a la explotación, sino también un componente central en el desarrollo de exploits de Kernel. Microsoft ya hizo muchos avances en esta área con la introducción de Virtualization Based Security (VBS) y uno de sus componentes como Kernel Data Protection (KDP).

Los consumidores del sistema operativo Windows, a su vez, deben asegurarse de usar estos avances para imponer el mayor costo posible a los posibles atacantes. Windows Defender Application Control (WDAC) se puede utilizar para garantizar que se implementen las medidas de seguridad de VBS y que existan políticas que prohíban la carga de controladores potencialmente peligrosos.

Estos esfuerzos son aún más importantes a medida que vemos cada vez más que los asistentes técnicos de productos básicos aprovechan los ataques BYOVD para realizar DKOM en el espacio Kernel.

 

Mixture of Experts | 12 de diciembre, episodio 85

Decodificación de la IA: Resumen semanal de noticias

Únase a nuestro panel de ingenieros, investigadores, responsables de producto y otros profesionales de talla mundial que se abren paso entre el revuelo de la IA para ofrecerle las últimas noticias e insights al respecto.

Referencias adicionales

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

Aprenda más sobre X-Force Red aquí. Programe una consulta gratuita con X-Force aquí.

Soluciones relacionadas
Soluciones de seguridad empresarial

Transforme su programa de seguridad con las soluciones del mayor proveedor de seguridad empresarial.

Explore las soluciones de ciberseguridad
Servicios de Ciberseguridad

Transforme su negocio y gestione el riesgo con servicios de consultoría de ciberseguridad, nube y seguridad gestionada.

    Explore los servicios de ciberseguridad
    Ciberseguridad de la inteligencia artificial (IA)

    Aumente la velocidad, precisión y productividad de los equipos de seguridad con soluciones cibernéticas potenciadas por IA.

    Explorar la ciberseguridad de IA
    Dé el siguiente paso

    Ya sea que necesite una solución de seguridad de datos, gestión de endpoints o gestión de identidad y acceso (IAM), nuestros expertos están listos para trabajar con usted para lograr una postura de seguridad sólida. Transforme su negocio y gestione el riesgo con un líder global del sector en servicios de consultoría de ciberseguridad, en la nube y de seguridad gestionada.

    Explore las soluciones de ciberseguridad Descubrir los servicios de ciberseguridad