<!-- Copyright (c) 2026 defconxt. All rights reserved. -->
<!-- Licensed under AGPL-3.0 — see LICENSE file for details. -->
---
name: analyzing-ransomware-encryption
description: >-
  Analyze ransomware encryption implementations to identify cryptographic weaknesses,
  recover keys, and assess decryption feasibility for incident response.
domain: cybersecurity
subdomain: malware-analysis
tags:
  - ransomware
  - encryption-analysis
  - crypto
  - key-recovery
  - incident-response
  - decryption
  - aes
  - rsa
version: "1.0"
author: defconxt
license: AGPL-3.0
metadata:
  mitre-attack: ["T1486"]
---

# Analyzing Ransomware Encryption

## Overview

Ransomware encrypts victim files using symmetric (AES, ChaCha20, Salsa20) and asymmetric (RSA, Curve25519) cryptography. Analysis focuses on identifying the encryption scheme, finding implementation flaws that enable key recovery, locating encryption keys in memory or process artifacts, and assessing decryption feasibility. Common weaknesses include reused keys/nonces, weak PRNG seeding, local key storage before exfiltration, and flawed hybrid encryption implementations. Even properly implemented encryption may be recoverable if the key exchange or key derivation is vulnerable.

## Prerequisites

- Python 3.10+ with `pycryptodome`, `pefile`, `yara-python`
- Ghidra or IDA for reverse engineering crypto routines
- x64dbg for runtime key extraction
- Sample encrypted files + original (for known-plaintext analysis)
- Volatility3 for memory forensics
- Understanding of AES, RSA, ChaCha20, key derivation functions

## Key Concepts

### Common Ransomware Encryption Schemes

| Family | Symmetric | Asymmetric | Key Generation |
|--------|-----------|-----------|----------------|
| LockBit 3.0 | AES-256-CTR | RSA-2048 | Per-file random key |
| BlackCat/ALPHV | AES-256-CTR + ChaCha20 | RSA-4096 | Per-file, intermittent |
| Conti | AES-256-CBC | RSA-4096 | Per-file with CSPRNG |
| REvil | Salsa20 | Curve25519 + AES-256 | Per-file random key |
| Dharma | AES-256-CBC | RSA-1024 | Session key |
| STOP/Djvu | Salsa20 | RSA-1024 | Online/offline key |

### Exploitable Weaknesses

| Weakness | Impact | Example |
|----------|--------|---------|
| Weak PRNG | Predictable keys | Using `time()` as seed |
| Reused nonce/IV | Key stream recovery | Same IV for all files |
| Static key | Single key decrypts all | Hardcoded key |
| Local key storage | Key on disk before C2 | Offline key fallback |
| ECB mode | Pattern preservation | No diffusion |
| Missing authentication | Bit-flip attacks | No HMAC/GCM |

## Workflow

### Step 1: Identify Encryption Scheme

```python
#!/usr/bin/env python3
"""Identify ransomware encryption scheme from encrypted files."""
import struct
from pathlib import Path

def analyze_encrypted_file(filepath: str, original: str = None) -> dict:
    """Analyze encrypted file properties."""
    data = Path(filepath).read_bytes()
    result = {
        "file": filepath,
        "size": len(data),
        "header_hex": data[:64].hex(),
        "footer_hex": data[-64:].hex() if len(data) > 64 else "",
        "entropy": 0.0,
        "encryption_indicators": [],
    }

    # Check for ransomware metadata appended to file
    # Many families append: encrypted_key + IV + original_size
    footer = data[-256:]

    # Check for RSA-encrypted key blob (typically 128/256/512 bytes)
    for key_size in [128, 256, 512]:
        if len(data) > key_size:
            blob = data[-key_size:]
            # High entropy blob at end suggests encrypted key
            from math import log2
            freq = [0] * 256
            for b in blob:
                freq[b] += 1
            entropy = -sum(
                (f / len(blob)) * log2(f / len(blob))
                for f in freq if f > 0
            )
            if entropy > 7.5:
                result["encryption_indicators"].append(
                    f"Possible {key_size*8}-bit encrypted key at EOF"
                )

    # Known-plaintext analysis
    if original:
        orig_data = Path(original).read_bytes()
        # XOR first block to detect key reuse
        xor_block = bytes(a ^ b for a, b in zip(data[:16], orig_data[:16]))
        result["xor_first_block"] = xor_block.hex()

        # Check if file was partially encrypted (intermittent)
        matching = sum(a == b for a, b in zip(data, orig_data))
        total = min(len(data), len(orig_data))
        result["unencrypted_ratio"] = round(matching / total, 3) if total > 0 else 0

    return result
```

### Step 2: Reverse Engineer Crypto Routine

```python
# Ghidra script: find_crypto_constants.py
"""Locate cryptographic constants in binary for algorithm identification."""

# AES S-Box (first 16 bytes)
AES_SBOX = bytes([0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5,
                   0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76])

# ChaCha20/Salsa20 constant
CHACHA_CONST = b"expand 32-byte k"
SALSA_CONST = b"expand 32-byte k"

# RSA public key indicators
RSA_INDICATORS = [b"RSA1", b"-----BEGIN PUBLIC KEY-----", b"\x30\x82"]

CRYPTO_CONSTANTS = {
    "AES": AES_SBOX,
    "ChaCha20": CHACHA_CONST,
    "Salsa20": SALSA_CONST,
}

def identify_crypto(data: bytes) -> list[str]:
    """Identify cryptographic algorithms by constants."""
    found = []
    for name, constant in CRYPTO_CONSTANTS.items():
        pos = data.find(constant)
        if pos != -1:
            found.append(f"{name} at offset 0x{pos:x}")
    for indicator in RSA_INDICATORS:
        pos = data.find(indicator)
        if pos != -1:
            found.append(f"RSA key at offset 0x{pos:x}")
    return found
```

### Step 3: Key Extraction from Memory

```bash
# Dump process memory with Volatility3
vol3 -f memory.dmp windows.memmap --pid <ransomware_pid> --dump

# Search for AES key schedule in memory
# AES-256 expanded key is 240 bytes with specific patterns
python3 -c "
import struct
data = open('pid.1234.dmp', 'rb').read()

# Search for AES key schedule pattern
# The expanded key has specific relationships between round keys
for i in range(len(data) - 240):
    candidate = data[i:i+240]
    # Check AES-256 key schedule properties
    # Round constant application leaves detectable patterns
    if candidate[0:4] != b'\x00\x00\x00\x00' and candidate[16:20] != b'\x00\x00\x00\x00':
        # Verify round key relationship: rk[i] = rk[i-Nk] XOR transform(rk[i-1])
        w0 = struct.unpack('>I', candidate[0:4])[0]
        w8 = struct.unpack('>I', candidate[32:36])[0]
        w7 = struct.unpack('>I', candidate[28:32])[0]
        # Additional validation needed...
        pass
"

# Alternative: use findaes tool
findaes memory.dmp
```

### Step 4: Attempt Decryption

```python
from Crypto.Cipher import AES, ChaCha20
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP

def decrypt_file_aes_cbc(encrypted_path: str, key: bytes, iv: bytes, output_path: str):
    """Decrypt file encrypted with AES-256-CBC."""
    data = Path(encrypted_path).read_bytes()

    # Some ransomware appends metadata — strip it
    # Adjust based on specific family's file format
    ciphertext = data[:-256]  # Example: last 256 bytes = metadata

    cipher = AES.new(key, AES.MODE_CBC, iv)
    plaintext = cipher.decrypt(ciphertext)

    # Remove PKCS7 padding
    pad_len = plaintext[-1]
    if 1 <= pad_len <= 16 and all(b == pad_len for b in plaintext[-pad_len:]):
        plaintext = plaintext[:-pad_len]

    Path(output_path).write_bytes(plaintext)
    print(f"[+] Decrypted {encrypted_path} -> {output_path}")

def decrypt_file_chacha20(encrypted_path: str, key: bytes, nonce: bytes, output_path: str):
    """Decrypt file encrypted with ChaCha20."""
    data = Path(encrypted_path).read_bytes()
    cipher = ChaCha20.new(key=key, nonce=nonce)
    plaintext = cipher.decrypt(data)
    Path(output_path).write_bytes(plaintext)

def check_weak_prng(encrypted_files: list[str]) -> bool:
    """Check if encryption uses predictable PRNG by comparing IVs."""
    ivs = []
    for f in encrypted_files:
        data = Path(f).read_bytes()
        # Extract IV (location varies by family — adjust offset)
        iv = data[:16]
        ivs.append(iv)

    # If all IVs are identical, key reuse is likely
    if len(set(ivs)) == 1:
        print("[!] All files use same IV — key stream reuse detected!")
        return True

    # Check for sequential IVs (counter-based, predictable)
    for i in range(1, len(ivs)):
        diff = int.from_bytes(ivs[i], 'big') - int.from_bytes(ivs[i-1], 'big')
        if diff == 1:
            print("[!] Sequential IVs detected — predictable encryption")
            return True

    return False
```

### Step 5: Document Findings

```yaml
# Ransomware encryption analysis report template
family: "Unknown/FamilyName"
variant: "v2.1"
encryption:
  symmetric:
    algorithm: "AES-256-CBC"
    key_size: 256
    mode: "CBC"
    iv_generation: "Random per-file"
  asymmetric:
    algorithm: "RSA-2048"
    key_size: 2048
    padding: "OAEP"
  key_derivation: "None (random key per file)"
  key_exchange: "Per-file AES key encrypted with RSA public key"
weaknesses_found:
  - description: "Offline fallback uses static key"
    exploitable: true
    decryption_possible: true
  - description: "IV derived from file path hash"
    exploitable: false
file_format:
  encrypted_content: "bytes 0 to N-256"
  encrypted_key: "last 256 bytes"
  iv: "bytes N-272 to N-256"
  original_size: "bytes N-280 to N-272"
decryption_feasibility: "POSSIBLE — offline key recoverable"
```

## Detection

```yaml
title: Ransomware Encryption Detection
id: 213763b5-1a0b-4bae-9df2-488a2e3c001f
status: experimental
description: Detects suspicious activity related to analyzing ransomware encryption techniques in malware analysis context
logsource:
  category: process_creation
  product: windows
detection:
  selection:
    CommandLine: "*analyzing*ransomware*"
  condition: selection
level: high
tags:
  - attack.t1486
  - attack.execution
falsepositives:
  - Sandbox detonation environments analyzing submitted samples
```

## Verification

- [ ] Encryption algorithm identified (symmetric + asymmetric)
- [ ] Key generation mechanism understood
- [ ] File format documented (header/footer structure)
- [ ] Crypto weaknesses assessed
- [ ] Key recovery attempted (memory dump, weak PRNG)
- [ ] Decryption tested on sample files
- [ ] Report generated with feasibility assessment
- [ ] Known decryptors checked (No More Ransom project)

## References

- [No More Ransom - Free Decryptors](https://www.nomoreransom.org/)
- [ID Ransomware](https://id-ransomware.malwarehunterteam.com/)
- [MITRE ATT&CK T1486 - Data Encrypted for Impact](https://attack.mitre.org/techniques/T1486/)
- [findaes - AES Key Finder](https://sourceforge.net/projects/findaes/)
- [PyCryptodome Documentation](https://pycryptodome.readthedocs.io/)
