Cluster25 Threat Intel Team
March 2, 2022
On 25.02.2022 cybercrime group Conti published the following statement on their shame blog:
The post was redacted several hours later with another one having more neutral tones, condemning the war and disaffiliating itself with the government while however emphasizing sentiments against the west. The post retained its threats of retaliation against critical infrastructure belonging to any Russia aggressor.
After that on 28.02.2022, likely one of the Conti members (or just a Ukrainian security researcher) published a first archive with internal valuable data and information belonging to the whole collective. The action was probably a direct consequence of such a clear-cut stance by the group on the current situation between Russia and Ukraine. Among this material there also appears to be an archive containing the source code of their ransomware of which we report a preliminary analysis.
ContiLocker is a ransomware developed by the Conti Ransomware Gang, a Russian-speaking criminal collective with suspected links with Russian security agencies. The project is developed in C++ on a Visual Studio 2015 version with Windows XP Nplatform toolset (v140_xp). The specified destination platform is the 10.0 (Windows10). The project structure is organized in different subfolder, where each one handle a specific module of the ransomware (like the “locker” folder for the encryption operations).
For specific operations (like the encryption mechanism) this uses different concurrent threads handled by the CreateIoCompletitionPort Windows API and different queues that are handled by the
GetQueuedCompletitionStatus and PostQueuedCompletitionStatus.
The WinMain function (main.cpp) starts with the dynamic resolution of the LoadLibraryA API through a manual inspection of the imported kernel32.dll (a manual implementation of the GetProcAddress API).
After that, the “API” module is invoked to execute an anti-DBI/anti-sandbox technique with the purpose of disable all the possible hooking’s on known DLLs. In fact, the following DLLs are loaded through the just resolved LoadLibraryA API:
For each loaded DLL, the CreateFileMappingW and the MapViewOfFile are invoked to access the mapped view into the address space of the calling process.
This view is used to manually access the NT header and the inner export directory. From the export directory each address of the exported functions is extracted, and the first bytes of the exported function are checked to identify a possible JMP/NOP/RET instruction that identifies an external hook.
If the current function is hooked, the VirtualProtect and the RtlCopyMemory API are invoked to overwrite the first bytes of the hooked function. Proceeding with the WinMain execution, a mutex called “kjsidugidf99439” is created to check for possible concurrent executions of the same payload. If another thread has the ownership of the mutex, the execution terminates here.
After that, the command lines arguments are checked from the GetCommandLineW API.
This ransomware accepts the following command line arguments:
- -h: specifies a file that contains the IPv4 of hosts to scan for network/shares encryption (separated by \n\r);
- -p: specifies a file that contains the system path to scan for file encryption (separated by \n\r);
- -m: specifies the encryption mode
- “all”: encrypt both local and network files
- “local”: encrypt only local files
- “net”: encrypt only network files
- “backups”: not implemented
- -log: if contains the value “enabled”, logs the ransomware actions/errors on the local file C:\\ CONTI_LOG.txt
Afterwards, the GetNativeSystemInfo API is invoked to extract the number of processors and the “threadpool” module is used to instantiate number_of_processors * 2 threads (both for local and/or network encryption, based on the specified flags).
Each thread allocates its own buffer for the upcoming encryption and initialize its own cryptography context through the CryptAcquireContextA API and an RSA public key.
Then, each thread waits in an infinite loop for a task in the TaskList queue (shared by each thread and accessed by the EnterCriticalSection API). In case a new task is available, the filename to encrypt is extracted from the task and, if the filename
corresponds to “stopmarker”, the thread execution is concluded.
In any other case, the “locker” module is invoked to encrypt the current file.
The encryption routine for a specific file starts with a random key generation (using the CryptGetRandom API) of a 32-bytes key and another random generation of an 8-bytes IV.
Subsequently, the random key and the random IV are stored in a custom FIleInfo structure and the random key is encrypted using the RSA key previously decoded.
Before the encryption phase if the restart manager DLL is loaded (rstrtmgr.dll), the RmStartSession, RmGetList and RmShutdown APIs are invoked to terminate each application that are using this specific resource or have a handle open on that
Then, based on the file extension, the file content is full encrypted or partially encrypted (20% encryption). In particular, the CheckForDataBases method is invoked to check for a possible full encryption against the following extensions:
- .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
Otherwise, the method CheckForVirtualMachines method is invoked to check for a possible 20% partial encryption ((file_size / 100) * 7) against the following extensions:
- vdi, .vhd, .vmdk, .pvm, .vmem, .vmsn, .vmsd, .nvram, .vmx, .raw, .qcow2, .subvol, .bin, .vsv, .avhd, .vmrs, .vhdx, .avdx, .vmcx, .iso
In other cases, the following pattern is followed:
- If the file size is lower than 1,04 GB: perform a full encryption.
- If the file size is between 1,04 GB and 5,24 GB: perform a header encryption (encrypt only the first 1048576 bytes).
- Otherwise perform a 50% partial encryption ((file_size / 100) * 100).
After chosen the encryption method, the first bytes of the file content are overwritten (before the encryption) with the information about the encryption mode and the key used for encryption. Then, the file content is encrypted using the random key previously encrypted with RSA and the file extension is changed to .EXTEN.
Now let’s see how these threads are invoked from the enumeration methods returning to the WinMain execution.
First, a COM bypass is used to delete the shadow copies from the Windows Management Instrumentation (WMI).
- The COM object is initialized through the CoInitializeEx API.
- The COM security levels are changed trough the CoInitializeSecurity API and the parameter cAuthSvc equals to -1 in order to disable the authentication.
- The CoCreateInstance API is used to locate the WMI trough the CLSID “CLSID_WbemLocator”.
- The WMI and the WQL (WMI Query Language) are accessed through the IWbemLocator::ConnectServer method.
- The WMI proxy security levels are changed through the CoSetProxyBlanket API in order to set the flag RPC_C_AUTHZ_NONE and avoid the authentication.
- The “SELECT * FROM Win32_ShadowCopy” query is invoked to identify the shadow copies ID’s and a command-line execution is used to delete each shadow copy “cmd.exe /c C:\\Windows\\System32\\wbem\\WMIC.exe shadowcopy where \”ID=’%s’\” delete”
Finally, the enumeration process starts. First, the file-system paths specified through the -p flag are iterated and for each path the ransomware note (R3ADM3.txt, not available in this leaked version) is dropped into the specified directory. After that, the APIs FindFirstFileW and FindNextFileW are used to iterate inside each directory ignoring the special files (like “.” or “..”).
The malware uses a whitelist for both directories and files to avoid the encryption of unnecessary data. The following directories names and file names are avoided during the enumeration process:
- Directories: “tmp”, “winnt”, “temp”, “thumb”, “$Recycle.Bin”, “$RECYCLE.BIN”, “System Volume Information”, “Boot”, “Windows”, “Trend Micro”
- Files: “.exe”, “.dll”, “.lnk”, “.sys”, “.msi”, “R3ADM3.txt”, “CONTI_LOG.txt”
If the file to encrypt is a directory, the described process is repeated recursively for all the subdirectories and subfiles. Finally, the file to encrypt is passed to the first available thread for the encryption process populating the TaskList queue. The following enumeration inspects all the logical drives of the infected system.
In fact, in addition to the paths specified by the -p flag, the GetLogicalDriveStringsW API is used to obtain the drives list. Then, for each logical drive, the root path is extracted, and the previous process is repeated for each subdirectory and subfiles.
The last enumeration process is used to enumerate the shares of the infected Windows system. In fact, the NetShareEnum API is used to retrieve information about each shared resource. For each resource, if the resource represents a disk drive, a special share (e.g., $IPC communications, ADMIN$ remote administrations, administrative shares) or a temporary share the share path is extracted (e.g., \\\\$IP\\$SHARE_NAME).
Then, each share path is used as a directory for the previously described process of directories and files encryption.
Additionally to the share enumeration, this ransomware presents a multi-thread component to scan for other IP’s in the reachable networks for a destructive lateral movement encryption. In particular, the WSAStartup and the WSAIoctl APIs are invoked to get a handler to LPFN_CONNECTEX for low-level binds and connections.
Then, the GetIpNetTable API is invoked to recover the ARP table of the infected system. For each entry of the ARP table, the specified IPv4 addresses are checked against the following masks:
If the current ARP IPv4 respect one of these masks, the IP subnet is extracted and added into a subnet’s queue. From this enumeration, two concurrent threads are created. The first thread is responsible for subnet scanning: for each possible address (from .0 to .255) in each extracted subnet the malware tries a connection on that IP on the SMB port (445) using the TCP protocol. For each successful connection, this first thread saves the valid IP’s in a queue and repeat the scan each 30 seconds.
The second thread wait for some valid IP in the IP’s queue and for each IP enumerate the shares using the NetShareEnum API repeating the process described for the share enumeration. The hexadecimal 0xFFFFFFFF is used as last IP address in the queue to kill both threads and conclude the second and last part of the network enumeration.
Concluding the ransomware execution, the WaitForSingleObject API is invoked on each thread to wait for the completion of encryption and enumeration operations before closing the main process.
The Conti gang is one of the best known and most feared criminal organizations in the digital world. The quantity and detail of the internal data that is gradually coming out about the collective can certainly represent a real earthquake in the landscape of cyber threats. As for the code, it appears to be very well modularized and managed. As we could have expected its quality is certainly high.