Cobalt Strike: 解密混淆过的流量-Part 4

https://blog.nviso.eu/2021/11/17/cobalt-strike-decrypting-obfuscated-traffic-part-4/
博文系列:Cobalt Strike:解密流量
Cobalt Strike:使用已知私钥解密流量 - 第 1 部分
Cobalt Strike:使用已知私钥解密流量 - 第 2 部分
Cobalt Strike:使用进程内存解密流量 - 第 3 部分
Cobalt Strike:解密混淆流量 - 第 4 部分(当前)
Cobalt Strike:解密 DNS 流量——第 5 部分
Cobalt Strike:内存转储 - 第 6 部分
Cobalt Strike:概述 – 第 7 部分
加密的 Cobalt Strike C2 流量可以被可塑性 C2 数据转换混淆。我们展示了如何对此类流量进行去混淆处理。

本系列博文介绍了解密 Cobalt Strike 流量的不同方法。在本系列的第 1 部分中,我们揭示了在恶意 Cobalt Strike 包中发现的私有加密密钥。在第 2 部分中,我们从 RSA 私钥开始解密 Cobalt Strike 流量。在第 3 部分中,我们将解释如果您不知道私有 RSA 密钥但确实有进程内存转储,如何解密 Cobalt Strike 流量。

在本系列的前 3 部分中,我们一直关注包含未更改的加密数据的流量:查询返回的数据和发布的数据只是加密数据。

使用可延展的 C2 数据转换,可以将这些加密数据转换为看起来更良性的流量。在我们将在这篇博文中看到的示例中,加密数据隐藏在 JavaScript 代码中。

但是我们如何知道信标是否使用此类指令来混淆流量?这在最新版工具 1768.py的分析结果中可以看出。让我们看一下我们在第 1 部分开始的信标的配置:Cobalt Strike: 解密混淆过的流量-Part 4_第1张图片
我们看到字段 0x000b(可延展的 C2 指令)只有一条指令:打印。这是默认设置,这意味着信标按原样接收加密数据:在解密之前不需要任何转换。

对于字段 0x000d(http post header),我们看到构建输出也只是一条指令:打印。这是默认设置,意味着加密数据由信标按原样传输:加密后不需要任何转换。

让我们看一个具有自定义可延展 C2 数据转换的示例:Cobalt Strike: 解密混淆过的流量-Part 4_第2张图片
在这里,我们看到的不仅仅是一条打印指令:“从结尾删除 1522 个字节”、“从开头删除 84 个字节”……

这些是转换(反混淆)传入流量的指令,以便随后可以对其进行解密。要详细了解其工作原理,我们将使用 Cyber​​Chef 手动进行转换。但是,要知道工具cs-parse-http-traffic.py可以自动进行这些转换。

这是信标对单个 GET 请求的网络捕获以及来自团队服务器 (C2) 的回复:Cobalt Strike: 解密混淆过的流量-Part 4_第3张图片
我们在这里看到的是信标对 C2 的 GET 请求(注意带有加密元数据的 Cookie)和 C2 的回复。此回复看起来像 JavaScript 代码,因为已使用可延展的 C2 数据转换使其看起来像 JavaScript 代码。

我们将此回复复制到 Cyber​​Chef 的输入字段中:Cobalt Strike: 解密混淆过的流量-Part 4_第4张图片
工具 1768.py 的输出中列出了我们需要遵循的说明,以对该回复进行去混淆处理:Cobalt Strike: 解密混淆过的流量-Part 4_第5张图片
所以让我们开始吧。首先,我们需要从回复末尾删除 1522 个字节。这可以通过 Cyber​​Chef 删除字节函数和负长度来完成(负长度意味着从末尾删除):Cobalt Strike: 解密混淆过的流量-Part 4_第6张图片
然后,我们需要从回复的开头删除 84 个字节:Cobalt Strike: 解密混淆过的流量-Part 4_第7张图片
然后还从头开始删除 3931 个字节:Cobalt Strike: 解密混淆过的流量-Part 4_第8张图片
现在我们最终得到了看起来像 BASE64 编码数据的输出。实际上,下一条指令是应用 BASE64 解码指令(准确地说:URL 的 BASE64 编码):Cobalt Strike: 解密混淆过的流量-Part 4_第9张图片
下一条指令是对数据进行异或。为此,我们需要 XOR 键。用于 XOR 的可延展 C2 指令使用一个 4 字节长的随机密钥,该密钥预先添加到 XOR 数据。所以为了恢复这个密钥,我们将二进制输出转换为十六进制:Cobalt Strike: 解密混淆过的流量-Part 4_第10张图片
前 4 个字节是 XOR 键:b7 85 71 17

我们将它与 Cyber​​Chef 的 XOR 命令一起使用:Cobalt Strike: 解密混淆过的流量-Part 4_第11张图片
请注意,前 4 个字节现在是 NULL 字节:正如预期的那样,XORing 字节与它们本身给出 NULL 字节。

最后,我们删除这 4 个 NULL 字节:Cobalt Strike: 解密混淆过的流量-Part 4_第12张图片
我们最终得到的是包含要由信标执行的 C2 命令的加密数据。这是通过遵循可延展的 C2 数据转换对数据进行去混淆的结果。现在我们可以使用进程内存转储继续解密,就像我们在第 3 部分中所做的那样。Cobalt Strike: 解密混淆过的流量-Part 4_第13张图片
工具cs-extract-key.py用于从进程内存中提取 AES 和 HMAC 密钥:它失败了,它无法在进程内存中找到密钥。

无法找到密钥的一种可能解释是进程内存已编码。Cobalt Strike 支持信标功能,称为睡眠面罩。启用此功能后,带有信标数据(包括密钥)的进程内存在信标休眠时进行异或编码。因此,只有当信标处于活动状态(通信或执行命令)时,其数据才会以明文形式显示。

我们可以尝试解码这个进程内存转储。工具cs-analyze-processdump.py是一个尝试解码具有活动睡眠掩码功能的信标的进程内存转储的工具。让我们在进程内存转储上运行它:Cobalt Strike: 解密混淆过的流量-Part 4_第14张图片
Cobalt Strike: 解密混淆过的流量-Part 4_第15张图片
该工具确实找到了一个 13 字节长的 XOR 密钥,并将解码的部分作为扩展名为 .bin 的文件写入磁盘。

该文件现在可以与 cs-extract-key.py 一起使用,它与以前的命令完全相同,但使用解码部分而不是编码的 .dmp 文件:

Cobalt Strike: 解密混淆过的流量-Part 4_第16张图片
现在我们已经恢复了加密密钥。

请注意,在图 16 中,该工具报告找到字符串 sha256\x00,而在第一个命令(图 13)中,未找到该字符串。缺少此字符串通常是信标使用睡眠掩码的一个很好的指示,并且应该在提取密钥之前使用工具 cs-analyze-processdump.py。

现在我们有了密钥,我们可以使用工具cs-parse-http-traffic.py解密网络流量:

Cobalt Strike: 解密混淆过的流量-Part 4_第17张图片
这失败了:原因是可延展的 C2 数据转换。工具 cs-parse-http-traffic.py 需要知道在解密之前应用哪些指令来对流量进行去混淆处理。就像我们手动使用 Cyber​​Chef 所做的一样,工具 cs-parse-http-traffic.py 需要自动执行此操作。这可以通过选项 -t 来完成。

请注意,工具 1768.py 的输出包含要执行的指令的简写符号(在方括号之间):Cobalt Strike: 解密混淆过的流量-Part 4_第18张图片
图 18:可延展 C2 指令的简写符号
对于要执行的任务(输入),它是:

7:输入,4,1:1522,2:84,2:3931,13,15

对于要发布(输出)的结果,它是:

7:输出,15,13,​​4

这些指令可以放在一起(使用分号作为分隔符)并通过选项 -t 提供给工具 cs-parse-http-traffic.py:Cobalt Strike: 解密混淆过的流量-Part 4_第19张图片
现在我们终于获得了解密的流量。此流量中没有实际的命令,只是“数据抖动”:即随机长度的随机数据,旨在更加混淆流量。

结论

我们看到了如何使用可延展的 C2 数据转换来混淆网络流量,以及我们如何按照说明对这种网络流量进行反混淆。

我们使用 Cyber​​Chef 手动完成此操作,但这当然不实用(我们这样做是为了说明这个概念)。要获取解码、加密的命令,我们还可以使用 cs-parse-http-traffic.py。就像我们在第 3 部分中所做的那样,我们从一个未知的密钥开始,我们在这里也这样做。唯一不同的是,我们还需要提供解码指令:Cobalt Strike: 解密混淆过的流量-Part 4_第20张图片
然后我们可以取这 3 个加密数据中的一个来恢复密钥。

因此,该过程与第 3 部分中解释的完全相同,除了必须使用选项 -t 来包含可延展的 C2 数据转换。

你可能感兴趣的