bahamut: Mi agente personal de evasion de EDRs en sistemas windows

2026-10-12 research

0. Contexto

La verdad, estaba harto de depender de agentes custom de internet para C2. Que si cierran que si no funcionan en X entorno, que si esta arquitectura está obsoleta etc etc etc.

Así que bueno, dije: "Quiero algo estable y modificable para mis entornos y EDRs. Así que dije a principios del verano: "Bueno, ya que Mythic me da una buena infraestructura, voy a hacer dos agentes: uno para Windows centrado en la evasión de EDRs modernas y otro de sistemas Linux para entornos empresariales y EDRs(Lo cual me interesa mucho. Recientemente solo he visto el agente de NaxDarks de Loky-rt, que cumple con un poco de mi visión a futuro).

A estos dos agentes los he bautizado como Bahamut y Adán. Bahmut siendo el de Windows y Adán el de Linux.

0.1 Decisiones técnicas preliminares

Si no estás muy metido en el desarrollo de payloads o agentes de C2, de manera muy resumida, hay dos arquitecturas que actualmente son las predominantes: la arquitectura like Cobalt Strike y una arquitectura basada en código PIC (posición independiente de código).

Como todo en esta vida tiene sus pros y contras:

La arquitectura estilo Cobalt está mucho más documentada, tiene muchas más compatibilidades, se lleva bien con muchísimas UDRLs y sleepmasks, etc. Problema: Es una arquitectura muy vista y usada (de ahí el meme original de que solo existe un C2 en realidad) y ya todos se saben sus truquitos.

Después está el código PIC (del cual aquí solo trataré desde la perspectiva de C y C++; he visto cosas locas en Go y Rust): Se puede ejecutar el código desde la posición de memoria que te dé un poco la gana, mucho más limpia (desde mi punto de vista), muy escalable y modular en proyectos. Pero el principal problema que tiene es que es una arquitectura muy nueva y poco documentada en comparación con la de Cobalt. El primer proyecto serio respecto a código independiente de código fue Stardust y Havoc C2 de 5spider hace 3 años ya.

Entonces yo me encontré con este dilema: las primeras versiones de Bahamut (no las que he ido publicando, unas que hice de testeo) funcionaban bajo una arquitectura estilo Cobalt muy parecida a la del agente de Xenon. Pero, siendo sinceros, me estaba dando más problemas que beneficios. Con cosas muy sencillas podía detectar en memoria mi propia agente; Crystal Palace lo solucionaba un poco, pero no es la solución ni al 90 % de los problemas que tuve. Aparte, no quiero ser el chico Crystal Palace español, jajajajajajaj.

Y por allá de julio salieron dos cosas muy interesantes:

  • https://specterops.io/blog/2026/06/24/disposable-tooling-building-llm-generated-mythic-agents-from-prompt-to-deployment/
  • https://github.com/Whispergate/Starburst

La primera es una investigación de SpecterOps donde enseñaban y demostraban un método eficiente para la creación de agentes usando modelos de inteligencia artificial. ¿¿¿Y qué tiene que ver esto???

Pues a los pocos días/semanas nació Starburst en base a este blog, una agente de código PIC bastante interesante y completa. A mí me llamó mucho la atención y resultaba que conocía a sus creadores.

Haciendo pruebas y viendo cómo funcionaba, vi que era altamente modificable y muy modular (casi nada dependía de otra funcionalidad) y mis dos neuronas hicieron click y dijeron: "¿No tenemos el nivel para hacer un agente PIC funcional desde 0? Sin embargo, han conseguido hacer este agente usando ayuda de IA. ¿Por qué no le imitamos? ¿O mejor por qué no cogemos su base y la reescribo a mi gusto??"

Y bueno, eso hice: empecé a reescribir Starburst a mi gusto y cambiando lo que no necesito por funciones nuevas (ya hablaremos más adelante). Y así estuve todo julio y casi todo agosto, viendo cómo modificarlo correctamente (antes de nada, una aclaración: obviamente he usado IA. Me parece lamentable tener que aclarar esto a día de hoy, pero siempre están los enamorados de su código por ahí. Sí, hasta los desarrolladores actuales de Havoc Premium y Adaptix usan IA, por favor).

A finales de agosto pregunté a los creadores si había algún problema con la existencia de Bahamut. A fin de cuentas, se basa en su propia arquitectura; me comentaron que mientras mantenga la misma licencia, no tienen problemas con los forks, así que tiré para adelante.

También aproveché que pregunté a its-a-feature (creador de Mythic) si la documentación de integración de agentes para Mythic 4 ya era la definitiva. Me lo confirmó, así que le di soporte a mi agente para la nueva versión y es con la que he estado trabajando principalmente

0.3 Intenciones con el agente

No lo voy a esconder; esto lo hago por tener una agente custom a mi media para mis laboratorios y para ganarme más reconocimiento. Al final, el pan no sale de debajo de una piedra.

Mi intención es dar una ponencia en la RootedCon 2027 de Madrid hablando de técnicas modernas de evasión de EDRs y presentando a Bahamut al mundo. Pero claro, me tienen que conceder la ponencia, jajaja.

No hay planes de hacer público Bahamut por dos razones: no estoy de acuerdo en muchas cosas con hacer de código abierto todo y porque no considero de buen gusto publicar el agente, siendo una versión muy modificada y mejorada de su versión original. Así que lo que he concluido es que, después de su presentación, iré mandando pull requests poco a poco con algunas de las implementaciones de Bahamut al agente de Starburst original, no todas porque algunas necesitan un cambio arquitectónico muy alto, pero sí varias implementaciones.

1. ¿Qué es Bahamut?

Bahamut es un agente de C2 (más concretamente, mythic) basado en arquitectura de código PIC que se caracteriza por intentar tener la máxima modularidad posible y ser lo más preciso y moderno en cuanto a evasión de EDRs y sin runtime de C, sin importaciones ni información de desenrollado propia.

1.2 ¿Qué tiene o qué va a tener Bahamut?

Esta es una pregunta interesante. Tenía bastantes ideas en la cabeza, de las cuales muchas se han ido y otras nuevas han entrado. Principalmente, he ido preguntando a la comunidad y a contactos muy buenos que tengo como creadores de agentes y C2 qué les gustaría ver en un agente moderno.

Y para no enseñar todo lo que tengo en la lista, voy a enseñar todo lo que puedo confirmaros que estará (no me gusta enseñar más de la cuenta; la gente que habla conmigo lo sabe, jajajajja):

  • Rediseño de sistema de spoof para hacerlo compatible con call stack spoofing
  • Implementación de la técnica de Moonwalk++
  • Implementación de Zilean as a sleep mask
  • Sistemas anti-debug y análisis (terminado bajo pruebas)
  • Aleatorización de hash (en pruebas)
  • Control Flow Guard en buenas condiciones (en proceso)
  • Un loader digno (aun planificando qué método usar)
  • ????

Sí que es cierto que en esta lista falta una cosa. La ejecución de BOF en memoria respaldada o BOF-PE para los amigos. Yo hice una implementación, pero justo el agente original sacó su propia versión; las comparé y eran muy parecidas. Así que decidí simplemente mejorar un poco la mía, pero siento que no es lo mejor añadirlo como algo exclusivo de mi agente.

2. Call stack spoofing

Para la primera versión elegí LoudSunRun. En vez de parchear solo la dirección de retorno, construye una pila sintética completa: planta frames falsos con tamaños calculados desde los metadatos de unwind (.pdata / UNWIND_INFO) de funciones legítimas del sistema y los encadena con un gadget trampolín (jmp [rbx]). El ret del target consume el slot del gadget, el gadget salta a la rutina de restauración (fixup), y esa rutina deshace la pila artificial, restaura los registros no volátiles del llamante y devuelve el control a la dirección original. Para un recorredor de pila (stack walker), la llamada nace en la cadena estándar de arranque de hilo gadget → BaseThreadInitThunk+0x14 → RtlUserThreadStart+0x21 → 0 y la memoria del agente nunca aparece.

El motor "que internamente se llama Draugr" se reescribió con la disciplina del agente (PIC, sin runtime de C ni imports, todo el estado en una única estructura en pila) y se expuso como perfil de compilación (spoof_profile: off / thread / worker / custom), con degradación segura: si el motor no inicializa, cada llamada cae de forma transparente a una llamada directa. Los call sites cubiertos son el transporte winhttp, los syscalls de inyección, CreateProcessW, CreateThread, los comandos de tokens y los delays de respaldo. Además, el motor cosecha la tabla de syscalls del agente y permite ejecutar un Nt* como syscall indirecto sobre la pila fabricada: se preparan r10/rax y se salta al trampolín syscall; ret de ntdll, de modo que el retorno del syscall cae dentro de ntdll mientras la cadena de llamadas está suplantada. Las dos técnicas no compiten: el return-address check y la inspección de la cadena de llamadas son observables distintos.

Cómo funciona por dentro

Un frame plantado no solo tiene que parecer legítimo; tiene que ser válido para el propio desenrollado. Por eso, por cada dirección de retorno plantada, el motor parsea el UNWIND_INFO de esa función y rellena exactamente los slots de registros guardados que el unwinder va a leer con los valores reales del llamante. Los prólogos que no se pueden reproducir fielmente (SET_FPREG, PUSH_MACHFRAME, unwind info encadenada) se rechazan: ese frame no entra en el pool.

Cada candidato gadget o frame se valida además en la inicialización contra el RtlVirtualUnwind real, ejecutándolo sobre una imagen sintética con centinelas: debe caer exactamente en el centinela de retorno (acuerdo de extensión) y restaurar cada registro desde su slot exacto (acuerdo de colocación). En la práctica, de 17 candidatos quedaron 3. Los gadgets se filtran por unwind utilizable, tamaño de prólogo suficiente para alojar los argumentos de pila reubicados y la alineación de rsp que exige el ABI x64, y se eligen al azar en cada llamada. En cuanto a los perfiles: el de thread suplanta la raíz estándar del hilo (consistente con su origen real, algo que los EDRs cruzan); el de worker no es resoluble en Windows 11 26100 y falla de forma segura; el custom permite RVAs definidos por el operador. Los offsets de retorno que el PoC traía hardcodeados se validan y ajustan contra la versión de Windows en ejecución.

Un gráfico sencillo para entender esto mismo sería este ejemplo de dtsec:

Diagrama de call stack spoofing por dtsec

Cómo fue la primera implementación

Al activarlo por primera vez, la primera llamada del transporte murió: winhttp lanza excepciones internas benignas, el unwinder recorrió los frames falsos y "restauró" registros callee-saved desde slots fabricados, dejando un RBP corrupto que acabó en un movaps [rbp-9] y un fail-fast 0xC0000409. De ahí salió la regla central del motor: cada slot que el unwinder lee debe contener el valor real del llamante, no basura.

Después apareció una familia de crashes deterministas que ocurrían después de que una llamada spoofed retornara correctamente. La causa era el contrato más frágil del diseño: la rutina en ensamblador (stub) direcciona la estructura de parámetros por offsets crudos, y al extenderla el ensamblador los calculaba para 16 frames mientras el C declaraba 10. El desajuste de 0x60 bytes hacía que el stub se saliera de la estructura buscando el terminador de su tabla y escribiera offsets arbitrarios sobre los frames del llamante. Se corrigió con un cambio de una línea más verificación de offsets, y el contrato C ↔ ensamblador quedó como solo aditivo (append-only) estricto.

Con el layout corregido llegó la primera pasada completa: self-tests en verde, fixup verified en cada llamada, WinHttpOpen y WinHttpReceiveResponse devolviendo handles reales, check-in, tasking, captura de pantalla y nueve ciclos de Zilean intercalados sin un solo fallo. Sobre esa base ya se podía intentar lo siguiente: unir el spoofing con el cifrado en memoria.

Introducir este concepto me lo recomendó el propio 5pider (creador de Havoc) y la verdad que ha sido una buena idea.

Recomendación de 5pider

3. El port de Moonwalk++

Bueno, lo anterior fue la base de lo que viene ahora: Moonwalk++ combina el stack spoofing con el cifrado de la propia imagen en memoria durante la llamada. Y creo que merece la pena insistir en algo: no son dos mecanismos pegados, sino la misma mierda haciendo dos trabajos. El mismo circuito que fabrica la cadena de llamadas los gadgets desync y los pivotes sobre la pila sintética es el que ejecuta los tramos de cifrado y descifrado. Se construye una vez y sirve para las dos capas.

La referencia es el PoC de klezVirus (Moonwalk++), en la línea de SilentMoonWalk (los propios klezVirus, trickster0 y waldo-irc) y del spoofing de direcciones de retorno. En Bahamut se añadió un segundo motor seleccionable (spoof_profile=moonwalk) que convive con Draugr: si cualquier parte del motor Moonwalk no inicializa, cada llamada cae al motor anterior o a una llamada directa de forma transparente.

Arquitectura del circuito Moonwalk++

Qué ocurre en una llamada?

Moonwalk planta una cadena distinta a la de Draugr. El frame más interno se toma de la imagen del proceso anfitrión (una función con prólogo SET_FPREG, localizada a partir del ImageBaseAddress del PEB más un RVA) y el siguiente frame se toma de wininet (un prólogo que empuja RBP). El gadget desync no es un jmp [rbx] cualquiera: se exige que esté precedido por un CALL real (lo que el proyecto original llama gadget eclipse), y el offset de retorno dentro de la función se randomiza en cada llamada. En medio, dos pivotes: uno pequeño, que es la dirección de retorno que verá el target, y uno grande, que salta por encima de la banda oculta para caer en el tramo de vuelta.

Con el circuito de auto-cifrado la secuencia completa es: el tramo de ida marca la imagen como RWX, la cifra con RC4 y la deja NOACCESS; el target se ejecuta mientras su pila es completamente sintética y su propio código es ilegible; al retornar, el pivote grande salta a la banda oculta, donde el tramo de vuelta la pone otra vez RWX, la descifra, restaura la protección original y el fixup recompone registros y rsp y devuelve el control al agente. Mientras tanto, un stack walker ve pivot → desync → frame del host → frame de wininet → BaseThreadInitThunk → RtlUserThreadStart → 0. Nunca aparece la memoria del agente, y durante todo el circuito la imagen es, además, ilegible.

Las adaptaciones

El PoC original cifra el blob completo. Bahamut no puede hacerlo: sus argumentos (URLs, cabeceras) viven dentro de la imagen, en .rdata. Por eso el circuito cifra solo la región de código y deja la cola de datos legible (el equivalente funcional de que el original mantenga sus argumentos fuera del blob). El límite no se fija a mano: hay un marcador autocontenido plantado en la imagen que el motor localiza en el arranque y del que deriva la frontera, con un margen de una página para que la página del propio marcador (y el gadget que lleva pegado) queden siempre fuera del rango cifrado.

El cifrado es byte a byte el del original. SystemFunction032 (RC4) con la clave estática de 8 bytes del PoC y la misma escalera de transiciones RWX → cifrar → NOACCESS a la ida, RWX → descifrar → RX a la vuelta. La única diferencia observable está en la restauración: el original fija RX; aquí se guarda la protección real de la imagen en el arranque y se restaura esa. En la práctica son idénticas, porque el cargador la deja en RX, pero es más correcto preservar lo que había.

Y luego está la adaptación imprescindible para un agente real: el PoC ignora el valor de retorno del target, pero el transporte de Bahamut vive de handles. Como las llamadas del tramo de vuelta (el VirtualProtect y el SystemFunction032 del descifrado) sobrescriben rax, el circuito recorre una secuencia de slots de retorno antes de esas llamadas: cada slot ejecuta un gadget que guarda el rax del target en un registro no volátil, y el fixup lo lee de vuelta. Sin eso, WinHttpOpen devolvía 1 (el BOOL del VirtualProtect) en lugar del handle de sesión.

Un grafico que e e planteado para entederlo mejor seria este (la verdad me agustado esta skill de diseño de graficos. creo que la usare mas a menudo):

Diagrama de flujo del circuito Moonwalk++ en Bahamut

Garantías y guardas

Las disciplinas de Draugr se aplican a las nuevas clases de gadget: cada candidato se valida contra el RtlVirtualUnwind real antes de entrar en su pool, y los frames se dimensionan y se rellenan con el mismo criterio unwind-safe. El circuito se niega a armarse si hay hilos de trabajo activos (los mismos que vigila Zilean) o si el motor no está operativo; en cualquiera de esos casos la llamada degrada a pure-spoof, y si el motor Moonwalk no inicializa, al motor Draugr. Después de cada llamada, los verificadores comprueban que el fixup corrió, que rsp no derivó, que el frame del llamante no se corrompió y que la imagen no quedó cifrada por el camino.

Cómo fue la implementación

La primera ejecución no llegó ni a la fase de circuito: la inicialización del motor moría a mitad de la construcción de sus pools, con un fallo que WinDbg atribuía a chakra simplemente porque el payload estaba incrustado dentro de su rango de direcciones. Eso es un fallo que aprendi a leer con calma: cuando el shellcode pisa el .text de un módulo, el depurador etiqueta las direcciones con el export más cercano de ese módulo, y la atribución no significa nada. La pista real eran los bytes de la config del agente apareciendo en la pila del hilo que moría: un error de índices en la búsqueda de frames. Corregido, el motor ya inicializaba y el circuito pudo empezar a depurarse.

Y ahí empezó la parte larga. El circuito se validó por rondas, y cada ronda destapó una capa:

  • La frontera código/datos se resolvió primero con un símbolo del enlazador, y descubrimos que eso no funciona en una imagen PIC volcada: las direcciones de datos externos se resuelven a través de celdas que nunca se rebasan en el dump. El marcador tenía que ser dato dentro de la imagen, encontrado en tiempo de ejecución.
  • La longitud que recibe SystemFunction032 tiene que coincidir exactamente con el rango marcado como NOACCESS; con la longitud completa, el bucle de RC4 se salía del rango y fallaba dentro del tramo de cifrado.
  • El pivote grande debe saltar por encima de todos los frames plantados; con una elección más corta aterrizaba dentro de un frame y la cadena moría.
  • La paridad de rsp importa: las llamadas del circuito son ABI normales y exigen alineación a 16 bytes; una secuencia de slots de retorno se ajustó hasta absorberla exactamente.
  • El fixup tenía un relleno muerto de 0x30 donde hacían falta 0x28, y el retorno aterrizaba un slot más arriba del previsto.
  • RIP=0 persistente al volver: el stub nunca guardaba la dirección de retorno original, así que el fixup saltaba a cero. Se añadió el almacenamiento, con un centinela que delata la regresión si vuelve a perderse.
  • El valor de retorno: la primera llamada que completó el circuito devolvió 1 en vez del handle (VirtualProtect cortando rax), y de ahí salió el gadget que guarda el valor de retorno.
  • Y la causa raíz del final: el marcador estaba colocado después de la frontera natural de la imagen. La longitud que el agente calcula para su imagen es solo código y termina justo antes de la zona de datos, así que cualquier marcador posterior queda fuera del rango que el motor escanea. La primera conclusión ("la toolchain del contenedor se lo come") era falsa: el marcador estaba presente, simplemente nunca era alcanzable. Se movió antes de esa frontera y el circuito encajó.

Resultado

La primeras pruebas completas quedaron así: marcador resuelto en el arranque, todas las llamadas del transporte en modo cifrado, handles reales devueltos en cada llamada (fixup verified), check-in completado, comandos despachados y ciclos de Zilean conviviendo sin fallos. Y la prueba visual es la que mejor lo resume: en el breakpoint del target, el depurador no puede leer la imagen (NOACCESS puro), la pila desenrolla limpia con frames del sistema, y al retornar el circuito todo vuelve a estar legible.

Dos capas, un solo circuito.

Resultado: imagen cifrada en depurador

Resultado: pila desenrollada con frames del sistema

Resultado: imagen descifrada tras retorno

4. Zilean

Mientras un agente duerme, su imagen sigue siendo perfectamente legible. Una sleep mask rompe esa evidencia: Zilean (port de la técnica del agente Demon de Havoc de 5pider) reemplaza el ciclo de sueño completo. El agente pone su imagen en RW, la cifra con RC4, duerme con ella cifrada, la descifra, restaura la protección original y vuelve. Pertenece a la familia de Ekko y Foliage (cadenas ROP que ejecutan el ciclo desde fuera de la imagen); la diferencia es el mecanismo: Zilean usa el hilo de espera del pool del sistema.

Cómo funciona

La cadena no se ejecuta en el hilo del agente. Se registra con RtlRegisterWait sobre un evento que nunca se señaliza: al no dispararse, cada paso vence por temporizador, lo que convierte la espera en un planificador ordenado. Todos los pasos se fuerzan al hilo de espera del sistema y a una sola ejecución, así que la cadena entera se serializa sobre un único hilo (la razón de que una sola pila capturada sirva para los trece pasos).

Los dos primeros callbacks son funciones de sistema (capturar el contexto del hilo de espera y señalizar el handshake); ningún código del agente actúa como callback directo, y la cadena sigue siendo válida bajo CFG/CIG. Cada paso es una copia del contexto capturado con un único cambio: se retrocede Rsp un slot, que es exactamente la dirección de retorno del bucle de espera de ntdll, de modo que cada paso vuelve limpio al despachador del sistema. La ejecución es un NtContinue por paso; opcionalmente el Rip apunta a un gadget de salto en ntdll con la API real en un registro, evitando referencias directas de memoria a la API.

Con stack spoofing la cadena son trece pasos: aparcar el hilo de espera, poner RW, cifrar, spoofear el hilo principal, dormir, despoofear, descifrar, restaurar la protección y señalar el final. La clave RC4 es de 16 bytes y fresca en cada ciclo, y todo el estado vive en la pila del hilo principal (fuera de la región cifrada) y se borra antes de volver.

El disparo es una única llamada atómica: libera el paso cero y aparca al hilo principal dentro de ntdll. Esa atomicidad garantiza que nunca se ejecuta código del agente mientras la imagen es ciphertext. Mientras duerme, además, el hilo principal queda despojado de identidad: su TIB y su contexto visible se sustituyen por los del hilo de espera, con un RIP plausible de código de sistema, usando un handle duplicado del propio hilo; al despertar se restaura.

Diagrama de la cadena ROP de Zilean

Guardas y recuperación

La máscara se niega a dormir si hay hilos de trabajo activos (ejecutarían ciphertext) y cae al ciclo XOR si no está inicializada. Y porque un error aquí significa ejecutar código cifrado, la recuperación es deliberadamente paranoica: cualquier cadena armada se libera y se drena con esperas acotadas, una comprobación sobre los primeros bytes decide si ya se descifró, en última instancia se fuerza el descifrado directo, se restaura la protección capturada y se deshace el spoofing. Nunca se vuelve a la imagen sin garantías.

Cómo fue la implementación

El port a Bahamut tuvo que adaptarse al modelo PIC sin runtime de C, con todo el estado en una única estructura y x64 como único objetivo. Las dos decisiones que lo definieron fueron el mecanismo: convertir el hilo de espera del pool en planificador y pila compartida, sacando además la ejecución del hilo del agente, y los callbacks de sistema, que evitan depender de código del agente durante la ventana más delicada. La validación se hizo con la traza que imprime cada ciclo: captura del contexto, número de pasos con estado del bypass y del spoofing, y finalización con la protección original. Con ella se verificaron más de quince ciclos consecutivos sin fallos, intercalados con el transporte spoofeado y, más tarde, con el circuito cifrado de Moonwalk++ (con el que convive: comparten SystemFunction032 pero con claves y rangos distintos, y nunca se solapan).

Resultado

Dormido, el agente es invisible a los escáneres de memoria: la imagen es ciphertext, el hilo aparcado parece un hilo más del pool y nada del agente está en ejecución. Al despertar, todo vuelve a estar en claro con su protección original. Sin fallos y transparente para el resto del agente.

¿Por qué Zilean y no otro?

Lo primero, a gustos, colores, ¿no? A mí me gusta la arquitectura de Zilean; me gusta que sea un Ekko, pero sin la mierda de Ekko. Que tenga RtlRegisterWait en vez de RtlCreateTimer me gusta más, consigue mejores resultados. Pero no es inevitable; de hecho, yo mismo me he hecho una pequeña herramienta que detecta mi Zilean.

Pero tampoco quería meter algo tipo foliage o Cronos; el tema de las cadenas APC me parece un lío; sí que he visto que para entornos demasiado concretos funciona muy bien. Pero en eso, en entornos demasiado concretos.

Y después encontré Shelter; este sí me llamó la atención con su diseño basado en ROP, AES-128, capacidad de cifrar el PE completo, retirar permiso de ejecución durante la espera y stack spoofing mediante unwinder. Pero está hecho en Rust, lo cual al principio me resultó interesante en el sentido de juntar C y Rust. Después leí sobre el tema y cómo se podría hacer y dije que no, excesivo lío para un agente. Pero me lo guardo para un futuro :)

5. Conclusiones

Bueno, yo me lo estoy pasando bien haciendo el agente e investigando más en detalle sobre cada técnica. Ya todas las conocía, pero nunca me había metido en tanta profundidad técnica, lo cual me está gustando. Al final quiero seguir añadiendo cosas y crear mi propio agente privado para mis entornos; no quiero depender de nadie, exceptuando de mí y mi IA, jajajajajaj. Sí que es cierto que no queda mucho para la rooted, pero yo creo que para entonces tendré algo digno de presentar.

También quiero hablar sobre la participación de la IA en este proyecto; la verdad es que ha sido de muchísima ayuda. Fácilmente, todo lo que llevo implementado yo solo habría tardado meses y, sin embargo, con ayuda de, además de modelos flash, en cuestión de dos meses he conseguido un avance impresionante. No voy a repetirme otra vez en mi opinión sobre el uso de la IA (la dije en el apartado de talks y en mi blog de el loader de Rust), pero yo estoy a favor.

Por si da curiosidad, los modelos que he usado han sido estos dos principalmente:

Modelos de IA utilizados en el desarrollo

Por lo demás, seguiré informando por X (Twitter) y Bluesky de nuevos añadidos y, cuando tenga suficientes cosas nuevas, haré un segundo blog (yo estimo que para diciembre).

Por lo demás, Como siempre digo, cualquier crítica o consulta es bien recibida siempre que sea con respeto.

Ref:

[1] A. Chester, "Disposable Tooling: Building LLM-Generated Mythic Agents from Prompt to Deployment," SpecterOps. Accessed. [Online]. Available: https://specterops.io/blog/2026/06/24/disposable-tooling-building-llm-generated-mythic-agents-from-prompt-to-deployment/

[2] "GitHub - Whispergate/Starburst: PIC shellcode agent based on Stardust framework and Crystal Palace," GitHub. Accessed. [Online]. Available: https://github.com/Whispergate/Starburst

[3] D. Tran, "An Introduction into Stack Spoofing," Nigerald. Accessed. [Online]. Available: https://dtsec.us/2023-09-15-StackSpoofin/

[4] "GitHub - susMdT/LoudSunRun: Stack Spoofing with Synthetic frames based on the work of namazso, SilentMoonWalk, and VulcanRaven," GitHub. Accessed. [Online]. Available: https://github.com/susMdT/LoudSunRun

[5] "SilentMoonwalk: Implementing a dynamic Call Stack Spoofer," klezVirus. Accessed [Online]. Available: https://klezvirus.github.io/posts/Stackmoonwalk/

[6] "Malware Just Got Its Free Passes Back!," klezVirus. Accessed. [Online]. Available: https://klezvirus.github.io/posts/Moonwalk-plus-plus/

[7] "GitHub - klezVirus/Moonwalk--: Moonwalk++: Simple POC Combining StackMoonwalking and Memory Encryption," GitHub. Accessed. [Online]. Available: https://github.com/klezVirus/Moonwalk--

[8] "Havoc/payloads/Demon/src/core/Obf.c at main · HavocFramework/Havoc," GitHub. Accessed. [Online]. Available: https://github.com/HavocFramework/Havoc/blob/main/payloads/Demon/src/core/Obf.c

[9] "OpenCode Go | Low cost coding models for everyone." Accessed. [Online]. Available: https://opencode.ai/go

[10] "Mythic 4.0 - Mythic," Mythic. Accessed. [Online]. Available: https://docs.mythic-c2.net/version-4.0/home

← back to blog