INTRODUCCIÓN
El 25 de febrero de 2022, el grupo de ciberdelincuencia Conti publicó la siguiente declaración en su blog:
La publicación fue redactada varias horas después con otra con tonos más neutrales, condenando la guerra y desafiliándose del gobierno, pero enfatizando los sentimientos contra Occidente. La publicación mantuvo sus amenazas de represalias contra la infraestructura crítica que pertenece a cualquier agresor de Rusia.
Después de eso, el 28 de febrero de 2022, probablemente uno de los miembros de Conti (o simplemente un investigador de seguridad ucraniano) publicó un primer archivo con datos valiosos internos e información perteneciente a todo el colectivo. La acción fue probablemente una consecuencia directa de una postura tan clara del grupo sobre la situación actual entre Rusia y Ucrania. Entre este material también parece haber un archivo que contiene el código fuente de su ransomware del cual informamos un análisis preliminar.
PERSPECTIVAS
ContiLocker es un ransomware desarrollado por Conti Ransomware Gang , un colectivo criminal de habla rusa con presuntos vínculos con las agencias de seguridad rusas. El proyecto está desarrollado en C++ sobre una versión de Visual Studio 2015 con el conjunto de herramientas Windows XP Nplatform ( v140_xp ). La plataforma de destino especificada es 10.0 (Windows10). La estructura del proyecto está organizada en diferentes subcarpetas, donde cada una maneja un módulo específico del ransomware (como la carpeta “locker” para las operaciones de cifrado).
Para operaciones específicas (como el mecanismo de encriptación), esto usa diferentes subprocesos simultáneos manejados por la API de Windows CreateIoCompletitionPort y diferentes colas que son manejadas por GetQueuedCompletitionStatus y PostQueuedCompletitionStatus .
La función WinMain (main.cpp) comienza con la resolución dinámica de la API LoadLibraryA a través de una inspección manual del kernel32.dll importado (una implementación manual de la API GetProcAddress ).
Después de eso, se invoca el módulo “API” para ejecutar una técnica anti-DBI/anti-sandbox con el fin de deshabilitar todos los posibles enganches en las DLL conocidas. De hecho, las siguientes DLL se cargan a través de la API LoadLibraryA recién resuelta:
- kernel32.dll
- ws2_32.dll
- advapi32.dll
- ntdll.dll
- rsstrtmgr.dll
- ole32.dll
- oleaut32.dll
- netapi32.dll
- iphlpapi.dll
- shlwapi.dll
- shell32.dll
Para cada archivo DLL cargado, CreateFileMappingW y MapViewOfFile se invocan para
acceder a la vista asignada en el espacio de direcciones del proceso de llamada.
Esta vista se utiliza para acceder manualmente al encabezado NT y al directorio de exportación interno. Del directorio de exportación se extrae cada dirección de las funciones exportadas, y se comprueban los primeros bytes de la función exportada para identificar una posible instrucción JMP / NOP / RET que identifique un hook externo.
Si la función actual está enganchada, VirtualProtect y RtlCopyMemory API se invocan para sobrescribir los primeros bytes de la función enganchada. Continuando con la ejecución de WinMain , se crea un mutex llamado ” kjsidugidf99439 ” para verificar posibles ejecuciones simultáneas de la misma carga útil. Si otro subproceso tiene la propiedad del mutex, la ejecución termina aquí.
Después de eso, los argumentos de las líneas de comando se verifican desde la API GetCommandLineW .
Este ransomware acepta los siguientes argumentos de la línea de comandos:
- -h: especifica un archivo que contiene el IPv4 de los hosts para escanear en busca de cifrado de redes/comparticiones (separados por \n\r);
- -p: especifica un archivo que contiene la ruta del sistema para buscar el cifrado de archivos (separados por \n\r);
- -m: especifica el modo de cifrado
- “todos”: encripta archivos locales y de red
- “local”: encripta solo archivos locales
- “net”: encripta solo archivos de red
- “copias de seguridad”: no implementado
- -log: si contiene el valor “habilitado”, registra las acciones/errores del ransomware en el archivo local C:\\ CONTI_LOG.txt
Luego, se invoca la API GetNativeSystemInfo para extraer la cantidad de procesadores y el módulo ” threadpool ” se usa para instanciar number_of_processors * 2 hilos (tanto para el cifrado local como de red, según los indicadores especificados).
Cada subproceso asigna su propio búfer para el próximo cifrado e inicializa su propio contexto de criptografía a través de la API CryptAcquireContextA y una clave pública RSA.
Luego, cada subproceso espera en un bucle infinito una tarea en la cola TaskList (compartida por cada subproceso y a la que accede la API EnterCriticalSection ). En caso de que haya una nueva tarea disponible, el nombre del archivo a cifrar se extrae de la tarea y, si el nombre del archivo
corresponde a ” stopmarker “, se concluye la ejecución del hilo.
En cualquier otro caso, se invoca el módulo “casillero” para cifrar el archivo actual.
La rutina de cifrado para un archivo específico comienza con una generación de clave aleatoria (utilizando la API CryptGetRandom ) de una clave de 32 bytes y otra generación aleatoria de un IV de 8 bytes .
Posteriormente, la clave aleatoria y el IV aleatorio se almacenan en una estructura FIleInfo personalizada y la clave aleatoria se cifra utilizando la clave RSA previamente decodificada.
Antes de la fase de cifrado, si se carga la DLL del administrador de reinicio ( rstrtmgr.dll ), se invocan las API RmStartSession , RmGetList y RmShutdown para terminar cada aplicación que usa este recurso específico o tiene un identificador abierto en ese
recurso.
Luego, según la extensión del archivo, el contenido del archivo se cifra total o parcialmente (cifrado al 20 % ). En particular, se invoca el método CheckForDataBases para verificar un posible cifrado completo contra las siguientes extensiones:
- .4dd, .4dl, .accdb, .accdc, .accde, .accdr, .accdt, .accft, .adb, .ade, .adf, .adp, .arc, .ora, .alf, .ask, .btr , .bdf, .cat, .cdb, .ckp, .cma, .cpd, .dacpac, .dad, .dadiagrams, .daschema, .db, .db-shm, .db-wal, .db3, .dbc, .dbf, .dbs, .dbt, .dbv, .dbx, .dcb, .dct, .dcx, .ddl, .dlis, .dp1, .dqy, .dsk, .dsn, .dtsx, .dxl, .eco , .ecx, .edb, .epim, .exb, .fcd, .fdb, .fic, .fmp, .fmp12, .fmpsl, .fol, .fp3, .fp4, .fp5, .fp7, .fpt, . frm, .gdb, .grdb, .gwi, .hdb, .his, .ib, .idb, .ihx, .itdb, .itw, .jet, .jtx, .kdb, .kexi, .kexic, .kexis, .lgc, .lwx, .maf, .maq, .mar, .mas.mav, .mdb, .mdf, .mpd, .mrg, .mud, .mwb, .myd, .ndf, .nnt, .nrmlib, .ns2, .ns3, .ns4, .nsf, .nv, .nv2, .nwdb, .nyf, .odb, .ogy, .orx, .owc, .p96, .p97, .pan, .pdb, .p dm, .pnz, .qry, .qvd, .rbf, .rctd, .rod, .rodx, .rpd, .rsd, .sas7bdat, .sbf, .scx, .sdb, .sdc, .sdf, .sis, .spg, .sql, .sqlite, .sqlite3, .sqlitedb, .te, .temx, .tmd, .tps, .trc, .trm, .udb, .udl, .usr, .v12, .vis, .vpd, .vvv, .wdb, .wmdb, .wrk, .xdb, .xld, .xmlff, .abcddb, .abs, .abx, .accdw, .adn, .db2, .fm5, .hjt, .icg, .icr, .kdb, .lut , .maw, .mdn, .mdt
De lo contrario, se invoca el método CheckForVirtualMachines para verificar un posible cifrado parcial del 20% ((file_size / 100) * 7) contra las siguientes extensiones:
- vdi, .vhd, .vmdk, .pvm, .vmem, .vmsn, .vmsd, .nvram, .vmx, .raw, .qcow2, .subvol, .bin, .vsv, .avhd, .vmrs, .vhdx, .avdx, .vmcx, .iso
En otros casos, se sigue el siguiente patrón:
- Si el tamaño del archivo es inferior a 1,04 GB: realice un cifrado completo.
- Si el tamaño del archivo está entre 1,04 GB y 5,24 GB: realice un cifrado de encabezado (cifre solo los primeros 1048576 bytes).
- De lo contrario, realice un cifrado parcial del 50 % ((file_size / 100) * 100).
Después de elegir el método de cifrado, los primeros bytes del contenido del archivo se sobrescriben (antes del cifrado) con la información sobre el modo de cifrado y la clave utilizada para el cifrado. Luego, el contenido del archivo se cifra utilizando la clave aleatoria previamente cifrada con RSA y la extensión del archivo se cambia a .EXTEN .
Ahora veamos cómo se invocan estos subprocesos desde los métodos de enumeración que regresan a la ejecución de WinMain.
En primer lugar, se utiliza una omisión de COM para eliminar las instantáneas del Instrumental de administración de Windows (WMI).
En detalles:
- El objeto COM se inicializa a través de la API CoInitializeEx .
- Los niveles de seguridad COM se cambian a través de la API CoInitializeSecurity y el parámetro cAuthSvc es igual a -1 para deshabilitar la autenticación.
- La API de CoCreateInstance se utiliza para ubicar el WMI a través del CLSID ” CLSID_WbemLocator “.
- Se accede a WMI y WQL (WMI Query Language) a través del método IWbemLocator :: ConnectServer .
- Los niveles de seguridad del proxy WMI se cambian a través de la API CoSetProxyBlanket para establecer el indicador RPC_C_AUTHZ_NONE y evitar la autenticación.
- Se invoca la consulta ” SELECT * FROM Win32_ShadowCopy ” para identificar los ID de las instantáneas y se utiliza una ejecución de línea de comandos para eliminar cada instantánea ” cmd.exe /c C:\\Windows\\System32\\wbem\\WMIC. exe shadowcopy donde \”ID=’%s’\” delete ”
Finalmente, comienza el proceso de enumeración. En primer lugar, se iteran las rutas del sistema de archivos especificadas a través del indicador -p y, para cada ruta, la nota de ransomware (R3ADM3.txt, no disponible en esta versión filtrada) se coloca en el directorio especificado. Después de eso, las API FindFirstFileW y FindNextFileW se usan para iterar dentro de cada directorio ignorando los archivos especiales (como “.” o “..”).
El malware utiliza una lista blanca tanto para directorios como para archivos para evitar el cifrado de datos innecesarios. Los siguientes nombres de directorios y archivos se evitan durante el proceso de enumeración:
- Directorios: “tmp”, “winnt”, “temp”, “thumb”, “$Recycle.Bin”, “$RECYCLE.BIN”, “Información del volumen del sistema”, “Arranque”, “Windows”, “Trend Micro”
- Archivos: “.exe”, “.dll”, “.lnk”, “.sys”, “.msi”, “R3ADM3.txt”, “CONTI_LOG.txt”
Si el archivo a encriptar es un directorio, el proceso descrito se repite recursivamente para todos los subdirectorios y subarchivos. Finalmente, el archivo para cifrar se pasa al primer subproceso disponible para el proceso de cifrado que llena la cola de la lista de tareas. La siguiente enumeración inspecciona todas las unidades lógicas del sistema infectado.
De hecho, además de las rutas especificadas por el indicador -p, la API GetLogicalDriveStringsW se usa para obtener la lista de unidades. Luego, para cada unidad lógica, se extrae la ruta raíz y se repite el proceso anterior para cada subdirectorio y subarchivo.
El último proceso de enumeración se utiliza para enumerar los recursos compartidos del sistema Windows infectado. De hecho, la API de NetShareEnum se utiliza para recuperar información sobre cada recurso compartido. Para cada recurso, si el recurso representa una unidad de disco, un recurso compartido especial (p. ej., comunicaciones $IPC, administraciones remotas ADMIN$, recursos compartidos administrativos) o un recurso compartido temporal, se extrae la ruta del recurso compartido (p. ej., \\\\$IP\\ $NOMBRE_COMPARTIDO ).
Luego, cada ruta compartida se utiliza como directorio para el proceso de cifrado de directorios y archivos descrito anteriormente.
Además de la enumeración compartida, este ransomware presenta un componente de subprocesos múltiples para buscar otras IP en las redes accesibles en busca de un cifrado de movimiento lateral destructivo. En particular, se invocan las API WSAStartup y WSAIoctl para obtener un controlador para LPFN_CONNECTEX para enlaces y conexiones de bajo nivel.
Luego, se invoca la API GetIpNetTable para recuperar la tabla ARP del sistema infectado. Para cada entrada de la tabla ARP, las direcciones IPv4 especificadas se comparan con las siguientes máscaras:
- 172.*
- 192.168.*
- 10.*
- 169.*
Si el ARP IPv4 actual respeta una de estas máscaras, la subred IP se extrae y se agrega a la cola de una subred. A partir de esta enumeración, se crean dos subprocesos simultáneos. El primer hilo es responsable del escaneo de subredes: para cada dirección posible (de .0 a .255) en cada subred extraída, el malware intenta una conexión en esa IP en el puerto SMB (445) usando el protocolo TCP. Por cada conexión exitosa, este primer hilo guarda las IP válidas en una cola y repite el escaneo cada 30 segundos.
El segundo subproceso espera alguna IP válida en la cola de IP y para cada IP enumera los recursos compartidos mediante la API de NetShareEnum repitiendo el proceso descrito para la enumeración de recursos compartidos. El hexadecimal 0xFFFFFFFF se usa como la última dirección IP en la cola para eliminar ambos subprocesos y concluir la segunda y última parte de la enumeración de la red.
Al concluir la ejecución del ransomware, se invoca la API WaitForSingleObject en cada subproceso para esperar la finalización de las operaciones de encriptación y enumeración antes de cerrar el proceso principal.
CONCLUSIÓN
La banda Conti es una de las organizaciones criminales más conocidas y temidas del mundo digital. La cantidad y el detalle de los datos internos que poco a poco van saliendo sobre el colectivo pueden suponer sin duda un auténtico terremoto en el panorama de las ciberamenazas. En cuanto al código, parece estar muy bien modularizado y administrado. Como era de esperar, su calidad es ciertamente alta.
Fuente: cluster25.io