The Eye Security Incident Response team was recently engaged to investigate a ransomware incident that we attributed to the 8base ransomware group. We quickly discovered that the encryptor was still running on some hosts. So we decided to find out whether a memory dump of the process would provide us with sufficient information to recover the encryption keys.
We came very close and, if we had been engaged slightly earlier, before the encryption process was completed, we would likely have succeeded. Below, we outline the steps and those necessary for success if we had been engaged earlier.
A closer look at the 8Base encryption process
The files are fully encrypted if they are less than 1.5 MB, but for larger files, only three chunks of 256 KB are encrypted. These are the first and last 256 KB and a 256 KB chunk in the middle of the file.
This is a common tactic to maximise damage and reduce the chances of file recovery while increasing encryption speed and minimising the chance of detection. The beginning and end of a file contain critical headers, metadata, format information and index tables. Without these, the file is likely useless. However, certain tools could still recover some information in specific file types, so the ransomware encrypts a random chunk of data in the middle of the file. This disrupts its structure and integrity.
The key creation process in this 8Base ransomware attack
The malware generates a new (pair of) AES key(s) for each drive or share it tries to encrypt. This random generator has already been attacked by CERT Polska with limited success (see "A Tale of Phobos: How We almost Cracked a Ransomware Using CUDA"). Therefore, it seems unlikely that we would have been able to brute-force the AES keys.
Each key is then encrypted using an RSA public key. The encrypted data also includes the volume serial number of the system drive and an identifier. This allows the threat actor to selectively release the AES keys for specific systems.
struct rsa_encrypted_data {
char key[32];
int volume_serial_nr;
int identifier; // config variable 1
};
After both keys are created, they are combined into one structure, which also contains a CRC checksum. This checksum is calculated via the concatenation of both keys, as evident from the code below.
If the AES key is still present in the memory, we should find a structure with the following shape:
struct key_combo {
char aes_key[32];
char rsa_encrypted_aes_key[128];
int crc_checksum;
};
Challenges to recovering the encryption keys from the memory
To search the raw memory dump, we used the following Python script to detect this structure:
# data is raw memory dump
import struct,zlib
for i in range(len(data)-128-32-4):
mem_crc = struct.unpack('I',data[i+32+128:i+32+128+4])
calc_crc = zlib.crc32(data[i:i+32+128])
if mem_crc == calc_crc:
print(f'Found aes key: {data[i:i+32]}')
Unfortunately, we could not locate the keys as they had been zeroed out directly after the malware finished encrypting a drive. This had left us with an extremely short window when they were available.
The alternative outcome – decrypting the files
The customer’s environment was fully recovered. You can read about it in our recent article "8Base Ransomware Investigation Uncovers Surprising Insights". But this process would have been somewhat different had we been able to recover the keys. Here are the steps we would have taken for file recovery.
At the end of each encrypted file, two data structures were added. The first one is encrypted metadata, and the last one is a plaintext footer. To recover the file, we start by reading the plaintext footer at the end of the file. This contains the information needed to find and decrypt the encrypted metadata. Here is the structure:
struct __declspec(align(1)) phobos_file_footer {
char always_zero[20];
char iv[16];
int padding_size;
char rsa_encrypted_part[128];
int metadata_offset;
char attacker_id[6];
};
The most important field is metadata_offset, which is an offset from the end of the file to its encrypted metadata. This can be decrypted using the recovered AES key and the iv from the footer with AES-256 in CBC mode. This reveals the following structure.
struct phobos_file_metadata {
int always_zero;
int encryption_mode;
int magic;
int nr_chunks;
int chunk_len;
int crc_checksum;
int orig_filename_offset; // offset from start of structure phobos_file_metadata
int padding;
//chunks for large files only if encryption mode == 1
LARGE_INTEGER chunk_offsets[nr_chunks];
char chunk_data[nr_chunks][chunk_len];
};
The most important field is the encryption_mode. Mode 1 is chunked, for files larger than 1.5MB and has a magic value of 0xAF77BC0F. Mode 2 is whole file encryption and has a magic value of 0xF0A75E12.
For fully encrypted files (mode 2), use the same AES key and initialisation vector to decrypt them. Remove the footer, metadata and the last few bytes of padding using the padding_size field.
For large files (mode 1), the data is included in the structure. To restore the file, seek to chunk_offsets[idx] and write chunk_data[idx] at that offset. And of course, also remove the metadata and footer at the end of the file.
Effective ransomware recovery: lessons from the 8Base case
Had we been engaged in this process earlier, we would have caught the encryption process before it was completed and destroyed the keys. The file recovery would have been simpler. Fortunately, as you will see from the incident recovery writeup, we restored the customer environment with minimal downtime.
Ready to talk about about improving your security posture? Get in touch to get all the details: