malware analysis

Analyzing Ransomware Encryption Mechanisms

Analyzes encryption algorithms, key management, and file encryption routines used by ransomware families to assess decryption feasibility, identify implementation weaknesses, and support recovery efforts. Covers AES, RSA, ChaCha20, and hybrid encryption schemes. Activates for requests involving ransomware cryptanalysis, encryption analysis, key recovery assessment, or ransomware decryption feasibility.

cryptanalysisencryptionmalwareransomwarereverse-engineering
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

When to Use

  • A ransomware infection has occurred and recovery requires understanding the encryption scheme used
  • Assessing whether decryption is possible without paying the ransom (implementation flaws, known decryptors)
  • Reverse engineering ransomware to identify the encryption algorithm, key derivation, and key storage mechanism
  • Developing a decryptor tool when a weakness in the ransomware's cryptographic implementation is identified
  • Classifying a ransomware sample by its encryption approach to attribute it to a known family

Do not use for production data recovery operations without first verifying the decryption method on test copies of encrypted files.

Prerequisites

  • Ghidra or IDA Pro for reverse engineering the ransomware binary
  • Python 3.8+ with pycryptodome library for testing encryption/decryption routines
  • Sample encrypted files and their corresponding plaintext originals (known-plaintext pairs)
  • Access to the ransomware binary (unpacked if applicable)
  • Familiarity with symmetric (AES, ChaCha20) and asymmetric (RSA) cryptographic algorithms
  • NoMoreRansom.org database for checking existing free decryptors

Workflow

Step 1: Identify the Encryption Algorithm

Determine which cryptographic algorithm the ransomware uses:

# Check for Windows Crypto API usage in imports
import pefile
 
pe = pefile.PE("ransomware.exe")
 
crypto_apis = {
    "CryptAcquireContextA": "Windows CryptoAPI",
    "CryptAcquireContextW": "Windows CryptoAPI",
    "CryptGenKey": "Windows CryptoAPI key generation",
    "CryptEncrypt": "Windows CryptoAPI encryption",
    "CryptImportKey": "Windows CryptoAPI key import",
    "BCryptOpenAlgorithmProvider": "Windows CNG (modern crypto)",
    "BCryptEncrypt": "Windows CNG encryption",
    "BCryptGenerateKeyPair": "Windows CNG asymmetric key gen",
}
 
print("Crypto API Imports:")
for entry in pe.DIRECTORY_ENTRY_IMPORT:
    for imp in entry.imports:
        if imp.name and imp.name.decode() in crypto_apis:
            print(f"  {entry.dll.decode()} -> {imp.name.decode()}: {crypto_apis[imp.name.decode()]}")
Common Ransomware Encryption Schemes:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
AES-256-CBC + RSA-2048:    Most common hybrid scheme (LockBit, REvil, Conti)
AES-256-CTR + RSA-4096:    Stream cipher mode variant (BlackCat/ALPHV)
ChaCha20 + RSA-4096:       Modern stream cipher (Hive, Royal)
Salsa20 + ECDH:            Curve25519 key exchange (Babuk)
AES-128-ECB:               Weak mode - potential decryption via known-plaintext
XOR-only:                  Trivial encryption - always recoverable
Custom algorithm:          Often contains implementation flaws

Step 2: Analyze Key Generation and Management

Reverse engineer how encryption keys are generated and stored:

Key Management Patterns in Ransomware:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1. STRONG (no recovery possible without key):
   - Per-file AES key generated with CryptGenRandom
   - AES key encrypted with embedded RSA public key
   - Encrypted key appended to each file or stored separately
   - RSA private key held only by attacker's C2 server
 
2. WEAK (potential recovery):
   - AES key derived from predictable seed (timestamp, PID)
   - Same AES key used for all files (single key compromise = full recovery)
   - Key transmitted to C2 before encryption starts (PCAP may contain key)
   - XOR with short repeating key (brute-forceable)
   - PRNG seeded with GetTickCount or time() (limited keyspace)
 
3. FLAWED IMPLEMENTATION:
   - ECB mode (preserves plaintext patterns)
   - Initialization vector (IV) reuse across files
   - Key stored in plaintext in memory (recoverable from memory dump)
   - Partial encryption (only first N bytes encrypted)

Step 3: Examine File Encryption Routine

Reverse engineer the file processing logic:

// Typical ransomware file encryption flow (decompiled pseudo-code from Ghidra):
 
void encrypt_file(char *filepath) {
    // 1. Check file extension against target list
    if (!is_target_extension(filepath)) return;
 
    // 2. Generate per-file AES key (32 bytes for AES-256)
    BYTE aes_key[32];
    CryptGenRandom(hProv, 32, aes_key);
 
    // 3. Generate random IV (16 bytes)
    BYTE iv[16];
    CryptGenRandom(hProv, 16, iv);
 
    // 4. Read file contents
    HANDLE hFile = CreateFile(filepath, GENERIC_READ, ...);
    BYTE *plaintext = read_entire_file(hFile);
 
    // 5. Encrypt with AES-256-CBC
    aes_cbc_encrypt(plaintext, file_size, aes_key, iv);
 
    // 6. Encrypt AES key with RSA public key
    BYTE encrypted_key[256];  // RSA-2048 output
    rsa_encrypt(aes_key, 32, rsa_pubkey, encrypted_key);
 
    // 7. Write: encrypted_data + encrypted_key + IV to file
    write_file(filepath, encrypted_data, encrypted_key, iv);
 
    // 8. Rename file with ransomware extension
    rename_file(filepath, strcat(filepath, ".locked"));
}

Step 4: Check for Cryptographic Weaknesses

Test the implementation for exploitable flaws:

from Crypto.Cipher import AES
import os
import struct
 
# Test 1: Check if same key is used for multiple files
# Compare encrypted versions of known files
def check_key_reuse(file1_enc, file2_enc):
    with open(file1_enc, "rb") as f:
        data1 = f.read()
    with open(file2_enc, "rb") as f:
        data2 = f.read()
 
    # Extract IVs (location depends on ransomware family)
    # If IVs are same and files share encrypted blocks -> same key
    iv1 = data1[-16:]  # Example: IV at end
    iv2 = data2[-16:]
    if iv1 == iv2:
        print("[!] Same IV detected - key reuse likely")
 
# Test 2: Check for predictable key derivation
# If key is derived from timestamp, iterate possible values
def brute_force_timestamp_key(encrypted_file, known_header, timestamp_range):
    with open(encrypted_file, "rb") as f:
        encrypted_data = f.read()
 
    for ts in timestamp_range:
        # Derive key the same way ransomware does
        import hashlib
        key = hashlib.sha256(str(ts).encode()).digest()
        iv = encrypted_data[-16:]
        cipher = AES.new(key, AES.MODE_CBC, iv)
        decrypted = cipher.decrypt(encrypted_data[:16])
 
        if decrypted[:len(known_header)] == known_header:
            print(f"[!] Key found! Timestamp: {ts}")
            return key
 
    return None
 
# Test 3: Check for ECB mode (pattern preservation)
def check_ecb_mode(encrypted_file):
    with open(encrypted_file, "rb") as f:
        data = f.read()
    # ECB produces identical ciphertext for identical plaintext blocks
    blocks = [data[i:i+16] for i in range(0, len(data), 16)]
    unique = len(set(blocks))
    total = len(blocks)
    if unique < total * 0.95:
        print(f"[!] ECB mode likely: {total-unique} duplicate blocks out of {total}")

Step 5: Attempt Key Recovery

Use identified weaknesses for key recovery:

# Recovery Method 1: Extract key from memory dump
# Volatility plugin to scan for AES key schedules
# vol3 -f memory.dmp windows.yarascan --yara-rule "aes_key_schedule"
 
# Recovery Method 2: Known-plaintext attack (weak algorithms)
def xor_key_recovery(encrypted_file, known_plaintext):
    """Recover XOR key from known plaintext-ciphertext pair"""
    with open(encrypted_file, "rb") as f:
        ciphertext = f.read()
 
    key = bytes(c ^ p for c, p in zip(ciphertext, known_plaintext))
    # Find repeating key length
    for key_len in range(1, 256):
        candidate = key[:key_len]
        if all(key[i] == candidate[i % key_len] for i in range(min(len(key), key_len * 4))):
            print(f"XOR key (length {key_len}): {candidate.hex()}")
            return candidate
    return None
 
# Recovery Method 3: Check NoMoreRansom for existing decryptors
# https://www.nomoreransom.org/en/decryption-tools.html

Step 6: Document Encryption Analysis

Compile findings into a structured report:

Analysis should document:
- Algorithm identified (AES, RSA, ChaCha20, custom)
- Key size and mode of operation (CBC, CTR, ECB, GCM)
- Key generation method (CSPRNG, predictable seed, static key)
- Key storage location (appended to file, registry, C2 transmission)
- File modification pattern (full encryption, partial, header-only)
- Targeted file extensions
- Ransom note format and payment infrastructure
- Decryption feasibility assessment (possible/impossible/partial)
- Recommended recovery approach

Key Concepts

Term Definition
Hybrid Encryption Combining symmetric (AES) for fast file encryption with asymmetric (RSA) for secure key wrapping; the standard ransomware approach
Key Wrapping Encrypting the per-file symmetric key with the attacker's RSA public key so only the attacker's private key can decrypt it
ECB Mode Electronic Codebook mode encrypts each block independently; preserves patterns in plaintext, a critical weakness enabling partial recovery
Known-Plaintext Attack Using a known original file and its encrypted version to derive the encryption key; effective against XOR and weak stream ciphers
Key Schedule The expanded form of an AES key in memory; scannable in memory dumps to recover encryption keys before they are erased
CSPRNG Cryptographically Secure Pseudo-Random Number Generator; ransomware using CryptGenRandom produces unpredictable keys
Partial Encryption Some ransomware only encrypts the first N bytes or every Nth block for speed; unencrypted portions may aid recovery

Tools & Systems

  • Ghidra: Reverse engineering suite for analyzing ransomware encryption routines at the assembly level
  • PyCryptodome: Python cryptographic library for implementing and testing decryption routines
  • NoMoreRansom.org: Free decryption tool repository maintained by Europol and security vendors for known ransomware families
  • Volatility: Memory forensics framework for extracting encryption keys from RAM dumps of infected systems
  • CryptoTester: Tool for identifying cryptographic algorithms based on constants and code patterns

Common Scenarios

Scenario: Assessing Decryption Feasibility for a Ransomware Incident

Context: An organization is hit with ransomware encrypting file servers. Management needs to know if decryption is possible without paying the ransom before making a recovery decision.

Approach:

  1. Identify the ransomware family from ransom note, file extension, and sample hash (check ID Ransomware)
  2. Check NoMoreRansom.org for existing free decryptors for this family
  3. Reverse engineer the encryption routine in Ghidra to identify the algorithm and key management
  4. Test for implementation weaknesses (key reuse, predictable seeds, ECB mode)
  5. Check if PCAP from the incident captured the key transmission to C2 (if key was sent before encryption)
  6. Scan memory dumps from affected machines for AES key schedules in RAM
  7. Report findings: decryption possible/impossible with specific technical justification

Pitfalls:

  • Testing decryption methods on the only copy of encrypted files (always work on copies)
  • Assuming all files use the same key without verifying (some ransomware uses per-file keys)
  • Not checking for volume shadow copies (vssadmin) which ransomware may have failed to delete
  • Confusing the file encryption algorithm with the key wrapping algorithm in reports

Output Format

RANSOMWARE ENCRYPTION ANALYSIS
================================
Sample:           lockbit3.exe
Family:           LockBit 3.0 / LockBit Black
SHA-256:          abc123def456...
 
ENCRYPTION SCHEME
File Cipher:      AES-256-CTR (per-file unique key)
Key Wrapping:     RSA-2048 (public key embedded in binary)
Key Generation:   CryptGenRandom (CSPRNG - unpredictable)
IV Generation:    Random 16 bytes per file
File Structure:   [encrypted_data][rsa_encrypted_key(256B)][iv(16B)][magic(8B)]
 
TARGETED EXTENSIONS
Total:            412 extensions targeted
Categories:       Documents (.doc, .xls, .pdf), Databases (.sql, .mdb),
                  Archives (.zip, .7z), Source code (.py, .java, .cs)
Excluded:         .exe, .dll, .sys, .lnk (system files preserved)
 
IMPLEMENTATION ANALYSIS
Key Strength:     STRONG - per-file random keys, no reuse
Mode Security:    STRONG - CTR mode with unique nonces
Key Storage:      RSA-encrypted key appended to each file
Shadow Copies:    Deleted via vssadmin and WMI
 
DECRYPTION FEASIBILITY
Without Key:      NOT POSSIBLE
  - No implementation flaws identified
  - RSA-2048 key wrapping prevents brute force
  - CSPRNG prevents key prediction
  - No existing free decryptor available
 
RECOVERY OPTIONS
1. Restore from offline backups (recommended)
2. Check for volume shadow copies (low probability - ransomware deletes them)
3. Memory forensics if machine was not rebooted (key may persist in RAM)
4. Negotiate with attacker (last resort - no guarantee of decryption)
Source materials

References and resources

Everything below is rendered for inspection. Script files are read-only and never run.

References 1

api-reference.md4.0 KB

API Reference: Ransomware Encryption Mechanism Analysis

PyCryptodome - Encryption Testing

AES Decryption

from Crypto.Cipher import AES
 
# AES-CBC
cipher = AES.new(key, AES.MODE_CBC, iv)
plaintext = cipher.decrypt(ciphertext)
 
# AES-CTR
cipher = AES.new(key, AES.MODE_CTR, nonce=nonce)
plaintext = cipher.decrypt(ciphertext)
 
# AES-ECB (weak mode used by some ransomware)
cipher = AES.new(key, AES.MODE_ECB)
plaintext = cipher.decrypt(ciphertext)

ChaCha20 Decryption

from Crypto.Cipher import ChaCha20
cipher = ChaCha20.new(key=key, nonce=nonce)
plaintext = cipher.decrypt(ciphertext)

RSA Key Analysis

from Crypto.PublicKey import RSA
key = RSA.import_key(open("pubkey.pem").read())
print(f"Key size: {key.size_in_bits()} bits")
print(f"Modulus (n): {key.n}")
print(f"Exponent (e): {key.e}")

pefile - Crypto API Import Detection

Syntax

import pefile
pe = pefile.PE("ransomware.exe")
for entry in pe.DIRECTORY_ENTRY_IMPORT:
    for imp in entry.imports:
        print(f"{entry.dll.decode()} -> {imp.name}")

Key Windows Crypto APIs

API Purpose
CryptAcquireContext Initialize crypto provider
CryptGenRandom CSPRNG random bytes
CryptGenKey Generate symmetric key
CryptEncrypt Encrypt data via CryptoAPI
CryptImportKey Import key blob
BCryptOpenAlgorithmProvider CNG algorithm handle
BCryptEncrypt CNG encryption
BCryptGenerateKeyPair CNG asymmetric keygen

Volatility 3 - Key Recovery from Memory

Syntax

vol3 -f memory.dmp windows.yarascan --yara-rule "aes_key"
vol3 -f memory.dmp windows.malfind
vol3 -f memory.dmp windows.pslist
vol3 -f memory.dmp windows.handles --pid <PID>

AES Key Schedule YARA Rule

rule AES_Key_Schedule {
    strings:
        $sbox = { 63 7c 77 7b f2 6b 6f c5 30 01 67 2b fe d7 ab 76 }
    condition:
        $sbox
}

Entropy Analysis Thresholds

Range Interpretation
0-1 Empty / uniform data
1-5 Normal code / plaintext
5-7 Compressed or obfuscated
7-7.9 Encrypted (block cipher)
7.9-8.0 Encrypted (stream cipher / AES-CTR)

Known Ransomware Encryption Schemes

Family File Cipher Key Wrapping Weakness
WannaCry AES-128-CBC RSA-2048 Key may persist in memory
LockBit 3.0 AES-256-CTR RSA-2048 None known
Conti AES-256-CBC RSA-4096 Leaked builder exposes keys
REvil Salsa20 ECDH None known
STOP/Djvu AES-256-CFB RSA-1024 Offline key variant decryptable
Hive ChaCha20 RSA-4096 Master key recovered by FBI
BlackCat AES-256 RSA-4096 None known
Babuk ChaCha20 ECDH (Curve25519) Leaked source code
Akira ChaCha20 RSA-4096 None known
Phobos AES-256-CBC RSA-1024 Weak RSA key size

File Structure Patterns

Common Ransomware File Layout

[encrypted_data][encrypted_aes_key(256B)][iv(16B)][magic_marker(4-8B)]

Identifying Appended Metadata

with open("file.locked", "rb") as f:
    f.seek(-280, 2)  # Seek 280 bytes from end
    tail = f.read()
    rsa_blob = tail[:256]   # RSA-2048 encrypted key
    iv = tail[256:272]      # AES IV (16 bytes)
    marker = tail[272:]     # Ransomware magic marker

NoMoreRansom / ID Ransomware

Identification

Upload encrypted file + ransom note to:
  https://id-ransomware.malwarehunterteam.com/

Free Decryptors

Check for available decryptors:
  https://www.nomoreransom.org/en/decryption-tools.html

Ghidra - Reverse Engineering Crypto Routines

Crypto Identification Steps

1. Search > For Strings > "AES", "RSA", "Crypt", "encrypt"
2. Search > For Bytes > AES S-Box: 63 7c 77 7b f2 6b
3. Imports > advapi32.dll / bcrypt.dll for Crypto API calls
4. Trace CryptEncrypt xrefs to find encryption routine
5. Identify key buffer size (16=AES-128, 32=AES-256)
6. Check for CryptGenRandom vs time()/GetTickCount seed

Scripts 1

agent.py13.5 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""Ransomware encryption mechanism analysis agent.

Analyzes encryption algorithms, key management, file encryption routines,
and assesses decryption feasibility for ransomware samples and encrypted files.
"""

import os
import sys
import hashlib
import math
import json
from collections import Counter


def compute_hash(filepath):
    """Compute SHA-256 hash of a file."""
    sha256 = hashlib.sha256()
    with open(filepath, "rb") as f:
        for chunk in iter(lambda: f.read(65536), b""):
            sha256.update(chunk)
    return sha256.hexdigest()


def shannon_entropy(data):
    """Calculate Shannon entropy of byte data."""
    if not data:
        return 0.0
    freq = Counter(data)
    length = len(data)
    return -sum((c / length) * math.log2(c / length) for c in freq.values())


CRYPTO_CONSTANTS = {
    bytes.fromhex("637c777bf26b6fc53001672bfed7ab76"): "AES S-Box (Rijndael)",
    bytes.fromhex("52096ad53036a538bf40a39e81f3d7fb"): "AES S-Box (continued)",
    bytes.fromhex("6a09e667bb67ae853c6ef372a54ff53a"): "SHA-256 initialization vector",
    b"expand 32-byte k": "ChaCha20/Salsa20 constant (256-bit key)",
    b"expand 16-byte k": "ChaCha20/Salsa20 constant (128-bit key)",
    bytes.fromhex("d1310ba698dfb5ac"): "Blowfish P-array fragment",
}

CRYPTO_API_NAMES = [
    b"CryptAcquireContext", b"CryptGenKey", b"CryptEncrypt", b"CryptDecrypt",
    b"CryptImportKey", b"CryptExportKey", b"CryptGenRandom", b"CryptDeriveKey",
    b"BCryptOpenAlgorithmProvider", b"BCryptEncrypt", b"BCryptGenerateKeyPair",
    b"BCryptGenerateSymmetricKey", b"BCryptCreateHash",
    b"RtlEncryptMemory", b"RtlDecryptMemory",
]

RANSOMWARE_EXTENSIONS = {
    ".locked": ["LockBit", "Generic"],
    ".encrypt": ["Generic"],
    ".crypt": ["CryptXXX", "Generic"],
    ".locky": ["Locky"],
    ".cerber": ["Cerber"],
    ".zepto": ["Locky variant"],
    ".odin": ["Locky variant"],
    ".aesir": ["Locky variant"],
    ".wncry": ["WannaCry"],
    ".WNCRY": ["WannaCry"],
    ".wnry": ["WannaCry"],
    ".wcry": ["WannaCry"],
    ".dharma": ["Dharma/CrySiS"],
    ".basta": ["Black Basta"],
    ".blackcat": ["BlackCat/ALPHV"],
    ".hive": ["Hive"],
    ".royal": ["Royal"],
    ".rhysida": ["Rhysida"],
    ".akira": ["Akira"],
    ".lockbit": ["LockBit 3.0"],
    ".conti": ["Conti"],
    ".ryuk": ["Ryuk"],
    ".maze": ["Maze"],
    ".revil": ["REvil/Sodinokibi"],
    ".sodinokibi": ["REvil/Sodinokibi"],
    ".phobos": ["Phobos"],
    ".makop": ["Makop"],
    ".stop": ["STOP/Djvu"],
    ".djvu": ["STOP/Djvu"],
}


def identify_ransomware_extension(filepath):
    """Identify ransomware family from file extension."""
    ext = os.path.splitext(filepath)[1].lower()
    if ext in RANSOMWARE_EXTENSIONS:
        return {"extension": ext, "families": RANSOMWARE_EXTENSIONS[ext]}
    for known_ext, families in RANSOMWARE_EXTENSIONS.items():
        if ext.endswith(known_ext):
            return {"extension": ext, "families": families}
    return {"extension": ext, "families": ["Unknown"]}


def scan_crypto_constants(filepath):
    """Scan binary for known cryptographic constants."""
    with open(filepath, "rb") as f:
        data = f.read()
    findings = []
    for const_bytes, description in CRYPTO_CONSTANTS.items():
        offset = data.find(const_bytes)
        if offset != -1:
            findings.append({
                "constant": description,
                "offset": f"0x{offset:08X}",
                "hex": const_bytes[:16].hex(),
            })
    return findings


def scan_crypto_apis(filepath):
    """Scan binary for Windows Crypto API string references."""
    with open(filepath, "rb") as f:
        data = f.read()
    found = []
    for api in CRYPTO_API_NAMES:
        if api in data:
            found.append(api.decode("ascii", errors="replace"))
    return found


def analyze_encrypted_file(filepath):
    """Analyze an encrypted file for ransomware characteristics."""
    with open(filepath, "rb") as f:
        data = f.read()

    file_size = len(data)
    entropy = shannon_entropy(data)

    # Check for appended metadata (many ransomware families append key material)
    tail_256 = data[-256:] if file_size >= 256 else data
    tail_entropy = shannon_entropy(tail_256)

    # Check for ECB mode (duplicate 16-byte blocks)
    blocks_16 = [data[i:i+16] for i in range(0, min(len(data), 65536), 16)]
    unique_16 = len(set(blocks_16))
    total_16 = len(blocks_16)
    ecb_ratio = 1.0 - (unique_16 / total_16) if total_16 > 0 else 0

    # Check for partial encryption (low entropy regions)
    chunk_size = min(4096, file_size // 4) if file_size > 16 else file_size
    first_entropy = shannon_entropy(data[:chunk_size]) if chunk_size > 0 else 0
    mid_offset = file_size // 2
    mid_entropy = shannon_entropy(data[mid_offset:mid_offset+chunk_size]) if chunk_size > 0 else 0
    last_entropy = shannon_entropy(data[-chunk_size:]) if chunk_size > 0 else 0

    # Detect magic bytes at tail (ransomware markers)
    tail_8 = data[-8:] if file_size >= 8 else data
    tail_marker = tail_8.hex() if all(b > 0 for b in tail_8) else None

    return {
        "file_size": file_size,
        "overall_entropy": round(entropy, 4),
        "tail_256_entropy": round(tail_entropy, 4),
        "ecb_duplicate_ratio": round(ecb_ratio, 4),
        "ecb_likely": ecb_ratio > 0.05,
        "partial_encryption": {
            "first_chunk_entropy": round(first_entropy, 4),
            "mid_chunk_entropy": round(mid_entropy, 4),
            "last_chunk_entropy": round(last_entropy, 4),
            "likely_partial": abs(first_entropy - mid_entropy) > 2.0,
        },
        "tail_marker_hex": tail_marker,
        "fully_encrypted": entropy > 7.5,
    }


def xor_key_recovery(encrypted_data, known_plaintext):
    """Attempt XOR key recovery from known plaintext-ciphertext pair."""
    if len(known_plaintext) == 0:
        return None
    key_stream = bytes(c ^ p for c, p in zip(encrypted_data, known_plaintext))
    # Detect repeating key
    for key_len in range(1, min(256, len(key_stream) // 2)):
        candidate = key_stream[:key_len]
        match = all(
            key_stream[i] == candidate[i % key_len]
            for i in range(min(len(key_stream), key_len * 4))
        )
        if match and key_len < len(key_stream):
            return {"key_hex": candidate.hex(), "key_length": key_len, "key_ascii": candidate.decode("ascii", errors="replace")}
    return None


def check_file_header_known_plaintext(encrypted_filepath):
    """Check if encrypted file retains known file header (partial encryption indicator)."""
    KNOWN_HEADERS = {
        b"%PDF": "PDF document",
        b"PK\x03\x04": "ZIP/DOCX/XLSX archive",
        b"\x89PNG": "PNG image",
        b"\xff\xd8\xff": "JPEG image",
        b"MZ": "PE executable",
        b"\x7fELF": "ELF executable",
        b"Rar!": "RAR archive",
        b"\xd0\xcf\x11\xe0": "OLE2 (DOC/XLS)",
        b"SQLite format 3": "SQLite database",
    }
    with open(encrypted_filepath, "rb") as f:
        header = f.read(16)
    for magic, filetype in KNOWN_HEADERS.items():
        if header[:len(magic)] == magic:
            return {"detected": True, "original_type": filetype,
                    "note": "File header intact - partial encryption or not encrypted"}
    return {"detected": False, "note": "No known file header found - likely fully encrypted from start"}


def assess_decryption_feasibility(crypto_constants, crypto_apis, enc_analysis):
    """Assess decryption feasibility based on analysis results."""
    weaknesses = []
    strong_points = []

    if enc_analysis.get("ecb_likely"):
        weaknesses.append("ECB mode detected - block patterns preserved, partial plaintext recovery possible")
    if enc_analysis.get("partial_encryption", {}).get("likely_partial"):
        weaknesses.append("Partial encryption detected - unencrypted file regions may aid recovery")
    if enc_analysis.get("overall_entropy", 8) < 6.0:
        weaknesses.append("Low entropy suggests weak or partial encryption")

    has_csprng = any("GenRandom" in api for api in crypto_apis)
    has_rsa = any("KeyPair" in api or "ImportKey" in api for api in crypto_apis)
    has_aes = any("AES" in c.get("constant", "") or "Rijndael" in c.get("constant", "") for c in crypto_constants)
    has_chacha = any("ChaCha" in c.get("constant", "") or "Salsa" in c.get("constant", "") for c in crypto_constants)

    if has_csprng:
        strong_points.append("CSPRNG key generation (CryptGenRandom) - keys not predictable")
    if has_rsa:
        strong_points.append("RSA key wrapping - per-file keys protected by asymmetric encryption")
    if has_aes:
        strong_points.append("AES encryption identified")
    if has_chacha:
        strong_points.append("ChaCha20/Salsa20 stream cipher identified")

    if not has_csprng:
        weaknesses.append("No CSPRNG detected - key generation may be predictable")

    feasibility = "NOT POSSIBLE" if len(strong_points) >= 2 and len(weaknesses) == 0 else \
                  "POSSIBLE" if len(weaknesses) >= 2 else \
                  "UNLIKELY - check for specific implementation flaws"

    return {
        "feasibility": feasibility,
        "weaknesses": weaknesses,
        "strong_points": strong_points,
        "recommendation": "Check NoMoreRansom.org and memory forensics" if feasibility != "NOT POSSIBLE"
                          else "Restore from backups; no cryptographic weakness found",
    }


def generate_report(sample_path=None, encrypted_path=None):
    """Generate full ransomware encryption analysis report."""
    report = {"analysis_type": "Ransomware Encryption Mechanism Analysis"}

    if sample_path and os.path.exists(sample_path):
        report["sample"] = {
            "path": sample_path,
            "sha256": compute_hash(sample_path),
            "size": os.path.getsize(sample_path),
            "entropy": round(shannon_entropy(open(sample_path, "rb").read()), 4),
        }
        report["crypto_constants"] = scan_crypto_constants(sample_path)
        report["crypto_apis"] = scan_crypto_apis(sample_path)

    if encrypted_path and os.path.exists(encrypted_path):
        report["encrypted_file"] = {
            "path": encrypted_path,
            "sha256": compute_hash(encrypted_path),
            "family_match": identify_ransomware_extension(encrypted_path),
        }
        report["encryption_analysis"] = analyze_encrypted_file(encrypted_path)
        report["header_check"] = check_file_header_known_plaintext(encrypted_path)

    if "crypto_constants" in report or "encryption_analysis" in report:
        report["feasibility"] = assess_decryption_feasibility(
            report.get("crypto_constants", []),
            report.get("crypto_apis", []),
            report.get("encryption_analysis", {}),
        )

    return report


if __name__ == "__main__":
    print("=" * 60)
    print("Ransomware Encryption Mechanism Analysis Agent")
    print("Algorithm identification, key analysis, decryption feasibility")
    print("=" * 60)

    if len(sys.argv) < 2:
        print("\n[DEMO] Usage:")
        print("  python agent.py <ransomware_binary>               # Analyze ransomware sample")
        print("  python agent.py <ransomware_binary> <encrypted_file>  # Full analysis")
        print("  python agent.py --encrypted <encrypted_file>      # Analyze encrypted file only")
        sys.exit(0)

    sample = None
    encrypted = None

    if sys.argv[1] == "--encrypted" and len(sys.argv) > 2:
        encrypted = sys.argv[2]
    else:
        sample = sys.argv[1]
        encrypted = sys.argv[2] if len(sys.argv) > 2 else None

    report = generate_report(sample_path=sample, encrypted_path=encrypted)

    if sample and os.path.exists(sample):
        info = report.get("sample", {})
        print(f"\n[*] Sample: {sample}")
        print(f"    SHA-256: {info.get('sha256', 'N/A')}")
        print(f"    Size: {info.get('size', 0)} bytes")
        print(f"    Entropy: {info.get('entropy', 0)}")

        print("\n--- Crypto Constants Found ---")
        for c in report.get("crypto_constants", []):
            print(f"  [{c['offset']}] {c['constant']}")

        print("\n--- Crypto API Imports ---")
        for api in report.get("crypto_apis", []):
            print(f"  {api}")

    if encrypted and os.path.exists(encrypted):
        fm = report.get("encrypted_file", {}).get("family_match", {})
        print(f"\n[*] Encrypted file: {encrypted}")
        print(f"    Extension: {fm.get('extension', '?')}")
        print(f"    Possible families: {', '.join(fm.get('families', ['Unknown']))}")

        ea = report.get("encryption_analysis", {})
        print(f"\n--- Encryption Analysis ---")
        print(f"  Overall entropy: {ea.get('overall_entropy', 0)}")
        print(f"  Fully encrypted: {ea.get('fully_encrypted', False)}")
        print(f"  ECB mode likely: {ea.get('ecb_likely', False)}")
        partial = ea.get("partial_encryption", {})
        print(f"  Partial encryption: {partial.get('likely_partial', False)}")

        hc = report.get("header_check", {})
        print(f"\n--- Header Check ---")
        print(f"  Known header: {hc.get('detected', False)}")
        print(f"  Note: {hc.get('note', '')}")

    if "feasibility" in report:
        f = report["feasibility"]
        print(f"\n--- Decryption Feasibility ---")
        print(f"  Assessment: {f['feasibility']}")
        print(f"  Weaknesses:")
        for w in f.get("weaknesses", []):
            print(f"    [!] {w}")
        print(f"  Strong points:")
        for s in f.get("strong_points", []):
            print(f"    [+] {s}")
        print(f"  Recommendation: {f['recommendation']}")

    print(f"\n[*] Full report:\n{json.dumps(report, indent=2, default=str)}")
Keep exploring