导语:在上一篇文章中我介绍了Ryuk的一些一本架构,本文我会接着介绍Ryuk和Hermes的其他技术相似之处(注入方法、加密方案),并对幕后团队的销赃行为进行分析。

上一篇,我介绍了Ryuk的一些一本架构,本文我会接着介绍Ryuk和Hermes的其他技术相似之处(注入方法、加密方案),并对幕后团队的销赃行为进行分析。

注入方法

Ryuk使用的注入技术非常简单,首先使用OpenProcess获取目标进程的句柄,然后使用VirtualAllocEx在其地址空间中分配缓冲区。分配的缓冲区大小有恶意软件映像那么大,并且需要和恶意软件映像位于相同的基址。

然后,恶意软件会将其当前的虚拟映像内容写入其中,并创建一个执行某些操作的线程。请注意,Ryuk通过将虚拟映像写入具有预定义分配基础的请求缓冲区后,由于缺少正确的代码重定位过程,就有可能发生所请求的地址不可用于分配的风险,从而导致代码注入执行失败。

8.png

可能导致失败的注入方法

注入代码

这些已经注入的代码中,包含了勒索软件用于加密文件的核心功能。不过加密功能的实现有分两步,首先它是通过使用预定义的密钥和字符串长度数组解密API函数名称字符串列表来启动的,然后使用这些数组来动态加载相应的函数。

不过在分析Ryuk时,为了对加密过程进行解密,研究人员创建了一个IDA Python脚本,来简化解密过程。IDA Python脚本将自动解密这些字符串并重命名相关变量,该脚本可以在下面的附录中找到。

接下来,恶意软件会尝试将虚拟文件写入Windows目录,该目录只允许具有Admin权限的用户访问。如果文件创建失败,它将会休眠一段时间,然后再以相同的方式尝试5次。如果还是失败,则Ryuk运行过程将自动终止。

如果文件成功创建,则它会将向Windows目录的子文件夹中再写入两个文件。第一个文件名为“PUBLIC”,其中包含一个RSA公钥,第二个文件是“UNIQUE_ID_DO_NOT_REMOVE”,其中包含一个硬编码密钥。这两个文件都可以用于加密,具体过程我会在下文介绍。

加密方案

Ryuk使了一个相对简单的三层信任模型( three-tier trust model),之所以要采用信任模型,是因为此模型的第一层含有攻击者持有的全局RSA密钥对(global RSA key pair),这在勒索软件的实现中是很典型的方法。在感染过程中,受害者在任何时候都看不到这个密钥对中的私钥。

第二层是则包含每个受害者的RSA密钥对,通常情况下,勒索软件会在运行中动态生成这个密钥对,然后立即使用更高层(也就是第三层)的全局密钥(global key)加密生成的私钥。不过也有例外,就是勒索软件有时会附带有预先嵌入的密钥对和预加密的私钥,这个方法之所以属于例外,是应为如果攻击者使用相同的样本感染多个受害者,或者如果在多个样本中嵌入相同的密钥对,则可能发生“一次付费,多次解密”,显然这并不是攻击者希望发生的。但是,假设为每个新样本生成了一个新的密钥对,那么此攻击模型就比较受攻击者的欢迎了。

9.png

嵌入的RSA公钥(注意初始字节06 02)

10.png

已加密的RSA私钥(使用了全局密钥,请注意初始字节07 02)

第三层是使用Win32API函数CryptGenKey为每个受害者文件生成的标准AES对称加密密钥,然后使用CryptExportKey导出密钥,使用第二层密钥加密,并将加密结果附加到加密文件。所以,在一个完整的攻击事件中,攻击者会在阅读了CryptExportKey的全部内容后,提供了第二层密钥作为hExpKey参数。大多数勒索软件会以明文形式导出AES密钥,然后使用CryptEncrypt或其他类似方法对结果进行加密。

11.png

生成每文件AES密钥的程序集,而这只是勒索软件 “加密文件”例程的一部分

一旦所有加密基元(cryptographic primitive)准备就绪,勒索软件就会对受害者系统上的每个驱动器和网络共享执行标准递归扫描,并加密每个文件和目录,当然除了包含来自硬编码白名单的任何文件或目录,其中包括“Windows”,“Mozilla”,“Chrome”,“RecycleBin”和“Ahnlab”。很明显,攻击者希望获取完整无缺的受害者访问过的网页浏览器,这样他们才可能跟踪到受害者的读取的赎金和购买加密货币等记录。不过研究人员至今还没有搞清楚,在这种攻击没有专门针对韩国用户的情况下,为什么攻击者要对受害者所安装的韩国终端保护产品进行加密,这也是Ryuk勒索软件是否就是HERMES勒索软件的变异版本的一个猜测依据。除了上文所讲的明显的相同标记外,它们的信任模型还存在一些细微差别。

根据Malwarebytes的一份报告,最初的HERMES实际上只会生成了每受害者的第二级RSA密钥对,而不是在恶意软件样本中嵌入硬编码副本。但由于加密功能的出现,包括加密文件格式及其相关的唯一file命令所使用的magic文件,在重新命名的版本中被大量复制,还有 “AhnLab” 相关文件的检查,这些文件甚至与Ryuk的目标受害者无关。

12.png

由Ryuk勒索软件加密过的文件的十六进制转储,其文件结构与HERMES中使用的文件结构明显相同,其中甚至还有与众不同的“HERMES”标记,恶意软件使用该标记来识别已加密的文件。

除了本地驱动器,Ryuk还将尝试加密网络资源。首先,它将通过调用WNetOpenEnum开始枚举,然后分配一个初始化为零的缓冲区。在调用WNetEnumResource函数的过程中,这个缓冲区将被此函数填充。如果枚举资源是其他资源的容器,则勒索软件将递归调用其网络资源枚举函数。

Ryuk找到的每个网络资源的名称,都将被整理到一个列表中,并用分号分隔开,在稍后,该列表的网络资源都将被加密。

最后,Ryuk将自动删除其加密密钥并执行.BAT文件,该文件将从磁盘中删除shadow volume副本和各种备份文件。

13.png

加密系统后,Ryuk要执行的批处理命令列表

对赎金的追踪过程

不过幸运的是,Ryuk勒索软件目前尚未广泛传播,这一传播特点与HERMES非常类似。HERMES一开始时也仅用于有针对性的攻击,这样研究人员想跟踪恶意软件的活动和收入就变得非常困难。目前研究人员所发现的几乎所有恶意软件样本中,都包含了一个独立的钱包,如果受害者通过这个钱包支付赎金后,不久这些资金被划分成许多块,并通过多个其他帐户销赃。

然而,赎金票据中所包含的钱包还是暴露一些赎金交易的痕迹,研究人员根据这些线索找到了最有可能用于销赃的钱包。另外研究人员还能够发现这些钱包之间的联系,因为赎金会在某一时刻被转移到几个关键的钱包里。这意味着Ryuk勒索软件处理赎金的过程,是一个分工极为严密的操作,且根据这几个关键的钱包,可以断定目前已经有几家固定的公司被定位为攻击目标。

在受害者向预先分配的钱包支付赎金后,大约25%的资金会被立即转移到新的钱包。不过,这些资金仍然可以在新创建的钱包中被研究人员找到,研究人员可以假设这些比特币稍后会被兑现。而剩余的75%金额,占了真个赎金的大头,它们会被怎么处理呢?研究人员发现,这些资金也会被转移到新的钱包。而在新钱包中,这部分中的25%又会被立即转移到新的钱包,然后兑现。其余的资金将再次依照上面的过程,依此兑现。

有趣的是,有几个钱包的使用比其他钱包的使用频率更高一些,且其中有几笔交易来自不同的赎金。研究人员分析,这些钱包实际上是赎金支付后用于销赃的核心中转站,通过分析这些中转站,研究人员能够估计出Ryuk勒索软件所获赎金的数额,下图就是研究人员发现的销赃模式。

14.png

从赎金支付到兑现阶段的比特币交易流程

总结

通过分析Ryuk的开发过程、加密过程再到赎金销赃过程,研究人员发现Ryuk攻击的目标是能够支付大量赎金的企业。根据目前的证据,Ryuk与HERMES勒索软件的关系紧密,研究人员分析其幕后的开发者很可能就是朝鲜Lazarus组织。在成功感染并获得价值约64万美元的比特币后,研究人员认为后续攻击互动将更为猛烈。

附录

字符串解密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()

IOCs

Ryuk的哈希值(MD5):

c0202cf6aeab8437c638533d14563d35

d348f536e214a47655af387408b4fca5

958c594909933d4c82e93c22850194aa

86c314bc2dc37ba84f7364acd5108c2b

29340643ca2e6677c19e1d3bf351d654

cb0c1248d3899358a375888bb4e8f3fe

1354ac0d5be0c8d03f4e3aba78d2223e

DROPPER木马的哈希值 (MD5):

5ac0f050f93f86e69026faea1fbb4450

源链接

Hacking more

...