使用GPU暴力破解Akira勒索软件(Linux/ESXI变种2024)加密文件
我最近帮助一家公司从不支付赎金的情况下恢复了被Akira勒索软件加密的数据。我将分享我的实现方法,并提供完整的源代码。 代码地址:https://github.com/yohanes/akira-bruteforce
需要说明的是,多年来有多个勒索软件变种被命名为Akira,目前有几个版本在流传。我遇到的这个变种从2023年底活跃至今(该公司今年被入侵)。
2023年中之前有一个早期版本存在漏洞,允许Avast创建解密器。但一旦公开,攻击者就更新了加密方式。我预计在我发布本文后,他们会再次更改加密方法。
解密Akira勒索软件
你可以在以下URL找到各种Akira恶意软件样本的哈希值: https://github.com/rivitna/Malware/blob/main/Akira/Akira_samples.txt 与我客户案例匹配的样本是: bcae978c17bcddc0bf6419ae978e3471197801c36f73cff2fc88cecbe3d88d1a 它被列为Linux V3版本。该样本可以在virus.exchange上找到(只需粘贴哈希值搜索)。 注意赎金消息和公钥/私钥会有所不同。
我们这样做不是因为它容易,而是因为我们以为它容易
我通常拒绝协助处理勒索软件案例的请求。但当我的朋友向我展示这个特定案例时,快速检查让我认为它是可解决的。
从我的初步分析中,我观察到以下情况:
- 勒索软件使用当前时间(纳秒级)作为种子
- 在我的Linux机器上,文件修改时间具有纳秒级精度
- 他们提供了部分日志(shell.log)的截图,显示了勒索软件执行的时间,精度为毫秒级
基于此,我最初的想法是:“这应该很容易——只需通过文件时间戳进行暴力破解。能有多难?”
我会详细解释,但结果比预期复杂得多:
- 恶意软件不依赖单个时间点,而是使用四个具有纳秒精度的时间点。前两个和后两个是相关的,因此我们不能简单地逐个时间点进行暴力破解
- 密钥生成很复杂,每个时间戳涉及1,500轮SHA-256计算。每个文件最终都有唯一的密钥
- VMware VMFS文件系统仅记录秒级精度的文件修改时间
- 并非所有ESXi主机在日志文件中都有毫秒级精度,有些只记录秒级精度。我仍不确定是哪个配置文件导致这种不同行为
- 恶意软件在执行期间使用多线程
- 文件修改时间反映的是文件关闭的时间,而不是开始写入的时间
逆向工程
代码是用C++编写的, notoriously难以阅读,但幸运的是,它没有被混淆。二进制文件是静态链接的(分析起来有点困难),但所有字符串都是明文的。错误消息表明使用了Nettle库,这使理解代码变得容易得多。
错误字符串的存在确实很有帮助
生成随机数的代码是这样的(实际代码在二进制文件的0x455f40处):
|
|
随机数生成器在yarrow256.c中实现。以下是相关代码,删除了不必要的部分。如注释所述:
重新种子时的迭代次数,yarrow论文中的P_t。应选择使重新种子需要大约0.1-1秒。
|
|
种子和加密
勒索软件调用随机数生成器四次:
|
|
每个generate_random调用使用当前纳秒时间戳作为种子。因此,需要识别四个唯一的时间戳。勒索软件为每个文件生成不同的密钥。
这些密钥然后使用RSA-4096加密并用PKCS#11填充,保存在文件末尾作为尾部。
文件被分成N个块,每个块的一定百分比被加密。这个百分比由勒索软件的-n参数定义。对于每个块:
- 前0xFFFF字节使用KCipher2加密
- 剩余字节使用Chacha8加密
下图显示了文件如何分割。注意,对于非常小的文件,知道Chacha8密钥和IV不是必需的。
在研究各种VMware文件类型后(我稍后会深入探讨),我确信最重要的文件(flat VMDK和sesparse文件)具有固定的头部,我可以利用这一点来攻击加密。
其他细节
在这一点上,我没有进行更深入的分析。但我确信我可以在以后逆向工程其余的算法,特别是:
- 如何将文件分割成块
- 加密如何在块之间进行,是否继续流?
这些细节以后会很重要。然而,现在如果我们不能成功暴力破解时间戳,其他步骤都无关紧要。
暴力破解可行性
方法如下:
- 生成两个时间戳(t3和t4)
- 将这些时间戳转换为种子并生成随机字节
- 使用这些字节作为KCipher2密钥和IV
- 加密已知明文并将结果与加密文件中的已知密文进行比较
让我们制定计划:
- 检查可行性:确定暴力破解是否足够快且实用
- 识别明文:暴力破解需要已知明文
- 估计种子初始化时间:我们需要知道加密种子初始化的时间,至少精确到秒级。这些知识可以将暴力破解范围减少到约10亿个值
最简单(但低效)的方法是尝试所有可能的时间戳对,其中T4 > T3。可能的对数为:N×(N−1)/2 当N = 10亿时,结果是500万亿个可能的对。
我们需要优化这一点。首先我们需要将所有纳秒转换为随机值:
- 在我的迷你PC CPU上,我估计处理速度为每秒100,000个时间戳到随机字节的计算(利用所有核心)
- 这意味着将所有时间戳转换为种子值需要约10,000秒(不到3小时)
- 转换后,这些值可以保存以供重用
- 后来,我使用GPU优化了过程,将转换时间从3小时减少到不到6分钟
如果我们有一个完全确定性的机器,没有任何中断,我们可以运行恶意软件,测量它,知道T3和T4之间的确切时间。但不幸的是我们没有:
- 恶意软件使用多线程
- 它运行在非空闲的机器上,T3和T4之间的距离基于调度器和系统当时的繁忙程度而变化
- 代码还调用了大量C++库,这些库分配和释放对象,使执行时间更加不可预测
明确地说:
- 我们需要枚举t3(每秒10亿个值)
- 我们不从t3 + 1开始,而是从t3 + 起始偏移量开始,因为我们知道种子值需要时间(在我的机器上至少一百万纳秒),这是"起始偏移量"
- 我们假设执行下一个代码只需要几百万纳秒(记住:由于CPU调度器可能会有中断,并且会执行几百万条指令)。这是"偏移范围"值
我们可以做的是尝试运行与恶意软件完全相同的代码,收集计时数据,并尝试找到一个统计上有意义的范围。使用我在上一篇文章中使用的相同技术,我没有重新创建算法并运行它,而是修改了恶意软件并在我的几台本地机器上进行了测试。不同机器之间的运行时间差异很大。
我的朋友Deny去数据中心并在被感染的真实硬件上进行了测试。结果是:时间范围变化很大,有时相当大。偏移量的正常范围约为2-4百万纳秒(因此偏移范围是2百万),但值从1.5-5百万不等(总偏移范围是4.5百万)。
我们仍然需要枚举4.5万亿个对,但这似乎是可行的。如果我们有一个能够每秒运行5000万次加密的系统,这个过程将需要几百天。然而,使用16个这样的系统,我们可以在CPU上在几个月内完成。通过租用额外的机器,我们可以进一步加快进程。后来,我使用GPU优化了这一点,实现了显著的速度提升。
我不确定我们能以多快的速度运行Kicpher2,但与chacha的快速比较和一些快速基准测试表明,仅使用CPU,我应该能在我的机器上每秒至少进行数百万次Kichper操作。
如前所述,如果t3和t4正确,我们将能够解密文件的前8个字节,并且它将解密为已知明文。
接下来检查从不同VMware文件获取明文的可行性
VMware文件类型
对于每个文件,我们需要一个明文样本:KCipher2的文件前8个字节(偏移量0)和另一个从偏移量65,535开始的8个字节(仅适用于大文件)。由于KCipher2的每个块是8字节,我们应该使用8字节明文。可以使用更少的字节(通过使用位掩码),但这可能会增加误报的风险。
Flat-VMDK
这是一个原始磁盘文件。如果你幸运的话,这可能是你唯一需要恢复的文件。但是,如果创建了快照(如本例中的客户),新数据将被写入sesparse文件。
要获取flat VMDK的前8个字节,你需要安装原始VM使用的相同操作系统。不同操作系统版本使用几种不同的引导加载器变体。
要确定使用了哪个操作系统,请检查相应的VMX文件。它应该包含部分可读的明文,允许你检查"guestOS"的配置。你可能会找到类似:guestOS=“ubuntu"的内容。但是,理想情况下,你已经记录了每个VM使用的操作系统,因此不必依赖此方法。
对于位置65,535的字节(Chacha8的明文),由于分区通常从后面的扇区开始,几乎总是保证为零。
Sesparse
如果你为VM创建快照,每个快照将有一个SESPARSE文件。我们可以从QEMU源代码中看到文件格式。 https://github.com/qemu/qemu/blob/master/block/vmdk.c 文件头是0x00000000cafebabe,在位置65,535,应该是0x0(至少,我在分析中观察到的)。
其他文件
其他文件对于恢复工作的VM不是关键的,但对于初始测试,了解时间分布是有帮助的。如果有许多具有相同时间戳的小文件,了解它们是否在特定时间戳范围内聚集是有用的。
以下是一些常见的文件签名来识别明文:
- NVRAM文件以以下内容开头:4d 52 56 4e 01 00 00 00
- VMDK文件(磁盘描述符)以字符串开头:# Disk Descriptor
- .VMX文件以以下内容开头:.encoding
- VMware日志文件具有以下格式的行:YYYY-MM-DD
由于这些文件是部分可读的,我们通常可以基于文件开头猜测初始时间戳(例如,日志的YYYY-MM-部分)。
通过识别这些文件中的明文,下一步是缩小时间戳范围以进行准确的暴力破解。
加密时间戳
既然我们知道暴力破解是可行的,并且我们既有明文又有密文,下一步是确定每个文件的加密时间(因为每个文件将有不同的密钥)。
ESXI日志
用于运行恶意软件的命令记录在shell.log文件中(包括n的设置,它定义了应该加密多少文件)。
一些ESXi主机在其日志中提供毫秒级精度,而其他主机仅提供秒级精度。此日志为我们提供了恶意软件启动的初始时间戳。
例如,如果日志显示恶意软件在10:00:01.500启动,我们可以在暴力破解时安全地忽略前5亿纳秒,这有助于缩小搜索范围。
文件系统时间戳和修改时间
不幸的是,ESXi文件系统不支持纳秒级精度。
另一个挑战是文件修改时间仅在文件关闭时记录。这意味着记录的时间戳可能不准确反映加密过程开始的时刻,而是反映它结束的时刻。
- 在Linux(使用大多数文件系统)中,时间戳精度是纳秒级
- 对于小文件,加密通常只需要几毫秒,因此时间戳很可能反映文件加密的确切秒数。下一步是确定较大文件的加密时间,其中过程需要更长时间,时间戳可能不太精确。
- 在VMFS中,精度是秒级
多线程加密
恶意软件使用多线程,其中每个文件在新线程中处理,工作线程池受CPU核心数限制。这既有优点也有缺点。
如果恶意软件针对单个目录且文件数少于CPU核心数,过程很简单——每个文件的时间戳将非常接近彼此。在ESXi机器上,通常有具有大量核心的CPU(在这种情况下,服务器有64个核心)。
当使用以下命令检查时间戳时:
|
|
我们应该能够识别首先加密的小文件。在暴力破解期间,我们可以同时检查该特定时刻的多个文件。
首先处理的文件将具有相似的时间戳,但对于后来处理的文件,情况变得更加复杂。对于较大的文件,加密可能需要几秒到几分钟,修改时间将反映文件关闭的时间,这显著晚于加密密钥实际生成的时间。
恶意软件使用boost::filesystem遍历目录和文件。boost::filesystem中的迭代器遵循readdir返回的顺序,这与使用ls -f或find .等命令观察到的顺序相同。
让我们考虑一个例子,我们有4个CPU核心和8个文件。如果文件很小(小于1 KB,例如VMDK描述符文件),它们的处理几乎是瞬时的(在毫秒内)。处理可能如下所示:
- 线程A、B和C各自找到并处理小文件(file_a、file_b、file_c),而线程D找到一个大文件(file_d)。所有四个文件立即处理。
- 一旦线程A、B和C完成,它们开始处理下一组文件(file_e、file_f、file_g)。但是,这些文件更大,需要更多的处理时间。
- 当其他三个线程仍在工作时,线程D完成处理大文件file_d并开始处理最终文件(file_h)。因此,file_h的开始时间戳将与file_d的完成时间一致。
现在,想象有数百个文件——很难确定确切的处理顺序。然而,一个一致的观察是,一个文件的加密开始时间可能与另一个文件的修改时间相同或非常接近。
这是因为,一旦线程完成处理并关闭文件(从而记录其修改时间),它将立即开始处理下一个可用文件。这创建了一个序列,其中一个文件的加密开始时间与前一个文件的修改时间密切相关。
因此,给定几百个文件和大量CPU核心,我们可能只有几秒钟的列表,恶意软件将在这些时间开始生成随机密钥。
所以现在我们有了谜题的最后一部分:我们知道加密是何时执行的。
网络文件系统
在审查客户的日志时,我注意到一些条目提到使用NFS。但经澄清后,确认NFS仅用于备份且未受影响。所有相关文件都存储在服务器的本地磁盘上。
如果使用了网络文件系统,会使过程复杂化。如果系统之间的网络时间没有完美同步,时间戳可能不准确或不可靠,进一步复杂化暴力破解过程。
创建暴力破解器
计划似乎很可靠,所以下一步是实现代码。我需要确认加密过程是否与恶意软件完全一样。
为了测试这一点,我修补了恶意软件代码,使gettime函数返回常量值0,确保测试期间结果可预测且一致。
KCipher2
我专注于KCipher2,因为并非所有文件都使用Chacha8密钥,特别是小文件。尽管KCipher2是一种标准加密算法,但它并不广为人知,我找不到它的优化实现。
在实验过程中,我注意到我的结果与网上可用的标准KCipher2实现不匹配。结果发现恶意软件在初始化向量和加密过程中包含了一个轻微的修改,特别是涉及字节序交换。
CUDA
我不是CUDA编程专家。大约10年前,我简要尝试过它,但当时无法为我工作的公司找到实际用例。
为了加速开发,我请ChatGPT(o1)将代码移植到CUDA。代码编译成功但产生了不正确的结果。结果发现ChatGPT稍微修改了常数表中的数字。手动纠正这些值后,代码开始工作。
尽管实现运行了,但我怀疑它是次优的,但我无法从ChatGPT(o1)获得进一步的优化建议。那时,我有两个选择:花更多时间优化代码,或者继续使用预测的偏移范围并在过程中改进代码。我选择立即开始测试并根据需要优化。不幸的是,这种方法被证明是浪费金钱,因为它没有产生任何成功的结果。
在项目开始时,我只有两个RTX 3060 GPU。一个专用于我的Windows机器,所以我只能在迷你PC上使用一个GPU(通过Oculink外部连接)。为了提高性能,我决定购买RTX 3090。与4090或更高型号相比,泰国的价格仍然合理。
我通过从内存读取密钥和IV,加密零块,并将结果写回内存来测试实现。性能令人失望,仅实现约6000万次加密每秒。以这种速度,整个过程将需要约10年,显然对于实际恢复来说太慢。
手动优化
我通过删除不必要的代码执行了一些手动优化以提高性能:
- 暴力破解只需要第一个块,因此不需要处理额外的块
- 代码被简化为仅加密零块,减少不必要的处理
- 由于只需要结果的前8个字节,忽略其余输出以最小化计算
共享内存
在研究CUDA的AES优化后,我发现使用共享内存显著提高了性能,与ChatGPT建议的相反。令人惊讶的是,将常量内存数据复制到共享内存所涉及的额外步骤在开销方面可以忽略不计,但使代码运行速度快了几倍。
避免内存写入
最初,我在GPU上执行加密并在主机(CPU)上进行匹配。然而,这种方法很慢,即使并行执行:
- 在GPU上生成加密
- 将结果复制到CPU
- 在新线程中执行匹配并向GPU提交下一批工作
我发现完全避免写入内存要快得多。相反,匹配过程直接在GPU上处理,除非找到匹配,否则不会向内存写入任何数据。这种方法显著减少了处理时间并提高了效率。
多文件匹配
对于每个t3和t4组合,匹配可以发生在共享相同秒级时间戳(但具有不同纳秒)的任何文件上。
为了提高效率,我们可以尝试同时匹配多个文件。但是,如果要匹配的文件太多,过程会显著减慢。目前,并行处理的文件数硬编码为32,以在性能和效率之间保持平衡。
循环
我考虑并实现了两种循环方式。对于每个t3值,我们可以启动一个GPU内核来检查所有偏移范围。然而,这种方法效率低下,因为它需要启动内核10亿次,导致显著开销。
或者,我们可以为每个偏移启动一个GPU内核。每个内核将执行必要的检查。这种方法要快得多,因为它将提交次数减少到仅"偏移范围”,约为2到450万个作业。
批量检查
最初,我的方法是向GPU提交任务,使用cudaDeviceSynchronize()等待结果,然后提交下一批工作。然而,这种方法被证明很慢。
- 向GPU提交工作,如果找到匹配,只需使用找到的标志标记它
- 每100步仅调用一次cudaDeviceSynchronize()检查结果。如果找到匹配,标志在继续之前重置为零
虽然这种方法显著提高了性能,但有可能如果两个偏移非常接近(小于100步),代码可能会错过其中一个。尽管在我的测试中从未出现此问题,但我添加了一个可选的循环模式。在此模式下,程序读取偏移列表并确保手动检查附近的偏移以避免错过任何潜在匹配。
最终速度
我相信GPU专家仍然可以找到进一步优化我的代码的方法。目前,我在RTX 3090上每秒实现约15亿次KCipher2加密。
对于测试10亿个值具有单个偏移,需要约0.7秒,包括检查匹配的时间(每批最多32个匹配)。 测试200万个偏移将需要在单个GPU上约16天,或使用16个GPU仅1天。
我还使用Runpod进行了测试,结果RTX 4090是理想选择。虽然它比3090贵约60%,但也快2.3倍。
使用4090,相同过程将在单个GPU上需要约7天。 使用16个GPU,过程可以在仅10个多小时内完成。
运行暴力破解
从成本角度来看,RTX 4090是此任务的绝佳选择, due to several factors:
- 不需要大内存
- 不需要浮点运算
- RTX 4090提供大量的CUDA核心,增强处理速度
- RTX 4090的租赁价格相对于其他高端GPU相对较低
如果4090不可用,考虑到其价格性能比,3090也是一个不错的替代品。
最初,我的客户考虑使用Google Cloud Platform(GCP)机器并寻求月租折扣。然而,此选项被证明极其昂贵(花费数万美元)。
经过一些研究,我找到了更具成本效益的替代方案:Runpod和Vast.ai。
Runpod
要暴力破解1秒(10亿纳秒),偏移范围为200万,将需要7天。RTX 4090的成本(在撰写本文时)为0.69美元/小时。暴力破解单秒将花费约116美元。租用16个GPU将在约10小时内完成工作,成本相同,但更快。
暴力破解450万的范围(这是我们需要的范围)花费261美元。根据加密文件的数量,你可能需要暴力破解10秒或更多。如果你有很多文件要恢复,周租或月租会更便宜。
注意:这些成本假设一切完美执行。任何错误或需要重复过程都会显著增加成本。总共,包括我所有的实验和测试,我花了大约1200美元。
Vast.ai
与runpod不同,当使用vast.ai时,你是在租用由vast.ai代理的某个随机人的机器。在进行暴力破解时,不会发送敏感数据,因此隐私不应成为问题。
使用vast AI,暴力破解成本可以减半,但这取决于你获得机器的运气。我测试的前几台机器不起作用(等待约10分钟后网络超时)。我还在从docker.io拉取docker镜像时遇到问题(我必须从另一个docker存储库选择另一个模板)。
其余工作
既然我找到了t3和t4的值,我可以尝试找到t1和t2的值。t1的值必须小于t3,时间偏移小于1000万纳秒。这可以在几分钟内使用单个GPU快速找到。
块分割算法
以下是用于将文件分割成部分的算法:
- enc_block_size:对于每个部分/块,这是要加密的字节数。前0xFFFFF将使用KCipher2加密,其余使用Chacha8加密
- part_size:块的大小
- encrypted_parts:要加密的块数
|
|
加密细节
恶意软件使用8轮Chacha变体称为chacha8,而不是许多网站报道的Chacha20。
对于kcipher2,我们将加密前65535字节(是的,不是65536)。这意味着第一个块将剩余一个字节,这需要用于下一个块。 对于cacha20,当开始新块时,我们只是丢弃加密流块的其余部分。
恢复步骤
要在不支付的情况下恢复文件,并不像运行通用解密器那么简单。你将需要:
- 获取文件的时间戳
- 获取文件的密文和明文
- 租用GPU
关于代码的说明
老实说,我最初编写此代码是一次性使用,专门用于此特定客户。共享的代码充满了实验逻辑、快速黑客,并且缺乏适当的测试。
除了删除一些客户特定的测试用例和注释外,我没有动力进一步清理它。它对于预期目的是功能性的。
我提供的软件仅包括主要的暴力破解和解密组件,旨在在你拥有必要的时间戳后使用。
我没有专用的系统来管理多个GPU。相反,我依赖基本的shell脚本和自定义脚本,当找到匹配时发送Telegram消息。代码"对我来说足够好",并且"对我来说有效"。
本质上,你将需要一位有能力的系统管理员,他理解过程并知道如何有效管理和排除系统故障。
构建代码
获取时间戳
我希望你没有接触文件,因为如果时间戳未知,所有恢复的希望都将消失。使用stat filename获取修改时间戳。使用find /vmfs/volumes -exec stat {} ; > /tmp/stats.txt获取所有内容的时间戳。
文件shell.log可以帮助确定要使用的最小时间戳。
获取密文
如上所述获取密文:
- 对于flat-vmdk,你需要从使用的确切操作系统(包括确切的安装方法,例如:使用BIOS/UEFI)中提取此内容
- 对于sesparse文件,使用头部0x00000000cafebabe
- 对于其他文件,参见我上面写的内容
测量服务器速度
你总是可以使用1.5-500万的偏移范围,但如果你的硬件太快或太慢,这可能不是正确的范围。你可以通过查看我的github存储库中的timing-patch-1文件夹和timing-patch-2文件夹来测量这一点。
第一个仅通过直接调用函数来测量时间范围。第二个用于加密目录,但已修补,以便在时间戳用作种子时将确切时间写入/tmp/log.bin。
分工
基于密文/明文和时间戳创建配置文件。你可以手动创建/分割此内容,或使用脚本生成它。我的代码不进行任何错误检查,确保时间戳是纳秒格式,确保所有明文和密文值正确。
租用GPU
如果你想要非常快速和简单的设置,使用runpod或其他服务。如果你想便宜,使用vast.ai,或在你自己的硬件上运行(约1000美元购买一个RTX 3090,以后可以转售)。
运行Kcipher2暴力破解
第一次暴力破解是为Kcipher找到t3和t4。
|
|
例如:
|
|
如果你有多个GPU,附加GPU索引
|
|
我建议在tmux内运行它,以便在网络断开的情况下你也没问题。
如上所述:这可能需要几天(取决于使用的GPU),因此请确保:
- 所有配置文件都良好
- 你使用正确的GPU索引
- 确保一切正常运行
- 使用nvidia-smi检查(使用runpod,我们也可以通过网络查看GPU状态)
- 制作通知系统,以便在output.txt创建/更新时提醒你
运行chacha8暴力破解
对于小文件,这不是必需的,但对于大文件是需要的。对于找到的每个偏移,使用上一步中找到的t3生成配置。在我的目标机器上,t1和t3之间的距离小于1000万,t1到t2约为1.5-500万。暴力破解应该只需要约10分钟。
解密文件
注意解密器将百分比硬编码为15%,因此如果攻击者使用不同的值,请在运行解密器之前更改此值。
一旦我们获得了t1、t2、t3和t4,运行解密器:
|
|
解密过程没有优化,因此需要一段时间来解密。
结论
可能99.9%的时间当你遇到勒索软件时,没有密钥将无法恢复。但如果你幸运,有时可以找到解决方案。解决这个问题花了我比预期长得多的时间,我以为需要一周,但花了将近三周才恢复整个VM文件集。
我还想补充一点,我找到了一个关于akira勒索软件的reddit线程,我不确定我拥有的勒索软件菌株是否与他们的相同,这就是为什么我继续自己的研究并开源它。我希望我的经验和代码对其他人有用。
每次我写关于勒索软件的东西(在我的印尼博客中),许多人会寻求勒索软件帮助。许多人甚至找不到勒索软件可执行文件(只有加密文件,这没有用)。仅检查勒索软件是否可恢复可能需要几个小时,并且需要大量努力(例如:如果恶意软件被混淆/保护)。所以请不要要求我免费这样做。