基本信息

报告名称:Rapid勒索病毒变种分析
作者:Bianca
报告更新日期:2020.12.8
样本发现日期:未知
样本类型:PE32 executable (GUI) Intel 80386, for MS Windows
样本文件大小/被感染文件变化长度:472K
样本文件MD5 校验值:40c5113e35dd653ca1fc1524d51da408
样本文件SHA1 校验值:c43028b0a2287d7e64199500d48ce7c5f864dc54
壳信息:
可能受到威胁的系统:32位Windows操作系统
相关漏洞:
已知检测名称:

简介

Rapid的勒索病毒使用了 RSA加AES对文件进行加密,它不仅会感染计算机上已有的文件,还会对新创建的文件进行加密。

被感染系统及网络症状

本节的主要目的是帮助潜在读者快速识别被感染后的症状。

文件系统变化

  • 它不仅会感染计算机上的已有文件,还会对新创建的文件进行加密。被加密的文件,其文件名被添加“.rapid”扩展名,同时文件大小增加了0x4D0字节;
  • 在进行加密的文件夹中创建名为“How Recovery Files.txt”的勒索提示文件;
  • 重启电脑弹出recovery.txt文件,和How Recovery Files.txt文件名相同。

注册表变化

[将要/可能]被[创建/修改/删除]的[注册表键/键值]

网络症状

被监听的端口,向指定目标及端口的网络活动及类型,等等

详细分析/功能介绍

1、解密shellcode到内存后执行

  • 静态分析

image.png
image.png

image.png

  1. # 解密脚本
  2. #!/usr/bin/python3
  3. # -*- coding: UTF-8 -*-
  4. import struct #处理二进制的包
  5. from ctypes import *
  6. '''
  7. c_uint32:unsigned int
  8. int.from_bytes(x, byteorder='little'):把bytes类型的变量x,转化为十进制整数,x的高位字节为后四位
  9. 由于加法或程序有可能大于0x100000000, 所以需要与0xffffffff.
  10. struct.pack('I', value):将value打包成unsigned int格式
  11. '''
  12. # first 0x40 byte of shellcode, for example
  13. shellcode = b"\x7c\x98\x39\x52\x45\x84\x52\x73\xA9\xEB\xDB\x07\x5F\xEC\x20\x1D" \
  14. b"\xD2\xE1\xE3x22\x02\x92\x36\xE8\x8C\x6C\x76\x2E\x11\x55\x42\xD3"\
  15. b"\x1F\x1A\x03\x94\xD8\xFF\x88\xD0\x90\x81\x72\xFB\xF0\x2F\x42\x14"\
  16. b"\x82\xC9\x8F\xBD\xD8\xE2\xDD\x3C\xBA\x62\xBB\x9B\x5D\x72\x09\xE5"
  17. def decrypt(enc_one, enc_two):
  18. key0 = c_uint32(0xc6ef3720)
  19. # four parts of one group
  20. key1 = c_uint32(0x5aabd3f6)
  21. key2 = c_uint32(0xf770d8de)
  22. key3 = c_uint32(0x9ad9a390)
  23. key4 = c_uint32(0x5a454ee9)
  24. tmp_one = c_uint32(0)
  25. times = 0x20
  26. value_one = c_uint32(int.from_bytes(enc_one, byteorder='little'))
  27. value_two = c_uint32(int.from_bytes(enc_two, byteorder='little'))
  28. while times > 0:
  29. # 由于加法或程序有可能大于0x100000000, 所以需要与0xffffffff.
  30. value_two.value = value_two.value - ((key0.value + value_one.value)\
  31. ^ ((key3.value + (value_one.value << 4) & 0xffffffff) & 0xffffffff)\
  32. ^ (key4.value + (value_one.value >> 5) & 0xffffffff) & 0xffffffff)
  33. tmp_one.value = (key0.value + value_two.value)\
  34. ^ ((key1.value + (value_two.value << 4) & 0xffffffff) & 0xffffffff)\
  35. ^ ((key2.value + (value_two.value >> 5) & 0xffffffff) & 0xffffffff)
  36. key0.value += 0x61c88647
  37. value_one.value -= tmp_one.value
  38. times -= 1
  39. result = struct.pack('I', value_one.value)
  40. result += struct.pack('I', value_two.value)
  41. return result
  42. if __name__ == "__main__":
  43. plain = bytes()
  44. for i in range(0, len(shellcode), 8):
  45. args_one = shellcode[i : i+4]
  46. args_two = shellcode[i+4 : i+8]
  47. plain += decrypt(args_one, args_two)
  48. print("decrypt over")

2、释放勒索病毒本体(分析内存中的shellcode)

shellcode解密勒索病毒主体,将主体各个section覆写原始进程的section.

知识补充:动态定位API函数

  • 解决shellcode通用性的问题,以调用MessageBox为例
    • kernel32.dll (LoadLibraryA) -> User32.dll (MessageBox)
  • win32下kernel32.dll和ntdll.dll默认加载

    • 首先通过段选择子FS在内存中找到当前的线程环境块TEB
    • TEB偏移位置为0x30的地方存放着指向进程环境块PEB的指针

      1. // Current PEB for 64bit and 32bit processes accordingly
      2. PVOID GetPEB()
      3. {
      4. #ifdef _WIN64
      5. return (PVOID)__readgsqword(0x0C * sizeof(PVOID));
      6. #else
      7. return (PVOID)__readfsdword(0x0C * sizeof(PVOID));
      8. #endif
      9. }
    • 进程环境块PEB中偏移位置为0x0C的地方存放着指向PEB_LDR_DATA结构体的指针,其中,存放着已经被进程装载的动态链接库的信息。

    • PEB_LDR_DATA结构体偏移位置为0x1C的地方存放着指向模块初始化链表的头指针InInitizationOrderModuleList.
    • 模块初始化链表InInitizationOrderModuleList中按顺序存放着PE装入运行时初始化模块信息,第一个链表结点是ntdll.dll,第二个链表结点就是kernel32.dll
    • 找到属于kernel32.dll的结点后,在其基础上再偏移0x08就是kernel32.dll在内存中的加载基地址(0x1C+0x1+0x08=0x20)
    • 从kernel32.dll的加载基址算起,偏移0x3C的地方就是其PE头
    • PE头偏移0x78的地方存放着指向函数导出表的指针
    • 导出表0x1C处的指针指向存储导出函数偏移地址(RVA)的列表,导出表偏移0x20处的指针指向存储导出函数函数名的列表
    • 函数的RVA地址和名字按照顺序存放在上述两个列表中,可以在名称列表中定位到所需的函数是第几个,然后在地址列表中找到对应的RVA获得RVA后,再加上前面已经得到的动态链接库的加载基址,就获得了所需API此刻在内存中的虚拟地址。

image.png

1)shellcode获取LoadLibraryA基地址

image.png

3)获得其他必要函数地址

image.png

2)分配内存解密勒索病毒

image.pngimage.png

image.png

4)填充导入表地址

5)判断是否需要重定位

6)跳转至勒索病毒OEP

3、Rapid勒索病毒执行

1)调用ShellExecuteA执行清理工作,防止用户恢复系统

  • 清除Windows卷影拷贝:防止受害者使用卷影拷贝恢复文件。
  • 禁用修复功能,忽略错误:降低系统崩溃概率。

image.png

2)添加计划任务

  • 创建计划任务

    image.png

    • “info.exe”:是勒索病毒程序自身的拷贝
    • schtasks create :创建新的计划任务
    • /sc MINUTE :计划的时间单位为分钟
    • /TN Encrypter:计划任务的名称为Encrypter
    • /TR info.exe:指定运行的程序为info.exe

      image.png

    • /sc ONLOGIN :每当用户登录时运行

    • /TN EncrypterST:计划任务的名称为EncrypterST

3)注册表添加开机启动项

  • “recovery.txt”文件是勒索信息文件

image.png

image.png

4) 对文件进行加密

  • 首先创建了一个PROV_RSA_FULL类型的CSP容器,然后将通过Base64的硬编码在程序中的RSA的公钥(命名为RSA1)导入

    • CryptAcquireContextA:连接CSP,获得指定CSP的密钥容器的句柄
    • CryptImportKey:导入解密用的公钥

    • image.png

  • 创建新的注册表键

image.pngimage.png

  • 创建新的注册表项
    • RegCreateKeyEx:创建指定的注册表项。如果键已经存在,函数将打开它
    • RegOpenKeyEx:打开一个指定的注册表键,得到的将要打开键的句柄
    • RegQueryValueEx:检索一个已打开的注册表句柄中,指定的注册表键的类型和设置值,返回0表示检索成功

image.pngimage.png
image.png

  • 创建了一个PROV_RSA_FULL类型的CSP容器,并且调用CryptGenKey()生成了随机RSA密钥对(命名为RSA2)

    image.png

    image.png

  • 调用CryptExportKey()导出刚刚生成的RSA2私钥数据,并调用RSA1公钥对RSA2私钥加密。

image.png

  • 加密完成后,将RSA私钥数据写入注册表项HKEY_CURRENT_USER\Software\EncryptKeys\local_enc_private_key,将数据长度写入注册表项HKEY_CURRENT_USER\Software\EncryptKeys\local_enc_private_key_len

image.png

image.png

  • 再次调用CryptExportKey()导出刚刚生成的随机RSA2公钥数据,这次不需要加密,直接写入注册表HKEY_CURRENT_USER\Software\EncryptKeys\local_public_key和HKEY_CURRENT_USER\Software\EncryptKeys\local_public_key_len

image.png

  • 找到可操作的所有盘符

image.png

  • 加密文件(跳过勒索信息文件)

image.png
image.png

  • 开始对文件进行加密,获取选取文件的大小,如果文件大小小于0x4D0字节,则直接进入加密流程;否则读取文件尾部0x20字节的数据,并判断这部分数据是不是加密标志“F5 D5 CD CD CD 7D CD CD 95 91 C1 C1 CD AD CD CD 41 CD 41 CD C1 99 FD 1D 59 95 81 FD 99 79 25 A5”,如果不是则进入加密流程,否则选取下一个文件。因为加密过的文件是大于0x4D0的,并且在其文件尾部添加了0x20个字节的加密标志

image.png

  • 程序进入加密流程后,会先调用CryptGenKey()生成随机AES密钥。并调用CryptExportKey()导出AES密钥数据,BLOBTYPE=PLAINTEXTKEYBLOB。使用RSA2公钥加密AES密钥,加密的是“BLOB格式数据+AES密钥+填充数据”这整个0x80字节的数据

image.png

  • 读取文件数据,使用AES密钥对读取的文件数据进行加密。

image.png

  • 加密数据回填
    • AES加密是按照128bit进行分组,当原文件字节数不是128bit的整数倍时,加密后的密文数据将会大于明文数据,所以程序在加密前在明文尾部填充了0x10个字节的0x00(一个AES分组的字节数)。向文件覆写加密后的数据,首先只写入原文件大小的密文数据,变多的0x10字节的数据接下来再写;继续向文件中写数据,写入0x4D0字节的数据。这0x4D0字节的数据包括五部分:第一部分0x10字节,这部分就是变多的数据;第二部分0x20字节,包含源文件文件大小的字符串和0xCD的填充数据;第三部分0x80字节,是被加密的AES密钥数据;第四部分0x400字节,是被加密的RSA2私钥数据;第五部分0x20字节,是文件加密标志数据。

image.png

  • 添加 .rapid 后缀

image.png

  • 显示勒索信息

image.png

相关服务器信息分析

本节可以提供一些详细的目标域名, IP 地址,邮件地址等等相关信息。这样可以方便企业/政府用户更好的了解/追踪该恶意代码的作者/运营者。

预防及修复措施

当然,如果就职于某行业内公司,本节通常会提供相关产品的修复操作步骤。
不过这里我们还是为那些没有安装安软的普通用户来介绍一下,需要安装的安全补丁,如何手动恢复被感染的环境,例如如何一步步的删除/修改相关注册表键值,文件等等。
1) 对重要的数据文件定期进行非本地备份。
2) 不要点击来源不明的邮件以及附件。
3) 重命名vssadmin.exe进程,防止勒索病毒利用它一次性清除文件的卷影副本。
4) 开启防火墙,并升级到最新版本,阻止勒索病毒与其C&C服务器通信。
5) 及时给电脑打补丁,修复漏洞。
6) 使用长度大于10位的复杂密码,禁用GUEST来宾帐户。
7) 尽量不要使用局域网共享,或把共享磁盘设置为只读属性,不允许局域网用户改写文件。
8) 关闭不必要的端口,如:445、135、139、3389等。

技术热点及总结

参考:https://www.freebuf.com/articles/network/162033.html

勒索病毒技术总结

1、母体执行

  • 解密shellcode,完成病毒换体
    • LoadLibrary、GEtProcAddress、LocalAlloc、VirtualProtect

2、执行变体代码

  • 创建一个互斥,防止自身被多次执行
    • OpenMutex、CreateMutex
  • 创建线程,监控感染主机上的指定进程或服务,解除对应进程的文件占用
    • 枚举进程:CreateToolhelp32Snapshot、Process32First、Process32Next、TerminateProcess
    • 枚举服务:OpenSCManagerW、EnumServicesStatusExW、StopService
  • 创建开机自启动项
    • HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Run

image.png

  • 删除磁盘卷影,防止受害者恢复数据
    • 卷影副本功能可提供网络共享上的文件的即时点副本。利用共享文件夹的卷影副本,用户可以查看网络文件夹在过去某一时间点的内容。
    • CreateProcess cmd
    • 删除磁盘卷影的命令:“mode con cp select=1251 vssadmin delete shadows /all /quiet Exit”
  • 扫描局域网共享目录并对扫描到的文件进行加密
    • 盘符:ABCDEFGHIJKLMNOPQRSTUVWXYZ
    • GetLogicalDrives函数获取磁盘驱动器
  • 遍历逻辑驱动器,每个驱动器创建一个文件加密线程,对逻辑驱动器下的文件进行加密
    • 内置的一段加密的RSA公钥对随机生成的AES密钥进行加密,并将加密后的内容发给黑客,黑客使用RSA私钥进行解密,得到加密文件的AES密钥。
    • 为了保障随机性,黑客对每个文件进行加密的时候都使用随机的IV,被加密后的文件按照特定的文件格式进行存储。
    • GetVolumeSerialNumber:获取C盘序列号
    • rdtsc:获取CPU自从启动以来的时钟周期数(也就是一个随机数)