使用GPU暴力破解Akira勒索软件(Linux/ESXI变种2024)加密文件

本文详细介绍了如何通过逆向工程分析Akira勒索软件的加密机制,利用GPU进行暴力破解时间戳种子,最终成功恢复被加密文件而不需要支付赎金的技术方案。

解密Akira勒索软件(Linux/ESXI变种2024)加密文件:使用大量GPU进行暴力破解

我最近帮助一家公司从不支付赎金的情况下恢复了被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处)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
void generate_random(char *buffer, int size)
{
    uint64_t t = get_current_time_nanosecond();
    char seed[32];
    //在实际代码中,它使用C++代码将int转换为字符串
    snprintf(seed, sizeof(seed), "%lld", t);
    struct yarrow256_ctx ctx;
    yarrow256_init(&ctx, 0, NULL);
    yarrow256_seed(&ctx, strlen(seed), seed);
    yarrow256_random(&ctx, size, buffer);   
}

随机数生成器在yarrow256.c中实现。以下是相关代码,删除了不必要的部分。如注释中所述:

重新播种时的迭代次数,yarrow论文中的P_t。应选择使得重新播种大约需要0.1-1秒。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
void
yarrow256_seed(struct yarrow256_ctx *ctx,
           size_t length,
           const uint8_t *seed_file)
{
  sha256_update(&ctx->pools[YARROW_FAST], length, seed_file);
  yarrow256_fast_reseed(ctx);
}

void
yarrow256_fast_reseed(struct yarrow256_ctx *ctx)
{
  uint8_t digest[SHA256_DIGEST_SIZE];
  unsigned i;   
  sha256_digest(&ctx->pools[YARROW_FAST], sizeof(digest), digest);
  /* 迭代 */
  yarrow_iterate(digest);
  aes256_set_encrypt_key(&ctx->key, digest);
  /* 派生新的计数器值 */
  memset(ctx->counter, 0, sizeof(ctx->counter));
  aes256_encrypt(&ctx->key, sizeof(ctx->counter), ctx->counter, ctx->counter); 
}

/* 重新播种时的迭代次数,yarrow论文中的P_t。
 * 应选择使得重新播种大约需要0.1-1
 * 秒。 */
#define YARROW_RESEED_ITERATIONS 1500

static void
yarrow_iterate(uint8_t *digest)
{
  uint8_t v0[SHA256_DIGEST_SIZE];
  unsigned i;
  
  memcpy(v0, digest, SHA256_DIGEST_SIZE);
  
  /* 在循环内部哈希时,i应从1运行到
   * YARROW_RESEED_ITERATIONS */
  for (i = 0; ++i < YARROW_RESEED_ITERATIONS; )
    {
      uint8_t count[4];
      struct sha256_ctx hash;
  
      sha256_init(&hash);

      /* 哈希 v_i | v_0 | i */
      WRITE_UINT32(count, i);
      sha256_update(&hash, SHA256_DIGEST_SIZE, digest);
      sha256_update(&hash, sizeof(v0), v0);
      sha256_update(&hash, sizeof(count), count);

      sha256_digest(&hash, SHA256_DIGEST_SIZE, digest);
    }
}

种子和加密

勒索软件调用随机数生成器四次:

1
2
3
4
generate_random(chacha8_key, 32);
generate_random(chacha8_nonce, 16);
generate_random(kcipher2_key, 16);
generate_random(kcipher2_key, 16);

每个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百万纳秒之间(因此偏移范围是200万),但值从1.5到500万不等(总偏移范围是450万)。

我们仍然需要枚举4.5万亿对,但这似乎是可行的。如果我们有一个能够每秒运行5000万次加密的系统,这个过程将需要几百天。然而,使用16个这样的系统,我们可以在CPU上在几个月内完成。通过租用额外的机器,我们可以进一步加快这个过程。后来,我使用GPU优化了这一点,实现了显著的速度提升。

我不确定Kicpher2能有多快,但与chacha的快速比较和一些快速基准测试表明,仅使用CPU,我应该能够在我的机器上每秒至少进行数百万次Kichper操作。

如前所述,如果t3和t4正确,我们将能够解密文件的前8个字节,并且它将解密为已知明文。

接下来检查从不同VMware文件获取明文的可行性

VMWare文件类型

对于每个文件,我们需要一个明文样本:文件的前8个字节用于KCipher2(偏移0)和从偏移65,535开始的另外8个字节(仅适用于大文件)。由于KCipher2的每个块是8字节,我们应该使用8字节明文。可以使用更少的字节(通过使用位掩码),但这可能会增加误报的风险。

Flat-VMDK

这是一个原始磁盘文件。如果你幸运,这可能

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计