Conti Locker Analysis
2022 is running wild!
Contents
1. Disclaimer
I won’t be releasing/sharing exact complete source-code out of respect to the person because of whom this all was possible. The twitter user @ContiLeaks. Also because of the security risks that are associated with such piece of software. Nobody would really want some scr1pt_k1di3s get their hands on such destructive software. You can always reach out to me to discuss further about it.
This analysis is purely for informational purposes. I am not responsible for action of any individual(s) that may be a result of this information directly or indirectly.
2. Introduction
According to TrendMicro, Conti, is assumed to be the successor of the Ryuk ransomware, and is currently one of the most notorious active Ransomware families.
Source: TrendMicro Research
ContiLeaks
On February 28th 2022, a user joined twitter with the username “@contileaks”. With the creation of the said account, came the first tweet, which was leak of chats from the messaging app “Jabber”, which was supposedly being used by the hacker group behind the Conti Ransomware.
Followed by more leaks, and a leak that shared the ZIP file with the name “conti_locker_v2.zip”.
Zipped Locker
The file was supposed to have source code for conti-locker (the actual ransomware which was used on victim PCs), and conti-decryptor (the decryptor which was used to decrypt encrypted files), and the conti-builder.
The file to most of our surprise was password protected.
The user then tweeted on March 1st, 2022:
conti src password shared only with trusted ppl for now. to avoid more damage!
The files were right in front but useless without the password. I personally tried all methods, resources available to me to somehow recover password for the zip file. But given the reputation of the place from where the file was coming, a plain “hashcat” attack was not enough.
Unzipped Locker
Later that day, around 15:10 UTC, the user tweeted again:
- conti source without locker src.*
This was the zip file with source code of “locker” and its compiled binaries removed. For most, it contained the decryptor, which is essentially a much needed piece of software for people/organizations that became victim of the ransomware. But for threat researchers and analysts as myself, the locker was equally important for analysis and extracting IOCs that can help in prevention of attack by this piece of software.
Luckily, since some contents of both the zip files (password-protected and non password-protected) had identical contents, a method of decryption/re-encryption was possible to use. Thanks to Wade Hickey’s Proof of Concept ( PoC), the contents of encrypted files were recovered!!
backdoor.js
One of the interesting findings from that unlocked ZIP file was the Git repository with the name backdoor.js.git
Here you can find the complete analysis of that repo and its content done by my colleague Luca.
3. Source Code Analysis : Locker
This malware analysis will be different from traditional ones. Usually, we have to rely on reverse engineering the sample and de-obfuscate the program if any obfuscation is present. The actual binaries, which were used on victim machines were quite heavily obfuscated. Here’s how the attacking binary looks in IDA:
Release build binary, obfuscated function names and compiled code.
As the source code was obtained, things became easier. The whole Visual Studio Solution can be used to load the project as if a person is working on it, with Debug builds option available. Here’s how debug version of the same binary looks in IDA:
Debug build binary, non-obfuscated
I’ll be using the code blocks from the actual source code itself (obviously, not complete code) for further analysis. Another great relief is that the code has comment blocks in it. After translation, it made the whole process much more easier.
a. Initialization
The WinMain()
function starts with calling api::InitializeApiModule()
. This function does following things in given order:
- Gets the address of
kernel32.dll
- Gets the address of
LoadLibraryA()
function inkernel32.dll
that is used for further DLL Injection. - Allocates the cache for API
After this, api::DisableHooks()
is called. It has a list of DLLs to remove the hooks of if they are loaded in memory (I’m not 100% sure about this and I can definitely be wrong, please feel free to ping me!) The list of DLLs: [ kernel32.dll
, ws2_32.dll
, Advapi32.dll
, ntdll.dll
, Rstrtmgr.dll
, Ole32.dll
, OleAut32.dll
, Netapi32.dll
, Iph1papi.dll
, Shlwapi.dll
, Shell32.dll
]. Each DLL name string is first passed to the function pLoadLibraryA()
and then if it returned true, it passes the the HMODULE
returned to removeHooks()
helper function.
b. Command Line Arguments
According to the function HandleCommandLine()
, there were 6 available flags, with 2 of them commented out in Debug builds.
- h : Host Lists # It is the path to the list of remote hosts/shares to attack (works only if Encryption Mode (-m) is "all" or "network"
- p : Specific Path # It is the path to the specific drictory to encrpyt instead the whole system.
- m : Encryption Mode # Encryption Mode to be used on the victim:
# "all" : Encrypt local files, backup files/servers and remote shares too. (Invokes Network Scanner)
# "local" : Encrypt only local files. (Doesn't invoke Network Scanner)
# "net" : Encrypt only remote shares too. (Invokes Network Scanner)
# "backups" : No affect of selection as it is only defined but no switch case available for it.
- log : Logging (enabled/NULL) # Enables logging and records any error that occurs during execution to the configured path in the source code ("C:\CONTI_LOG.txt")
- prockiller : ProcessKiller module # Enables the built in process killer module
- pids : PIDs to kill # List of PIDs to be used by Process Killer
c. Modifying the Code
For debug purposes, I tried to modify the code by putting couple of printf()
and wprintf()
. Even the program compiled successfully, none of them worked. There was absolutely no output as I nuked my Sandbox with the ransomware. Everything was being executed on a spawned thread. (Due to lack of my experience with Windows executables, I can’t be 100% sure what would’ve been the reason).
d. Searching for files
Before diving into the search and destruction of data, the executable dynamically allocates the number of threads to be used by the threadpool.
It fetches the number of processors/cores available from SysInfo.dwNumberOfProcessors
and then sets the number of threads to double of it for Local Encryption (Threads to search and encrypt files locally) and does the same for Network Encryption (Threads to search and encrypt network hosts and shares).
-
Blacklists
Another interesting aspect of Conti was the “Blacklists” it had hardcoded in the source code itself. The two blacklists included Directories to skip and file extensions to skip
e. Cryptanalysis
The only fixed part of the encryption process is the RSA Public Key that was hard coded before compilation. Since its RSA-4096 bit key, factorization attacks are meaningless to obtain the private key. Now this is the part that caught my attention almost immediately since I’ve worked on a similar flow couple of years ago when I was working on Datanoid! It has almost a similar approach to encrypt files. Each file will be encrypted using a unique key-IV pair, which itself will be encrypted and added as a header to the encrypted file. I won’t be sharing the code snippets for the encryption parts for obvious reason. The encryption for files proceeds as follows:
- Use a RSA 2048/4096 Keypair
- Generate Random 32-byte Key and random 8-byte IVs for each individual file
- Use that key and IV to encrypt that specific file using some stream cipher (chacha in this case)
- Encrypt the key and IV using the RSA Public Key
- Prepend 524 bytes to the encrypted file bytes and create a custom header. Append 10 more bytes to store encryption metadata (A byte to set
EncryptMod
, a byte forDataPercent
, and remaining 8 bytes for settingOriginalFileSize
). The metadata is important for (This new extension is configured inglobal_parameters.cpp
and has to be configured the same for the decryptor for it to work). Destroy original file.
Only way to decrypt the file is to obtain the RSA private key of which the Public Key was hard coded in the locker.
Interesting part about Conti locker is that it doesn’t encrypt every file the same. Here’s a breakdown of file selection for encryption and the level of encryption.
- Databases : FullEncrypt (Files with 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
,.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
] ) - VirtualMachines : PartlyEncrypt (20%) (Files with one of the following extensions: [
.vdi
,.vhd
,.vmdk
,.pvm
,.vmem
,.vmsn
,.vmsd
,.nvram
,.vmx
,.raw
,.qcow2
,.subvol
,.bin
,.vsv
,.avhd
,.vmrs
,.vhdx
,.avdx
,.vmcx
,.iso
]) - Files smaller than 1MB : FullEncrypt
- Files between 1MB and 5MB : EncryptHeader
- Files larger than 5MB : Partially Encrypt (50%)
FullEncrypt()
was the function that encrypted the complete file.
EncryptHeader()
encrypted the first 1 MB of the file.
PartltyEncrypt()
accepted a number as the last parameter which was used for a switch case in the function that decided. The switch case was as such:
/*
FileInfo : Handle to the file being encrypted
FileInfo-> FileSize : Size of file
*/
switch (DataPercent) {
case 20:
PartSize = (FileInfo->FileSize / 100) * 7;
StepsCount = 3;
StepSize = (FileInfo->FileSize - (PartSize * 3)) / 2;
break;
case 50:
PartSize = (FileInfo->FileSize / 100) * 10;
StepsCount = 5;
StepSize = PartSize;
break;
default:
return FALSE;
}
To avoid encryption keys to be dumped in memory, the locker calls RtlSecureZeroMemory()
routine to wipe the memory used by Keys and IVs.****
This was some really sophisticated piece of software.
4. Source Code Analysis : Decryptor
a. Cryptanalysis
Decryptor was fairly a simpler executable. The noticeable difference from locker was the inclusion of the RSA Private Key instead of the Public Key component of the same key. It is necessary to decrypt the headers of the encrypted files to obtain actual keys and IVs to decrypt the files and obtain original files back. The decryption is as follows:
- Searches for files with the configured extension and avoids the blacklist of directories and extensions as same as locker.
- Read first 534-bytes of the opened file. This is the custom header which has:
- header[0:524] ⇒ Encrypted Key and IVs
- header[524:534] ⇒ 10-byte metadata stored by the locker.
- metadata[0] ⇒ Encryption Mode (FULL, HEADER, PARTLY)
- metadata[1] ⇒ Data Percent (for PARTLY Encryption Mode)
- metadata[2:10] ⇒ Original File size
- Decrypt the encryption key and IVs using the private key.
- Use the metadata to decide the decryption mode from
DecryptFull()
,DecryptHeader()
, orDecryptPartly()
. - Use the decrypted encryption key and IVs with cipher used with locker and decrypt the data.
- Write the data to a new file and remove the extension appended by the locker to restore normal usage in Windows.
b. Performance
Decryptor is not configured with the dynamic threading as good as locker. It just uses number of threads equal to number of processor/cores available for both, local threadpool and network threadpool.
5. Conclusion
This was the first time I was dealing with such an analysis. A windows malware/ransomware. I’m no expert at programming, but I was so dumbfounded by the clarity and amazing structure of the source code. It seems safe to assume that the creators are quite experienced and also believer of Open Source Software as pieces of Open Source code are present in the source code, with credit and mentions. Impressive indeed!
Being on the victim end of this ransomware won’t be a pleasant experience. Even I made Datanoid with similar mindset of providing ultimate encryption to store sensitive files. I never thought someone with similar approach will be creating a piece of software that’ll be causing havoc everywhere.
If you, the reader, find any information that seems to incorrect, or are interested to discuss this further, please feel free to hit me up at my socials or drop an email. (Ofc, without a phishing link please. :”D)