Definición del cargador reflexivo Cobalt Strike

Hombre con una sudadera con capucha negra usando una computadora portátil en la calle, representando a un hacker.

Si bien la IA de próxima generación y los componentes de machine learning de las soluciones de seguridad continúan mejorando las capacidades de detección basadas en el comportamiento, en su núcleo muchos aún dependen de las detecciones basadas en firmas. Cobalt Strike al ser un marco popular de comando y control (C2) del equipo rojo utilizado tanto por los actores de amenazas como por los equipos rojos desde su debut, sigue siendo muy utilizado por 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 personalizarlo 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, dado el exceso de firmas de Cobalt Strike, restringimos su uso a la simulación de actores de amenazas menos sofisticados y, en su lugar, aprovechamos otros C2 de terceros e internos al realizar ejercicios de equipo rojo más avanzados.

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

  • Herramientas internas personalizadas.
  • Cargadores internos personalizados.
  • Marco C2 interno personalizado.
  • Inversión continua en la expansión de las capacidades y el sigilo de los marcos C2 alternativos 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 estos actores de amenazas. Para los equipos rojos dispuestos a realizar el esfuerzo 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 capacitación del equipo rojo.

A medida que seguimos ampliando nuestras capacidades de C2, compartimos algunos insights sobre cómo nos hemos basado en la infraestructura Cobalt Strike en el pasado, específicamente mediante el desarrollo de cargadores reflexivos personalizados. También se pretende que los defensores comprendan cómo funciona Cobalt Strike para crear detecciones más sólidas.

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.

Aprovechar el marco con cargadores reflexivos

Esta entrada en el 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 realce con técnicas avanzadas que actualmente no están presentes en la herramienta. Las publicaciones futuras profundizarán en el desarrollo de características específicas de evasión y cómo implementarlas en nuestro cargador reflexivo Cobalt Strike.

Para comenzar, esta publicación cubrirá:

  • Los problemas con la carga de un implante C2 desde el disco con Windows DLL Loader.
  • Los conceptos y la mecánica del proceso de carga reflexiva 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 a través de la lente de un desarrollador de herramientas de seguridad ofensivas, destacaremos las oportunidades de detecciones y evasiones. Algunos aspectos del desarrollo se omitirán o simplificarán, y lo alentamos a llenar los vacíos depurando proyectos de cargadores reflexivos existentes, reconstruyéndolos desde cero o buscando capacitación.

Cargar la DLL de baliza

El implante Cobalt Strike C2, conocido como Beacon, es una biblioteca de enlace dinámico (DLL) de Windows, y la capacidad modular de usar nuestro propio cargador DLL en Cobalt Strike se conoce como cargador reflexivo definido por el usuario (UDRL).

El cargador DLL de Windows integrado

Normalmente, el cargador DLL de Windows integrado es responsable de cargar archivos DLL en el espacio de memoria virtual de un proceso. El cargador de DLL de Windows existe principalmente dentro del espacio del usuario, aunque cruza el espacio del kernel al asignar archivos DLL desde el disco.

El uso de Windows DLL Loader presenta algunos inconvenientes 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 kernel son activados por el cargador de DLL de Windows.

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

Los tres principales puntos de detección que evita la carga reflexiva son:

  1. Evita malware con firma en el sistema de archivos.
  2. Evita eventos de carga de imágenes del kernel, que pueden ser monitoreados por soluciones de seguridad.
  3. Evita nuestra DLL de implante C2 que figura en el bloque de entorno de proceso (PEB).

Cargador reflexivo frente a Windows DLL Loader

La carga reflexiva se puede considerar simplemente como cargar un archivo DLL sin procesar directamente desde la memoria, en lugar de cargarlo desde el sistema de archivos.

La carga reflexiva y el cargador de DLL de Windows integrado sirven para el mismo propósito de cargar una DLL desde el formato de archivo sin procesar en el 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 frente a formato de dirección virtual

Un concepto clave que hay que entender al cargar una DLL es saber que el formato de la DLL será diferente en el disco y en la 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 muy juntas entre sí.
  • Los desplazamientos 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.
  • Los desplazamientos son direcciones virtuales relativas (RVA).
  • Al ejecutar en un proceso, la DLL y otros módulos determinan las ubicaciones mediante RVA.
  • Este formato ocupa más espacio de memoria.

Baliza física frente a baliza virtual

Al examinar una DLL de baliza HTTP en la herramienta PE-Bear de Aleksandra Doniec, vemos las diferencias entre la dirección sin procesar y la dirección virtual para cada sección de la DLL:

Tabla que lista las direcciones sin procesar y virtuales de cada sección de la DLL de baliza.

Tabla que lista las direcciones sin procesar y virtuales de cada sección de la DLL de baliza.

El tamaño de esta DLL de baliza HTTP/S es 0x52000  bytes (327KB ) cuando se carga en el espacio de memoria virtual de un proceso, en comparación con el tamaño de  0x44000  bytes (272KB ) 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 empaquetadas estrechamente juntas en su formato de archivo sin procesar.

PE-Bear ofrece una representación visual de nuestra DLL de baliza tal y como existe en formato de archivo sin procesar frente al formato de dirección virtual:

Representación visual de la DLL de baliza en formato sin procesar frente a formato virtual

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

Baliza de carga con el cargador DLL de Windows

Si bien no es el movimiento más inteligente para realizar durante una simulación de adversario, colocar una DLL de baliza sin procesar sin ofuscación en el disco y cargarla con el cargador de DLL de Windows es una excelente manera de desmitificar tanto la carga de balizas como la de DLL. Básicamente, la baliza es solo una DLL. El cargador de DLL de Windows y un cargador reflexivo simplemente cargan una DLL en un proceso.

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

  1. Generar una DLL de baliza sin ofuscación.
  2. Crear un programa que:
    1. Utiliza la LoadLibrary API para cargar nuestro DLL de baliza 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 de baliza en la misma carpeta.
  4. Ejecute nuestro programa.

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

En primer lugar, desactivamos todas las opciones de Malleable PE que impiden que el cargador de DLL de Windows descargue nuestra DLL de baliza. Para ello, modificamos nuestro perfil de Malleable C2 y deshabilitamos las opciones de evasión de Malleable PE ubicadas en el bloque de escenario:

Se modificó el bloque de escenario del perfil de Malleable C2 para deshabilitar las funciones de evasión de Cobalt Strike.

Se modificó el bloque de escenario del perfil de Malleable C2 para deshabilitar las funciones de evasión de Cobalt Strike.

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

Captura de pantalla realizada para la entrada en el blog.

Nos conectamos al Team Server con el cliente Cobalt Strike. A continuación, creamos unWindows Stageless Payload  con la opción de salida establecida en sin procesar y el oyente establecido en https . Guardamos la carga útil como beacon.dll .

Captura de pantalla de la creación de una DLL de baliza "sin procesar" desde Cobalt Strike Client

Captura de pantalla de la creación de una DLL de baliza "sin procesar" desde Cobalt Strike Client

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 de baliza desde el disco mediante el cargador de DLL de Windows.

Código C de Windows para cargar la DLL de baliza desde el disco mediante el cargador de DLL de Windows.

Utilizamos laKernel32.LoadLibraryA  API para cargar nuestro DLL de baliza sin procesar desde el disco. Esta API llamará al cargador DLL de Windows integrado, que cargará nuestra DLL de baliza desde el disco en el espacio de memoria virtual de nuestro proceso de host.

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

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

Nuestro programa debe conocer el punto de entrada de nuestra DLL de baliza virtual para ejecutar nuestra DLL de baliza virtual. Esto se puede hacer dinámicamente dentro del programa analizando los encabezados de la DLL de la baliza 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 y codificaremos manualmente el punto de entrada RVA de nuestra DLL de baliza en nuestro programa. Con PE-Bear, descubrimos que el punto de entrada del RVA a la baliza es 0x1D840 :

Captura de pantalla de cómo encontrar el RVA del punto de entrada de la DLL de la baliza mediante PE-Bear

Captura de pantalla de cómo encontrar el RVA del punto de entrada de la DLL de la baliza mediante PE-Bear

El LoadLibraryA  La API devuelve la dirección base de nuestra DLL de baliza virtual. Simplemente agregamos esto al RVA del punto de entrada 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 de baliza y nuestro programa ejecutable de cargador de balizas en el mismo directorio, el cargador de DLL de Windows podrá descubrir nuestra DLL mientras realiza su rutina de carga.

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

La DLL de baliza y el programa cargador colocados en el mismo directorio.

La DLL de baliza y el programa cargador colocados en el mismo directorio.

Ejecución de nuestro programa

Desde nuestro escritorio de Windows, hacemos doble clic en nuestro loadBeaconDLL.exe programar y establecer una conexión activa de baliza con nuestro servidor de equipo.

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

Conexión exitosa a C2 Team Server desde la DLL de baliza cargada mediante el cargador de 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 de DLL en memoria tiene más de una década de antigüedad y se ha utilizado en Metasploit y otras herramientas de seguridad ofensiva destacadas.

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 Cobalt Strike tiene para ofrecer. La principal desventaja de usar un cargador reflexivo definido por el usuario (UDRL) personalizado es que las funciones 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 utiliza una UDRL, y se aplican parches en la DLL de la baliza mediante el motor Cobalt Strikes Maleable PE en la creación de la carga útil de la baliza. Sin embargo, actualmente características como obfuscate deben ser gestionadas por la UDRL, mientras que otras como sleepmask y cleanup puede gestionarse con una baliza con una integración UDRL adecuada.

Métodos de carga reflexiva

Método original del cargador reflexivo

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 ReflectiveLoader  exportación.
  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 reflexivamente.
Diagrama del cargador reflexivo original, cargando una DLL en la memoria virtual.

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

Anteposición del 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 método de carga reflexiva y robusto que puede cargar cualquier archivo PE (EXE o DLL).

Diagrama de un cargador reflexivo antepuesto a una DLL, cargando una DLL en la memoria virtual.

Diagrama de un cargador reflexivo antepuesto a una DLL, cargando una DLL en la memoria virtual.

Método de carga reflexiva de Cobalt Strike

La implementación de la carga reflexiva de 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, ReflectiveLoader  la función se compila y exporta dentro de la DLL de baliza original. Cuando un operador genera una carga útil de baliza desde el cliente Cobalt Strike, el motor de Malleable PE de Cobalt Strike parchea la DLL de baliza sin procesar para informar al cargador reflexivo sobre las opciones de Malleable PE que debe usar. El encabezado DOS de la baliza está parcheado para llamar la ReflectiveLoader exportación en un desplazamiento codificado de forma fija. Los bytes parcheados iniciales del encabezado de denegación del servicio (DoS) de la baliza, que llama a la ReflectiveLoader  exportación, se denominará en este blog "stub del cargador reflexivo de llamadas".

Cuando se carga una UDRL en Cobalt Strike, y un operador genera una carga útil de baliza desde el cliente de Cobalt Strike, el motor de Malleable PE de Cobalt Strike parchea el shellcode del cargador reflexivo en el desplazamiento del archivo sin procesar del ReflectiveLoader  exportar.

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

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

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

Talón de cargador reflexivo de llamada de baliza

Al observar los bytes iniciales en el desensamblador PE-Bear, podemos ver que la DLL de la baliza 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 Cobalt Strikes C2. Estos bytes deben ser ejecutables y dar como resultado una no operación (nop ).

después de ejecutar opcionalmente los bytes antepuestos nops  y mágicos, el stub del cargador reflexivo de llamadas:

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

Confirmamos que el desplazamiento del archivo sin procesar para la ReflectiveLoader  exportación sea 0x16E3C  mirando el directorio de exportación de las DLL de baliza:

Captura de pantalla del uso de PE-Bear para determinar el desplazamiento sin procesar del archivo de exportación de ReflectiveLoader.

Captura de pantalla del uso de PE-Bear para determinar el desplazamiento sin procesar del archivo de exportación de ReflectiveLoader.

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

Para descubrir el desplazamiento del archivo sin procesar de la ReflectiveLoader  exportación, primero necesitamos saber la diferencia entre las .text  secciones dirección virtual y sin procesar. Con la diferencia conocida, podemos simplemente restarlo de la ReflectiveLoader  RVA de exportación, para descubrir el ReflectiveLoader  desplazamiento del archivo sin procesar de exportación.

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

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

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

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

Podemos confirmar esto en PE-Bear haciendo clic derecho en el ReflectiveLoader  RVA de la función de exportación y luego clic en Follow RVA:17A3C . El visor hexadecimal del widget anterior pasará a mostrar la ReflectiveLoader  exportación en su desplazamiento de archivo sin procesar.

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

  • Un hilo ejecuta la DLL de baliza sin procesar.
  • El stub del cargador reflexivo de llamadas llama a la ReflectiveLoader  exportación en una posición conocida del archivo sin procesar.
  • El cargador reflectivo carga la DLL de la baliza 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 de la baliza virtual al stub del cargador reflexivo de la llamada.
  • El stub del cargador reflexivo de llamadas llama al punto de entrada de la DLL de baliza virtual.
Diagrama que muestra las principales fases del proceso de carga reflexiva de la DLL de baliza por parte de Cobalt Strike.

Diagrama que muestra las principales fases del proceso de carga reflexiva de la DLL de baliza por parte de Cobalt Strike.

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 de baliza, el código del cargador reflexivo debe ser shellcode puro.

La forma más fácil de crear shellcode complejo es escribirlo en C sin dependencias externas. Luego, el archivo C se compila en un archivo de objeto. Todo debe estar incluido en el text del archivo de objeto. Finalmente, sabemos que 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 de objeto de cargador reflexivo y aplicarlo en la DLL de baliza sin procesar en el desplazamiento del archivo sin procesar de la ReflectiveLoader  exportación. Esto se hace en el script de UDRL Aggressor como se ve a continuación:

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

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

Nuestro script UDRL Aggressor tiene escritura Cobalt Strike en nuestro shellcode reflexivo del cargador realizando estos pasos:

  • Abrimos un$handle a nuestro archivo de objeto UDRL con elopenf de negocio.
  • Con el archivo$handle leemos el flujo de bytes y lo guardamos en la$data variable de matriz de bytes.
  • Luego cerramos el archivo$handle con elclosef de negocio.
  • La
    <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>
    
    función Cobalt Strike Aggressor incorporada analizará nuestro archivo de objetos UDRL desde la$data matriz de bytes, localizará la.text de nuestro archivo de objetos UDRL, extraerá el archivo .text y lo guardará en$loader variable de matriz de bytes.
  • La
    <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 Maleable PE para descubrir el desplazamiento del archivo sin procesar de nuestraReflectiveLoader exportación y parcheará nuestro shellcode UDRL desde$loader variable de matriz de bytes.
  • Finalmente, devolvemos la DLL de baliza modificada a Cobalt Strike y guardamos nuestro archivo del cliente.

Fases de carga reflexivas

Cobalt Strike ha hecho el trabajo por nosotros con respecto a la extracción la sección .text de nuestro archivo de objeto de cargador reflexivo, parcheando nuestro shellcode de cargador reflexivo y llamando a nuestro cargador reflexivo con el stub de llamada de cargador reflexivo ubicado en el encabezado de la DLL de baliza.

Estas son las fases que debemos desarrollar para cargar reflexivamente la baliza:

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

Fase 1: Encontrar la dirección base de la DLL de baliza sin procesar

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

  • Buscar hacia atrás encabezados MZ y PE
  • Buscar hacia atrás
  • Obtener la dirección base de la DLL de baliza sin procesar desde el stub del cargador reflexivo.

Encontrar nuestra posición en la memoria

Cuando usamos un método que busca hacia atrás, primero debemos obtener la dirección actual del puntero de instrucción de nuestro hilo (RIP ). Podemos usar este sencillo truco para getRip :

  1. En nuestra UDRL creamos una función llamada getRip .
  2. Llamamos getRip  que empujará 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 getRip  función, simplemente copiamos la dirección de retorno de la persona que llama desde la parte superior de la pila.
  4. En la codificación x64 Windows C, las funciones pueden devolver un valor. Este valor devuelto se devuelve a la persona que llama a través del RAX  registro. Al mover la dirección de retorno de la persona que llama al RAX  registro, devolvemos la dirección de retorno del llamante a este.
Código ensamblador Intel x64 para obtener la dirección base sin procesar de la DLL de baliza del registro RDI.

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

Búsqueda hacia atrás de encabezados MZ y PE

El proyecto original del cargador reflexivo busca hacia atrás los encabezados MZ y PE. Estos encabezados se han convertido en puntos de detección. Para superar este Cobalt Strike, se agregaron las magic_mz  y magic_pe  características de evasión de Malleable PE.

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

  • “Anule los primeros bytes (encabezado MZ incluido) de la DLL reflexiva de baliza. Se requieren instrucciones válidas. Siga las instrucciones que cambien el estado de la CPU con instrucciones que deshagan el cambio”.

Cuando está configurado, los MZ--  bytes en el desfase del archivo sin procesar e0x00  y el PE00  bytes en el desplazamiento del archivo sin procesar 0x80  son conocidos por el cargador reflexivo. El motor Malleable PE los integra en el DLL de baliza.

Estos bytes deben ser algo único, o el cargador reflexivo no podrá encontrarlos. Además, los bytes para el encabezado MZ deben ser de no-operación y ejecutables. No pueden ser valores como 0x00  o la baliza puede fallar. Este puede ser un posible punto de detección.

Busca de un huevo hacia atrás

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 de baliza sin procesar. Este método utiliza un cazador de capaz de buscar hacia atrás desde RIP , que busca dos instancias repetidas de un huevo único de 64 bits en el conocido beacon.dll+0x50  desplazamiento de 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 de denegación del servicio (DoS)", que no es necesario cuando se carga reflexivamente la baliza.

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

Aggressor para escribir un huevo en la DLL de baliza sin procesar y mostrar los cambios en la consola de scripts de Cobalt Strike.

Aggressor para escribir un huevo en la DLL de baliza sin procesar y mostrar los cambios en la consola de scripts de Cobalt Strike.

El código UDRL debe conocer el valor de huevo escrito en la DLL de baliza sin procesar por el script UDRL. Con el huevo conocido, el cazador de huevos busca hacia atrás dos instancias del huevo, como se ve en el siguiente código:

Código ensamblador Intel x64 para un cazador de huevos que busca hacia atrás dos instancias de un huevo de 64 bits.

Código ensamblador Intel x64 para un cazador de huevos que busca hacia atrás dos instancias de un huevo de 64 bits.

  • Tanto la secuencia de comandos del agresor de la UDRL como el código C de la UDRL se pueden modificar para usar diferentes huevos.

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

Secuencia de comandos de agresor para enmascarar MZ, PE y bytes no utilizados del banner de denegación del servicio (DoS) ubicado en los encabezados de la DLL de baliza sin procesar.

Secuencia de comandos de agresor para enmascarar MZ, PE y bytes no utilizados del banner de denegación del servicio (DoS) ubicado en los encabezados de la DLL de baliza sin procesar.

Obtención de la dirección base de la DLL de baliza sin procesar del stub Call Reflective Loader

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

Para examinar esto más a fondo en el depurador, generamos una baliza, anteponemos un punto de interrupción (0xCC ), y abrimos la baliza en x64dbg. Dado que el punto de interrupción se antepone, la dirección base de la baliza 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 de la baliza sin procesar:

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

Captura de pantalla de X64dbg que muestra el paso a través del stub del cargador reflexivo de llamadas para ver que la dirección base de la DLL de baliza 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 del DLL de la baliza 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 de la baliza sin procesar desde el registro RDI.

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

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

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

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

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

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

Evasiones

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

Fase 3: Asignación de memoria para baliza virtual

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

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

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

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

Evasiones

Esto puede ir un paso más allá con UDRL. En su lugar, se puede utilizar la versión NTAPI de estas funciones. Además, las funciones NTAPI 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 del asignador se establece en VirtualAlloc  el perfil Cobalt Strike Malleable C2, actualmente el proyecto BokuLoader utilizará una llamada directa al sistema a NtAllocateVirtualMemory  para asignar memoria para la DLL de baliza 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 de la baliza 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 de la baliza virtual.

  • El número de llamada del sistema se descubre mediante el método HellsGate.
  • Si existe un hook de usuarioland en el stub de llamada al sistema, se emplea 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 de baliza virtual, necesitamos copiar las secciones de la baliza de sus compensaciones de archivos sin procesar, tal como existen en la DLL de baliza sin procesar, a la memoria asignada en sus compensaciones virtuales relativas.

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 de baliza virtual, tendremos que cambiar las protecciones de memoria de la sección .text a 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 de código simplificado, del proyecto BokuLoader, que demuestra esto: esto:.

Ejemplo de código del proyecto BokuLoader que muestra secciones copiadas del DLL de baliza sin procesar al DLL de baliza virtual.

Ejemplo de código del proyecto BokuLoader que muestra secciones copiadas del DLL de baliza sin procesar al DLL de baliza virtual.

Evasiones

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

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

En el proyecto público de BokuLoader, los encabezados de la DLL de baliza no se copian de la DLL de baliza sin procesar a la DLL de baliza virtual. Actualmente, los primeros 0x1000  bytes de la DLL de la baliza virtual son nulos (0x00‘s ). Desde mis pruebas, la baliza no depende de sus encabezados después de que la baliza se haya 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 cifre las secciones. La UDRL podría descifrar las secciones en la memoria, utilizando una clave compartida entre la UDRL y el script de UDRL Aggressor.

Fase 5: Carga de dependencias DLL

La baliza HTTP/S x64 se basa en cuatro DLL para funcionar correctamente. Si estas DLL no están cargadas actualmente en el proceso, nuestro cargador reflexivo deberá cargarlas.

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

Captura de pantalla de PE-Bear que enumera las DLL del directorio de importación de la DLL de la baliza.

Captura de pantalla de PE-Bear que enumera las DLL del directorio de importación de la DLL de la baliza.

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 diversas maneras, con diferentes consideraciones de seguridad operativa. Algunos métodos son:

Si la DLL ya existe en el proceso, las API de Windows anteriores se pueden seguir usando para obtener las direcciones base de la DLL, aunque esto puede desencadenar alertas de detección no deseadas.

Alternativamente, el PEB mantiene un puntero a la estructura 

<un título="https://learn.microsoft.com/mx-es/windows/win32/api/winternl/ns-winternl-peb_ldr_data" href="https://learn.microsoft.com/mx-es/windows/win32/api/winternl/ns-winternl-peb_ldr_data">_PEB_LDR_DATA</a>

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

InMemoryOrderModuleList

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

Si la DLL no existe en InMemoryOrderModuleList , actualmente BokuLoader utiliza el NTDLL.LdrLoadDll  API para cargar la dependencia de DLL en la memoria, aprovechando el cargador de 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 usar correctamente una DLL cargada de forma reflexiva. El proyecto DarkLoadLibrary puede ser capaz de cargar correctamente una DLL en la 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 de DLLs cargadas recorriendo la InMemoryOrderModuleList.

Ejemplo de código del proyecto BokuLoader que muestra cómo se pueden resolver las direcciones de DLLs cargadas recorriendo la 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. Las direcciones API deberán escribirse en la tabla de direcciones de importación (IAT) de la DLL de la baliza virtual. De este modo, la baliza sabe a qué dirección saltar cuando necesita llamar a API como WININET.HttpSendRequest

La entrada de importación deberá resolverse a través de la cadena ordinal o de nombre.

En la imagen a continuación, vemos que la DLL de baliza 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 algunas entradas de importación para la DLL de baliza que deben resolverse por ordinal.

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

El cargador reflexivo Cobalt Strike incorporado utiliza la Kernel32.GetProcAddress  API 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.

El NTDLL.LdrGetProcedureAddress también es capaz de manejar cadenas de nombres y ordinales. Si la dirección retornada para la entrada de importación es un reenviador a otra DLL, BokuLoader se asigna de manera predeterminada a NTDLL.LdrGetProcedureAddress para resolver al reenviador.

Al escribir el IAT, el enlace se puede implementar escribiendo las direcciones virtuales de las funciones de enlace que hemos implementado en lugar de la dirección virtual de las API previstas. Siempre que la salida esperada se devuelva a la baliza cuando se llame a la dirección en el IAT, podemos ejecutar código adicional antes de volver a la baliza. Las publicaciones futuras y los lanzamientos públicos de BokuLoader demostrarán cómo podemos aprovechar el enganche IAT para características avanzadas de evasión.

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

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

Fase 7: Resolución de reubicaciones

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

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

Captura de pantalla de PE-Bear que muestra la dirección base de la imagen de la DLL de baliza.

Captura de pantalla de PE-Bear que muestra la dirección base de la imagen de la DLL de baliza.

Antes de comenzar a escribir reubicaciones, necesitamos calcular el delta entre la dirección base de nuestra DLL de baliza virtual y la dirección base codificada.

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

Captura de pantalla de la obtención del 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, agregamos 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 de baliza virtual.

En la siguiente imagen, podemos ver que las entradas de reubicación de balizas 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 .

Agregamos 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 agrega la dirección a la dirección base delta, para obtener la dirección virtual para la reubicación tal como existe en la DLL de baliza virtual:

Para cada entrada de reubicación, tendremos que verificar que el tipo sea

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

. Si esto es falso, omitiremos escribir la reubicación.

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

Si te interesa saber más sobre cómo realizar reubicaciones PE, consulta el código de la función doRelocations en el proyecto público BokuLoader. Antes de publicar esta entrada en el blog, cambié el código de reubicación de ensamblador a código C legible por humanos, para ayudar a otros que desean conocer los detalles técnicos de cómo se hace esto.

Fase 8: Ejecución de la baliza

La ejecución de la baliza se puede desglosar en tres pasos:

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

Hacer que la baliza virtual sea ejecutable

Si la memoria que asignamos para nuestra DLL de baliza virtual es READWRITE_EXECUTE , no necesitamos cambiar las protecciones de memoria para que la baliza funcione correctamente sin fallar.

Si asignamos nuestra memoria de baliza virtual como no ejecutable (READWRITE ), tendremos que cambiar la .text  sección de nuestra DLL de baliza 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 de UDRL como variables.

En el proyecto público BokuLoader, los cambios en las protecciones 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 muestra cómo cambiar la sección .text de la DLL de la baliza virtual a ejecutable.

Ejemplo de código del proyecto BokuLoader que muestra cómo cambiar la sección .text de la DLL de la baliza virtual a ejecutable.

Hay .data  una sección de nuestra DLL de baliza virtual debe tener los permisos READWRITE . Si no se puede escribir en la sección, nuestra DLL de baliza puede bloquearse durante la ejecución.

Inicializar la DLL de la baliza virtual

Para que la DLL de la baliza virtual se ejecute correctamente, primero debe inicializarse llamando al punto de entrada de la DLL de la baliza virtual. El primer argumento es la dirección base de la DLL de la baliza virtual. El segundo argumento es fwdReason  y debe establecerse en DLL_PROCESS_ATTACH (1) .

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

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

Ejecución de nuestra DLL de baliza virtual

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

A diferencia de una DLL típica donde el primer argumento hinstDLL  a 

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

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

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

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

Reflexiones finales

Esperamos que esta entrada en el 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 toneladas de oportunidades de evasión que se pueden implementar a través de la carga reflexiva. Con una comprensión más profunda de estos conceptos, las organizaciones pueden prepararse mejor para una defensa exitosa contra las amenazas cibernéticas.

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

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.