In late July, BleepingComputer stated that the notorious DarkSide ransomware gang has rebranded as BlackMatter. DarkSide disappeared after the high-profile attack on Colonial Pipeline this year, but they seem to be back under a new name.
We recently came across a sample of the BlackMatter ransomware, posted by GrujaRS. As not yet much information is available on this ransomware, we decided to write about its inner workings and technical details. As expected, we found some similarities between DarkSide and BlackMatter.
BlackMatter employs slight obfuscation such as dynamic API resolving and basic string encryption mechanisms. In this blog, we will give a general overview of the ransomware. Furthermore, we will discuss the obfuscations, as well as the file encryption and some other details.
Like many ransomwares do, BlackMatter also creates a mutex upon startup to prevent multiple instances from running in parallel. In the upcoming sections, we will discuss several aspects of the ransomware in detail.
Dynamic Win32 API resolving
A common practice to avoid disclosing information about a malware’s behavior is to resolve Win32 API function addresses at runtime. This way, function names are not visible in the Import Address Table (IAT) of the binary, and static analysis tools cannot gain information from those. BlackMatter also employs this dynamic Win32 API resolving. It resolves the addresses of functions it uses first after starting. In the screenshot below, we see that it first resolves the HeapCreate and HeapAlloc functions, and proceeds to load functions from more than 10 different libraries.
If we zoom in on the ‘decrypt_and_resolve_funcs’ function, we find that it decrypts and resolves multiple hashes from a given block. BlackMatter contains a block of encrypted hashes for each library it requires functions from, followed by a trailing 0xCCCCCCCC. Each encrypted hash is first decrypted by a 32-bit XOR with key: 0x22065FED. After decryption, the hash is resolved. The hash of a function name is computed using two similar hash functions H0 and H1, where H0 computes the hash of a Unicode library name (including NULL terminator) and H1 computes the hash of an ASCII function name (including NULL terminator). The two hash functions, both performing ROR-13 operations, are given in the screenshot below.
The hash for a function name is then computed by first calling x = H0 for the library name. The initial value for this hash is zero. To compute the final hash, H1 is then called using ‘x’ as the initial value.
For each Win32 API function it resolves, BlackMatter manually creates a so called ‘call stub’ for the function. This stub is a small block of Assembly code that proxies or relays an API function call. Sometimes a call stub solely jumps to the required API function, but in other cases it performs some additional operations such as decryption or decoding. In the screenshot below, we see that after resolving ‘func_addr’, the call stub is created.
When an API function is called, the call stub is called instead. The actual function address remains encrypted in memory, and the call stub decrypts it before relaying the call. As we can see in the screenshot below, the call stub is quite simple. It decrypts the function address using the same 32-bit XOR we previously encountered. The value 0xDEADBEEF in the screenshot is a placeholder for the actual API function address.
String encryption is commonly employed in malware to thwart static analysis. BlackMatter also employs a simple string encryption algorithm, like what was used for API resolving. Throughout the code, encrypted strings are constructed on the stack by DWORDs instead of characters. The resulting buffers are decrypted inline using a simple XOR. The string decryption algorithm is given in the screenshot below.
Obtaining the configuration
BlackMatter also contains an embedded configuration, and it is encoded in a similar way DarkSide encoded theirs. In this case, the configuration is located in the “.rsrc” segment. The configuration starts with a 32-bit initial state value for the decryption algorithm. In this case, this value is 0xFFCAA1EA. A DWORD indicating the size is next, followed by the encrypted configuration. The decryption algorithm is shown in the screenshot below and is based on a slightly customized linear congruential generator.
The decrypted content is then decompressed using the aPlib algorithm. The decrypted and decompressed configuration starts with an RSA-1024 public key. Furthermore, the configuration includes a few Base64-encoded strings that contain:
- Command & control hostnames
- AES-128 encryption key for command & control
- Names of services to kill
- File and directories to avoid
- Ransom note (also encrypted with the algorithm above)
Naturally, we want to know how the file encryption process works in the BlackMatter ransomware. Like DarkSide, the BlackMatter ransomware first kills blacklisted processes and services, empties the recycle bins for all drives, and deletes shadow copies. It then finds files on the filesystem to encrypt.
The file encryption process is set up using I/O completion ports. These completion ports provide an efficient model for handling many concurrent I/O operations on a system with multiple CPUs. An I/O completion port is created with a given number of threads using the CreateIoCompletionPort function. After creation, a queue is created internally, to which threads can push completion statuses. One can queue a status using the PostQueuedCompletionStatus function, and a thread can pull this status from the queue using the GetQueuedCompletionStatus function.
A user-defined data context structure can be attached to a status to keep track of variables during encryption if its first member is an OVERLAPPED structure. Using the completion status queue, a control flow of I/O actions can be constructed. The file encryption function in the BlackMatter ransomware uses the state numbers 0 to 3 to create a control flow. This control flow is shown in the screenshot below.
As we can see, the encryption starts with a zero: read the next data block from the file. The status then switches to 1: encrypting the block of data previously read and writing it back to the file. The file footer is written when the status is set to 2, and when all I/O tasks have been completed, the encryption is finished with status number 3.
The user-defined context structure is created in the screenshot below. It contains for example the number of bytes to encrypt in a file. If the file is smaller than 1MiB, the entire file is encrypted. If the file is larger, the first 1MiB is encrypted.
Files are encrypted using the Salsa20 stream cipher algorithm, and the corresponding keys are encrypted using RSA-1024. If we zoom in on the ‘inits_salsa20_state’ function, we find that the Salsa20 matrix is initialized at random without a key and nonce. In the screenshot below, the matrix is constructed using 8-byte random values. The nonce is left at zero.
BlackMatter first checks whether the CPU inside the victim’s system supports the RDRAND instruction. If so, the next random number is generated by combining two calls to this instruction. If the victim’s CPU does not support RDRAND, the CPU timestamp counter is used instead. The next random number is then constructed by combining the lower 32 bits of two timestamp counters. One is rolled 13 bits to the left, and one 13 bits to the right. The screenshot below shows how these random numbers are generated.
The random Salsa20 matrix is encrypted with the RSA-1024 public key included in the configuration and written to the footer in the encrypted file. The footer contains some more information about the file, such as its original size and a magic value that allows BlackMatter to identify already encrypted files. The file encryption process is also quite like DarkSide’s.
Command & control
BlackMatter contacts command & control hosts to collect information about the victim. The hostnames it contacts are stored in the embedded configuration we previously discussed. BlackMatter first attempts to contact the command & control hosts using HTTPS and falls back to HTTP if it fails. The sent HTTP POST requests target a randomly generated URI consisting of key/value pairs with characters from the Base64 alphabet. The HTTP data associated to the requests also consists of similar key/value pairs. The difference is: one of the pairs contains encrypted data.
The HTTP requests are facilitated by the WinInet library, including functions such as InternetOpenW, InternetConnectW and HttpSendRequestW. An interesting characteristic of the requests is the User-Agent header. BlackMatter contains multiple hardcoded user agents, from which it randomly selects one to use in its connections. The user agents are shorter than the default ones, as we can see in the screenshot below.
As we previously mentioned, the encrypted victim information is placed in one of the key/value pairs in the HTTP POST data. The position of the pair in the string is randomly determined. We can determine which pair to decrypt by Base64-decoding each pair in the data string and checking the size of the results. We attempt decryption on all pairs having a size aligned to a 16-byte boundary. The encryption algorithm used here is AES-128 in ECB mode (independent block-by-block encryption). The key is placed in the configuration, starting 16 bytes after the RSA-1024 public key. In the sample we analyzed, the key is (hex-encoded): A6F330B09CD47B4FB9214F7836AA46AD. After decrypting, we get the following JSON object.
As we can see, we analyzed version 1.2 of BlackMatter. The object contains host and user information, such as identifiers for the victim, computer name and username, and available disk space on the infected system. The information in the JSON object is similar to what DarkSide sent to their command & control hosts, as they also collected system, user and disk information.
In this blog, we presented a detailed analysis of the BlackMatter ransomware. Others suggested that BlackMatter is a rebrand of the DarkSide ransomware, and the code similarities we found support this suggestion. The sample is available for download at MalwareBazaar.
Indicators of Compromise (IoCs)