Skip to content
KitploitKITPLOIT
HerramientasBlog
Enviar
HerramientasBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

··Feeds·Contacto·Privacidad·© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
ShellGhost — Una técnica de evasión basada en memoria que hace que el shellcode sea invisible desde el inicio hasta el final del proceso. | Kitploit
Herramientas/GitHubGitHub/lem0nsec/shellghost
Herramientas de Cifrado/DescifradoForensia de MemoriaExplotaciónShellcodeAnálisis de BinariosRed TeamingDesarrollo de Payloads
GitHublem0nsec/shellghost

ShellGhost

Una técnica de evasión basada en memoria que hace que el shellcode sea invisible desde el inicio hasta el final del proceso.

Ver Repositorio
1.2k14050hace 2 añosRevisado por Kitploit

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir

ShellGhost

Una técnica de evasión basada en memoria que hace que el shellcode sea invisible desde el inicio hasta el final del proceso.


Motivación

Quería compartir esta POC de autoinyección de shellcode para mostrar algunos conceptos de evasión de AV/EDR que pueden ser útiles para Red Teaming. Hace apenas unas semanas se me ocurrió una técnica personalizada de evasión en memoria a la que llamé ShellGhost. Esta técnica surge de la necesidad de tener un código que ejecute un shellcode 'invisible' desde el inicio hasta el final del proceso.


Manejo del flujo de ejecución del hilo

ShellGhost se basa en el manejo de excepciones vectorizadas (VEH) en combinación con breakpoints de software para detener cíclicamente la ejecución del hilo, reemplazar el breakpoint ejecutado con una instrucción de shellcode cifrada con RC4, descifrar la instrucción y reanudar la ejecución después de restaurar la protección de memoria a RX. Cuando se genera la siguiente EXCEPTION_BREAKPOINT, el manejador de excepciones reemplaza la instrucción de shellcode anterior con un nuevo breakpoint, de modo que la asignación nunca revelará el shellcode completo en un estado no cifrado. Esto ocurre dentro de una página de memoria privada que inicialmente está marcada como LECTURA/ESCRITURA. Tener una asignación PRV RW no se considerará un 'Indicador de Compromiso' por parte de escáneres de memoria como PE-Sieve y Moneta. Cuando la asignación se vuelve RX y se escanea la página, no se encontrará nada más que breakpoints. Esto sucede mientras el shellcode está realmente en ejecución. La siguiente imagen muestra que un reverse shell está en ejecución, pero Moneta no encuentra ningún IOC (aparte de que el binario no está firmado).

El intento de escanear el proceso con Pe-Sieve tiene un resultado aún mejor:


Mapeo de shellcode

El mapeo de shellcode es la funcionalidad principal de ShellGhost. Esta táctica permite que el hilo ejecute instrucciones de forma intermitente sin exponer nunca el shellcode completo en memoria. Esto es posible porque la posición de cada instrucción individual del shellcode que ejecuta el hilo corresponde a la posición de un determinado breakpoint dentro de la página de memoria asignada. ShellGhost resuelve esta posición calculando la dirección virtual relativa (RVA) desde el RIP del hilo hasta la dirección base de la página de memoria asignada y la suma a la dirección base del shellcode cifrado / instrucciones cifradas. La cantidad de breakpoints que se reemplazarán no siempre es la misma, sino que varía según la cantidad de opcodes que cada instrucción necesita para generarse e interpretarse correctamente (CUOTA). Entonces, por ejemplo, la instrucción 'POP RBP' es igual a '5D', lo que significa que solo se reemplazará un breakpoint. Por el contrario, la instrucción 'JMP RAX' requiere los opcodes 'FF E0', por lo que se reemplazarán dos breakpoints. Por esta razón, creé la siguiente estructura de datos en C.

root@kitploit:~
typedef struct CRYPT_BYTES_QUOTA {

	DWORD RVA;		// offset a la instrucción cifrada
	DWORD quota;	// número de opcodes que generan la instrucción

} CRYPT_BYTES_QUOTA, * PCRYPT_BYTES_QUOTA;

Los breakpoints no se reemplazan inmediatamente por sus instrucciones correspondientes. Esto se debe a que las instrucciones deben pasar por una rutina de descifrado antes de ejecutarse. Aquí es donde entra en juego el DWORD quota. ShellGhost se basa en la ahora popular 'SystemFunction032' para realizar el descifrado RC4. A diferencia de XOR, RC4 no es un esquema de cifrado de un solo byte. Esto significa que el shellcode no se puede cifrar y descifrar de una sola vez. Esta es también otra razón por la que cada instrucción se trata por separado. Después de reemplazar los breakpoints, la longitud del búfer que necesita SystemFunction032 será igual a la 'cuota de instrucción', que nuevamente representa el número de opcodes que componen la instrucción específica. Así que, por ejemplo, considere el siguiente fragmento.

root@kitploit:~

CRYPT_BYTES_QUOTA instruccion[200];
instruccion[5].quota = 2

USTRING buf = { 0 }; 	// contendrá el búfer a descifrar y su longitud
USTRING key = { 0 }; 	// contendrá la clave RC4 y su longitud

buf.Length = 2 		// longitud del búfer, o longitud de la instrucción a descifrar

Sabemos que la instrucción número 5 del shellcode se compone de 2 opcodes, por lo que se pasará una longitud de búfer de 2 a SystemFunction032. Esto es importante porque intentar descifrar todo el shellcode con una sola llamada a SystemFunction032 lo corromperá por completo.

¿Cómo se realiza el mapeo del shellcode?

El shellcode debe mapearse con ShellGhost_mapping.py antes de la compilación. El script extrae cada instrucción individual y la trata como un pequeño shellcode independiente. Las instrucciones se cifran una por una y se imprimen en formato C todas juntas como unsigned char. El resultado se puede codificar directamente en el código C. A continuación se muestra un ejemplo de cómo se ven las instrucciones de shellcode cifradas de MSF para calc.exe.

Este shellcode tiene 98 instrucciones, por lo que se declaran 98 estructuras CRYPT_BYTES_QUOTA. Cuando el código se ejecuta, estas estructuras deben poblarse con las RVA y CUOTAS de instrucciones adecuadas. El parámetro '-1' le indica al script de mapeo que imprima el fragmento de código que hace esto.

Ajuste de parámetros de Winapi

Los shellcodes x64 de Metasploit suelen tener parámetros de cadena de winapi almacenados entre instrucciones. Es decir, un shellcode x64 de MSF que llama a Winexec no empuja una serie de bytes con un byte nulo al final para tener el primer parámetro de cadena en la pila. Más bien, el registro RCX (primer parámetro) es un puntero dentro del propio shellcode, como se muestra en la siguiente imagen.

Esto significa que los breakpoints cuya posición está relacionada con la cadena nunca se resolverán, porque el RIP nunca tocará esa posición. De hecho, este código resuelve las instrucciones reales del shellcode por las que pasa el RIP, no los parámetros que nunca se ejecutarán como instrucciones. Para solucionar esto, noté que los shellcodes de MSF siempre almacenan un puntero a la winapi que están llamando dentro del registro RAX, y luego hacen un salto al propio registro. Entonces, cuando ShellGhost VEH detecta que el breakpoint resuelto es 'JMP RAX' y el registro RCX contiene un puntero a una posición dentro del shellcode, intenta también resolver lo apuntado por RCX. Posteriormente, la ejecución no regresa a la memoria asignada. En cambio, RAX (dirección de winapi) se copia en RIP y la ejecución del hilo se reanuda desde la winapi, anulando así el 'JMP RAX' y manteniendo la memoria asignada como RW. Esto es necesario para los reverse shells que llaman a WaitForSingleObject, lo que haría que el hilo durmiera después del 'JMP RAX' mientras deja la memoria como RX durante el tiempo que el shell siga vivo. El siguiente fragmento de código contiene las dos condiciones que deben cumplirse para que ShellGhost ajuste el registro RCX cuando contiene una cadena de parámetros de winapi y permita que el shellcode de MSF emita correctamente la llamada a la función (WinExec en el ejemplo).

root@kitploit:~
<snip>
	
if (*(PWORD)exceptionData->ContextRecord->Rip == 0xe0ff) // si RIP es 'JMP RAX'

<snip>

if ((contextRecord->Rcx >= (DWORD_PTR)allocation_base) && (contextRecord->Rcx <= ((DWORD_PTR)allocation_base + sizeof(sh)))) // si RCX está dentro de la asignación

<snip>

RDX, R8 y R9 (segundo, tercer y cuarto parámetros) aún no están cubiertos.

Diferencias y similitudes con otras técnicas

ShellcodeFluctuation es un concepto de evasión en memoria muy similar. Al igual que él, la memoria asignada aquí 'fluctúa' de RW a RX. En contraste, ShellGhost introduce las siguientes mejoras:

  • Cifrado RC4 más 'Mapeo de shellcode' en lugar de XOR de un solo byte
  • No es necesario enganchar funciones
  • Soporte para shellcodes de Metasploit

Sin embargo, ShellGhost está lejos de ser una técnica perfecta. Todavía sufre del mayor inconveniente que tienen todas estas técnicas, a saber, la necesidad de tener memoria ejecutable privada en algún momento durante la ejecución. Técnicas más avanzadas como Foliage ya encontraron una manera de evitarlo. Además, una asignación de memoria llena de breakpoints de software puede ser detectada por una regla YARA. La siguiente imagen muestra a Moneta detectando correctamente un IOC para la asignación PRV RX.

Cuando se trata de evadir una solución EDR, el escaneo de memoria es solo una parte de un panorama más amplio. La ausencia total de IOCs no significa necesariamente que un binario que utiliza esta técnica resulte efectivo contra un EDR determinado. Por lo que puedo decir, he experimentado situaciones en las que la solución ni siquiera permite lanzar el binario de la forma en que lo estás haciendo. La otra cara de la moneda es que los IOCs no siempre son indicadores precisos, y algunos pueden resultar ser falsos positivos. Dicho esto, esto es solo una técnica cruda y una inspiración que espero que el lector aprecie. El Red Teamer sabe que, al igual que los componentes de un EDR, la evasión en memoria es solo un componente del motor.

Notas

La compilación requiere deshabilitar el enlace incremental. Este proyecto de VS ya tiene todas las opciones de compilador/enlazador configuradas.

Descargar herramienta