近日,Proofpoint 公司的病毒分析工程师发现一批利用Rig 漏洞利用包传播CryptoShield勒索软件。这波攻击大多利用flash漏洞进行,对于flash漏洞,本人就不再班门弄斧。我们今天就重点分析这一波攻击中所使用的CryptoShield勒索软件。该勒索软件使用了RSA和AES加密算法,使用RSA生成的公钥做为AES算法的密钥,使用AES加密用户文件。在用户机器无法与CC服务器通信的情况下,勒索软件会使用自身硬编码的密钥进行文件的加密,这就为单机受害用户提供了恢复文件的可能,文末提供于单机受害用户的文件恢复方法。
CryptoShield加密后提示文件显示的内容如下:
# RESTORING FILES #.HTML与# RESTORING FILES #.TXT内容
我们得到的可执行文件大小不足100K, 可执行文件中的资源文件保存了加密过勒索程序。可执行文件起到了loader的作用。
Loader运行后,会从自身读取类型为“BKJSYHFIOAJSHBGYHFJHASIODFJHAHIJOSKLFAS”的资源数据,然后通过RC4算法解密读取到的资源数据得到勒索程序,随后在内存动态加载勒索软件并跳到勒索程序的入口点处执行,对受害用户的磁盘进行扫描加密。
1. 加载资源过程:
资源内容:
读取资源后,判断资源中的第一个byte是不是字符’Z’,如果不是,就进行解密资源操作。
解密算法为RC4,解密密钥为“P*2&%@jJFHSUFH3”,
解密过后的内容为PE文件,可以将文件保存出来留做后面分析。
随后,程序在内存中加载解密出的PE文件并到解密出的PE的OEP处执行
上面解密出来的PE文件才是真正的勒索程序。我们将从勒索程序的用户标识ID生成算法,加密过程,RSA密钥产生过程三方面来对勒索软件进行分析。
用户标识ID由两部分组成,第一部分来自于受害用户的用户名信息,对用户名使用ROL7算法。第二部分来自于第一个磁盘分区的卷标序列号:
程序并没有使用多线程技术,因此对文件的遍历过程耗费的时间较长。勒索程序会根据文件大小(0-50M,50-100M,100M-256M)遍历三次系统文件,首先加密0-50M的文件内容,其次加密50-100M的文件内容,最后加密100M-256M的文件内容。在每次遍历文件系统时,程序首先遍历盘符“A”到盘符“Z”,如果盘符类型为DRIVE_FIXED 、DRIVE_REMOVABLE 、DRIVE_REMOTE 或RIVE_RAMDISK时,遍历该盘符中所有下面扩展名的文件,同时,如果在特定的文件目录下的文件也会跳过不进行加密。
三次遍历系统文件的函数,第一个参数表示此次遍历中最大文件大小maxFileSize,第二个参数表示此次遍历中最小文件大小minFileSize,第三个参数表示用于生成AES密钥的RSAKey。
加密的文件后缀名列表:
排除下面目录中的文件:
遍历下面类型的磁盘,遍历符合加密条件的文件。
对遍历到的文件,调用加密函数进行加密,有四个参数:RSA密钥,指向文件内容的内存buf,文件大小,保存密文所需要的字节数。对文件使用参数一中的RSA密钥做为AES_256的密钥,调用AES_256对称算法对文件进行加密。
当程序无法与CC服务器通信时,程序会使用保存在文件中的硬编码的RSA密钥。而当程序可以连接到CC服务器时,程序会生成新的RSA密钥对文件进行加密。
1. 程序会判断CC地址的通信情况,如果程序不能与CC地址正常通信,则使用文件中硬编码的RSA密钥。
文件中RC4过的硬编码的RSA密钥如下图选中部分:
通过RC4算法(密钥为:OJ&*(&218u9yheIUTYEW^&Q),解密出RSA密钥:
2. 在程序能够与CC地址通信的情况下,程序会生成加密用的RSAkey,同时将生成的RSA的公钥使用RC4加密后保存到文件“ExcelFavorite.acl”文件中。
然后使用将本地的ExcelFavorite.acl文件,上传到CC服务器中。
基于上面的分析,在程序能正常连接CC服务器的情况下,程序在本地生成RSA密钥对,并将其中的公钥经过RC4加密后上传到CC服务器中删除保存在ExcelFavorite.acl文件中的公钥信息,随后使用该RSA公钥做为AES-256的加密密钥,调用AES-256算法对文件进行加密。
另外,为了实现持久化攻击,勒索软件还会将自己加入启动项,程序会在注册表的下面两个位置创建开机启动项:
同时,为了防止受害用户恢复系统,勒索程序会将系统备份删除
为了防止用户通过数据恢复类软件恢复保存在本地的ExcelFavorite.acl文件,勒索程序会在将ExcelFavorite.acl文件上传到CC控制服务器后,两次生成ExcelFavorite.acl文件进行覆盖后,最后再通过DeleteFile删除acl文件。
通过上面的分析,文件最终的加密算法为AES-256,但勒索软件将加密用的密钥文件已经进行了多次重写后删除。在没有密钥的情况下,无法恢复用户的数据文件。但在CC服务器失效或者用户无法联网的情况下,勒索软件会通过内置的密钥对文件进行加密,在这种情况下,我们可以通过在勒索软件中提取出密钥,再通过该密钥解密用户文件。对此,我们实现了一个简单解密文件的程序,程序源代码在文章附件中。
EncryptDecrypt程序输出三个参数,分别为:
Keyfile:我们在勒索程序中提取出来的解密密钥
encrypted_file:待解密的文件路径
plain_file:解密后的文件保存路径
软件运行命令及解密前后的文件对比如下图:
近几年来,勒索软件的数量和类型正呈现出爆发式增长,这一方面是由于更多的勒索软件的源代码加入了开源的阵列,从而使勒索软件更新换代更加容易。另一方面也由于对于被勒索软件加密过的文件很难被恢复,受害人常常为了取回文件而支付赎金,这又激起勒索软件作者的开发愿望。也可以看到,反勒索软件阵营也变得越来越强大,很多的厂商开始开发反勒索产品,开始互相沟通,交流反勒索经验,共享勒索的威胁情报。但基于勒索软件的加密方式,我们仍建议要把预防放在优先位置,及时备份文件,不轻易点开陌生人邮件,及时更新安全防护软件。
https://www.bleepingcomputer.com/news/security/cryptomix-variant-named-cryptoshield-1-0-ransomware-distributed-by-exploit-kits/
https://isc.sans.edu/forums/diary/CryptoShield+Ransomware+from+Rig+EK/22047/?adbsc=social69980656&adbid=829751979773620225&adbpl=tw&adbpr=4487645412
硬编码的RSA公钥文件的十六进制表示
06 02 00 00 00 A4 00 00 52 53 41 31 00 08 00 00
01 00 01 00 F3 18 13 A2 19 C1 39 C0 77 F2 97 6C
7E 09 32 52 AF 64 05 3E 49 90 07 C6 4C 18 22 0E
03 C5 9B 44 73 BA 35 EA C1 1B 38 1B 4F 09 D2 A0
16 60 41 FB A1 38 C1 C4 89 CC DE B3 92 7C 91 9F
19 6C AC 5C 94 89 9E 70 E2 AB FF 3D B8 DA 41 8A
3B 38 10 F7 B7 4B E7 94 1F D4 38 B2 5D 41 0A 77
8D F0 95 B7 0D E1 8D 5D B2 AF 28 91 78 61 DE 1F
DE CF EF F7 AA 00 EC DB 10 99 A5 52 7E 84 2B 25
B9 57 0C B7 AB 61 80 19 9A 1C BD CE 3E 30 15 A3
3D C4 68 9A 2A 00 3D 72 0E 25 DD 2B 90 8C 7C 79
85 3F 7B F6 96 C7 94 CF B3 AE 00 E3 58 DC 3C 47
84 55 3D F1 F3 04 EC 5A 05 B0 89 54 60 5D 8C 8F
1C BC 4D 5D C6 99 9B 33 09 6F A0 41 43 0C FC 26
53 70 03 F1 AE F7 83 EF 4B 7E 2F 99 C7 28 43 B0
74 7A B6 0F 83 4C 28 15 C4 95 C8 96 BB FF AE 02
03 48 8A 67 0B E6 4E AD 70 A1 17 95 D4 4C FE F0
F8 40 31 A9
EncryptDecrypt代码
#include "stdafx.h"
#include "stdafx.h"
#include <windows.h>
#include <tchar.h>
#include <conio.h>
#define BUFFER_SIZE 512
#define BLOCK_SIZE 512
int MyDecrypt(_TCHAR* strPrivateKeyFile, _TCHAR* strEncryptedFile, _TCHAR* strPlainFile);
// Main
int _tmain(int argc, _TCHAR* argv[])
{
int iResult = 0;
if (argc == 4)
{
// Decrypt
iResult = MyDecrypt(argv[1], argv[2], argv[3]);
}
else
{
// Show usage
_tprintf(_T("Usage:\n"));
_tprintf(_T("- Decrypt : EncryptDecrypt keyfile encrypted_file plain_file\n"));
iResult = 1;
}
_tprintf(_T("\n << Press any key to continue >> \n"));
_getch();
return iResult;
}
int MyDecrypt(_TCHAR* strKeyFile, _TCHAR* strEncryptedFile, _TCHAR* strPlainFile)
{
// Variables
HCRYPTPROV hCryptProv = NULL;
HCRYPTKEY hKey = NULL;
HCRYPTHASH phHash = NULL;
DWORD dwPrivateKeyLen = 0;
DWORD dwDataLen = 0;
BYTE* pbPrivateKey = NULL;
BYTE* pbData = NULL;
HANDLE hPrivateKeyFile = NULL;
HANDLE hEncryptedFile = NULL;
HANDLE hPlainFile = NULL;
DWORD lpNumberOfBytesWritten = 0;
__try
{
// Acquire access to key container
if (!CryptAcquireContext(&hCryptProv, 0, NULL, PROV_RSA_AES, CRYPT_VERIFYCONTEXT))
{
// Error
_tprintf(_T("CryptAcquireContext error 0x % x\n"), GetLastError());
// Try to create a new key container
if (!CryptAcquireContext(&hCryptProv, _T("AlejaCMa.EncryptDecrypt"), NULL, PROV_RSA_FULL, CRYPT_NEWKEYSET))
{
// Error
_tprintf(_T("CryptAcquireContext error 0x % x\n"), GetLastError());
return 1;
}
}
// Open private key file
HANDLE hRsaFile;
if ((hRsaFile = CreateFile(
strKeyFile,
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_FLAG_SEQUENTIAL_SCAN,
NULL
)) == INVALID_HANDLE_VALUE)
{
// Error
_tprintf(_T("CreateFile error 0x % x\n"), GetLastError());
return 1;
}
// Get file size
DWORD dwRsaContentSize = 0;
if ((dwRsaContentSize = GetFileSize(hRsaFile, NULL)) == INVALID_FILE_SIZE)
{
// Error
_tprintf(_T("GetFileSize error 0x % x\n"), GetLastError());
return 1;
}
// Create a buffer for the private key
BYTE* pbRsaContent = NULL;
if (!(pbRsaContent = (BYTE *)malloc(dwRsaContentSize)))
{
// Error
_tprintf(_T("malloc error 0x % x\n"), GetLastError());
return 1;
}
// Read private key
if (!ReadFile(hRsaFile, pbRsaContent, dwRsaContentSize, &dwRsaContentSize, NULL))
{
// Error
_tprintf(_T("ReadFile error 0x % x\n"), GetLastError());
return 1;
}
// Open encrypted file
if ((hEncryptedFile = CreateFile(
strEncryptedFile,
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_FLAG_SEQUENTIAL_SCAN,
NULL
)) == INVALID_HANDLE_VALUE)
{
// Error
_tprintf(_T("CreateFile error 0x % x\n"), GetLastError());
return 1;
}
// Get file size
if ((dwDataLen = GetFileSize(hEncryptedFile, NULL)) == INVALID_FILE_SIZE)
{
// Error
_tprintf(_T("GetFileSize error 0x % x\n"), GetLastError());
return 1;
}
// Create a buffer for the encrypted data
if (!(pbData = (BYTE *)malloc(dwDataLen)))
{
// Error
_tprintf(_T("malloc error 0x % x\n"), GetLastError());
return 1;
}
// Read encrypted data
if (!ReadFile(hEncryptedFile, pbData, dwDataLen, &dwDataLen, NULL))
{
// Error
_tprintf(_T("ReadFile error 0x % x\n"), GetLastError());
return 1;
}
if (CryptCreateHash(hCryptProv, CALG_SHA_256, 0, 0, &phHash))
{
if (CryptHashData(phHash, pbRsaContent, 0x114u, 0) && CryptDeriveKey(hCryptProv, 0x6610u, phHash, CRYPT_EXPORTABLE, &hKey))
{
if (CryptDecrypt(hKey, 0, 1, 0, pbData, &dwDataLen))
{
// Create a file to save the plain text
if ((hPlainFile = CreateFile(
strPlainFile,
GENERIC_WRITE,
0,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL
)) == INVALID_HANDLE_VALUE)
{
// Error
_tprintf(_T("CreateFile error 0x % x\n"), GetLastError());
return 1;
}
// Write the plain text the file
if (!WriteFile(
hPlainFile,
(LPCVOID)pbData,
dwDataLen,
&lpNumberOfBytesWritten,
NULL
))
{
// Error
_tprintf(_T("WriteFile error 0x % x\n"), GetLastError());
return 1;
}
}
CryptDestroyKey(hKey);
}
CryptDestroyHash(phHash);
}
CryptReleaseContext(hCryptProv, 0);
return 0;
}
__finally
{
// Clean up
if (!pbPrivateKey) {
free(pbPrivateKey);
}
if (!pbData) {
free(pbData);
}
if (hPrivateKeyFile) {
CloseHandle(hPrivateKeyFile);
}
if (hEncryptedFile) {
CloseHandle(hEncryptedFile);
}
if (hPlainFile) {
CloseHandle(hPlainFile);
}
if (hKey) {
CryptDestroyKey(hKey);
}
if (hCryptProv) {
CryptReleaseContext(hCryptProv, 0);
}
}
}