Definición del cargador reflexivo Cobalt Strike

Hombre con sudadera negra con capucha usando un ordenador portátil en la calle representando a un hacker

Aunque los componentes de inteligencia artificial y machine learning de próxima generación de las soluciones de seguridad siguen mejorando las capacidades de detección basadas en el comportamiento, en esencia muchos siguen dependiendo de las detecciones basadas en firmas. Cobalt Strike, un popular marco de mando y control (C2) del equipo rojo utilizado tanto por los actores de amenazas como por los equipos rojos desde su lanzamiento, sigue contando con el firme respaldo de las soluciones de seguridad.

Para continuar con el uso operativo de Cobalt Strikes en el pasado, en el equipo de IBM® X-Force Red Adversary Simulation invertimos importantes esfuerzos de investigación y desarrollo para personalizar Cobalt Strike con herramientas internas. Algunas de nuestras herramientas internas específicas de Cobalt Strike tienen versiones públicas, como “InlineExecute-Assembly”, “CredBandit” y “BokuLoader”. En los últimos dos años, debido al exceso de firmas de Cobalt Strike, hemos restringido su uso a la simulación de actores de amenazas menos sofisticados y, en su lugar, utilizamos otros C2 de terceros y internos cuando realizamos ejercicios más avanzados del equipo rojo.

A través de los esfuerzos de investigación y desarrollo, hemos encontrado un mayor éxito operativo en los ejercicios avanzados del equipo rojo con:

  • Herramientas internas personalizadas.
  • Cargadores internos personalizados.
  • Marco C2 interno personalizado.
  • Continuar invirtiendo en la ampliación de las capacidades y el sigilo de los marcos alternativos de C2 de terceros.

Sin embargo, todavía hay una gran cantidad de actores de amenazas que aprovechan las copias pirateadas de Cobalt Strike, y sigue siendo importante poder simular a estos actores de amenazas. Para los equipos rojos dispuestos a realizar esfuerzos de investigación y desarrollo, aún pueden encontrar el éxito operativo con Cobalt Strike mientras simulan a estos adversarios. Además, Cobalt Strike es una gran herramienta de aprendizaje, que los recién llegados pueden aprovechar para obtener experiencia práctica con un marco C2 a través de cursos de formación del equipo rojo.

A medida que continuamos ampliando nuestras capacidades de C2, estamos compartiendo conocimiento sobre cómo nos hemos basado en el marco Cobalt Strike en el pasado, específicamente mediante el desarrollo de cargadores reflexivos personalizados. También está destinado a que los defensores comprendan cómo funciona Cobalt Strike para crear detecciones más sólidas.

Construir sobre el marco con cargadores reflexivos

Esta entrada de blog es la primera de una serie que sirve como introducción y cubre los conceptos básicos del desarrollo de un cargador reflexivo Cobalt Strike. A medida que avancemos en esta serie, construiremos sobre esta base y haremos referencia a esta publicación.

Al final de esta serie, nuestro objetivo es crear un cargador reflexivo que se integre con las características de evasión existentes de Cobalt Strike e incluso las mejore con técnicas avanzadas que actualmente no están presentes en la herramienta. Las próximas publicaciones profundizarán en el desarrollo de características específicas de evasión y en cómo implementarlas en nuestro cargador reflexivo Cobalt Strike.

Para empezar, esta publicación cubrirá:

  • Los problemas con la carga de un implante C2 desde el disco con el cargador DLL de Windows.
  • Los conceptos y la mecánica del proceso de carga reflexivo de Cobalt Strike.
  • Los requisitos de diseño necesarios para construir un cargador reflexivo eficaz.
  • Las fases involucradas en el proceso de carga reflexiva.

A medida que exploramos la carga reflexiva de Cobalt Strike desde la perspectiva de un desarrollador de herramientas de seguridad ofensiva, destacaremos las oportunidades para la detección y la evasión. Algunos aspectos del desarrollo se omitirán o simplificarán, y le animamos a llenar los vacíos depurando los proyectos de cargadores reflexivos existentes, reconstruyéndolos desde cero o buscando formación.

Cargar la DLL beacon

El implante Cobalt Strike C2, conocido como Beacon, es una biblioteca de vínculos dinámicos (DLL) de Windows, y la capacidad modular de utilizar nuestro propio cargador DLL en Cobalt Strike se conoce como User-Defined Reflective Loader (UDRL).

El cargador DLL de Windows integrado

Normalmente, el cargador DLL de Windows integrado es responsable de cargar los archivos DLL en el espacio de memoria virtual de un proceso. El cargador DLL de Windows existe principalmente en el espacio del usuario, aunque se traslada al espacio del núcleo cuando asigna DLL desde el disco.

El cargador DLL de Windows presenta algunas desventajas cuando se utiliza durante simulaciones de adversarios:

  • La DLL sin procesar debe estar presente en el sistema de archivos.
  • La DLL sin procesar debe estar libre de ofuscación.
  • Los eventos de carga de imágenes del núcleo son activados por el cargador DLL de Windows.

Por lo tanto, usar el cargador DLL de Windows para cargar nuestra DLL beacon no es una solución ideal. Para superar estos desafíos, cargamos la DLL beacon de la memoria con un cargador reflexivo.

Los tres principales puntos de detección que evitan las cargas reflexivas son:

  1. Evita malware con firma en el sistema de archivos.
  2. Evita los eventos de carga de imágenes del núcleo, que pueden monitorizarse con soluciones de seguridad.
  3. Evita nuestra DLL de implante C2 enumerada en el Process Environment Block (PEB).

Cargador reflexivo vs. Cargador DLL de Windows

La carga reflexiva puede entenderse simplemente como cargar una DLL sin procesar directamente desde la memoria, en lugar de cargarla desde el sistema de archivos.

La carga reflexiva y el cargador DLL de Windows tienen el mismo propósito: cargar un archivo DLL desde un formato de archivo sin procesar al espacio de memoria virtual de un proceso. Sin embargo, la carga reflexiva tiene una ventaja clave sobre el cargador DLL de Windows, ya que no requiere que el archivo DLL exista en el sistema de archivos. Esta carga en memoria permite un número ilimitado de fases de carga en cadena, ya que la DLL del implante C2 puede ocultarse dentro de capas de cifrado y codificación dentro de la memoria del proceso.

Formato de archivo sin procesar vs. formato de dirección virtual

Un concepto clave que hay que entender cuando se carga una DLL, es saber que la DLL tendrá un formato diferente en disco que en memoria. Las principales diferencias entre la DLL en formato de archivo sin procesar y el formato de dirección virtual son:

Formato de archivo sin procesar:

  • El formato de la DLL tal como existe en un sistema de archivos.
  • Las secciones de la DLL están compactadas entre sí.
  • Las compensaciones se basan en el inicio del archivo DLL sin procesar tal y como existiría en el disco.
  • Este formato ocupa menos espacio de memoria.

Formato de dirección virtual:

  • El formato de la DLL tal como existe en el espacio de memoria virtual de un proceso.
  • Las secciones están espaciadas.
  • Las compensaciones son direcciones virtuales relativas (RVA).
  • Al ejecutarse en un proceso, la DLL y otros módulos determinan las ubicaciones mediante RVA.
  • Este formato ocupa más espacio de memoria.

Beacon sin procesar vs. beacon virtual

Examinando una DLL beacon HTTP en la herramienta PE-Bear de Aleksandra Doniec, vemos las diferencias entre la dirección bruta y la dirección virtual de cada sección de la DLL:

Tabla que enumera las direcciones crudas y virtuales de cada sección de la DLL beacon.

Tabla que enumera las direcciones crudas y virtuales de cada sección de la DLL beacon.

Esta DLL beacon HTTP/S es 0x52000  bytes (327KB ) de tamaño cuando se cargan en el espacio de memoria virtual de un proceso, en comparación con 0x44000  bytes (272KB ) en tamaño tal como existe en el sistema de archivos. Esta diferencia de tamaño se debe a que las secciones están espaciadas en formato de dirección virtual, en lugar de estar muy juntas en su formato de archivo sin procesar.

PE-Bear ofrece una representación visual de nuestra DLL beacon tal como existe en formato de archivo sin procesar frente a espacio de direcciones virtual:

Representación visual de la DLL beacon en formato sin procesar frente al formato virtual

Representación visual de la DLL de baliza en formato sin procesar (izquierda) frente a formato virtual (derecha)

Carga Beacon con el cargador DLL de Windows

Aunque no es la medida más acertada durante una simulación de adversario, colocar una DLL beacon sin ofuscar en el disco y cargarla con el cargador DLL de Windows es una excelente manera de desmitificar tanto beacon omo la carga de DLL. Básicamente, beacon es solo una DLL. El cargador DLL de Windows y un cargador reflexivo simplemente cargan una DLL en un proceso.

Para cargar la DLL beacon con el cargador DLL de Windows, realizamos los siguientes pasos:

  1. Generar una DLL de baliza en bruto sin ofuscación.
  2. Crear un programa que:
    1. Utiliza el LoadLibrary API para cargar nuestra DLL beacon desde el disco.
    2. Ejecute nuestra baliza llamando al punto de entrada de la DLL de la baliza virtual.
  3. Coloque nuestro programa ejecutable y nuestra DLL beacon en la misma carpeta.
  4. Ejecute nuestro programa.

Generación de una DLL beacon sin procesar sin ofuscación

En primer lugar, desactivamos todas las opciones de Malleable PE que hacen que nuestra DLL beacon pueda descargarse con el cargador DLL de Windows. Para ello, modificamos nuestro perfil Malleable C2 y desactivamos las opciones de evasión de Maleable PE situadas en el bloque de etapas:

Bloque de etapa de perfil Malleable C2 modificado para deshabilitar las características de evasión de Cobalt Strike.

Bloque de etapa de perfil Malleable C2 modificado para deshabilitar las características de evasión de Cobalt Strike.

Después de modificar el perfil, reiniciamos el Cobalt Strike Team Server, suministrando nuestro no_evasion.profile  perfil como argumento.

Captura de pantalla realizada para la entrada de blog

Nos conectamos al servidor del equipo con el cliente Cobalt Strike. Luego creamos unWindows Stageless Payload  con la opción de salida configurada en Raw y el listener en https . Guardamos la carga útil como beacon.dll .

Captura de pantalla de la creación de una DLL beacon "sin etapas" a partir del cliente Cobalt Strike

Captura de pantalla de la creación de una DLL beacon "sin etapas" a partir del cliente Cobalt Strike

Creación de nuestro programa Beacon DLL Loader

Usando el siguiente código, creamos un programa en C llamado loadBeaconDLL.c  y lo compilamos:

Código C de Windows para cargar la DLL beacon desde el disco utilizando el cargador DLL de Windows.

Código C de Windows para cargar la DLL beacon desde el disco utilizando el cargador DLL de Windows.

Utilizamos la API Kernel32.LoadLibraryA  para cargar nuestro DLL de beacon sin procesar desde el disco. Esta API llamará al cargador DLL de Windows integrado, que cargará nuestra DLL beacon desde el disco en el espacio de memoria virtual de nuestro proceso anfitrión.

Como parte del proceso de carga, el cargador DLL de Windows inicializará nuestra DLL beacon llamando a su punto de entrada con DLL_PROCESS_ATTACH (1)  como argumento.

Después de que el cargador DLL de Windows haya cargado e inicializado nuestra DLL beacon en el espacio de memoria virtual de nuestro proceso, tendremos que volver a llamar al punto de entrada de la DLL beacon virtual con el argumento 0x4.

Nuestro programa debe conocer el punto de entrada de nuestra DLL beacon virtual para ejecutar nuestra DLL beacon virtual. Esto se puede hacer dinámicamente dentro del programa analizando los encabezados de la DLL beacon virtual para la dirección virtual relativa (RVA) del punto de entrada, o podemos ver rápidamente qué es y codificar el valor.

Para nuestra prueba de concepto, descubriremos manualmente y codificaremos el punto de entrada RVA de nuestra DLL beacon en nuestro programa. Con PE-Bear descubrimos que el punto de entrada del RVA a la baliza es 0x1D840 :

Captura de pantalla de la búsqueda del punto de entrada RVA del archivo DLL beacon utilizando PE-Bear.

Captura de pantalla de la búsqueda del punto de entrada RVA del archivo DLL beacon utilizando PE-Bear.

Las LoadLibraryA  La API devuelve la dirección base de nuestra DLL beacon virtual. Simplemente añadimos esto al punto de entrada RVA para determinar el punto de entrada.

Con nuestro código listo, compilamos nuestro programa C en un ejecutable de Windows:

Comando utilizado para compilar nuestro programa.

Comando utilizado para compilar nuestro programa.

Posicionamiento de nuestro programa y Beacon DLL en el sistema de archivos

Al colocar nuestra DLL beacon y nuestro programa ejecutable de cargador beacon en el mismo directorio, el cargador DLL de Windows podrá descubrir nuestra DLL mientras realiza su rutina de carga.

Colocamos ambos beacon.dll  y loadBeaconDLL.exe  en el sistema de archivos dentro del mismo directorio:

La DLL beacon y el programa de carga están en el mismo directorio.

La DLL beacon y el programa de carga están en el mismo directorio.

Ejecutar nuestro programa

Desde nuestro escritorio de Windows, hacemos doble clic en nuestro programa loadBeaconDLL.exe y establecemos una conexión beacon activa con nuestro servidor de equipo.

Conexión correcta a C2 Team Server desde la DLL beacon cargada mediante el cargador DLL de Windows.

Conexión correcta a C2 Team Server desde la DLL beacon cargada mediante el cargador DLL de Windows.

Carga reflexiva de Cobalt Strike

Cobalt Strike utiliza una versión modificada del proyecto Reflective Loader de Stephen Fewer. Este legendario cargador DLL en memoria tiene más de una década de antigüedad y se ha utilizado en Metasploit y otras notables herramientas de seguridad ofensivas.

Consideraciones sobre el uso de UDRL

A lo largo de los años, el cargador reflexivo Cobalt Strike se ha mejorado para manejar todas las características de evasión de Malleable PE que ofrece Cobalt Strike. La principal desventaja de utilizar un cargador reflexivo definido por el usuario (UDRL) personalizado es que las características de evasión de Malleable PE pueden o no ser compatibles de fábrica.

Algunas características de evasión se implementan completamente cuando se usa una UDRL y se integran en la DLL beacon mediante el motor Malleable PE de Cobalt Strikes durante la creación de la carga útil de beacon. Sin embargo, actualmente características como obfuscate deben ser gestionadas por la UDRL, mientras que otras como sleepmask y cleanup se pueden gestionar mediante un beacon con una integración de URL adecuada.

Métodos de carga reflexiva

Método de cargador reflexivo original

El proyecto original de Reflective Loader requiere compilar el ReflectiveLoader  en nuestro proyecto DLL y exportarlo dentro de nuestra DLL de implante C2.

Luego, otro proyecto es responsable de:

  1. Descubrir la dirección virtual de la exportación ReflectiveLoader .
  2. Ejecutar la exportación ReflectiveLoader , que devuelve el punto de entrada a nuestra DLL cargada.
  3. Llamar al punto de entrada de la DLL cargada de forma reflexiva.
Diagrama del cargador reflexivo original, cargando una DLL en la memoria virtual.

Diagrama del cargador reflexivo original, cargando una DLL en la memoria virtual.

Anteponer el método de carga reflexiva

Un método alternativo es anteponer el cargador reflexivo a la DLL. Esto permite cargar cualquier DLL no administrada y no requiere compilar la DLL a partir del código fuente. Se trata de un sólido método de carga reflexiva capaz de cargar cualquier archivo PE (EXE o DLL).

Diagrama de un cargador reflexivo añadido a una DLL, cargando una DLL en memoria virtual.

Diagrama de un cargador reflexivo añadido a una DLL, cargando una DLL en memoria virtual.

Método de carga reflexiva de Cobalt Strike

La implementación de la carga reflexiva en Cobalt Strike utiliza un híbrido de los dos métodos anteriores. Este método de carga reflexiva puede resultar familiar para quienes conocen cómo Meterpreter de Metasploit realiza la carga reflexiva.

Al igual que el método original del cargador reflexivo, la función ReflectiveLoader  se compila y exporta en la DLL beacon original. Cuando un operador genera una carga útil de beacon desde el cliente Cobalt Strike, el motor Malleable PE de Cobalt Strike parchea la DLL de baliza sin procesar para informar al cargador reflexivo sobre las opciones Malleable PE que debe utilizar. El encabezado DOS de beacon está parcheado para llamar a la exportación ReflectiveLoader en una compensación codificada. Los bytes parcheados iniciales del encabezado DOS de beacon, que llaman a la exportación ReflectiveLoader , se denominará en este blog como el "stub del cargador reflexivo de llamadas".

Cuando se carga una UDRL en Cobalt Strike y un operador genera una carga útil de beacon desde el cliente Cobalt Strike, el motor Malleable PE de Cobalt Strike parchea el shellcode del cargador reflexivo en la compensación del archivo sin procesar de la exportación ReflectiveLoader  .

Cuando el motor Malleable PE completa el parcheo de la DLL beacon sin procesar, la DLL beacon sin procesar se entrega al operador en un formato ejecutable similar al shellcode.

Diagrama del cargador reflexivo Cobalt Strike, cargando la DLL beacon en la memoria virtual.

Diagrama del cargador reflexivo Cobalt Strike, cargando la DLL beacon en la memoria virtual.

Stub de cargador reflexivo de llamada de beacon

Al observar los bytes iniciales en el desensamblador PE-Bear podemos ver que la DLL beacon en sí es ejecutable:

El stub del cargador reflexivo de llamadas se muestra como códigos de operación de ensamblaje ejecutables.

El stub del cargador reflexivo de llamadas se muestra como códigos de operación de ensamblaje ejecutables.

Los bytes iniciales MZAR  son personalizables a través de las opciones de Malleable PE en el perfil C2 de Cobalt Strikes. Estos bytes deben ser ejecutables y dar como resultado una no operación (nop ).

Después de ejecutar opcionalmente los bytes nops  y magic antepuestos, el stub del cargador reflexivo de llamadas:

  • Crea un marco de pila.
  • Utiliza direccionamiento relativo RIP para determinar la dirección base de la DLL beacon sin procesar.
  • Llama a la exportación ReflectiveLoader  en en la posición conocida  0x16E3C  de archivo sin procesar.
  • Llama al punto de entrada de la DLL beacon cargada.

Confirmamos que el archivo sin procesar se desplaza con la exportación ReflectiveLoader  es 0x16E3C  consultando el directorio de exportación de archivos DLL beacon:

Captura de pantalla usando PE-Bear para determinar la compensación sin procesar del archivo de exportación ReflectiveLoader.

Captura de pantalla usando PE-Bear para determinar la compensación sin procesar del archivo de exportación ReflectiveLoader.

Tal como existe dentro del directorio de exportación, la dirección para la sección ReflectiveLoader está en formato RVA, en referencia a la DLL beacon en su estado virtual. Dado que la exportación ReflectiveLoader  es ejecutable, sabemos que se encuentra en el.text de la DLL beacon.

Para descubrir la compensación del archivo sin procesar de la exportación ReflectiveLoader , primero necesitamos saber la diferencia entre las secciones .text  virtuales y las direcciones sin procesar. Una vez conocida la diferencia, simplemente podemos restarla del RVA de la exportación ReflectiveLoader , para la compensación del archivo sin procesar de la exportación ReflectiveLoader .

Las direcciones virtuales y sin procesar de las secciones .text  se enumeran dentro de los encabezados de sección de la DLL beacon:

Direcciones sin procesar y virtuales de la sección .text de la DLL beacon.

Direcciones sin procesar y virtuales de la sección .text de la DLL beacon.

La diferencia entre las dos es 0xC00  bytes. Restando el ReflectiveLoader  RVA de exportación de 0x17A3C  por la diferencia, descubrimos que la compensación del archivo sin procesar es 0x16E3C .

Podemos confirmarlo en PE-Bear haciendo clic con el botón derecho en el RVA de la función de exportación ReflectiveLoader  y el haciendo clic en Follow RVA:17A3C . El visor hexadecimal del widget de arriba pasará a ver la exportación ReflectiveLoader  en su compensación de archivo sin procesar.

En resumen, el flujo del proceso de carga reflexiva de Cobalt Strike es:

  • Un hilo ejecuta la DLL beacon sin procesar.
  • El stub del cargador reflexivo de llamadas llama a la exportación ReflectiveLoader  en una compensación de archivo conocido sin procesar.
  • El cargador reflexivo carga la DLL beacon sin procesar en la memoria virtual del proceso anfitrión.
  • Después de la carga, el cargador reflexivo devuelve el punto de entrada de la DLL beacon virtual al stub del cargador reflexivo de la llamada.
  • El stub del cargador reflexivo de llamadas llama al punto de entrada de la DLL beacon virtual.
Diagrama que muestra las fases principales de cómo Cobalt Strike realiza la carga reflexiva de la DLL beacon.

Diagrama que muestra las fases principales de cómo Cobalt Strike realiza la carga reflexiva de la DLL beacon.

Requisitos de diseño de cargadores reflexivos

Código independiente de posición

Dado que nuestro cargador reflexivo se ejecuta antes de que se cargue la DLL beacon, el código del cargador reflexivo debe ser shellcode puro.

La forma más sencilla de crear shellcode complejo es escribirlo en C sin dependencias externas. A continuación, el archivo C se compila en un archivo de objeto. Todo debe estar incluido en la sección text del archivo objeto. Por último, arrancamos la.text sección para obtener el shellcode del cargador reflexivo.

Cómo Cobalt Strike inserta nuestra UDRL

El motor Malleable PE de Cobalt Strike se encargará del trabajo de obtener el shellcode de nuestro archivo objeto de cargador reflexivo y de conectarlo a la DLL beacon sin procesar en la compensación del archivo sin procesar de la exportación ReflectiveLoader . Esto se hace en el script UDRL Aggressor, como se muestra a continuación:

Script Aggressor para escribir shellcode de cargador reflexivo en la DLL beacon sin procesar aprovechando Cobalt Strike.

Script Aggressor para escribir shellcode de cargador reflexivo en la DLL beacon sin procesar aprovechando Cobalt Strike.

Nuestro script UDRL Aggressor hace que Cobalt Strike escriba en nuestro shellcode del cargador reflexivo siguiendo estos pasos:

  • Abrimos un$handle en nuestro archivo de objetos UDRL con laopenf empresariales.
  • Con el archivo$handle leemos el flujo de bytes y lo guardamos en la variable de matriz de bytes$data .
  • Luego cerramos el archivo$handle con laclosef empresariales.
  • La función integrada Aggressor de Cobalt Strike
    <a href="https://hstechdocs.helpsystems.com/manuals/cobaltstrike/current/userguide/content/topics_aggressor-scripts/as-resources_functions.htm#extract_reflective_loader">extract_reflective_loader</a>
    
    analizará nuestro archivo de objetos UDRL desde la matriz de bytes$data , localizará la sección.text de nuestro archivo de objetos UDRL, extraerá el archivo .text y lo guardará en la$loader .
  • La función integrada Aggressor de Cobalt Strike
    <a href="https://hstechdocs.helpsystems.com/manuals/cobaltstrike/current/userguide/content/topics_aggressor-scripts/as-resources_functions.htm#setup_reflective_loader">setup_reflective_loader</a>
    
    la función Cobalt Strike Aggressor utilizará el motor Malleable PE para descubrir la compensación del archivo sin procesar de nuestraReflectiveLoader exportación y parcheará nuestro shellcode UDRL desde la$loader .
  • Por último, devolvemos la DLL beacon modificada a Cobalt Strike y guardamos el archivo del cliente.

Fases de carga reflexiva

Cobalt Strike ha hecho el trabajo por nosotros en lo que respecta a la extracción de la sección del.text de nuestro archivo de objeto de cargador reflexivo, parchear nuestro shellcode del cargador reflexivo y llamar a nuestro cargador reflexivo con el stub del cargador reflexivo ubicado en el encabezado de la DLL beacon.

Estas son las fases que debemos desarrollar para cargar bBeacon de forma reflexiva:

  1. Encontrar la DLL beacon sin procesar
  2. Analizar encabezados DLL beacon
  3. Asignar memoria para DLL beacon virtual
  4. Cargar secciones en el espacio de memoria virtual
  5. Cargar dependencias de DLL
  6. Resolver la tabla de direcciones de importación
  7. Resolver los traslados
  8. Ejecutar beacon

Fase 1: encontrar la dirección base de la DLL beacon sin procesar

Existen varios métodos diferentes que podemos usar para descubrir la dirección de la DLL beacon en forma de memoria. Algunos métodos son:

  • Buscar retrospectivamente encabezados MZ y PE
  • Buscar retrospectivamente un egg
  • Obtener la dirección base de la DLL beacon sin procesar del stub de llamada del cargador reflexivo

Encontrar nuestra posición en la memoria

Cuando se utiliza un método que busca retrospectivamente, primero debemos obtener la dirección actual del puntero de instrucción de nuestro hilo (RIP ). Podemos utilizar este sencillo truco para getRip :

  1. En nuestra UDRL creamos una función llamada getRip .
  2. Llamamos getRip  que colocará la dirección que sigue al "call getRip ” en la parte superior de la pila. Esta es la dirección de retorno.
  3. Luego, en nuestra función getRip  , simplemente copiamos la dirección de retorno de la llamada desde la parte superior de la pila.
  4. En la programación en C para Windows x64, las funciones pueden devolver un valor. Este valor devuelto se devuelve a la persona que llama a través del registro RAX . Al mover la dirección del retorno de la persona que llama al registro RAX , estamos devolviendo la dirección de retorno a la persona que llama.
Código ensamblador Intel x64 para obtener la dirección base sin procesar de la DLL beacon del registro RDI.

Código ensamblador Intel x64 para obtener la dirección base sin procesar de la DLL beacon del registro RDI.

Buscar retrospectivamente encabezados MZ y PE

El proyecto original del cargador reflexivo busca retrospectivamente los encabezados MZ y PE. Estos encabezados se han convertido en puntos de detección. Para solucionarlo, Cobalt Strike añadió las características de evasión magic_mz  y magic_pe  de Malleable PE.

La documentación de Cobalt Strike indica que la opción magic_mz :

  • "Anule los primeros bytes (encabezado MZ incluido) de la DLL reflexiva de beacon. Se requieren instrucciones válidas. Siga las instrucciones que cambian el estado de la CPU con instrucciones que deshacen el cambio".

Cuando se configura, los bytes MZ--  bytes en la compensación del archivo sin procesar 0x00  y el PE00  en la compensación del archivo sin procesar 0x80  son conocidos por el cargador reflexivo. El motor Malleable PE los incorpora a la DLL beacon.

Estos bytes deben ser algo únicos, o el cargador reflexivo no podrá encontrarlos. Además, los bytes del encabezado MZ deben ser de operaciones nulas y ejecutables. No pueden ser valores como 0x00  o beacon puede bloquearse. Este puede ser un posible punto de detección.

Buscar retrospectivamente un egg

Después de descubrir este posible punto de detección, desarrollé un método diferente, pero similar, para encontrar la dirección base de la DLL beacon sin procesar. Este método utiliza un buscador de eggs capaz de buscar retrospectivamente desde RIP , que busca dos instancias repetidas de un egg único de 64 bits en la conocida beacon.dll+0x50  compensación del archivo sin procesar.

La dirección beacon.dll+0x50  se eligió porque esta es la ubicación del banner “Este programa no se puede ejecutar en modo DOS”, que no es necesario cuando se carga beacon de forma refleja.

Como no tenemos acceso fácil al motor Java Malleable PE, se puede utilizar el script UDRL Aggressor  BokuLoader.cna  para escribir el egg 0xB0C0ACDC  en el beacon. El siguiente código muestra cómo se puede modificar la DLL beacon sin procesar para contener el egg:

Script Aggressor para escribir un egg en la DLL beacon sin procesar y mostrar los cambios en la consola de scripts de Cobalt Strike.

Script Aggressor para escribir un egg en la DLL beacon sin procesar y mostrar los cambios en la consola de scripts de Cobalt Strike.

El código UDRL debe conocer el valor del egg escrito en la DLL beacon sin procesar por el script UDRL. Una vez que se conoce el egg, el buscador de eggs busca retrospectivamente dos instancias del egg, como se ve en el siguiente código:

Código ensamblador Intel x64 para un buscador de eggs que busca retrospectivamente dos instancias de un egg de 64 bits.

Código ensamblador Intel x64 para un buscador de eggs que busca retrospectivamente dos instancias de un egg de 64 bits.

  • Tanto el script UDRL Aggressor como el código C de UDRL pueden modificarse para utilizar eggs diferentes.

Ahora que ya no se utilizan los encabezados MZ y PE, podemos eliminarlos en el script UDRL Aggressor:

Script Aggressor para enmascarar MZ, PE y bytes no utilizados del banner de DOS ubicados en los encabezados de la DLL beacon sin procesar.

Script Aggressor para enmascarar MZ, PE y bytes no utilizados del banner de DOS ubicados en los encabezados de la DLL beacon sin procesar.

Obtener la dirección base de Beacon DLL sin procesar del stub de cargador reflexivo de llamada

También hay otra forma, específica de Cobalt Strike, para descubrir la dirección base de la DLL beacon sin procesar. Como vimos anteriormente, los bytes iniciales en el stub del cargador reflexivo de llamadas almacenan la dirección base de la DLL beacon sin procesar en el registro RDI  antes de llamar al cargador reflexivo. En lugar de buscar un egg retrospectivamente desde RIP , podemos simplemente obtener el valor del registro RDI  al inicio de nuestro código de cargador reflexivo.

Para analizar esto con más detalle en el depurador, generamos un beacon y anteponemos un punto de interrupción (0xCC ), y abrimos el beacon en x64dbg. Dado que el punto de interrupción se antepone, la dirección base del beacon sin procesar está en +1  de la memoria asignada. Como vimos anteriormente, el stub del cargador reflexivo de llamadas utilizaRIP direccionamiento relativo para obtener la dirección base de la DLL beacon sin procesar:

Captura de pantalla de X64dbg que muestra paso a paso el stub del cargador reflexivo de llamadas para ver que la dirección base de la DLL beacon sin procesar se guarda en el registro RDI antes de llamar al cargador reflexivo.

Captura de pantalla de X64dbg que muestra paso a paso el stub del cargador reflexivo de llamadas para ver que la dirección base de la DLL beacon sin procesar se guarda en el registro RDI antes de llamar al cargador reflexivo.

A continuación se muestra un ejemplo práctico de cómo obtener la dirección base de la DLL beacon sin procesar a partir del stub del cargador reflexivo de llamadas:

Código C de ensamblaje en línea para obtener la dirección base de la DLL beacon sin procesar desde el registro RDI.

Código C de ensamblaje en línea para obtener la dirección base de la DLL beacon sin procesar desde el registro RDI.

Fase 2: Análisis de los encabezados de la DLL beacon

Con la dirección base de la DLL de beacon sin procesar, ahora podemos obtener los valores necesarios para cargar el beacon en el espacio de direcciones virtuales del proceso.

La siguiente tabla enumera los valores que necesitamos de los encabezados de la DLL beacon sin procesar, las ubicaciones en las que los encontraremos y sus tipos:.

Tabla con los valores del encabezado de la DLL beacon sin procesar que son útiles para cargar la DLL beacon.

Tabla con los valores del encabezado de la DLL beacon sin procesar que son útiles para cargar la DLL beacon.

Evasiones

No todo el contenido de los encabezados es necesario para cargar la DLL beacon. Los valores obligatorios se pueden reempaquetar u ofuscar. Los valores no requeridos pueden eliminarse o aleatorizarse.

Fase 3: Asignación de memoria para beacon virtual

Una vez que conocemos elSizeOfImagef del encabezado de la DLL beacon sin procesar, necesitamos asignar memoria de este tamaño. Este espacio de memoria contendrá nuestra DLL beacon virtual.

Se pueden usar diferentes métodos para asignar memoria para la DLL beacon virtual. Los diferentes métodos utilizarán diferentes tipos de memoria. Los diferentes métodos compatibles con el cargador reflexivo predeterminado de Cobalt Strike son:

Tabla que muestra las opciones de asignación de memoria de Cobalt Strike para la DLL beacon virtual.

Tabla que muestra las opciones de asignación de memoria de Cobalt Strike para la DLL beacon virtual.

Evasiones

Esto puede ir un paso más allá con la UDRL. En su lugar, se puede utilizar la versión NTAPI de estas funciones. Además, las funciones NTAP podían llamarse mediante llamadas directas o indirectas al sistema, lo que puede o no ayudar a reforzar las capacidades de evasión.

Cuando el método asignador se establece en VirtualAlloc  en el perfil Malleable C2 de Cobalt Strike, el proyecto BokuLoader utilizará una llamada directa del sistema a NtAllocateVirtualMemory  para asignar memoria para la DLL beacon virtual:

Ejemplo de código del proyecto BokuLoader que muestra cómo se utiliza una llamada directa al sistema para asignar memoria a la DLL beacon virtual.

Ejemplo de código del proyecto BokuLoader que muestra cómo se utiliza una llamada directa al sistema para asignar memoria a la DLL beacon virtual.

  • El número de llamada del sistema se descubre mediante el método HellsGate.
  • Si existe un hook en el espacio de usuario en el stub de llamada al sistema, se utiliza el método HalosGate.

La siguiente imagen muestra un ejemplo de código del uso de los métodos HellsGate y HalosGate para determinar los números de llamada del sistema:

Ejemplo de código del proyecto BokuLoader que muestra cómo se descubren las llamadas al sistema desde el proceso.

Ejemplo de código del proyecto BokuLoader que muestra cómo se descubren las llamadas al sistema desde el proceso.

Fase 4: Carga de secciones en el espacio de memoria virtual

Ahora que hemos asignado memoria para nuestra DLL beacon virtual, necesitamos copiar las secciones del beacon de sus compensaciones de archivo sin procesar, tal como existen en la DLL beacon sin procesar, a la memoria asignada en sus compensaciones virtuales relativos.

Si asignamos nuestra memoria conREADWRITE tendremos que rastrear la dirección de la sección .text y su tamaño. Antes de llamar al punto de entrada de la DLL beacon virtual, tendremos que cambiar las protecciones de memoria de la sección .text al ejecutable.

Asignar nuestra memoria con READWRITE_EXECUTE facilita el proceso de carga reflexiva pero aumenta las posibilidades de detección por parte de las soluciones de seguridad.

A continuación se muestra un ejemplo simplificado de código, del proyecto BokuLoader, que demuestra esto:

Ejemplo de código del proyecto BokuLoader que muestra secciones copiadas de la DLL beacon sin procesar a la DLL beacon virtual.

Ejemplo de código del proyecto BokuLoader que muestra secciones copiadas de la DLL beacon sin procesar a la DLL beacon virtual.

Evasiones

Algunas características de evasión con respecto a las secciones de carga son:

  • No copiar los encabezados beacon a la DLL beacon virtual.
  • Desasignar el espacio de memoria en la DLL beacon virtual donde existirían los encabezados.

En el proyecto público BokuLoader, los encabezados de la DLL beacon no se copian de la DLL beacon sin procesar a la DLL beacon virtual. Actualmente, los primeros 0x1000  bytes de la DLL beacon virtual son nulos (0x00‘s ). Según mis pruebas, el beacon no depende de sus encabezados una vez que se ha cargado correctamente en la memoria virtual. Evitar copiar los encabezados puede ayudar a evadir los escáneres en memoria, pero estos bytes nulos también podrían ser un posible punto de detección.

Otra posible oportunidad de evasión es hacer que el script UDRL Aggressor encripte las secciones. Las secciones podían descifrarse en memoria mediante la UDRL, usando una clave compartida entre la UDRL y el script UDRL Aggressor.

Fase 5: Carga de dependencias DLL

El beacon HTTP/S x64 depende de cuatro DLL para funcionar correctamente. Si estas DLL no están cargadas actualmente en el proceso, nuestro cargador reflexivo tendrá que cargarlas.

Las cuatro DLL se enumeran en el directorio de importación de la DLL beacon HTTP/S:

Captura de pantalla de PE-Bear con una lista de las DLL del directorio de importación de la DLL beacon.

Captura de pantalla de PE-Bear con una lista de las DLL del directorio de importación de la DLL beacon.

El cargador reflexivo Cobalt Strike integrado utiliza la API kernel32.LoadLibraryA para la carga de DLL.

Evasiones

La carga de DLL se puede lograr de varias maneras diferentes, con diferentes consideraciones de seguridad operativa. Algunos métodos son:

Si la DLL ya existe en el proceso, aún se pueden utilizar las API de Windows mencionadas anteriormente para obtener las direcciones de la DLL, aunque esto puede desencadenar alertas de detección no deseadas.

Alternativamente, el PEB mantiene un puntero a la estructura 

<a title="https://learn.microsoft.com/es-es/windows/win32/api/winternl/ns-winternl-peb_ldr_data" href="https://learn.microsoft.com/es-es/windows/win32/api/winternl/ns-winternl-peb_ldr_data">_PEB_LDR_DATA</a>

. Dentro, hay una lista enlazada con todas las DLL cargadas en el proceso y su información relativa (

InMemoryOrderModuleList

). BokuLoader lo aprovecha para descubrir la información de la DLL, evitando llamadas innecesarias a la API.

Si la DLL no existe en el InMemoryOrderModuleList , BokuLoader utiliza la API NTDLL.LdrLoadDll  para cargar la dependencia DLL en la memoria, aprovechando el cargador DLL de Windows integrado.

La carga reflexiva anidada no se puede utilizar fácilmente para cargar dependencias de DLL porque los cargadores reflexivos generalmente no registran la DLL en el proceso. El código externo a la DLL no puede utilizar correctamente una DLL cargada de forma reflexiva. El proyecto DarkLoadLibrary puede ser capaz de cargar correctamente una DLL en memoria sin activar un evento de carga de imagen del núcleo.

Ejemplo de código del proyecto BokuLoader que muestra cómo se pueden resolver las direcciones base de DLL cargadas recurriendo a InMemoryOrderModuleList.

Ejemplo de código del proyecto BokuLoader que muestra cómo se pueden resolver las direcciones base de DLL cargadas recurriendo a InMemoryOrderModuleList.

Fase 6: Resolución de la tabla de dirección de importación

Con las DLL necesarias cargadas en el proceso, se deben resolver las API enumeradas en el directorio de importación. A continuación, las direcciones API deberán escribirse en la tabla de direcciones de importación (IAT) de la DLL beacon virtual. De esta forma, beacon sabe a qué dirección dirigirse cuando necesita llamar a API como WININET.HttpSendRequest

La entrada de importación deberá resolverse mediante el ordinal o la cadena de nombre.

En la imagen de abajo, vemos que la DLL beacon de Cobalt Strike utiliza una combinación de ordinales y cadenas de nombre para las entradas de importación:

Captura de pantalla de PE-Bear que muestra que algunas entradas de importación de la DLL beacon deben resolverse por ordinal.

Captura de pantalla de PE-Bear que muestra que algunas entradas de importación de la DLL beacon deben resolverse por ordinal.

El cargador reflexivo Cobalt Strike integrado utiliza la API Kernel32.GetProcAddress  para resolver direcciones virtuales para entradas de importación.

Evasiones

Algunos métodos de evasión para resolver direcciones API son:

  • Implementaciones de código personalizado de GetProcAddress
  • NTDLL.LdrGetProcedureAddress

BokuLoader utiliza una implementación de código personalizado deGetProcAddress para resolver la dirección de la entrada de importación, manejando tanto cadenas de nombres como ordinales.

La NTDLL.LdrGetProcedureAddress es capaz de gestionar tanto cadenas de nombres como ordinales. Si la dirección devuelta para la entrada de importación es un reenviador a otra DLL, BokuLoader toma por defecto NTDLL.LdrGetProcedureAddress para resolver el remitente.

Al escribir el IAT, el enganche se puede implementar escribiendo las direcciones virtuales de las funciones hook que hemos implementado en lugar de la dirección virtual de las API previstas. Mientras que el resultado esperado se devuelva a beacon cuando se llama a la dirección en la IAT, podemos ejecutar código adicional antes de volver a Beacon. Las publicaciones futuras y las versiones públicas de BokuLoader demostrarán cómo podemos aprovechar el hook IAT para características avanzadas de evasión.

Con un lanzamiento reciente, el proyecto público BokuLoader apoya la característica Malleable PE obfuscate del perfil Cobalt Strike C2 con una implementación personalizada. Al modificar la clave de enmascaramiento en elBokuLoader.cna El script UDRL Aggressor, la ofuscación se puede mejorar eligiendo su propia clave XOR de un solo byte.

En lo que respecta a la seguridad operativa, es importante saber que los motores de comparación de patrones son capaces de aplicar fuerza bruta a máscaras XOR de un solo byte. En futuras publicaciones se demostrará cómo podemos crear nuestro propio motor Malleable PE utilizando la funcionalidad de scripting Aggressor de Cobalt Strikes para ofuscar el beacon y superar la coincidencia de patrones.

Fase 7: Resolución de reubicaciones

La DLL beacon tiene muchas reubicaciones que deben resolverse y escribirse en la tabla de reubicación base de la DLL beacon virtual antes de ejecutarla.

En PE-Bear podemos ver que la DLL beacon por defecto tiene la dirección base de imagen de 0x180000000 :

Captura de pantalla de PE-Bear mostrando la dirección base de la imagen del DLL beacon.

Captura de pantalla de PE-Bear mostrando la dirección base de la imagen del DLL beacon.

Antes de empezar a escribir las reubicaciones, tenemos que calcular el delta entre la dirección base de nuestra DLL beacon virtual y la dirección base codificada.

Por ejemplo, supongamos que la dirección base de nuestra DLL beacon virtual es 0x7FFC44FE0000 . Restamos la dirección base codificada de la dirección base de nuestra DLL beacon virtual para obtener el delta de dirección base:

Captura de pantalla que muestra el delta de la dirección base.

A continuación, para determinar la dirección virtual de cada entrada de reubicación en la tabla de reubicación base, añadimos el delta de dirección base a la dirección de entrada de reubicación codificada para determinar la reubicación dentro de nuestra DLL beacon virtual.

En la imagen de abajo podemos ver que las entradas de reubicación de los beacons se escriben al revés en formato little-endian:

Captura de pantalla de PE-Bear que muestra que algunas entradas de reubicación existen en formato little-endian.

Captura de pantalla de PE-Bear que muestra que algunas entradas de reubicación existen en formato little-endian.

La dirección codificada para esta entrada de reubicación es 0x1800341C8 .

Añadimos esta dirección al delta de la dirección base, para obtener la dirección virtual de la reubicación tal como existe en la DLL de la baliza virtual:

Captura de pantalla en la que se añade la dirección a la diferencia de la dirección base, para obtener la dirección virtual para la reubicación tal y como existe en la DLL beacon virtual:

Para cada entrada de reubicación tendremos que comprobar que el tipo es

<a title="https://learn.microsoft.com/es-es/windows/win32/debug/pe-format" href="https://learn.microsoft.com/es-es/windows/win32/debug/pe-format">IMAGE_REL_BASED_DIR64 (0xA)</a>

. Si es falso, nos saltaremos la escritura de la reubicación.

Una vez que determinamos la dirección virtual de la reubicación tal como existe dentro de la DLL beacon virtual, la escribimos en el espacio de memoria que contiene la dirección de entrada de reubicación codificada.

Si le interesa aprender más sobre cómo hacer reubicaciones de PE, eche un vistazo al código de la función doRelocations en el proyecto público BokuLoader. Antes de publicar esta entrada de blog, cambié el código de reubicaciones del código de ensamblaje al código C, que espero sea legible para humanos, para ayudar a otros que quieran saber los detalles técnicos de cómo se hace esto.

Fase 8: Ejecución de beacon

La ejecución de beacon se puede dividir en tres pasos:

  • Asegurarse de que las secciones DLL beacon virtual tienen los permisos de memoria correctos.
  • Inicializar la DLL beacon virtual.
  • Llamar al punto de entrada de la DLL beacon virtual.

Hacer que beacon virtual sea ejecutable

Si la memoria que asignamos para nuestra DLL beacon virtual es READWRITE_EXECUTE , no necesitamos cambiar las protecciones de la memoria para que el beacon funcione correctamente sin que se bloquee.

Si asignáramos nuestra memoria de beacon virtual como no ejecutable (READWRITE ), tendremos que cambiar la sección .text  de nuestra DLL beacon virtual a ejecutable. La ubicación y el tamaño virtual de la sección .text  debería haberse guardado previamente dentro de nuestra función principal UDRL como variables.

En el proyecto público BokuLoader, los cambios de protección de memoria se realizan mediante llamadas directas al sistema a NTProtectVirtualMemory , como se ve en el siguiente ejemplo de código:

Ejemplo de código del proyecto BokuLoader que demuestra el cambio de la sección .text de la DLL beacon virtual a ejecutable.

Ejemplo de código del proyecto BokuLoader que demuestra el cambio de la sección .text de la DLL beacon virtual a ejecutable.

El .data  la sección de nuestra DLL beacon virtual debería tener los permisos READWRITE . Si la sección no se puede escribir, nuestra DLL beacon puede bloquearse durante la ejecución.

Inicialización de la DLL de baliza virtual

Para que la DLL beacon virtual funcione correctamente, primero debe inicializarse llamando al punto de entrada de la DLL beacon virtual. El primer argumento es la dirección base de la DLL beacon virtual. El segundo argumento es el fwdReason  y debe ajustarse a DLL_PROCESS_ATTACH (1) .

Ejemplo de código del proyecto BokuLoader que inicializa la DLL beacon virtual.

Ejemplo de código del proyecto BokuLoader que inicializa la DLL beacon virtual.

Ejecución de nuestra DLL beacon virtual

Tras inicializar la DLL beacon virtual, podemos devolver el punto de entrada del beacon virtual al stub del cargador reflexivo de llamadas, o podemos llamar al punto de entrada de la DLL beacon virtual en nuestra UDRL con el fwdReason  establecido en 0x4 .

A diferencia de una DLL típica en la que el primer argumento hinstDLL  a 

<a href="https://learn.microsoft.com/es-es/windows/win32/dlls/dllmain">DLLMAIN</a>

sería la dirección base de la DLL virtual, beacon espera la dirección base de la DLL beacon sin procesar. Si no se proporciona, algunas características de evasión de Malleable PE pueden fallar.

Ejemplo de código del proyecto BokuLoader que muestra dos formas diferentes de ejecutar la DLL beacon virtual.

Ejemplo de código del proyecto BokuLoader que muestra dos formas diferentes de ejecutar la DLL beacon virtual.

Reflexiones finales

Esperamos que esta entrada de blog ayude tanto a los equipos rojos como a los equipos azules a comprender mejor Cobalt Strike y el proceso de carga reflexiva. Todavía hay miles de oportunidades de evasión que se pueden implementar a través de la carga reflexiva. Al comprender mejor estos conceptos, las organizaciones pueden prepararse mejor para defenderse con éxito contra las ciberamenazas.

Las futuras publicaciones de esta serie se centrarán en la integración de UDRL con las características actuales de evasión de Cobalt Strike, profundizarán en las características de evasión no documentadas ya presentes en el BokuLoader público, así como en características avanzadas que aún no se han lanzado al público. ¡Estén atentos para obtener información más detallada y técnicas para aprender cómo llevar su juego Cobalt Strike al siguiente nivel con el desarrollo de UDRL!

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.