前言
近日,Proofpoint 公司的病毒分析工程师发现一批利用Rig 漏洞利用包传播CryptoShield勒索软件。这波攻击大多利用flash漏洞进行,对于flash漏洞,本人就不再班门弄斧。我们今天就重点分析这一波攻击中所使用的CryptoShield勒索软件。该勒索软件使用了RSA和AES加密算法,使用RSA生成的公钥做为AES算法的密钥,使用AES加密用户文件。在用户机器无法与CC服务器通信的情况下,勒索软件会使用自身硬编码的密钥进行文件的加密,这就为单机受害用户提供了恢复文件的可能,文末提供于单机受害用户的文件恢复方法。
CryptoShield加密后提示文件显示的内容如下:
# RESTORING FILES #.HTML与# RESTORING FILES #.TXT内容
我们得到的可执行文件大小不足100K, 可执行文件中的资源文件保存了加密过勒索程序。可执行文件起到了loader的作用。
Loader分析
Loader运行后,会从自身读取类型为“BKJSYHFIOAJSHBGYHFJHASIODFJHAHIJOSKLFAS”的资源数据,然后通过RC4算法解密读取到的资源数据得到勒索程序,随后在内存动态加载勒索软件并跳到勒索程序的入口点处执行,对受害用户的磁盘进行扫描加密。
1. 加载资源过程:
资源内容:
读取资源后,判断资源中的第一个byte是不是字符’Z’,如果不是,就进行解密资源操作。
解密算法为RC4,解密密钥为“P*2&%@jJFHSUFH3”,
解密过后的内容为PE文件,可以将文件保存出来留做后面分析。
随后,程序在内存中加载解密出的PE文件并到解密出的PE的OEP处执行
勒索程序
上面解密出来的PE文件才是真正的勒索程序。我们将从勒索程序的用户标识ID生成算法,加密过程,RSA密钥产生过程三方面来对勒索软件进行分析。
用户标识ID的生成
用户标识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对称算法对文件进行加密。
RSA密钥的产生
当程序无法与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:解密后的文件保存路径
软件运行命令及解密前后的文件对比如下图:
总结
近几年来,勒索软件的数量和类型正呈现出爆发式增长,这一方面是由于更多的勒索软件的源代码加入了开源的阵列,从而使勒索软件更新换代更加容易。另一方面也由于对于被勒索软件加密过的文件很难被恢复,受害人常常为了取回文件而支付赎金,这又激起勒索软件作者的开发愿望。也可以看到,反勒索软件阵营也变得越来越强大,很多的厂商开始开发反勒索产品,开始互相沟通,交流反勒索经验,共享勒索的威胁情报。但基于勒索软件的加密方式,我们仍建议要把预防放在优先位置,及时备份文件,不轻易点开陌生人邮件,及时更新安全防护软件。
参考:
附EncryptDecrypt代码:
硬编码的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); } } }