分析勒索软件加密机制
何时使用
- 勒索软件感染发生后,恢复数据需要了解所使用的加密方案
- 评估在不支付赎金的情况下是否可能解密(实现缺陷、已知解密器)
- 逆向分析勒索软件,以确定加密算法、密钥派生和密钥存储机制
- 在识别到勒索软件加密实现存在弱点时,开发解密工具
- 按照加密方法对勒索软件样本进行分类,以归属到已知家族
请勿 在未首先对加密文件的测试副本验证解密方法的情况下,用于生产数据恢复操作。
先决条件
- Ghidra 或 IDA Pro,用于逆向分析勒索软件二进制文件
- Python 3.8+,并安装
pycryptodome库,用于测试加密/解密例程 - 加密文件的样本及其对应的明文原始文件(已知明文对)
- 能够访问勒索软件二进制文件(若已加壳则需脱壳)
- 熟悉对称加密(AES、ChaCha20)和非对称加密(RSA)算法
- NoMoreRansom.org 数据库,用于检查已有的免费解密器
工作流程
步骤 1:识别加密算法
确定勒索软件使用了哪种加密算法:
# 检查导入表中是否使用 Windows Crypto API
import pefile
pe = pefile.PE("ransomware.exe")
crypto_apis = {
"CryptAcquireContextA": "Windows CryptoAPI",
"CryptAcquireContextW": "Windows CryptoAPI",
"CryptGenKey": "Windows CryptoAPI 密钥生成",
"CryptEncrypt": "Windows CryptoAPI 加密",
"CryptImportKey": "Windows CryptoAPI 密钥导入",
"BCryptOpenAlgorithmProvider": "Windows CNG(现代加密)",
"BCryptEncrypt": "Windows CNG 加密",
"BCryptGenerateKeyPair": "Windows CNG 非对称密钥生成",
}
print("Crypto API 导入:")
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()]}")
常见勒索软件加密方案:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
AES-256-CBC + RSA-2048: 最常见的混合方案(LockBit, REvil, Conti)
AES-256-CTR + RSA-4096: 流密码模式变体(BlackCat/ALPHV)
ChaCha20 + RSA-4096: 现代流密码(Hive, Royal)
Salsa20 + ECDH: Curve25519 密钥交换(Babuk)
AES-128-ECB: 弱模式 - 可能通过已知明文解密
仅 XOR: 简单加密 - 总是可恢复
自定义算法: 通常包含实现缺陷
步骤 2:分析密钥生成与管理
逆向分析加密密钥是如何生成和存储的:
勒索软件中的密钥管理模式:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1. 强(无密钥则无法恢复):
- 使用 CryptGenRandom 生成每文件 AES 密钥
- 使用嵌入的 RSA 公钥加密 AES 密钥
- 加密后的密钥附加到每个文件末尾或单独存储
- RSA 私钥仅由攻击者的 C2 服务器持有
2. 弱(可能恢复):
- AES 密钥从可预测的种子(时间戳、PID)派生
- 所有文件使用相同的 AES 密钥(单个密钥泄露 = 完全恢复)
- 密钥在加密前传输到 C2(PCAP 中可能包含密钥)
- 使用短重复密钥的 XOR(可暴力破解)
- PRNG 使用 GetTickCount 或 time() 播种(密钥空间有限)
3. 有缺陷的实现:
- ECB 模式(保留明文模式)
- 初始化向量 (IV) 跨文件重用
- 密钥以明文形式存储在内存中(可从内存转储中恢复)
- 部分加密(仅加密前 N 个字节)
步骤 3:检查文件加密例程
逆向分析文件处理逻辑:
// 典型的勒索软件文件加密流程(Ghidra 反编译伪代码):
void encrypt_file(char *filepath) {
// 1. 检查文件扩展名是否在目标列表中
if (!is_target_extension(filepath)) return;
// 2. 生成每文件 AES 密钥(AES-256 为 32 字节)
BYTE aes_key[32];
CryptGenRandom(hProv, 32, aes_key);
// 3. 生成随机 IV(16 字节)
BYTE iv[16];
CryptGenRandom(hProv, 16, iv);
// 4. 读取文件内容
HANDLE hFile = CreateFile(filepath, GENERIC_READ, ...);
BYTE *plaintext = read_entire_file(hFile);
// 5. 使用 AES-256-CBC 加密
aes_cbc_encrypt(plaintext, file_size, aes_key, iv);
// 6. 使用 RSA 公钥加密 AES 密钥
BYTE encrypted_key[256]; // RSA-2048 输出
rsa_encrypt(aes_key, 32, rsa_pubkey, encrypted_key);
// 7. 写入文件:encrypted_data + encrypted_key + IV
write_file(filepath, encrypted_data, encrypted_key, iv);
// 8. 使用勒索软件扩展名重命名文件
rename_file(filepath, strcat(filepath, ".locked"));
}
步骤 4:检查加密弱点
测试实现是否存在可被利用的缺陷:
from Crypto.Cipher import AES
import os
import struct
# 测试 1:检查是否对多个文件使用相同的密钥
# 比较已知文件的加密版本
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()
# 提取 IV(位置取决于勒索软件家族)
# 如果 IV 相同且文件共享加密块 -> 相同密钥
iv1 = data1[-16:] # 示例:IV 在末尾
iv2 = data2[-16:]
if iv1 == iv2:
print("[!] 检测到相同 IV - 很可能密钥重用")
# 测试 2:检查是否存在可预测的密钥派生
# 如果密钥从时间戳派生,则迭代可能的值
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:
# 以勒索软件相同的方式派生密钥
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"[!] 找到密钥!时间戳: {ts}")
return key
return None
# 测试 3:检查是否为 ECB 模式(模式保留)
def check_ecb_mode(encrypted_file):
with open(encrypted_file, "rb") as f:
data = f.read()
# ECB 对相同的明文块产生相同的密文块
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 模式:{total-unique} 个重复块(共 {total})")
步骤 5:尝试密钥恢复
利用已识别的弱点进行密钥恢复:
# 恢复方法 1:从内存转储中提取密钥
# 使用 Volatility 插件扫描 AES 密钥调度
# vol3 -f memory.dmp windows.yarascan --yara-rule "aes_key_schedule"
# 恢复方法 2:已知明文攻击(弱算法)
def xor_key_recovery(encrypted_file, known_plaintext):
"""从已知明文-密文对恢复 XOR 密钥"""
with open(encrypted_file, "rb") as f:
ciphertext = f.read()
key = bytes(c ^ p for c, p in zip(ciphertext, known_plaintext))
# 查找重复密钥长度
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_len}):{candidate.hex()}")
return candidate
return None
# 恢复方法 3:检查 NoMoreRansom 上已有的解密器
# https://www.nomoreransom.org/en/decryption-tools.html
步骤 6:记录加密分析
将发现整理成结构化报告:
分析应记录:
- 识别出的算法(AES、RSA、ChaCha20、自定义)
- 密钥大小和操作模式(CBC、CTR、ECB、GCM)
- 密钥生成方法(CSPRNG、可预测种子、静态密钥)
- 密钥存储位置(附加到文件、注册表、C2 传输)
- 文件修改模式(完全加密、部分加密、仅头部加密)
- 被针对的文件扩展名
- 赎金票据格式和支付基础设施
- 解密可行性评估(可能/不可能/部分)
- 建议的恢复方法
关键概念
| 术语 | 定义 |
|---|---|
| 混合加密 | 结合对称加密(AES)实现快速文件加密与非对称加密(RSA)实现安全的密钥封装;标准的勒索软件方法 |
| 密钥封装 | 用攻击者的 RSA 公钥加密每文件的对称密钥,以便只有攻击者的私钥才能解密它 |
| ECB 模式 | 电子密码本模式独立加密每个块;保留明文中的模式,是允许部分恢复的关键弱点 |
| 已知明文攻击 | 使用已知的原始文件及其加密版本推导加密密钥;对 XOR 和弱流密码有效 |
| 密钥调度 | AES 密钥在内存中的扩展形式;可在内存转储中扫描以在密钥被擦除前恢复加密密钥 |
| CSPRNG | 密码学安全伪随机数生成器;使用 CryptGenRandom 的勒索软件产生不可预测的密钥 |
| 部分加密 | 某些勒索软件为了速度只加密前 N 个字节或每隔 N 个块;未加密的部分可能有助于恢复 |
工具与系统
- Ghidra:逆向工程套件,用于在汇编级别分析勒索软件加密例程
- PyCryptodome:Python 密码学库,用于实现和测试解密例程
- NoMoreRansom.org:由 Europol 和安全厂商维护的免费解密工具仓库,涵盖已知勒索软件家族
- Volatility:内存取证框架,用于从受感染系统的 RAM 转储中提取加密密钥
- CryptoTester:基于常量和代码模式识别加密算法的工具
常见场景
场景:评估勒索软件事件中解密的可行性
背景:某组织遭到勒索软件攻击,文件服务器被加密。管理层需要在做出恢复决策之前,了解在不支付赎金的情况下是否可能解密。
方法:
- 通过赎金票据、文件扩展名和样本哈希识别勒索软件家族(检查 ID Ransomware)
- 在 NoMoreRansom.org 上检查该家族是否存在已有的免费解密器
- 在 Ghidra 中逆向分析加密例程,以识别算法和密钥管理方式
- 测试实现弱点(密钥重用、可预测种子、ECB 模式)
- 检查事件中的 PCAP 是否捕获了向 C2 传输的密钥(如果在加密前发送了密钥)
- 扫描受影响机器的内存转储,查找 RAM 中的 AES 密钥调度
- 报告结果:解密可能/不可能,并附上具体的技术理由
陷阱:
- 在加密文件的唯一副本上测试解密方法(始终在副本上操作)
- 假设所有文件使用相同的密钥而不验证(某些勒索软件使用每文件密钥)
- 未检查卷影副本(vssadmin),勒索软件可能未能删除它们
- 在报告中混淆文件加密算法与密钥封装算法
输出格式
勒索软件加密分析
================================
样本: lockbit3.exe
家族: LockBit 3.0 / LockBit Black
SHA-256: abc123def456...
加密方案
文件加密: AES-256-CTR(每文件唯一密钥)
密钥封装: RSA-2048(公钥嵌入二进制文件中)
密钥生成: CryptGenRandom(CSPRNG - 不可预测)
IV 生成: 每个文件 16 字节随机数
文件结构: [encrypted_data][rsa_encrypted_key(256B)][iv(16B)][magic(8B)]
目标扩展名
总计: 412 个扩展名被针对
类别: 文档 (.doc, .xls, .pdf),数据库 (.sql, .mdb),
归档 (.zip, .7z),源代码 (.py, .java, .cs)
排除项: .exe, .dll, .sys, .lnk(系统文件被保留)
实现分析
密钥强度: 强 - 每文件随机密钥,无重用
模式安全性: 强 - CTR 模式,使用唯一随机数
密钥存储: RSA 加密后的密钥附加到每个文件末尾
卷影副本: 通过 vssadmin 和 WMI 删除
解密可行性
无密钥: 不可能
- 未发现实现缺陷
- RSA-2048 密钥封装阻止暴力破解
- CSPRNG 阻止密钥预测
- 没有可用的现有免费解密器
恢复选项
1. 从离线备份恢复(推荐)
2. 检查卷影副本(可能性低 - 勒索软件会删除它们)
3. 如果机器未重启,进行内存取证(密钥可能仍在 RAM 中)
4. 与攻击者谈判(最后手段 - 不保证解密)