Ataques de manipulación directa de objetos del kernel (DKOM) contra proveedores de ETW.

Retrato de vista lateral de un hombre mirando la pantalla del ordenador mientras trabaja hasta tarde por 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 utilizar su acceso para poner en escena capacidades de posexplotación del kernel de Windows. En los últimos años, las cuentas públicas han demostrado cada vez más que los atacantes menos sofisticados están utilizando esta técnica para lograr sus objetivos. Por lo tanto, es importante que pongamos el foco en esta capacidad y aprendamos más sobre su impacto potencial. En concreto, en esta publicación evaluaremos cómo se puede utilizar la posexplotación del kernel para cegar los sensores ETW y relacionaremos esto con las muestras de malware identificadas en el mundo real el año pasado.

Introducción

Con el tiempo, las medidas 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 nada desdeñable para la posexplotación. Los atacantes se enfrentan a un coste constante para desarrollar e iterar tácticas, técnicas y procedimientos (TTP) para evitar la detección heurística. En el equipo de simulación de adversarios de IBM Security X-Force, nos enfrentamos al mismo problema. Nuestro equipo tiene la tarea de simular capacidades de amenazas avanzadas en algunos de los entornos más grandes y reforzados. La combinación de soluciones de seguridad complejas y bien ajustadas con equipos de centros de operaciones de seguridad (SOC) bien entrenados puede ser muy exigente para las técnicas de espionaje. En algunos casos, el uso de una TTP específica queda completamente obsoleto en un plazo de tres a cuatro meses (normalmente vinculado a pilas tecnológicas específicas).

Los atacantes pueden optar por aprovechar la ejecución de código en el kernel de Windows para manipular algunas de estas protecciones o para evitar por completo una serie de sensores del espacio de usuario. La primera demostración publicada de esta capacidad fue en 1999 en la revista Phrack Magazine. En los años transcurridos desde entonces, se han registrado varios casos en los que los actores de amenazas (TA) han utilizado rootkits del kernel para la posexplotación. Algunos ejemplos antiguos son la familia Derusbi y el Lamberts Toolkit.

Tradicionalmente, este tipo de capacidades se han limitado en su mayoría a los TA avanzados. Sin embargo, en los últimos años, hemos visto cómo más atacantes comunes utilizan primitivas de explotación Bring Your Own Vulnerable Driver (BYOVD) para facilitar las acciones en los endpoints. En algunos casos, estas técnicas han sido bastante primitivas, limitadas a tareas sencillas, pero también ha habido demostraciones más potentes.

A finales de septiembre de 2022, investigadores de ESET publicaron un informe técnico sobre dicha capacidad del kernel utilizada 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 artículo expone una serie de primitivas de manipulación directa de objetos del kernel (DKOM) que la carga útil utiliza para cegar la telemetría del sistema operativo, el antivirus y el EDR. Las investigaciones públicas disponibles sobre estas técnicas son escasas. Para la defensa, resulta crítico comprender más a fondo las técnicas de posexplotación del kernel. Un argumento clásico y simplista que se oye a menudo es que un atacante con privilegios elevados puede hacer cualquier cosa, así que ¿por qué deberían modelarse las capacidades en ese escenario? Se trata de una postura débil. Los defensores deben comprender qué capacidades tiene un atacante cuando sus privilegios son elevados, qué fuentes de datos siguen siendo fiables (y cuáles no), qué opciones de contención existen y cómo se pueden detectar las técnicas avanzadas (incluso si no existen capacidades para realizar esas detecciones). En esta publicación me centraré específicamente en parchear las estructuras del Kernel Event Tracing for Windows (ETW) para que los proveedores sean ineficaces o inoperables. Proporcionaré algunos antecedentes sobre esta técnica, analizaré cómo un atacante puede manipular las estructuras Kernel ETW y profundizaré 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 novedades sobre tecnología, respaldadas por conocimientos de expertos

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

¡Gracias! Se ha suscrito.

Su suscripción se enviará en inglés. Encontrará un enlace para darse de baja en cada boletín. 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 función de seguimiento de alta velocidad integrada en el sistema operativo Windows. Permite el registro de eventos y actividades del sistema por parte de aplicaciones, controladores y el sistema operativo, lo que proporciona una visibilidad detallada del comportamiento del sistema para la depuración, el análisis del rendimiento y el diagnóstico de seguridad.

En esta sección, ofreceré una descripción general de alto nivel de Kernel ETW y su superficie de ataque asociada. Esto será útil para comprender mejor los mecanismos implicados en la manipulación de los proveedores ETW y los efectos asociados a dichas manipulaciones.

Superficie de ataque de Kernel ETW

Investigadores de Binarly dieron una charla en BHEU 2021, en la que se discutió 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 contra sensores de EDR que ciegan el ETW (Binarly)

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

un gráfico que muestra y describe ataques a proveedores de ETW en modo kernel
Figura 2 – Veni, no vidi, no vici: ataques contra sensores de EDR que ciegan el ETW (Binarly)

Esta publicación solo tiene en cuenta los ataques de la primera categoría de ataques que se muestra en la “Figura 2”, en la que el seguimiento está desactivado o alterado de alguna manera.

Como precaución, al considerar estructuras opacas en Windows, siempre es importante recordar que estas están sujetas a cambios y, de hecho, cambian con frecuencia entre las distintas versiones de Windows. Esto es especialmente importante al manipular datos del kernel, ya que los errores probablemente provocarán 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 muestra 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 se produce dentro de la función interna EtwpRegisterKMProvider, pero hay dos conclusiones principales que extraer 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 _ETW_REG_ENTRY cuando la llamada se realiza correctamente. Esta estructura de datos y algunas de sus propiedades anidadas proporcionan vías para manipular el proveedor ETW según la investigación de Binarly.

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

ETW_REG_ENTRY

A continuación se muestra un listado completo de 64 bits de la estructura _ETW_REG_ENTRY. Se pueden encontrar más detalles 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 más información sobre los elementos de esta estructura de datos, puedes consultar la documentación oficial de Microsoft y el Vergilius Project. La configuración de esta estructura afecta directamente al funcionamiento 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

Aunque es bueno tener algunos conocimientos teóricos, siempre es mejor ver ejemplos concretos de uso para comprender mejor un tema. Veamos brevemente un ejemplo. La mayoría de los proveedores ETW del kernel críticos se inicializan dentro de nt!EtwpInitialize, que no se exporta. Al examinar esta función, se observan unos quince proveedores.

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

Si tomamos como ejemplo la entrada Microsoft-Windows-Threat-Intelligence (EtwTi), podemos comprobar el parámetro global ThreatIntProviderGuid para recuperar el GUID de este proveedor.

Captura de pantalla del código para EtwTi Provider GUID
Figura 5 – GUID del proveedor EtwTi

Al buscar este GUID en línea, se verá inmediatamente que hemos podido recuperar el valor correcto (f4e1897c-bb5d-5668-f1d8-040f4d8dd344).

Veamos un caso en el que se utiliza el parámetro de identificador de registro, EtwThreatIntProvRegHandle, y analicemos cómo se utiliza. Un lugar en el que se hace referencia al identificador es nt!EtwTiLogDriverObjectUnLoad. Por el nombre de esta función, podemos intuir que su finalidad es generar eventos cuando el núcleo 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 invocan aquí pasando el identificador de registro como uno de los argumentos. Veamos una de estas subfunciones para comprender mejor 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
    …
}

A continuación, la función realiza comprobaciones similares basadas en niveles. Por último, la función devuelve verdadero o falso para indicar si un proveedor está habilitado para el registro de eventos en un nivel y una palabra clave específicos. Puede encontrar más detalles 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 el funcionamiento del proveedor. Por el contrario, si un atacante pudiera manipular esas estructuras, podría influir en el flujo de control de la persona que realiza la llamada para eliminar o impedir que se registren los eventos.

Atacando identificadores de registro

Echando un vistazo a la superficie de ataque declarada por Binarly y basándonos en nuestro ligero análisis, podemos plantear algunas estrategias para interrumpir la recopilación de eventos.

  • Un atacante puede establecer a NULL el puntero _ETW_REG_ENTRY. Cualquier función que haga referencia al identificador de registro asumiría entonces que el proveedor no se ha inicializado.
  • Un atacante puede establecer a NULL el puntero _ETW_REG_ENTRY->GuidEntry->ProviderEnableInfo. Esto debería desactivar efectivamente 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 operar el proveedor.
  • Un atacante puede sobrescribir propiedades de la estructura de datos  _ETW_REG_ENTRY->GuidEntry->ProviderEnableInfo para manipular la configuración del proveedor.
    • IsEnabled: se establece en 1 para habilitar la recepción de eventos del proveedor o para ajustar la configuración utilizada al recibir eventos del proveedor. Se establece en 0 para desactivar la recepción de eventos del proveedor.
    • Level: valor que indica el nivel máximo de eventos que desea que escriba el proveedor. El proveedor suele escribir un evento si su nivel es inferior o igual a este valor, además de cumplir los criterios MatchAnyKeyword y MatchAllKeyword.
    • MatchAnyKeyword: máscara de bits de 64 bits de palabras clave que determinan las categorías 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 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 los criterios Level y MatchAnyKeyword.

Técnicas de búsqueda del kernel

Ahora tenemos una buena idea de cómo sería un ataque de DKOM a 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 manera de encontrar estos identificadores de registro.

Voy a exponer dos técnicas principales para encontrar estos identificadores y mostrar la variante de uno que utiliza 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, ya que este no es un límite de seguridad. Hay muchas formas de filtrar los punteros del kernel que solo están restringidas en escenarios de entorno aislado o LowIL. Para añadir 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 momento, es posible analizar esos módulos en el disco y convertir los offsets brutos de los archivos en direcciones virtuales relativas 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 lo ideal, ya que puede generar más telemetría de detección.

Método 1: cadenas de gadgets

Siempre que sea posible, esta es la técnica que prefiero porque hace que las filtraciones sean más portátiles entre las versiones del módulo porque se ven menos afectadas por los cambios en los parches. La desventaja es que depende de que existan cadenas de gadgets para el objeto que quiere 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 CALL de nt!EtwRegister
Figura 8 – Desensamblado completo de CALL de 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 en 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 vez de eso, tenemos que ver dónde se hace referencia a esta global y ver si se usa en una función cuya dirección pueda filtrarse.

captura de pantalla del código para las referencias 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 del kernel para leer los datos de esa dirección.
  • El global se utiliza al principio de la función. Esto es muy útil porque significa que probablemente no necesitaremos construir lógica de análisis de instrucciones compleja para encontrarla.

Al observar el ensamblado, se muestra la siguiente disposición.

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 entonces sencilla. Leemos una serie de bytes utilizando nuestra vulnerabilidad y buscamos la primera instrucción mov R10 para calcular el desfase 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 pueden utilizarse para filtrar diversas 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 tener este aspecto.

MmUnloadSystemImage -> MiUnloadSystemImage -> MiGetPdeAddress

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

Método 2: escaneo de memoria

La otra opción principal para filtrar estos identificadores de registro ETW es usar escaneo de memoria, ya sea desde memoria activa del kernel o desde un módulo en disco. Recuerde que al escanear módulos en disco es posible convertir desplazamientos de archivo a RVAs.

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 entenderlo mejor:

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 que pueda gestionar cadenas de bytes comodín. Esta es una tarea que se deja al lector.

Una vez identificado un índice de inicio y de parada, es posible consultar todas las instrucciones intermedias.

  • Las instrucciones CALL potenciales pueden identificarse basándose en el opcode para CALL que es 0xe8.
  • Posteriormente, se utiliza una lectura de tamaño DWORD para calcular el desfase relativo de la posible instrucción CALL.
  • Este desplazamiento se añade a la dirección relativa de CALL y se incrementa en cinco (el tamaño de la instrucción de ensamblado).
  • Por último, este nuevo valor puede compararse con nt!EtwRegister para encontrar todas las ubicaciones CALL válidas.

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

Parcheado ETW de Lazarus

Obtuve una muestra del DLL del FudModle mencionado en el informe técnico de ESET y lo analicé. Este DLL carga un controlador Dell vulnerable firmado (a partir de un recurso codificado XOR en línea) y, a continuación, dirige el controlador para parchear muchas estructuras del kernel con el fin de limitar la telemetría en el host.

Captura de pantalla del código del hash FUDModule de Lazarus
Figura 13 – Hash de FudModule de Lazarus

Como parte final de esta publicación, quiero revisar la estrategia que utiliza Lazarus para encontrar los identificadores de registro de Kernel ETW. Se trata de una variación del método de escaneado que comentamos anteriormente.

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 FudModule de Lazarus
Figura 14 – Descompilación de búsqueda parcial de ETW de FudModule de Lazarus

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 puede introducirse, eliminarse o modificarse 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 pasan por alto muchas entradas utilizando esta técnica. Puede que Lazarus también haya realizado análisis adicionales para determinar que las entradas omitidas no son importantes o no es necesario parchearlas. Las entradas perdidas se destacan a continuación. El empleo de esta estrategia permite a Lazarus omitir 0x7b1de0 bytes mientras realiza el escaneo, lo que 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 saltan las cinco primeras coincidencias antes de empezar 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 búsqueda parcial de ETW de FudModule de Lazarus
Figura 16 – Descompilación de búsqueda parcial de ETW de FudModule de Lazarus

El código es un poco obtuso, pero obtenemos los aspectos más 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 fin dentro de una estructura de configuración de malware (asignada en la inicialización).

Por último, veamos qué hace Lazarus para desactivar estos proveedores.

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

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

No estoy del todo satisfecho con la técnica mostrada 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 empezar el escaneo en un desplazamiento dentro de ntoskrnl parece cuestionable porque el desplazamiento del escaneo puede variar en función de la versión de ntoskrnl.
  • Saltarse arbitrariamente las 5 primeras coincidencias parece igual de 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 o _ETW_GUID_ENTRY o _TRACE_ENABLE_INFO.

Volví a implementar esta técnica para la ciencia; sin embargo, hice algunos ajustes en la técnica.

  • Se utiliza un algoritmo de búsqueda de velocidad optimizada para encontrar todos los bytes 0xe8 en ntoskrnl.
  • Después, se realiza un posprocesamiento para determinar cuáles de esas instrucciones CALL son válidas y sus destinos respectivos.
  • 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 eso, se necesita algo de lógica adicional para filtrar las llamadas restantes.
  • Por último, todos los GUID se resuelven en su formato 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 de los proveedores se reducen o eliminan por completo, se produce una pérdida de información, pero al mismo tiempo no todos los proveedores señalan eventos sensibles a la seguridad.

Sin embargo, algunos subgrupos de estos proveedores son sensibles a la seguridad. El ejemplo más obvio 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 registrarse en este proveedor. Del mismo modo, los procesos de espacio de usuario que reciban 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 ha sido y sigue siendo un área importante y controvertida en la que Microsoft y los proveedores externos deben esforzarse por salvaguardar la integridad del sistema operativo. La corrupción de datos en el kernel no solo es una característica de la posexplotación, sino también un componente central en el desarrollo de exploits del kernel. Microsoft ya ha hecho muchos avances en esta área con la introducción de seguridad basada en virtualización (VBS) y uno de sus componentes como Kernel Data Protection (KDP).

Los consumidores del sistema operativo Windows, a su vez, deben asegurarse de que se benefician de estos avances para imponer el mayor coste posible a los posibles atacantes. El controlde aplicaciones de Windows Defender (WDAC) se puede utilizar para garantizar que las protecciones de VBS estén implementadas 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 cómo los TA de productos básicos aprovechan los ataques BYOVD para realizar DKOM en el espacio del kernel.

 

Mixture of Experts | 12 de diciembre, episodio 85

Descifrar 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 bullicio de la IA para ofrecerle las últimas noticias y conocimientos al respecto.

Referencias adicionales

  • Veni, No Vidi, No Vici: ataques a sensores EDR (detección y respuesta de endpoints) ciegos ETW (diapositivas BHEU 2021) - aquí
  • Veni, No Vidi, No Vici: ataques a sensores EDR (detección y respuesta de endpoints) ciegos ETW (vídeo BHEU 2021) – aquí
  • Avanzar en la seguridad de Windows (BlueHat Shanghai 2019) – aquí
  • Cómo explotar una vulnerabilidad "simple": ¡en 35 sencillos pasos o menos! – aquí
  • Explotar una vulnerabilidad "simple" - Parte 1.5 - La filtración de información - aquí
  • Introducción a inteligencia de amenazas ETW – aquí
  • TelemetrySourcerer – aquí
  • Asistente de políticas WDAC - aquí

Obtenga más información sobre X-Force Red aquí. Programe una consulta sin coste con X-Force aquí.

Soluciones relacionadas
Soluciones de seguridad para la empresa

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)

    Mejore la velocidad, la precisión y la productividad de los equipos de seguridad con soluciones de ciberseguridad basadas en IA.

    Explore la ciberseguridad de la IA
    Dé el siguiente paso

    Tanto si necesita soluciones de seguridad de datos, de gestión de endpoints o de gestión de identidades y accesos (IAM), nuestros expertos están dispuestos a trabajar con usted para lograr una posición de seguridad sólida. Transforme su empresa y gestione los riesgos con un líder de la industria mundial mundial en consultoría de ciberseguridad, cloud y servicios de seguridad gestionados.

    Explore las soluciones de ciberseguridad Descubra los servicios de ciberseguridad