Mythic C2, Xenon y Crystal Palace

2026-07-01 — research

Introducción

La mayoría de los operadores de red team pasan semanas construyendo el reflective loader perfecto y luego envían el payload desnudo, esperando que el formato de salida predeterminado del framework C2 no los delate. Normalmente lo hace. Los default loaders que vienen con los agentes C2 de código abierto son los componentes más signatured de toda la cadena de payloads detectables byte por byte por cada EDR del mercado, con reglas YARA escritas antes incluso de que descargues el framework.

La solución no es encontrar un C2 mejor. La solución es separar responsabilidades: elige la mejor plataforma para operaciones (Mythic), elige el mejor agente para objetivos Windows (Xenon) y elige la mejor cadena de herramientas para construir evasive loaders (Crystal Palace). Luego compónlos en build time.

Este artículo trata sobre esa composición. Explica cómo la arquitectura de microservicios de Mythic permite custom build pipelines, cómo el formato de salida shellcode de Xenon delega en Crystal Palace en build time, cómo el sistema User-Defined Reflective Loader (UDRL) permite a los operadores conectar pilas de evasión completamente personalizadas sin modificar una sola línea del código fuente del agente C2.

No es un tutorial. No es una guía paso a paso. Es un análisis arquitectónico de un pipeline donde cada capa es independientemente intercambiable y cada capa aborda una parte diferente de la superficie de detección.

Para un análisis completo de Crystal Palace en sí mismo el DSL .spec, el BTF mutation cocktail, el hooking IAT con attach y addhook, el modelo de composición PICO y las primitivas de evasión at link time consulta el artículo complementario: crystal-palace. tradecraft link PIC y evasion de EDRs.


1. Mythic: La Plataforma de Composición

Frase de @its_a_feature_ creador de mythic: This is something I've learned over the past almost 8yrs of doing Mythic dev - you can't make something that everybody is going to like, but making something extensible enough that people can adapt to their needs is valuable

Mythic es un framework C2 multijugador y multiplataforma creado por Cody Thomas (@its_a_feature_). No es simplemente otro servidor de comando y control. Es una plataforma de composición diseñada desde cero con la filosofía de que cada componente el agente, el perfil de comunicación, el formato de mensaje, el build pipeline debe ser un servicio independiente y conectable.

1.1 Arquitectura de Microservicios

Mythic se ejecuta como un conjunto de contenedores Docker orquestados por docker-compose. Los servicios principales son:

┌──────────────────────────────────────────────────────────────────────┐
│      ARQUITECTURA DE MYTHIC       │
├──────────────────────────────────────────────────────────────────────┤
│                  │
│ ┌──────────────┐  ┌──────────────┐  ┌──────────────┐   │
│ │ React Web UI │  │ Nginx  │  │ Hugo Docs │   │
│ │ (interfaz │  │ (proxy  │  │ (docs por │   │
│ │ operador) │  │ inverso) │  │ agente)  │   │
│ └──────┬───────┘  └──────┬───────┘  └──────┬───────┘   │
│   │     │     │     │
│   └────────────────────┼────────────────────┘     │
│        │          │
│     ┌─────────▼─────────┐        │
│     │ Servidor GoLang │        │
│     │ (GraphQL, WS) │        │
│     └─────────┬─────────┘        │
│        │          │
│    ┌───────────────┼───────────────┐      │
│    │    │    │      │
│  ┌────────▼────────┐ ┌───▼────┐ ┌────────▼──────────┐    │
│  │ PostgreSQL  │ │RabbitMQ│ │ Jupyter Notebook │    │
│  │ (base datos) │ │ (cola) │ │ (scripting)  │    │
│  └─────────────────┘ └───┬────┘ └───────────────────┘    │
│        │          │
│    ┌────────────────┼────────────────┐      │
│    │    │    │      │
│  ┌──────▼──────┐ ┌──────▼──────┐ ┌──────▼──────┐    │
│  │ Contenedor │ │ Contenedor │ │ Contenedor │    │
│  │ Agente  │ │ Perfil C2 │ │ Traductor │    │
│  │ (Xenon,  │ │ (httpx,  │ │ (cripto  │    │
│  │ Apollo...) │ │ smb, tcp) │ │ personal.) │    │
│  └─────────────┘ └─────────────┘ └─────────────┘    │
│                  │
│ Cada caja es un contenedor Docker. Se comunican por gRPC    │
│ a través de RabbitMQ. Cualquier contenedor puede residir en   │
│ cualquier host.              │
│                  │
└──────────────────────────────────────────────────────────────────────┘

El operador interactúa con una interfaz web basada en React. El servidor GoLang maneja toda la lógica de negocio mediante APIs GraphQL y WebSockets. PostgreSQL almacena todo payloads, tareas, callbacks, credenciales, archivos, notas del operador. RabbitMQ es el bus de mensajes que conecta todos los contenedores.

Lo que hace potente esta arquitectura para el trabajo de evasión es que el agente y el C2 profile son contenedores separados que se comunican a través de una interfaz gRPC bien definida. El contenedor del C2 profile maneja todas las cuestiones a nivel de transporte peticiones HTTP, named pipes SMB, conexiones TCP. El contenedor del agente maneja la generación de payloads, definiciones de comandos y traducción de tareas. Ninguno conoce los detalles internos del otro. Esta separación significa que puedes cambiar los C2 profiles sin tocar el agente, y viceversa.

1.2 El Contrato de Generación de Payloads

Cada agente de Mythic define una función build() un método en Python o GoLang que recibe todos los parámetros proporcionados por el operador (SO objetivo, comandos seleccionados, configuración C2, flags de build time) y devuelve un BuildResponse que contiene los bytes crudos del payload finalizado.

El BuildResponse es el único contrato. A Mythic no le importa cómo se construyó el payload ya sea compilado con mingw-w64, enlazado con Crystal Palace, empaquetado con UPX o generado por un script Python personalizado. Solo necesita bytes y un código de estado.

Esta es la superficie de integración donde entra Crystal Palace. Como build() puede hacer literalmente cualquier cosa invocar compiladores, llamar a linkers externos, ejecutar Makefiles, procesar archivos ZIP subidos puede orquestar todo el pipeline de Crystal Palace como un paso de build time. El operador hace clic en "Generate Payload" en la interfaz de Mythic. La función build() compila la DLL del agente, compila el UDRL personalizado, invoca the Crystal Palace linker y devuelve el archivo .bin finalizado. El operador no ve nada de esto. Simplemente descarga el payload.

1.3 Parámetros de Compilación e Interfaces Dinámicas

El sistema de parámetros de build time de Mythic merece mención porque es lo que hace posible la subida del UDRL. Los agentes definen objetos BuildParameter con tipos String, Boolean, ChooseOne, File y reglas opcionales HideCondition que muestran u ocultan parámetros según otras selecciones.

Cuando Xenon define:

output_type = ChooseOne([exe, dll, shellcode])
custom_udrl = Boolean (oculto a menos que output_type = shellcode)
udrl_file = File (oculto a menos que custom_udrl = true)

La interfaz de Mythic los renderiza dinámicamente: el operador selecciona "shellcode", aparece la casilla "Custom UDRL", la marca, aparece el campo de subida de archivo. Esta interfaz condicional significa que el operador nunca ve opciones irrelevantes. Solo ve los parámetros necesarios para su caso de uso específico.

El tipo File es particularmente importante para el flujo de trabajo UDRL. Cuando el operador sube un archivo, Mythic lo almacena en su file store y pasa un UUID a la función build(). El constructor obtiene el contenido del archivo, lo escribe en un directorio temporal, lo descomprime y procede con la build time. El archivo ZIP del operador que contiene loader.spec, Makefile y código fuente viaja a través del sistema de archivos de Mythic, es extraído por el constructor, y es compilado y enlazado por Crystal Palace. Todo automático. Todo en build time.


2. Xenon: Un Agente Estilo Cobalt Strike para Mythic

Xenon es un agente Windows x64 para Mythic creado por @c0rnbread. Está intencionadamente modelado según la experiencia de operador de Cobalt Strike nombres de comandos similares, flujo de trabajo post-explotación similar, modelo de inyección similar. Pero se ejecuta sobre la infraestructura de código abierto de Mythic, no sobre la pila comercial de Fortra.

2.1 Formatos de Salida

Xenon soporta tres formatos de salida, cada uno con un caso de uso diferente:

EXE Un ejecutable Windows estándar. El código del agente se compila como un archivo PE con mingw-w64. Este es el formato más simple pero el menos evasivo. La cabecera PE, la import table y el disk artifact crean superficie de detección. Útil para pruebas de laboratorio y entornos donde no se requiere sigilo.

DLL Una biblioteca de enlace dinámico de Windows. El agente se compila como una DLL que exporta una función configurable (por defecto: DllRegisterServer). Puede cargarse con rundll32.exe, custom loaders o DLL sideloading. Mejor que EXE para sigilo ya que las DLL son cargadas por otros procesos, pero sigue teniendo los problemas de la cabecera PE y la import table.

Shellcode El formato más evasivo. La DLL del agente Xenon se envuelve en un reflective loader y Crystal Palace la enlaza en position-independent code con go() en el offset cero. Sin cabeceras PE, sin import table, sin requisitos de disk artifacts más allá de la entrega inicial. Aquí es donde Crystal Palace entra en escena, y es el formato en el que se centra este artículo.

2.2 Conjunto de Comandos

Xenon incluye un conjunto completo de comandos organizados en dos categorías: en proceso (se ejecutan dentro del propio proceso del agente) y post-explotación (se ejecutan en un proceso generado mediante fork-and-run).

Comandos en proceso se ejecutan directamente en el memory space del agente y devuelven resultados inmediatamente. Estos incluyen operaciones estándar como ps, ls, pwd, cd, shell, upload, download, make_token, steal_token, rev2self, sleep, socks y link/unlink para agentes peer-to-peer.

Ejecución de BOF ejecuta Beacon Object Files en proceso mediante el COFF loader de Xenon. El comando inline_execute toma un archivo .o compilado y sus argumentos, resuelve símbolos COFF dinámicamente y ejecuta la función go() del BOF en el hilo actual del agente. Esto permite ejecutar herramientas como SharpUp.exe o Seatbelt.exe sin crear un nuevo proceso.

Comandos de post-explotación siguen el patrón fork-and-run generan un sacrificial process, inyectan una DLL de capacidad (convertida a PIC por Crystal Palace), ejecutan la capacidad, capturan la salida y terminan el proceso. Los comandos en esta categoría son:

  • execute_assembly ejecuta un ensamblado .NET en un proceso remoto y recupera la salida. Es el equivalente de Xenon al execute-assembly de Cobalt Strike.
  • powerchell ejecuta scripts de PowerShell en un proceso remoto usando una DLL post-ex personalizada.
  • mimikatz ejecuta mimikatz.exe en un proceso remoto. Este no usa Crystal Palace; usa donut para convertir el binario mimikatz a shellcode, que luego se inyecta.

2.3 Perfiles C2 Maleables

A través del C2 profile httpx, Xenon soporta comunicaciones HTTP maleables el operador proporciona un archivo de configuración JSON o TOML que especifica cómo el agente formatea sus peticiones HTTP, dónde residen los datos (cabeceras, cookies, parámetros de consulta, cuerpo), qué transformaciones aplicar (base64, XOR, netbios) y cómo es el formato de respuesta del servidor. Esto da al operador control sobre la superficie de detección de red.

Los perfiles SMB y TCP permiten el enlace de agentes peer-to-peer un agente de largo alcance que se comunica por HTTPX puede generar un agente SMB que se encadena a través del mismo operador, o un agente TCP que tiende un puente a un segmento de red diferente.

2.4 Soporte para Kit de Inyección de Procesos

Xenon acepta BOFs de inyección de procesos personalizados a través de su comando register_process_inject_kit, compatible con el formato Process Inject Kit de Cobalt Strike. Esto significa que los operadores pueden aportar sus propias técnicas de inyección early-bird APC, process hollowing, module stomping sin modificar el agente.

Esto es relevante para la discusión de Crystal Palace porque el kit de inyección opera in the runtime, no in the build time. El pipeline de Crystal Palace se encarga de introducir el implante en memoria con una clean call stack y memory allocation evasiva. El kit de inyección se encarga de cómo las capacidades post-ex posteriores se inyectan en otros procesos. Se complementan mutuamente.


3. El Pipeline de Compilación de Shellcode

La salida shellcode es donde todo converge. Este es el flujo exacto, desde la interfaz de Mythic hasta el archivo .bin final.

3.1 Paso a Paso

┌──────────────────────────────────────────────────────────────────────────┐
│  PIPELINE DE COMPILACIÓN DE SHELLCODE DE XENON (builder.py)   │
├──────────────────────────────────────────────────────────────────────────┤
│                   │
│ ┌─────────────────────┐             │
│ │ El operador elige: │             │
│ │ • output = shellcode │            │
│ │ • custom_udrl = true │            │
│ │ • sube loader.zip │            │
│ │ • configura C2  │            │
│ │ • selecciona comandos│            │
│ └─────────┬───────────┘             │
│   │                │
│   ▼                │
│ ┌─────────────────────────────────────────────────────────────┐   │
│ │ Se ejecuta la función build() de builder.py     │   │
│ │                │   │
│ │ 1. Copia agent_code/ al directorio temporal de build time │   │
│ │                │   │
│ │ 2. Serializa config C2, perfil maleable, params de build │   │
│ │  → estampa en Include/Config.h       │   │
│ │                │   │
│ │ 3. Si custom_udrl: obtiene el ZIP subido, extrae a   │   │
│ │  agent_code/Loader/custom/, ejecuta "make" en ese dir │   │
│ │  → produce objetos COFF del loader (bin/*.x64.o)  │   │
│ │                │   │
│ │ 4. Si custom_postex_udrl: mismo proceso para cargador  │   │
│ │  post-ex → produce objetos COFF post-ex     │   │
│ │                │   │
│ │ 5. Ejecuta "make shellcode" en agent_code/     │   │
│ │  → mingw-w64 compila la DLL del agente Xenon    │   │
│ │  → artifact_x64.sc.dll         │   │
│ │                │   │
│ │ 6. Ejecuta the Crystal Palace linker:     │   │
│ │  ./link {loader}/loader.spec {dll_path} {output.bin} │   │
│ │  → loader.spec: carga COFF del UDRL, hace PIC,   │   │
│ │  incrusta DLL enmascarada XOR, exporta shellcode crudo │   │
│ │  → salida: out.x64.bin         │   │
│ │                │   │
│ └──────────────────────────┬──────────────────────────────────┘   │
│        │           │
│        ▼           │
│    ┌────────────────────────┐        │
│    │ BuildResponse:   │        │
│    │ • payload = bytes .bin │        │
│    │ • status = Success  │        │
│    │ • filename = .bin  │        │
│    └────────────────────────┘        │
│                   │
└──────────────────────────────────────────────────────────────────────────┘

Los pasos 1-4 ocurren en el constructor. El paso 5 es la build time de la DLL del agente mediante mingw-w64. El paso 6 es la invocación de Crystal Palace el momento en que la DLL compilada y el UDRL personalizado se fusionan en un único blob position-independent.

3.2 Lo Que Hace Crystal Palace en Este Paso

Cuando el constructor invoca ./link loader.spec artifact_x64.sc.dll out.x64.bin, Crystal Palace lee el archivo .spec ya sea la especificación of the Xenon loader por defecto o la especificación UDRL personalizada del operador y ejecuta lo siguiente:

  1. Carga los archivos objeto COFF del UDRL compilados en el paso 3.
  2. Los transforma en position-independent code (make pic +gofirst), asegurando que go() esté en el offset cero.
  3. Habilita Dynamic Function Resolution (dfr "resolve" "ror13"), para que cada referencia MODULE$Function en el loader resolves at runtime mediante PEB walk y comparación de hash.
  4. Fusiona la biblioteca de código cerrado libtcg.x64.zip (mergelib), proporcionando PicoLoad, ParseDLL, LoadDLL, ProcessImports y otras primitivas de carga reflectiva.
  5. Genera una máscara XOR aleatoria per-build time, encripta la DLL de Xenon con ella, antepone la longitud y enlaza el blob encriptado en una sección con nombre (link "dll").
  6. Si la especificación incluye hooks (attach, addhook), parchea los objetos COFF para redirigir esas API calls a wrapper functions at link time.
  7. Si la especificación incluye mutaciones (+mutate, +regdance, +blockparty, +shatter, +disco, magic, ised), las aplica aleatorizando secuencias de instrucciones, cegando constantes, dispersando patrones y reordenando basic blocks.
  8. Si la especificación incluye un YARA self-test (rule), computa la regla YARA contra la salida y falla la build time si hay auto-coincidencia.
  9. Exporta el blob de shellcode crudo sin cabeceras PE, sin tabla de secciones, sin directorio de importaciones, go() garantizado en el byte cero.

El resultado es un archivo .bin que es estructuralmente único en cada build time. Incluso si el operador genera dos payloads con configuración C2 idéntica con un minuto de diferencia, la salida binaria es diferente a nivel de byte debido a la aleatorización per-build time.

Para un desglose completo del pipeline del Crystal Palace linker, las directivas del DSL .spec, las primitivas del BTF mutation cocktail y la arquitectura de hooking attach/addhook, consulta el artículo complementario: crystal-palace. tradecraft link PIC y evasion de EDRs.


4. El Sistema de Cargador Personalizado UDRL

El User-Defined Reflective Loader (UDRL) es el mecanismo de Xenon para reemplazar el reflective loader por defecto por uno personalizado. Este es el punto de integración donde los operadores inyectan su pila de evasión en el proceso de generación de payloads.

4.1 Qué Es un UDRL

┌──────────────────────────────────────────────────────────────────────┐
│    VISIÓN GENERAL DE LA ARQUITECTURA UDRL     │
├──────────────────────────────────────────────────────────────────────┤
│                  │
│ ┌─────────────────────────────────────────────────────────────┐  │
│ │     loader.zip (subido a Mythic)     │  │
│ │ ┌──────────────────────────────────────────────────────────┐│  │
│ │ │ Makefile   ← target por defecto compila COFF  ││  │
│ │ │ loader.spec  ← instrucciones de build Crystal Pal. ││  │
│ │ │ src/             ││  │
│ │ │ ├── loader.c ← go(), alloc memoria, carga DLL  ││  │
│ │ │ ├── hooks.c  ← wrappers API, sleep masking  ││  │
│ │ │ ├── spoof.c  ← call stack spoofing (Draugr) ││  │
│ │ │ ├── draugr.asm ← stub ensamblador para stack frames ││  │
│ │ │ ├── pico.c  ← gestión sub-PICO, despacho hooks ││  │
│ │ │ ├── cleanup.c ← limpieza memoria, descarga gadgets ││  │
│ │ │ ├── services.c ← helpers (hashing, IAT walk) ││  │
│ │ │ ├── loader.h ← cabeceras compartidas, DECLSPEC_IMP ││  │
│ │ │ └── tcg.h  ← declaraciones API libtcg   ││  │
│ │ └──────────────────────────────────────────────────────────┘│  │
│ └─────────────────────────────────────────────────────────────┘  │
│        │          │
│        ▼          │
│ ┌─────────────────────────────────────────────────────────────┐  │
│ │   builder.py (se ejecuta en el servidor Mythic)  │  │
│ │                │  │
│ │ 1. Extrae loader.zip a agent_code/Loader/custom/   │  │
│ │ 2. Ejecuta "make" → compila *.c a bin/*.x64.o    │  │
│ │ 3. Ejecuta Crystal Palace:         │  │
│ │  ./link loader.spec {agent_dll_path} {output.bin}   │  │
│ │ 4. Devuelve output.bin como BuildResponse.payload   │  │
│ └─────────────────────────────────────────────────────────────┘  │
│        │          │
│        ▼          │
│ ┌─────────────────────────────────────────────────────────────┐  │
│ │    loader.spec (directivas clave)     │  │
│ │                │  │
│ │ load "bin/loader.x64.o"          │  │
│ │  make pic +gofirst +optimize +mutate +regdance   │  │
│ │  +blockparty +shatter +disco        │  │
│ │ load "bin/hooks.x64.o"  merge       │  │
│ │ load "bin/spoof.x64.o"  merge       │  │
│ │ load "bin/draugr.x64.bin" linkfunc "draugr_stub"   │  │
│ │ dfr "resolve" "ror13"          │  │
│ │ mergelib "../libtcg.x64.zip"         │  │
│ │ attach "KERNEL32$LoadLibraryA" "_LoadLibraryA"   │  │
│ │ attach "KERNEL32$VirtualAlloc" "_VirtualAlloc"   │  │
│ │ attach "KERNEL32$VirtualProtect" "_VirtualProtect"   │  │
│ │ preserve "KERNEL32$LoadLibraryA" "init_frame_info"   │  │
│ │ magic "0x7FFFFFFF, 0xD34DB33F, 0xCAFEBABE, 0xDEADC0DE"  │  │
│ │ pack $NOP "b" 0x90           │  │
│ │ ised insert "call findModuleByHash" $NOP +safe    │  │
│ │ rule "" 10 3 10-16           │  │
│ │ generate $MASK 128; push $DLL; xor $MASK; link "dll"  │  │
│ │ run "pico.spec" → link "pico"        │  │
│ │ export              │  │
│ └─────────────────────────────────────────────────────────────┘  │
│                  │
└──────────────────────────────────────────────────────────────────────┘

Un UDRL es un archivo ZIP que contiene:

loader.zip
├── Makefile   ← el target por defecto compila los objetos COFF
├── loader.spec  ← spec file de Crystal Palace
└── src/
 ├── loader.c  ← función go(), memory allocation, carga de DLL
 ├── hooks.c  ← wrappers de hooks API, sleep masking
 ├── spoof.c  ← stack spoofing de llamadas con Draugr
 ├── draugr.asm ← stub ensamblador para construcción de stack frames
 ├── pico.c  ← gestión de sub-PICO, despacho de hooks
 ├── cleanup.c  ← limpieza de memoria, descarga de módulo gadget
 ├── services.c ← funciones auxiliares (hashing, IAT walk)
 ├── loader.h  ← cabeceras compartidas, structs, DECLSPEC_IMPORT
 ├── tcg.h   ← declaraciones API libtcg (PicoLoad, ParseDLL, etc.)
 └── ...   ← cualquier archivo fuente adicional que añada el operador

El Makefile compila cada archivo .c en un objeto COFF separado. El archivo .spec carga estos objetos, los transforma, los fusiona, aplica hooks y mutaciones, e incrusta la DLL del agente.

4.2 Crystal-Kit-Xenon: Adaptación para Mythic

El Crystal-Kit original de RastaMouse fue diseñado para Cobalt Strike. El fork compatible con Xenon Crystal-Kit-Xenon adapta the loader a las convenciones específicas de Xenon. Las diferencias clave:

Convención de entry point. El beacon de Cobalt Strike espera que se llame primero a DllMain con código de razón 1 (DLL_PROCESS_ATTACH) y luego otra vez con código de razón 4 (una convención específica de Cobalt Strike para entrar en el bucle de sleep pasando un puntero de código como handle de módulo). Xenon sigue un patrón estándar: DllMain genera los hilos de comunicación y retorna. El loader must mantener vivo el host process después de llamar al entry point, típicamente con un Sleep(INFINITE) o una espera de evento. No hay una segunda llamada a DllMain.

Export table walk. Los agentes basados en Sliver (y algunas configuraciones de Xenon) exportan StartW en lugar de depender de DllMain. El loader recorre la export table de la DLL manualmente para encontrar y llamar al entry point correcto. No se puede usar GetProcAddress en esta etapa porque la DLL cargada por libtcg no está registrada en la lista de módulos del PEB el SO no sabe que existe, por lo que GetProcAddress falla.

Inicialización de TLS de Go. Cuando el implante se compila con Go (como los agentes Sliver), el planificador de goroutines almacena su puntero en gs:[0x28], un slot de Thread Local Storage. LoadDLL de libtcg no inicializa el directorio TLS. El loader must llamar a TlsAlloc para asignar un slot, escribir el valor apropiado y ejecutar cualquier callback TLS listado en el directorio TLS de la DLL. Esto debe ocurrir después de fix_section_permissions porque los callbacks residen en .text, que está RW durante la carga y debe convertirse en RX antes de ejecutarlos.

Estos ajustes son pequeños en volumen de código pero críticos en la práctica. Sin ellos, el agente falla silenciosamente sin salida de diagnóstico.

4.3 El Flujo de Trabajo Operativo

El flujo del operador es simple:

  1. Escribe (o adapta) un UDRL personalizado siguiendo la estructura Crystal-Kit.
  2. Comprímelo como loader.zip.
  3. En la interfaz de Mythic, selecciona output_type = shellcode, marca custom_udrl = true y sube el ZIP.
  4. Configura los C2 profiles, selecciona comandos y haz clic en "Generate."

El builder.py de Mythic extrae el ZIP, ejecuta make, invoca Crystal Palace y devuelve el .bin finalizado. El operador lo descarga y lo despliega con un loader de su elección.

El mismo patrón funciona para post-ex loaders (custom_postex_udrl), que siguen un contrato .spec separado cargan una DLL de capacidad, pasan argumentos a través de lpvReserved y salen del proceso tras la ejecución.


5. Cargadores de Post-Explotación: Fork-and-Run con Crystal Palace

Los comandos de post-explotación de Xenon usan un patrón de inyección fork-and-run. Se genera un sacrificial process, se inyecta una DLL de capacidad, la capacidad se ejecuta, la salida se captura a través de una named pipe y el proceso termina. La DLL de capacidad debe convertirse a PIC para la inyección, y Crystal Palace maneja esta conversión.

5.1 El Pipeline de Conversión

┌──────────────────────────────────────────────────────────────────────────┐
│    PIPELINE DEL CARGADOR DE POST-EJECUCIÓN      │
├──────────────────────────────────────────────────────────────────────────┤
│                   │
│ EL OPERADOR EMITE EL COMANDO           │
│ ─────────────────────────            │
│                   │
│ ┌─────────────────────────────────────────────────────────────────┐  │
│ │ execute_assembly -Assembly SharpUp.exe -Arguments "audit"  │  │
│ └─────────────────────────────┬───────────────────────────────────┘  │
│        │           │
│        ▼           │
│ ┌─────────────────────────────────────────────────────────────────┐  │
│ │    crystal_utilities.py        │  │
│ │                 │  │
│ │ 1. Determina qué post-ex loader usar:      │  │
│ │  • Personalizado: postex_{payload_uuid}/ (del ZIP subido) │  │
│ │  • Por defecto: Loader/post-ex/        │  │
│ │                 │  │
│ │ 2. Obtiene la DLL de capacidad del file store Mythic │  │
│ │  → Escribe a archivo temporal        │  │
│ │                 │  │
│ │ 3. Escribe los argumentos del comando a archivo temporal  │  │
│ │                 │  │
│ │ 4. Enlace Crystal Palace:          │  │
│ │  ./link {postex}/loader.spec {dll_path} {out.bin} \   │  │
│ │   %ARGFILE='{arg_file_path}'       │  │
│ │                 │  │
│ │ 5. Devuelve bytes PIC (.bin)         │  │
│ └─────────────────────────────┬───────────────────────────────────┘  │
│        │           │
│        ▼           │
│ ┌─────────────────────────────────────────────────────────────────┐  │
│ │   loader.spec de Post-Ex (directivas clave)    │  │
│ │                 │  │
│ │ load "bin/loader.x64.o"          │  │
│ │  make pic +gofirst +optimize +mutate      │  │
│ │ dfr "resolve" "ror13"           │  │
│ │ mergelib "../libtcg.x64.zip"         │  │
│ │                 │  │
│ │ push $DLL   ← DLL de capacidad       │  │
│ │  preplen              │  │
│ │  link "dll"             │  │
│ │                 │  │
│ │ load %ARGFILE  ← argumentos del comando de archivo temp │  │
│ │  preplen              │  │
│ │  link "dll_args"           │  │
│ │                 │  │
│ │ export              │  │
│ └─────────────────────────────┬───────────────────────────────────┘  │
│        │           │
│        ▼           │
│ ┌─────────────────────────────────────────────────────────────────┐  │
│ │  En tiempo de ejecución (dentro del target process)  │  │
│ │                 │  │
│ │ 1. El agente genera sacrificial process (svchost.exe)   │  │
│ │ 2. El agente asigna memoria RWX, escribe .bin → remote thread │  │
│ │ 3. El PICO se ejecuta:           │  │
│ │  go()              │  │
│ │  → ParseDLL + LoadDLL + ProcessImports     │  │
│ │  → DllMain(hDll, DLL_PROCESS_ATTACH, dll_args)   │  │
│ │  → La DLL de capacidad se ejecuta con args vía lpvReserved │  │
│ │  → Salida canalizada al agente vía named pipe    │  │
│ │  → KERNEL32$ExitProcess(0)         │  │
│ │ 4. El agente captura la salida, el proceso termina    │  │
│ └─────────────────────────────────────────────────────────────────┘  │
│                   │
│ El operador ve: "[+] results from SharpUp audit..."      │
│                   │
└──────────────────────────────────────────────────────────────────────────┘

El pipeline se ejecuta at runtime del comando, no en build time. Cuando el operador emite execute_assembly con argumentos, la función convert_postex_dll_to_pic orquesta la conversión en el servidor Mythic. Obtiene la DLL de capacidad del file store, la escribe junto con los argumentos en archivos temporales, invoca Crystal Palace con la especificación del post-ex loader y la directiva %ARGFILE, y devuelve los bytes PIC listos para inyección.

El post-ex loader sigue un contrato específico:

5.2 UDRLs Post-Ex Personalizados

Al igual que el primary loader del agente, el post-ex loader puede reemplazarse por un UDRL personalizado. El operador sube un ZIP con un loader.spec, Makefile y archivos fuente adaptados para ejecución post-ex. El constructor almacena esto por payload en un directorio con nombre UUID (postex_{payload_uuid}). Cuando se ejecuta un comando como execute_assembly, la función crystal_utilities.py consulta la configuración de build time del agente para determinar si usar el post-ex loader personalizado o el predeterminado.

Esto significa que diferentes payloads pueden use different loaders post-ex. Un payload podría usar un loader with aggressive patching de ETW/AMSI antes de ejecutar la DLL de capacidad. Otro podría usar un loader con module stomping para la inyección de la capacidad. La elección se hace en build time, persiste durante toda la vida de ese payload.


6. La Capa de Inyección del Cargador

Una vez que tienes un archivo .bin la salida de Crystal Palace con go() en el offset cero necesitas un loader para inyectarlo en un target process. Esta es una responsabilidad separada del build pipeline, pero completa la cadena.

El loader utilizado en este pipeline es un stage-0 stager en Rust un PE Windows x64 que transporta el PICO cifrado dentro de su propia sección .rdata y lo descifra solo en memoria. Evita completamente las APIs Win32 de alta señalización, llamando a cada servicio nativo del sistema a través de una 16-entry indirect syscall table (Tartarus Gate con stub recovery Halo's Gate).

El pipeline en runtime, en orden: comprobación anti-debug vía PEB, población de la syscall table mediante PEB walk (sin GetModuleHandleW), segunda comprobación anti-debug vía NtQueryInformationProcess(ProcessDebugPort), retraso sandbox aleatorio de 5–15 segundos mediante NtDelayExecution, descifrado del payload con AES-256-CTR (codificado en palabras para baja entropía), generación del target process mediante NtCreateUserProcess (por defecto: RuntimeBroker.exe, con PPID spoofing opcional vía PS_ATTRIBUTE_PARENT_PROCESS), remote patching de ETW (xor eax, eax; nop; ret en NtTraceEvent+3), remote patching de AMSI (mov eax, 0x8007000E; ret en AmsiScanBuffer), NtAllocateVirtualMemory + NtWriteVirtualMemory + NtCreateThreadEx para inyectar y ejecutar el PICO, y finalmente limpieza con NtClose antes de salir.

La evasión en disco se compone de: VERSIONINFO spoofing de Chrome, cadenas cifradas con obfstr y payload codificado en palabras. Las features opcionales de build time añaden detección de VM/sandbox (anti-vm) y self-protective DACL (dacl).

La evasión del loader y la evasión en build time de Crystal Palace son capas independientes. El loader maneja la entrega; Crystal Palace maneja los internos del payload. El contrato de go() en el offset cero es la interfaz limpia entre ambos.


7. La Imagen Completa

┌──────────────────────────────────────────────────────────────────────────┐
│    MYTHIC → XENON → CRYSTAL PALACE → OBJETIVO     │
├──────────────────────────────────────────────────────────────────────────┤
│                   │
│ TIEMPO DE COMPILACIÓN (ocurre en el servidor Mythic, dentro del   │
│ contenedor Docker)              │
│ ──────────────────────────────────────────────────────────────────── │
│                   │
│ ┌────────────┐ ┌────────────────┐ ┌───────────────────┐   │
│ │ Mythic UI │ │ builder.py  │ │ Crystal Palace │   │
│ │   │───►│    │───►│     │   │
│ │ • tipo  │ │ • serialización│ │ • carga UDRL COFF │   │
│ │ salida │ │ de config │ │ • hace PIC  │   │
│ │ • conf C2 │ │ • compila DLL │ │ • resolución DFR │   │
│ │ • comandos │ │ • compila UDRL │ │ • incrusta DLL │   │
│ │ • subir │ │ • invoca CP │ │ XOR    │   │
│ │ UDRL ZIP │ │ • devuelve  │ │ • aplica hooks │   │
│ └────────────┘ │ bytes  │ │ • muta binario │   │
│     └────────────────┘ │ • self-test  │   │
│           │ • exporta .bin │   │
│           └────────┬──────────┘   │
│             │      │
│             ▼      │
│           ┌───────────────────┐   │
│           │ xenon_httpx.bin │   │
│           │ shellcode crudo │   │
│           │ go() en offset 0 │   │
│           └────────┬──────────┘   │
│             │      │
│ ──────────────────────────────────────────────────────────────────── │
│ TIEMPO DE EJECUCIÓN (ocurre en el objetivo, dirigido por el operador) │
│ ──────────────────────────────────────────────────────────────────── │
│             │      │
│             ▼      │
│ ┌──────────────┐ ┌────────────────┐ ┌─────────────────────┐  │
│ │ Stager Rust │ │ Proceso  │ │ Dentro del PICO: │  │
│ │ (stage-0) │───►│ Objetivo  │───►│      │  │
│ │    │ │ (por defecto: │ │ 1. Descifra DLL XOR│  │
│ │ • PEB walk │ │ RuntimeBroker │ │ 2. ParseDLL   │  │
│ │ • anti-debug │ │ .exe)   │ │ 3. LoadDLL   │  │
│ │ • sandbox │ │    │ │ 4. ProcessImports │  │
│ │ • patched │ │ PICO inyectado:│ │ 5. fix sections │  │
│ │ ETW  │ │ • NtAlloc RWX │ │ 6. configura hooks │  │
│ │ • patched │ │ • NtWrite .bin │ │ del PICO  │  │
│ │ AMSI  │ │ • NtCreateThrd │ │ 7. llama DllMain │  │
│ │ • DACL  │ │    │ │ 8. Xenon responde │  │
│ │ • descifrado │ │    │ │      │  │
│ │ AES  │ │    │ │      │  │
│ │ • NtCreate │ │    │ │      │  │
│ │ UserProcess│ │    │ │      │  │
│ └──────────────┘ └────────────────┘ └─────────────────────┘  │
│                   │
│ Cada capa es independientemente intercambiable:       │
│ ¿Cambiar el C2 profile? → Actualiza la config de httpx.    │
│ ¿Cambiar the loader? → Sube un nuevo ZIP UDRL.      │
│ ¿Cambiar la entrega? → Intercambia the loader Rust/C.    │
│ ¿Cambiar la inyección? → Despliega un BOF de kit de inyección.  │
│                   │
└──────────────────────────────────────────────────────────────────────────┘

8. Por Qué Este Pipeline Es Importante

El pipeline Mythic + Xenon + Crystal Palace no es la única forma de construir un implante evasivo. Sin embargo, es uno de los pocos pipelines donde cada componente es independientemente intercambiable y donde la capa de evasión se integra en build time en lugar de añadirse después.

8.1 Modularidad en Cada Capa

Cada capa aborda una parte diferente de la superficie de detección:

  • Perfil C2 detección de red (patrones de tráfico, huellas TLS, intervalos de beacon).
  • Binario del agente detección de comportamiento (creación de procesos, cadenas de API calls, patrones post-ex).
  • UDRL / Crystal Palace detección estática (firmas YARA, patrones de bytes) y detección en tiempo de carga (pilas de llamadas, memory allocation, hooking IAT).
  • Cargador personalizado detección de entrega (disk artifacts mediante spoofing de VERSIONINFO de Chrome y cadenas ofuscadas, detección basada en IAT mediante PEB walk y syscalls indirectas, detección de inyección mediante NtCreateUserProcess y NtCreateThreadEx).
  • Kit de inyección de procesos detección de inyección (handles entre procesos, creación de hilos, inyección APC).

Como cada capa es independiente, puedes actualizar una sin rediseñar las otras. Si surge una nueva técnica de stack spoofing, actualizas spoof.c en tu ZIP UDRL, lo vuelves a subir y reconstruyes. El C2 profile, el binario del agente y the loader de entrega no se ven afectados.

8.2 Hooking IAT Transparente

La capacidad individual más importante que Crystal Palace aporta a este pipeline es addhook. A través del interceptor controlado _GetProcAddress, puedes cambiar cómo el agente Xenon llama a cualquier API Win32 sin modificar una sola línea del código fuente de Xenon, sin recompilar la DLL del agente y sin que el agente sea consciente de que algo ha cambiado.

Cuando the loader llama a ProcessImports para resolver la import table de la DLL del agente, el GetProcAddress hooked comprueba cada nombre de función contra los hooks registrados. Si el agente quiere Sleep, obtiene _Sleep (que encripta la imagen, suplanta la pila, duerme, descifra y retorna). Si quiere VirtualAlloc, obtiene _VirtualAlloc. La IAT del agente se reasigna completamente a tus wrapper functions, aplicado en tiempo de carga, de forma transparente.

Este mecanismo se explica en profundidad en el artículo complementario de Crystal Palace, pero la consecuencia operativa es directa: los desarrolladores de Xenon escriben el agente para llamar a APIs Win32 estándar. Tú, el operador, decides en build time cuáles de esas llamadas son interceptadas y qué sucede cuando lo son.

8.3 Unicidad por Compilación

Cada build time de Crystal Palace produce bytes estructuralmente únicos. El BTF cocktail +mutate, +regdance, +blockparty, +shatter, +disco combinado con el constant blinding (magic) y la instruction scattering (ised) asegura que dos payloads compilados con un minuto de diferencia con configuración C2 idéntica tengan representaciones binarias completamente diferentes.

Esto rompe la economía del desarrollo de firmas estáticas. Un analista no puede escribir una regla YARA y detectar todos los payloads de Xenon, ni siquiera todos los payloads del mismo operador. Necesitaría desarrollar reglas per-build time, lo cual es impracticable para la detección de malware conocido a escala.

8.4 Comparación con Otros Ecosistemas

Mythic + Xenon + CP Cobalt Strike + UDRL Havoc + Default Metasploit + sRDI
Personalización del loader Completa (subida ZIP UDRL) Completa (UDRL en CNA) Modificación de código fuente Limitada
Mutación estática Sí (BTF cocktail) Sí (si se usa CP) No No
Hooking IAT Sí (addhook) Sí (addhook en CP) No No
Personalización post-ex Sí (UDRL postex personalizado) Sí (kit post-ex) Modificación de código fuente Fijo
Integración en build time El builder de Mythic invoca CP El script CNA invoca CP Solo Makefile msfvenom fijo
Perfiles C2 Conectables (httpx, smb, tcp) Fijos (HTTP/S, DNS, SMB) Conectables Fijos
Código abierto No (comercial)

9. Conclusiones

Mythic proporciona la plataforma. Xenon proporciona el agente. Crystal Palace proporciona la capa de evasión. Juntos forman un pipeline donde cada componente es independientemente personalizable y donde el tradecraft de evasión de detecciones se incorpora al payload en build time en lugar de aplicarse como una ocurrencia tardía.

El flujo de trabajo operativo es limpio: el operador selecciona parámetros en la interfaz de Mythic, opcionalmente sube un UDRL personalizado con su pila de evasión preferida, hace clic en Generate y recibe un archivo .bin shellcode crudo con go() en el offset cero, estructuralmente único, con hooking IAT aplicado, sleep masking incrustado y stack spoofing compilada. Lo inyecta con un loader de su elección. El implante se ejecuta. El operador opera.

Lo que hace duradera esta arquitectura es la separación de responsabilidades. Los desarrolladores del agente se centran en la lógica C2 y la implementación de comandos. Los desarrolladores del loader se centran en las primitivas de evasión y el manejo de API consciente de la detección. El operador se centra en la misión. Cada capa evoluciona independientemente, y las mejoras en una capa se propagan a todos los payloads sin tocar las demás.


References

[1] Cody Thomas, "Mythic C2 Framework Documentation," docs.mythic-c2.net, 2026. https://docs.mythic-c2.net/home

[2] c0rnbread, "Xenon: Cobalt Strike-like Windows Agent for Mythic," GitHub, 2024-2026. https://github.com/MythicAgents/Xenon

[3] c0rnbread, "Xenon Wiki Evasion and User-Defined Reflective Loaders," MythicAgents Wiki. https://github.com/MythicAgents/Xenon/wiki/Evasion

[4] c0rnbread, "Crystal-Kit-Xenon: Crystal Kit Compatible with Mythic Xenon Agent," GitHub, 2024-2026. https://github.com/nickswink/crystal-kit-xenon

[5] Raphael Mudge, "Crystal Palace Documentation and Linker Script Language," Tradecraft Garden, 2025-2026. https://tradecraftgarden.org/crystalpalace.html

[6] RastaMouse, "Crystal-Kit: Cobalt Strike Evasion with Crystal Palace," GitHub, 2024-2026. https://github.com/rasta-mouse/Crystal-Kit

[7] Lorenzo Meacci, "Bypassing EDR in a Crystal Clear Way KaplaStrike," lorenzomeacci.com, 2026. https://lorenzomeacci.com/bypassing-edr-in-a-crystal-clear-way

[8] RastaMouse, "GadgetHunter Call Stack Spoofing Gadget Scanner," GitHub, 2024. https://github.com/rasta-mouse/GadgetHunter

[9] MythicMeta, "Mythic Community Overview Agents and C2 Profiles." https://mythicmeta.github.io/overview/

[10] Companion post: crystal-palace. tradecraft link PIC y evasion de EDRs

Para apoyar la redacion de este documento se an utilizado 2 modelos de inteliguencia artificial. En este caso gemma 4 31B y deepseek R1 14B desde ollama


Esta publicación tiene fines educativos y de investigación únicamente. Las técnicas descritas solo deben utilizarse en entornos autorizados con permiso explícito.

← back to blog