
Una implementación de PoC de una técnica de evasión para terminar el hilo actual y restaurarlo antes de reanudar la ejecución, mientras se implementan cambios de protección de páginas durante la no ejecución.
██████╗ ███████╗ █████╗ ████████╗██╗ ██╗███████╗██╗ ███████╗███████╗██████╗
██╔══██╗██╔════╝██╔══██╗╚══██╔══╝██║ ██║██╔════╝██║ ██╔════╝██╔════╝██╔══██╗
██║ ██║█████╗ ███████║ ██║ ███████║███████╗██║ █████╗ █████╗ ██████╔╝
██║ ██║██╔══╝ ██╔══██║ ██║ ██╔══██║╚════██║██║ ██╔══╝ ██╔══╝ ██╔═══╝
██████╔╝███████╗██║ ██║ ██║ ██║ ██║███████║███████╗███████╗███████╗██║
╚═════╝ ╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚══════╝╚══════╝╚══════╝╚══════╝╚═╝
Una implementación PoC de una técnica de evasión para terminar el hilo actual y restaurarlo antes de reanudar la ejecución, mientras se implementan cambios de protección de página durante la no ejecución.

Los métodos de suspensión y ofuscación son bien conocidos en la comunidad de maldev, con diferentes implementaciones, tienen el objetivo de ocultarse de los escáneres de memoria mientras se duerme, generalmente cambiando las protecciones de página e incluso agregando características interesantes como cifrar el shellcode, pero hay otro punto importante para ocultar nuestro shellcode, y es ocultar el hilo de ejecución actual. Spoofear la pila está bien, pero después de pensarlo un poco, pensé que no es necesario spoofear la pila… si no hay pila :)
La usabilidad de esta técnica queda a criterio del lector, pero en cualquier caso, creo que es una forma interesante de repasar algunos temas y aprender algo de maldev para aquellos que, como yo, están comenzando en este mundo.
La implementación principal mostrada aquí contiene todo lo que necesitamos para sacar de la pila en la sección de datos, como variables globales, pero pronto se publicará una implementación moviendo todo al heap. Su objetivo es mostrar algunas modificaciones clave que deben realizarse para que este código sea pic e inyectable.
Este repositorio se refleja entre GitHub y GitLab.
Todo lo expuesto aquí proviene de mi comprensión de los diferentes temas tratados, ya sea por lectura o experiencia durante el desarrollo. Soy consciente de que no soy un experto y lo último que quiero es difundir información errónea, así que si crees que algo no es correcto, me encantaría que me lo hicieras saber, puedes contactarme en twitter, o abriendo issues en este repositorio. Muchas gracias por tu comprensión. :)
El objetivo principal de esta técnica es claro, terminar el hilo actual y restaurarlo antes de reanudar la ejecución, pero ¿qué significa exactamente esto y qué nuevas restricciones impone?
Para poder restaurar la ejecución, necesitamos guardar dos cosas antes de terminar el hilo: primero, el estado de la CPU, y segundo, la pila, y configurarlos nuevamente después de que se lance el nuevo hilo.
Hablé sobre nuevas restricciones que aparecerán en esta técnica, y hay dos grandes: Primero, necesitamos almacenar fuera de la pila cualquier cosa que se necesite desde el momento en que el hilo termina hasta que se restaura la pila, y como verás, crea algunos nuevos desafíos.
Segundo, siempre necesitamos al menos otro hilo ejecutándose en nuestro proceso, ya que estamos terminando nuestro hilo, si no hay otros hilos, el proceso finalizará. No creo que esto sea un gran problema, ya que la mayoría de los agentes se inyectan en otros procesos, podemos suponer que ese proceso mantendrá al menos un hilo ejecutándose.
Podemos ver en este PoC 4 funciones principales:
Cuando estamos a punto de guardar la pila, surge una pregunta: ¿cuánto de la pila necesita ser guardado?
Revisemos primero qué hay en la pila después de que llamamos a la función DeathSleep (esta es la función que guarda el contexto, la pila y prepara todo para la ofuscación y restauración)

Como podemos ver, cada función tiene tres partes:
La porción mínima de la pila que obviamente necesitamos guardar es todo lo que está dentro de nuestro programa principal, eso significa su shadow space, su dirección de retorno y todo hasta la función DeathSleep. Cualquier cosa anterior no es realmente necesaria (guardar la pila utilizada por la función de entrada tiene sus ventajas, pero discutiremos eso más adelante), ya que esa es la pila utilizada por las rutinas de Windows para lanzar nuestro nuevo hilo. Aparte de esto, también decidí almacenar el shadow space de la función DeathSleep (no es realmente necesario, pero facilita el cálculo de Rsp en el momento de despertar).
Entonces, al final, estamos guardando esto:

Cada función en una compilación estándar debe estar compuesta por 3 partes: el prólogo, el código de la función y el epílogo.
Rsp (puntero de pila) solo debe modificarse en el prólogo y epílogo de la función. El prólogo incrementa el puntero de la pila (recuerda que incrementar la pila significa reducir direcciones, ya que van en direcciones opuestas), para guardar registros, para contener todas sus variables locales y luego para contener el shadow space, y el epílogo hace exactamente lo contrario.
Esto significa que el puntero de pila dentro del código de la función siempre debe apuntar al final del shadow space (púrpura en la imagen anterior), y la suma del tamaño de pila de la función y el shadow space se puede encontrar calculando cuánto incrementa el puntero de la pila el prólogo. Este valor se puede calcular fácilmente utilizando la información contenida en las tablas de desenrollado (unwind tables), una explicación sobre su uso es algo que no cubriremos aquí, pero en resumen, esas tablas se utilizan para permitir que cualquier otro hilo o proceso se mueva correctamente a través de la pila para ver su contenido, manejar excepciones o analizarla.