本文翻译自:https://research.checkpoint.com/ryuk-ransomware-targeted-campaign-break/
研究人员分析发现Ryuk勒索软件与HERMERS勒索软件有很多相似之处,因为研究人员认为使用Ryuk进行攻击的运营者要么是HERMES的运营者,或者Ryuk的开发者获取了HERMES恶意软件的源代码。
一般勒索软件都是通过大规模垃圾邮件活动和漏洞利用工具进行传播,而Ryuk更倾向于一种定制化的攻击。事实上,其加密机制也主要是用于小规模的行动的,比如只加密受感染网络中的重要资产和资源。
从收集的样本中,一共有2个版本的勒索信发给了受害者,一封比较长的勒索赎金为50比特币(约32万美元),一封短的勒索赎金15-35比特币(最高22.4万美元),这就意味着有两种不同级别的攻击。
图1: 勒索信1
图2: 勒索信2
HERMES勒索软件首次出现于2017年10约,当时攻击的目标是台湾的远东国际银行。在攻击中,Lazarus组织通过SWIFT攻击窃取了6000万美元。可以说HERMES勒索软件的目标之一是银行网络。
下面是对Ryuk和HERMES勒索软件的一个比较,通过比较,研究人员认为两个勒索软件的作者相同,或者Ryuk本就属于HERMES勒索软件。
研究人员检查Ryuk的代码发现其加密逻辑与HERMES勒索软件的加密逻辑相似。进一步比较加密文件的函数,研究人员发现其结构非常相似。下图是调用流图的比较:
图3: Ryuk和Hermes加密函数的调用流图
事实上,Ryuk甚至没有修改加密文件中的maker,两个恶意软件中用于确定文件是否被加密的用于生成、存放和验证maker的代码都是相同的:
图4: Ryuk和Hermes的maker生成
图5: Ryuk和Hermes的maker检查
另外,两个勒索软件中激活之前提到的路径的函数会执行相同的动作。比如,两个白名单文件夹,都在相同路径下写入batch脚本(window.bat
),删除影子目录和备份文件的脚本也相同,都释放了PUBLIC
和UNIQUE_ID_DO_NOT_REMOVE
文件到磁盘。
Ryuk的32位和64位版本中的以上逻辑都是相同的。不同架构的代码相似性也是底层代码相同的一个标记。
Ryuk的dropper非常简单和直接,含有勒索软件的32位和64位模块。在执行时,dropper会用srand和GetTickCount函数生成一个5个字母的随机文件名。
前面提到的payload文件就会根据受害者的Windows版本写入对应目录中。如果版本是Windows XP或Windows 2000,文件就会创建到\Documents and Settings\Default User\``目录中,如果是其他系统版本,就创建到
\users\Public``目录中。
如果文件创建失败,dropper会尝试写入自己的目录,使用的文件名是原文件名+大写字母V。文件创建后,dropper会检查进程是否在Wow64下运行,然后根据检查的结果写入适当的payload(32位或64位)。
最后,在中止程序前,dropper会调用ShellExecuteW
来执行刚才写入的Ryuk勒索软件payload。
运行时,Ryuk勒索软件会执行一个几秒钟的休眠,然后检查是否执行某参数。如果成功通过,就作为一个到文件的路径,该文件会用DeleteFileW
方法删除。基于恶意软件的dropper代码,参数就是路径本身。然后,勒索软件会执行taskkill来杀掉超过40个进程和180个服务,使预先定义号的服务和进程停止运行。这些服务和进程大多数反病毒软件、数据库、备份和文档编辑软件的进程。
图6: 杀掉的进程和服务列表
为了确保恶意软件在系统重启后执行,Ryuk使用了一种直接转发的驻留技术,即用命令将自己写入运行注册表中:
reg add /C REG ADD “HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Run” /v “svchos” /t REG_SZ /d
然后提升到SeDebugPrivilege权限以扩展后续动作中的能力,线程结构数组为注入做准备。数组中的每个记录表示系统中运行的一个运行进程,含有进程名、PID、表示进程所有者账户类型的数字。
图7: 表示系统中运行进程的数组记录
将前面提到的进程列表放在一起后,Ryuk就会循环并尝试注入代码到每个进程的地址空间。
Ryuk使用了一种基本的注入技术,首先用OpenProcess获取目标进程的句柄,然后用VirtualAllocEx分类缓存到其地址空间。分配的缓存为恶意软件镜像的大小,而且要求放置在系统的基地址中。
然后恶意软件会将当前虚拟镜像内容写入,并创建可以实现一些动作的线程。通过在预定义的分配基中的请求缓存中写入虚拟镜像,又没有适当的代码重定位过程,Ryuk存在请求的地址无法分配的风险,这就可能导致注入的代码在执行过程中出现错误。
图8: 注入方法和导致执行失败的bug
注入的代码有勒索软件用于文件加密的核心功能。首先是用预定义的key和字符串长度数组来解密API函数名字符串列表,字符串长度数组会被用于动态加载相关的函数。
在分析解密过程时,研究人员创建了IDA python脚本来自动解密字符串并对相关变量进行重命名。脚本代码见附录。
然后,恶意软件会尝试将仿制的文件写入Windows目录,而只有管理员权限才可以完成这个工作。如果文件创建失败,恶意软件就会尝试休眠一段时间,然后尝试进行5次同样的动作。如果尝试仍然失败,Ryuk就会终止这个动作。
如果文件成功创建,就会在Windows目录中的子文件夹中写入2个文件。第1个是PUBLIC
,含有RSA公钥,第2个是UNIQUE_ID_DO_NOT_REMOVE
,含有硬编码的key。这两个文件都是用于下一阶段的加密过程。
勒索软件会用一个相对直接的三层可信模型。可信模型的底层是攻击者的全局RSA密钥对,在感染期间密钥对的私钥对受害者是不可见的。模型的第二层是每个受害者的RSA密钥对,一般勒索软件会在传输过程中生成密钥对,然后用第三层的全局密钥加密生成的私钥。但是Ryuk会用提前嵌入的密钥对和提前加密的私钥。
这是一种非传统的方法,如果相同的样本被用于感染多个受害者或相同的密钥对嵌入到多个样本中,则易受到pay-once, decrypt-many
攻击。如果对每个新的样本生成新的密钥对,那么就是一个安全的模型。
图9: 嵌入每个受害者的RSA公钥
图10: 用全局密钥加密的嵌入每个受害者的RSA公钥
第三层是一个使用Win32API function CryptGenKey对每个受害者文件生成密钥的标准AES对称加密。然后用CryptExportKey输出密钥,用第二层的密钥加密文件,并将加密的结果添加到加密文件中。在实际场景中,作者会读取CryptExportKey的文档,并提供二层密钥作为hExpKey参数。大多数勒索软件会以明文的形式输出AES key,然后用CryptEncrypt或类似的方法加密结果。
图11: 每个文件生成AES key的集合
一旦所有的加密原语到位后,勒索软件会对受害者系统上的驱动和网络共享执行标准的递归扫描,加密除硬编码的白名单外的所有文件和目录,白名单包括“Windows”, “Mozilla”, “Chrome”, “RecycleBin”, “Ahnlab”
。攻击者把web浏览器加入黑名单的一个原因是受害者需要读取勒索信息,购买加密货币等。但是攻击者加密受害者的韩国终端保护产品副本的原因尚不清楚,尤其是攻击的目标并不是韩国用户。
研究人员还有一点疑惑就是HERMES勒索软件如何被重用并重命名为Ryuk勒索软件。除了明显重新maker的reademe文件外,从可信模型上看,没有什么差别。
图12: Ryuk勒索软件加密的文件
除了本地驱动外,Ryuk还会加密网络资源。首先调用WNetOpenEnum开始枚举,然后分配一个初始值为0的缓存。然后通过调用WNetEnumResource函数来填充缓存区。如果枚举的资源是其他资源的容器,勒索软件就会递归地调用网络资源枚举函数。
对于Ryuk找出的网络资源,资源名会添加到一个之后用于加密这些网络资源的列表中,并用分号(“,”)隔开。
最后,Ryuk会破坏加密密钥,并执行删除影子备份和其他备份文件的.bat
文件。
图13: 加密系统后Ryuk执行的batch命令列表
Ryuk勒索软件并未广泛传播,与其前身HERMES类似,只被用于特定的目标攻击,因此很难追踪恶意软件作者的活动和收入。对于每个恶意软件样本,都有唯一的钱包地址,受害者支付了赎金之后,资金会通过多个账户进行分流和传递。
研究人员通过勒索信中的钱包地址分析了整个交易流,发现不同的钱包之间有一定的联系,再某个特定点,资金会通过转账转移到多个主要钱包。这也说明利用Ryuk勒索软件的活动是一个多方协作的攻击活动。
图14: 从勒索赎金到提现阶段的比特币交易流
研究人员分析发现,不管从攻击的本质还是从恶意软件内部的工作流程来看,Ryuk与HERMES勒索软件都有一定的相关性,并与Lazarus组织联系在了一起。在感染受害者并获得64万美金后,研究人员认为该攻击活动并没有结束,未来会有更多的用户成为Ryuk的受害者。
字符串解密Python代码:
""" Ryuk strings decrypter
This is an IDA Python based script which can be used to decrypt the encrypted
API strings in recent Ryuk ransomware samples. After the decryption, the
script will rename the encrypted string in order to ease analysis.
Ryuk sha-256: 8d3f68b16f0710f858d8c1d2c699260e6f43161a5510abb0e7ba567bd72c965b
"""
__author__ = "Itay Cohen, aka @megabeets_"
__company__ = "Check Point Software Technologies Ltd"
import idc
from idaapi import *
def decryptStrings (verbose = True):
encrypted_strings_array = 0x1400280D0
lengths_array = 0x1400208B0
num_of_encrypted_strings = 68
key = 'bZIiQ'
if verbose:
print ("[!] Starting to decrypt the strings\n\n")
# Iterate over the encrypted strings array
for i in range(num_of_encrypted_strings):
# Get the length of the encrypted string
string_length = idc.Dword(lengths_array + i*4)
# Get the offset of the encrypted string
string_offset = encrypted_strings_array + i*50
# Read bytes from
# For IDA version < 7, use get_many_bytes()
encrypted_buffer = get_bytes(string_offset, string_length)
decrypted_string = ''
# Decrypt the bytes and save it to
for idx, val in enumerate(encrypted_buffer):
decrypted_string += chr( ord(val) ^ ord(key [idx % len(key)]))
# Set name for the string variable in IDA
idc.MakeName (string_offset, "dec_" + decrypted_string)
# Print to the ouput window
if verbose:
print("0x%x : %s" % (string_offset, decrypted_string,))
if verbose:
print ("\n[!] Done.")
# Execute the decryption function
decryptStrings()