
Thread Stack Spoofing - PoC de una técnica avanzada de evasión en memoria que permite ocultar mejor la asignación de memoria del shellcode inyectado de los escáneres y analistas.
Una implementación PoC para una técnica avanzada de evasión en memoria que suplanta la pila de llamadas de un hilo. Esta técnica permite eludir reglas de examen de memoria basadas en hilos y ocultar mejor los shellcodes mientras están en memoria del proceso.
Esta es una implementación de ejemplo para la técnica Thread Stack Spoofing con el objetivo de evadir analistas de malware, AV y EDR que buscan referencias a los marcos del shellcode en la pila de llamadas de un hilo examinado. La idea es ocultar las referencias al shellcode en la pila de llamadas del hilo, disfrazando así las asignaciones que contienen el código del malware.
Esta implementación, junto con mi ShellcodeFluctuation, ofrece a la comunidad de Seguridad Ofensiva implementaciones de ejemplo para ponerse al día con lo que ofrecen los productos C2 comerciales, para que podamos hacerlo igual de bien en nuestras herramientas de Red Team. 💪
La implementación actual difiere mucho de lo publicado originalmente.
Esto se debe a que me di cuenta de que existe una forma mucho más simple de terminar el procesamiento de la pila de llamadas del hilo y ocultar los marcos relacionados con el shellcode simplemente escribiendo 0 en la dirección de retorno del primer marco que controlamos:
void WINAPI MySleep(DWORD _dwMilliseconds)
{
[...]
auto overwrite = (PULONG_PTR)_AddressOfReturnAddress();
const auto origReturnAddress = *overwrite;
*overwrite = 0;
[...]
*overwrite = origReturnAddress;
}
La implementación anterior, que utilizaba StackWalk64, se puede consultar en este commit c250724.
Esta implementación es mucho más estable y funciona bien tanto en Debug como en Release bajo las dos arquitecturas: x64 y x86.
Así es como se puede ver una pila de llamadas cuando NO está suplantada:

Y así, cuando la suplantación de la pila del hilo está activada:

Arriba podemos ver que el último marco en nuestra pila de llamadas es nuestro callback MySleep.
Cabe preguntarse si esto ofrece inmediatamente nuevas oportunidades de IOCs. Las reglas de caza pueden buscar hilos cuyas pilas de llamadas no se desenrollen hasta los siguientes puntos de entrada de hilo esperados que se encuentran en las bibliotecas del sistema:
kernel32!BaseThreadInitThunk+0x14
ntdll!RtlUserThreadStart+0x21
Sin embargo, la pila de llamadas del hilo suplantado puede parecer extraña al principio; un breve examen de mi sistema mostró que también hay otros hilos que no se desenrollan hasta los puntos de entrada anteriores:

La captura de pantalla anterior muestra un hilo de Total Commander x64 sin modificar. Como podemos ver, su pila de llamadas se asemeja bastante a la nuestra en términos de los marcos iniciales de la pila de llamadas.
¿Por qué deberíamos preocuparnos por falsificar cuidadosamente nuestra pila de llamadas cuando hay procesos que muestran características que simplemente podemos imitar?
El algoritmo aproximado es el siguiente:
dbghelp.dll, llamar a SymInitialize.kernel32!Sleep apuntando de vuelta a nuestro callback.VirtualAlloc + memcpy + CreateThread. El hilo debe comenzar desde nuestra función runShellcode para evitar que la StartAddress del hilo apunte a algún lugar inesperado y anómalo (como ntdll!RtlUserThreadStart+0x21).MySleep.0, lo que efectivamente debería finalizar la pila de llamadas.::SleepEx para permitir que Beacon duerma mientras espera más comunicaciones.Las direcciones de retorno de las funciones están dispersas por toda el área de memoria de la pila del hilo, apuntadas por el registro RBP/EBP.
Para encontrarlas en la pila, primero necesitamos recolectar los punteros de marco y luego desreferenciarlos para sobrescribirlos:

(la imagen anterior fue tomada del artículo de Eli Bendersky titulado Stack frame layout on x86-64)
*(PULONG_PTR)(frameAddr + sizeof(void*)) = Fake_Return_Address;
La implementación inicial de ThreadStackSpoofer hacía esto en las funciones walkCallStack y spoofCallStack, pero la implementación actual muestra que estos esfuerzos no son necesarios para mantener una pila de llamadas sigilosa.
Uso:
C:\> ThreadStackSpoofer.exe <shellcode> <spoof>
Donde:
<shellcode> es una ruta al archivo shellcode<spoof> cuando sea 1 o true habilitará la suplantación de la pila del hilo, cualquier otra cosa la deshabilita.Ejemplo de ejecución que suplanta la pila de llamadas del hilo de beacon:
PS D:\dev2\ThreadStackSpoofer> .\x64\Release\ThreadStackSpoofer.exe .\tests\beacon64.bin 1
[.] Leyendo bytes del shellcode...
[.] Enganchando kernel32!Sleep...
[.] Inyectando shellcode...
[+] El shellcode ahora se está ejecutando.
[>] Dirección de retorno original: 0x1926747bd51. Finalizando la pila de llamadas...
===> MySleep(5000)
[<] Restaurando dirección de retorno original...
[>] Dirección de retorno original: 0x1926747bd51. Finalizando la pila de llamadas...
===> MySleep(5000)
[<] Restaurando dirección de retorno original...
[>] Dirección de retorno original: 0x1926747bd51. Finalizando la pila de llamadas...
Mira el código y su implementación, comprende el concepto y vuelve a implementar el concepto dentro de tus propios cargadores de shellcode que utilizas para llevar a cabo tus compromisos de Red Team. Esta es otra técnica para la evasión avanzada en memoria que aumenta las posibilidades de tu equipo de no ser detectado por antivirus, EDR y analistas de malware que examinan tus implantes.
Al desarrollar tu cargador de shellcode avanzado, también podrías querer implementar:
BeaconEyeRW (desde RX/RWX) y cifrar su contenido - utilizando la técnica de Shellcode Fluctuation - justo antes de dormir (lo que podría evadir escáneres como Moneta o pe-sieve)