
PoC para frustrar/derrotar a los analistas de malware
El trabajo que sigue es un POC para permitir que el malware se "vincule" a una víctima particular con el fin de frustrar los esfuerzos de los analistas de malware.
No asumo ninguna responsabilidad por el uso malicioso de cualquier idea o código contenido en este proyecto. Proporciono esta investigación para seguir educando a los profesionales de la seguridad informática y ofrecer formación adicional / material de reflexión para analistas de malware, ingenieros inversos y el equipo azul en general.
La primera vez que el malware se ejecuta en una víctima, cifra con AES la carga útil real (un RDLL) utilizando datos ambientales de esa víctima. Cada vez subsiguiente que se ejecuta el malware, recopila la misma información ambiental, descifra con AES la carga útil almacenada como un arreglo de bytes dentro del malware y la ejecuta. Si falla el descifrado / la carga útil no se ejecuta, el malware se elimina a sí mismo. Protección contra ingenieros inversos y analistas de malware.


No sentí que hubiera terminado con este proyecto, así que volví e hice una reescritura bastante sustancial. La investigación original y las técnicas se pueden encontrar aquí.
Los cambios principales son los siguientes:
Hay bastantes cosas diferentes que se pueden tomar del código fuente de este proyecto para usar en otros lugares. Espero que sea útil para alguien.
Hubo algunas deficiencias con la versión original de BeatRev que decidí intentar solucionar.
Stage2 era anteriormente un ejecutable independiente que se almacenaba como el flujo de datos alternativo (ADS) de Stage1. Para lograr el cifrado AES por víctima y el posterior descifrado y ejecución, cada vez que se ejecutaba Stage1, leía el ADS, lo descifraba, volvía a escribir en el ADS, llamaba a CreateProcess y luego volvía a cifrar Stage2 y lo escribía en el disco en el ADS. Esto era una gran cantidad de operaciones de E/S y, por supuesto, la llamada a CreateProcess no era ideal.
Me topé con la investigación de Steven Fewer sobre las DLL reflectantes y parecía encajar bien. Stage2 ahora es un RDLL; nuestro malware / ejecutor de shellcode / lo que sea que queramos proteger se puede portar al formato RDLL y almacenar como un arreglo de bytes dentro de Stage1 que luego se descifra en tiempo de ejecución y Stage1 lo ejecuta. Esto elimina todas las operaciones de E/S y la llamada a CreateProcess de la Versión 1 y es un cambio bienvenido.
Stage1 no tenía ningún tipo de medidas reales de evasión de AV programadas; esto fue intencional, ya que es trabajo extra y no era realmente el objetivo de esta investigación. Durante la reescritura, lo tomé como un desafío adicional y agregué hashing de API para eliminar funciones de la Tabla de Direcciones de Importación de Stage1. Esto ha ayudado con la detección y Stage1 tiene una tasa de detección de 4/66 en VirusTotal. Me sentí cómodo subiendo Stage1 dado que ya está vinculado a la máquina original en la que se ejecutó y la firma del archivo cambia constantemente debido al cifrado AES que ocurre.
Recientemente comencé a prestar atención a la entropía como medio para detectar malware; para intentar reducir la entropía, que de otra manera es muy alta, que un blob binario enorme cifrado con AES le da a un ejecutable, investigué la integración de shellcode almacenado como UUID. Debido a que el binario se almacena en representación de cadena, hay una entropía general más baja en el ejecutable. Usando esta técnica, la entropía de Stage0 ahora es ~6.8 y la de Stage1 ~4.5 (en una escala máxima de 8).
Finalmente, es una tarea enorme integrar y producir un Stage0 completo debido a todas las piezas que deben manipularse. Para facilitar esto, hice una aplicación constructora que ingiere un archivo de plantilla Stage0.c, un stub de Stage1, un stub de Stage2 y un archivo de shellcode sin procesar (esto se construyó pensando en que Stage2 es un ejecutor de shellcode que contiene shellcode de CobaltStrike) y produce un payload Stage0 compilado para usar en el objetivo.
El código de DLL reflectante de Stephen Fewer contiene algunas instrucciones específicas del compilador de Visual Studio; estoy seguro de que es posible portar la técnica a MingW, pero no tengo las habilidades para hacerlo. El problema principal aquí es que el shellcode de CobaltStrike (sin estado es ~265K) debe ir dentro del RDLL y ser compilado. Para solucionar esto e integrarlo bien con el resto del proceso, escribí mi RDLL Stage2 para que contenga un bloque de memoria de variable global del tamaño del shellcode de CS; este bloque de memoria de ~265K tiene un marcador de posición pequeño que se puede localizar en el binario compilado. El código en src/Stage2 ya tiene esto agregado.
Una vez compilado, este Stage2stub se transfiere a Kali donde se puede realizar un parche binario para colocar el shellcode real de CS en el lugar de la memoria que le corresponde. Esto produce el Stage2 completo.
Para evitar el desastre de E/S y CreateProcess descrito anteriormente, Stage0 también debe parchear el Stage2 completo en el Stage1 compilado; esto es necesario para permitir que Stage2 se cifre una vez en el objetivo, además de evitar que Stage2 se almacene por separado en el disco. El mismo concepto descrito anteriormente para Stage2 es realizado por Stage0 en el objetivo para ensamblar el payload final de Stage1. Cabe señalar que se usa la función memmem para localizar el marcador de posición dentro de cada stub; esta función no está disponible en Windows, por lo que se utilizó una implementación personalizada. Gracias a Foxik384 por su código.
Para realizar un parche binario, debemos asignar la memoria necesaria de antemano; esto tiene un efecto compuesto, ya que Stage1 ahora debe ser lo suficientemente grande como para contener también a Stage2. Con el paso adicional de convertir Stage2 a una cadena UUID, Stage2 se infla en tamaño, al igual que Stage1 para contenerlo. Un RDLL de Stage2 con un tamaño compilado de ~290K resulta en un payload de Stage0 de ~1.38M y un payload de Stage1 de ~700K.
La aplicación constructora solo admite la creación de EXE x64. Sin embargo, con un poco más de trabajo, en teoría se podría hacer que Stage0 sea una DLL, así como Stage1, y que todo el ciclo de vida exista como un secuestro de DLL en lugar de un ejecutable independiente.
Estas instrucciones te pondrán en camino para usar este POC.