Summary
According to multiple online resources, Conti is one of the most active ransomware families in the last year. One of the infamous attacks happened against HSE healthcare (https://threatpost.com/conti-ransomware-fail-costly/166263/), where the attackers asked for a $20 million ransom. As mentioned by Cybereason at https://www.cybereason.com/blog/cybereason-vs.-conti-ransomware, Conti is sold as a RaaS (Ransomware as a Service) in underground forums, and it has been deployed by the TrickBot gang. One of the major concerns is the fact that the ransomware group employs the double extortion method: they also steal sensitive data from the victim and ask for a ransom. The malware can run with different parameters such as “-p”, “-m”, “-size”, “-log” and “-nomutex” and creates a mutex called “YUIOGHJKCVVBNMFGHJKTYQUWIETASKDHGZBDGSKL237782321344” to ensure that only one instance of the ransomware is running at a single time. The malware deletes all volume shadow copies on the computer using wmic and COM objects. The ransomware targets all valid drives on the system and the SMB shares that could be accessed. The files are encrypted using a custom ChaCha8 implementation that uses randomly generated keys (32 bytes) and nonces (8 bytes), which are encrypted by a hard-coded public RSA key. The encryption is very fast because the sample uses multiple threads to encrypt the files and avoids encrypting larger files entirely (>5MB).
Analyst: @GeeksCyber
Technical analysis
SHA256: 58ca4e482db7cf5c924256e53d8516d422e76cf4b85b43dc2b9ba0c7cb471ff7
The malware does a good job at obfuscating most of the stack strings, and the decryption algorithms used to decrypt them are changing every time (there are some variations of operations and constants). One example of a custom decryption algorithm is shown below:
The important APIs are imported during runtime rather than including all of them in the IAT (import address table). There is a hashing algorithm used to determine which functions should be imported (the first parameter is the hash value, and the second parameter represents an offset):
The process retrieves the command-line string by calling the GetCommandLineW function:
The CommandLineToArgvW API is utilized to obtain an array of pointers to the command line arguments (similar to argv and argc values in C):
The binary can run with different parameters: “-p”, “-m”, “-log”, “-size” and “-nomutex” (we will describe the workflows depending on these parameters in the upcoming paragraphs). It parses the parameters used by the program and compares them with the list, as shown in figure 5:
Command line parameters | Functionality |
-p directory | encrypt the directory with a single thread |
-m local | encrypt the available drives with multiple threads |
-m net | encrypt the network shares with multiple threads |
-m all | encrypt the available drives and the network shares with multiple threads |
-m backups | Not implemented (maybe encrypt backup directories?) |
-size chunk | chunk mode for encrypting large files |
-log logfile | logging mode (log different activities in a log file) |
-nomutex | no mutex created |
The process decrypts the mutex name using an algorithm similar to the one presented above:
A new mutex is created to ensure that only one instance of malware is running at a single time (if it runs with the “-nomutex” parameter, no mutex is created):
GetNativeSystemInfo is used to get information about the current system, as highlighted in the next figure:
The malicious process creates 2 (the number of processors) new threads that deal with the encryption, as we’ll describe later on:
The CreateToolhelp32Snapshot API is used to retrieve a snapshot of all processes (0x2 = TH32CS_SNAPPROCESS):
The binary enumerates the processes on the system by calling the Process32FirstW and Process32NextW routines:
The malware is looking for the “explorer.exe” process and saves its process ID in a buffer:
The CoInitializeEx routine is utilized to initialize the COM library on the current thread:
CoInitializeSecurity is used to register and set the default security values for the process (0x3 = RPC_C_IMP_LEVEL_IMPERSONATE):
The malware uses COM objects and wmic to delete all volume shadow copies. It calls the CoCreateInstance API to create an IWbemLocator object with the CLSID {dc12a687-737f-11cf-884d-00aa004b2e24}:
The same function is used to create a new IWbemContext interface with the CLSID {44aca674-e8fc-11d0-a07c-00c04fb68820}, as displayed in figure 17:
The binary uses the ConnectServer function to connect to the local “ROOT\CIMV2” namespace and retrieves a pointer to an IWbemServices object:
CoSetProxyBlanket sets the authentication information that will be used to make calls on a proxy (0xA = RPC_C_AUTHN_WINNT – NTLMSSP, 0x3 = RPC_C_AUTHN_LEVEL_CALL and 0x3 = RPC_C_IMP_LEVEL_IMPERSONATE):
The ransomware obtains an enumerator of all shadow copies by executing the following query “SELECT * FROM Win32_ShadowCopy”:
The following string that is decrypted using a custom algorithm will be used to delete all shadow copies on the system:
The Wow64DisableWow64FsRedirection API is utilized to disable file system redirection for the current thread:
The malware creates a new process to delete the shadow copy corresponding to the specified ID, as follows:
File system redirection for the current thread is restored using a function call to Wow64RevertWow64FsRedirection:
All valid drives on the system are targeted by the malware, which uses the GetLogicalDriveStringsW API to retrieve them:
The WSAStartup routine initiates the use of the Winsock DLL by the current process, as highlighted in the next figure:
The binary creates a new socket via a call to WSASocketW (0x2 = AF_INET, 0x1 = SOCK_STREAM, 0x6 = IPPROTO_TCP):
WSAIoctl is utilized to retrieve a pointer to an extension function supported by the associated service provider (0xc8000006 = SIO_GET_EXTENSION_FUNCTION_POINTER):
The file extracts the standard host name for the local computer by calling the gethostname function:
The local IP address of the local machine is retrieved by calling the gethostbyname routine:
CreateIoCompletionPort is utilized to create an I/O (input/output) completion port that is not associated with a file handle (0xffffffff = INVALID_HANDLE_VALUE):
The ransomware extracts the ARP entries from the local machine and returns the information in a MIB_IPNETTABLE structure:
The IP address in a hex format from each entry is converted to an ASCII string (dotted-decimal format IP address):
The process is interested in local IP addresses only because it compares the IP addresses with the following strings: “172.”, “192.168.”, “10.” and “169.”. One such comparison is shown below:
The binary creates 2 new threads by calling the CreateThread API, and we’ll detail their execution flows in the following paragraphs:
The malware sends an I/O completion packet to the I/O completion port created earlier, as highlighted in figure 37:
Thread activity – sub_10BAF90
The CreateTimerQueue function is utilized to create a queue for timers (objects that specify a callback function to be called at a specific time):
The threads communication is done using the I/O completion port that was created by the main thread. The GetQueuedCompletionStatus function is utilized to receive a buffer sent by the main thread:
The thread creates a new socket by calling the WSASocketW routine (0x2 = AF_INET, 0x1 = SOCK_STREAM, 0x6 = IPPROTO_TCP and 0x1 = WSA_FLAG_OVERLAPPED):
The bind API associates the local address with the newly created socket:
The CreateIoCompletionPort function associates the TCP socket created earlier with the existing I/O completion port (it allows the process to receive notification of the completion of I/O operation involving the socket handle):
The ransomware uses the ntohs routine to convert 0x1bd (445) from network byte order (big endian) to host byte order (little endian):
The process tries to connect to multiple IP addresses on port 445 (192.168.10.x and 192.168.164.x) via the LPFN_CONNECTEX callback function, as highlighted in figure 44:
The CreateTimerQueueTimer API is utilized to create a timer-queue timer. Basically, when the timer expires (0x7530 = 30 seconds in our case), the callback function (0x010BAF60) is called:
Process Monitor captures the network connections initiated by the thread:
setsockopt is used to set the SO_UPDATE_CONNECT_CONTEXT option for the socket (0xffff = SOL_SOCKET, 0x7010 = SO_UPDATE_CONNECT_CONTEXT):
In order to check whether a connection is successful, the malware calls the getsockopt function with the parameter 0x700c = SO_CONNECT_TIME:
The ransomware doesn’t target the network shares available on the local host:
The WSAAddressToStringW routine is used to convert all components of a sockaddr structure into a human-readable string representation:
Thread activity – sub_10BA880
The malicious process retrieves information about the SMB shares on the IPs that were successfully accessed, as shown in figure 51 (here we use the local IP address for debugging purposes):
The “ADMIN$” share will not be encrypted by the malware:
“-log” parameter
If the malware runs with the “-log” parameter, the following strings are constructed and included in the log file:
The binary creates the log file passed as a parameter, as displayed in the next figure:
The current date and time in UTC are retrieved by calling the GetLocalTime API:
The time extracted above is written in the log file:
An example of a log file that contains a list of actions performed by the malware is displayed below:
“-m backups” parameter
This seems to be a testing parameter because there is no additional action performed by the process:
“-nomutex” parameter
No mutex is created if the malware runs with this parameter.
“-m local” parameter
The ransomware creates 2 (the number of processors) new threads. It encrypts all the available drives on the system.
“-m net” parameter
The ransomware creates 2 (the number of processors) new threads. It encrypts all the network shares that could be accessed.
“-m all” parameter
A combination between the 2 cases from above.
“-p directory” parameter
Thread activity – sub_10BC7D0
CryptAcquireContextA is utilized to acquire a handle to the Microsoft RSA and AES Cryptographic Provider, as shown below:
The CryptImportKey function is used to import a public RSA key:
The process creates the ransom note in every directory that it encrypts:
The following 4-byte constants suggest that the encryption algorithm is ChaCha8, as described at https://arxiv.org/pdf/1907.11941.pdf:
The WriteFile API is used to populate the ransom note:
The content of the ransom note is displayed in figure 65:
The malware enumerates the files in the targeted directory using the FindFirstFileW and FindNextFileW APIs:
There is a call to PathIsDirectoryW that checks if the file path is a valid directory:
The following extensions/files are skipped by the malware (“.LSNWX” is the extension of encrypted files):
CryptGenRandom generates 32 random bytes that will be used as the ChaCha8 key:
CryptGenRandom generates 8 random bytes that will be used as the ChaCha8 nonce:
The RSA public key is used to encrypt the ChaCha8 key and the nonce, as shown in the pictures:
The CreateFileW function is utilized to open the files that will be encrypted:
The file extension is compared against a list of multiple targeted extensions (the entire list can be found in the Appendix):
There is a second comparison that occurs between the extensions. Some of them include virtual disk files, ISO files and others:
It’s important to mention that if a file extension doesn’t belong to these lists, that doesn’t mean the file will not be encrypted. For example, the txt file used as an example will be encrypted by the ransomware however, it follows a different execution flow that will be detailed in a few paragraphs. The encrypted ChaCha8 key and nonce are part of the encrypted file, as follows:
There are 3 different cases: small (<1MB), medium (between 1MB and 5MB) and large files (> 5MB) to be encrypted. In the first case, our file has a size of 8 bytes, and then the following 10-byte buffer is appended to the end:
The process reads the content of a file by calling the ReadFile API:
The file content is encrypted using a custom implementation of the ChaCha8 algorithm:
A snippet of the ChaCha8 algorithm implemented by the malware is presented below:
The encrypted buffer has the same size as the initial one:
The WriteFile routine is utilized to fill out the encrypted file:
The file extension is changed to “.LSNWX”, and the encryption activity is complete at this point:
The encrypted file has the following content:
Now, if the file size is between 1MB and 5MB, and the extension doesn’t belong to the targeted lists, the 10-byte buffer is different (note the file size):
In this case, the ransomware only encrypts 1MB of the file:
Whether the file size is between 1MB and 5MB and the extension belongs to the targeted lists, the 10-byte buffer is changing again:
The entire file is encrypted in this case:
In the case of file size that is greater than 5MB and the extension doesn’t belong to the targeted lists, the next buffer will be appended to the encrypted file:
The interesting part of the encryption comes now because the process encrypts 5 chunks of (file size/100 * 10) bytes. In our case, this value is (0x7A1200/0x64*0xa) = 0xC3500 bytes (the malware skips some bytes before encrypting 0xC3500 bytes again):
The last case is when the file size is greater than 5MB, and the extension belongs to the targeted lists. The 10-byte buffer looks like in figure 94:
The value 0x14 allows the ransomware to encrypt files in 3 chunks of (file size/100 * 7). In our case, this value is (0x7A1200/0x64*0x7) = 0x88B80 bytes.
References
MSDN: https://docs.microsoft.com/en-us/windows/win32/api/
Appendix
List of targeted 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
- .oqy
- .orx
- .owc
- .p96
- .p97
- .pan
- .pdb
- .pdm
- .pnz
- .qry
- .qvd
- .rbf
- .rctd
- .rod
- .rodx
- .rpd
- .rsd
- .sas7bdat
- .sbf
- .scx
- .sdb
- .sdc
- .sdf
- .sis
- .spq
- .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
- .vdi
- .vhd
- .vmdk
- .pvm
- .vmem
- .vmsn
- .vmsd
- .nvram
- .vmx
- .raw
- .qcow2
- .subvol
- .bin
- .vsv
- .avhd
- .vmrs
- .vhdx
- .avdx
- .vmcx
- .iso