CobaltStrike 威胁狩猎(一)知己知彼 —— 认识CS通信流量到解密
前言使用已知的私钥来解密CS通信流量。背景是研究团队发现了6个恶意Cobalt Strike的私钥可用于解密C2网络流量。HTTPS信道的通信加密分析CS HTTPS Channel 本质上是 AES Over HTTPSAES 的加密Key由beacon端生成的并且通过一个 encrypted metadata blob (默认是一个cookie值) 和TeamServer端同步这个密钥。而RSA加密则会用来加密这个 metadata (HTTPS TLS 提供的非对称加密)Beacon 里面有RSA加密所使用的public key而TeamServer端则用private key可以解密数据。Beacon - RSA Public Key TeamServer - RSA Private KeyPublic key 和 private key 存放在.cobaltstrike.beacon_keys中这个文件是由TeamServer在启动时候生成。研究人员通过扫描互联网上的teamserver发现有很多是使用相同public key的Teamserver进而推测他们的.cobaltstrike.beacon_keys文件是相同的。原因是他们使用了泄露的破解版本CS并且使用了里面默认自带的.cobaltstrike.beacon_keys文件没有进行任何改动。Didier Stevens 开发了一个工具使用这个工具可以从Beacon Payload 文件中提取CS Profile文件这个工具默认会去识别RSA加密所使用的public key如果这个公钥对应的是一个已知的公开版本的 私钥那么私钥会被识别出来https://blog.didierstevens.com/2021/10/11/update-1768-py-version-0-0-8/接着指定-V参数就可以显示对应的 private key 这个是泄露的私钥接着拿到这个RSA的private key我们就可以解密 C2 的通信流量进一步再解密出 metadata使用泄露的 private.key 解密C2流量接着拿 Malware-Traffic-Analysis.net 上的一个样本 2021-02-02-Hancitor-with-Ficker-Stealer-and-Cobalt-Strike-and-NetSupport-RAT.pcap.zip 来实战解密一下。这个pcap里面抓到有 CS Beacon 和 teamserver 之间通信的流量。这个样本相对比较简单因为实际上它的Beacon是使用HTTP 通信的但密钥交换步骤还是通过TLS进行的并且使用的RSA加密的密钥使用了已知的私钥如果使用的是未知的私钥的话那么我们未必能够从流量测直接入手因为流量通过HTTPS加密而我们没有私钥去解密流量进而也就无法推到出从私钥衍生的HMAC和AES Key也就无法解密通信数据。这种情况下通常我们得从其它方面入手可能是磁盘上的文件也可能是运行中的进程的内存。需要先找到Beacon的主Payload然后对其进行解析分析从里面提取Beacon中的public key然后看是否对应已知的private key如果找不到对应的已知private key那么也是无法对它的通信数据进行解密。但作者提供了一个方法可以从Beacon中直接提取 HMAC 和 AES Key进而解密通信数据。分析Payload投递流量首先我们用Wireshark打开pcap文件并寻找stager下载后续大payload的包含完整payload的数据包。默认payload执行的流量可以分为两部分1. 小payload - stager 一百字节上下用来下载后面的大payload2. 大payload - 其中包含核心植入物 beacon.dll第一步我们在pcap中查找任何关于stager的信息http.request.uri matches /....$基于这个算法我们可以使用 这个工具来扫描CS stager http serverhttps://blog.didierstevens.com/2021/04/18/metatool-py/More info on this checksum process can be found here.工具的输出显示这是一个下载 32-bit full beacon (CS x86) 的合法URL路径。提取Stageless Payload接着我们看到含有大payload的数据包把这里面的数据提取出来看到有一个这个把它另存到文件EbHm.vir中然后我们可以使用 tool 1768.py 来分析二阶段的payload。我们看到1768.py 直接就把beacon的metadata给解密了之所以能解密是因为这个beacon所使用的就是泄露的 known 的RSA 密钥。接着让我们来仔细看一下这些配置信息。首先Option 0x0000 代表这是一个 HTTP 信标它是通过HTTP Channel 进行通信的它通过连接到 192.254.79[.]71Option 0x0008的 8080 端口Option 0x0002来实现这一点。GET请求使用路径 /ptjOption 0x0008POST请求使用路径 /submit.phpOption 0x000a对于我们的分析而言关键信息在于该Beacon所使用的公钥存在已知的私钥Option 0x0007。因此基于这些信息我们知道 Beacon 会向 TeamServer 发送 GET 请求以获取指令。如果TeamServer有需要Beacon执行的命令它将用加密数据来回复该 GET 请求。而当Beacon需要将命令的执行结果发回TeamServer时它将使用包含加密数据的 POST 请求发送。如果TeamServer没有给Beacon的命令则不会发送加密数据。这并不一定意味着对 GET 请求的响应中就不会包含数据了 ——— 操作员可以通过修改C2 Profile 配置来伪装通信。解密C2通信流量过滤之后找到beacon 发送 加密 metadata的 GET 请求数据包这段 加密的 cookie 是使用beacon中的 RSA public key 进行加密的。TeamServer接收到之后会使用teamserver端的 private key 进行解密。cs-decrypt-metadata.py 这个脚本里面内置了有几组 泄露的 private key尝试直接解密看看能不能解密这里我们可以看到已解密的metadata。对我们来说Raw Keycaeab4f452fe41182d504aa24966fbd0 至关重要。我们将使用该密钥来解密流量AES 和 HMAC 密钥均由此Raw Key派生而来。我们分析第一个通信的请求和响应数据包使用 cs-parse-http-traffic.py 这个脚本可以解密这些加密的c2通信数据。由于数据经过加密我们需要提供Raw Key选项 -r caeab4f452fe41182d504aa24966fbd0此外由于数据包捕获中包含除了纯Cobalt Strike C2流量以外的其他流量最好设置一个显示过滤器 选项 -Y http and ip.addr 192.254.79.71 and frame.number 6703以便工具能够忽略所有非C2流量的HTTP数据。从内存dump获取加密key来解密C2流量我们将讲解如何在不知道RSA私钥但拥有进程内存转储的情况下对Cobalt Strike流量进行解密。Cobalt Strike 的网络流量可以通过正确的 AES 和 HMAC 密钥进行解密。在 [第 2 部分](https://blog.nviso.eu/2021/10/27/cobalt-strike-using-known-private-keys-to-decrypt-traffic-part-2/) 中我们通过使用 RSA 私钥解密元数据获得了这些密钥。获取 AES 和 HMAC 密钥的另一种方法是从活跃Beacon的进程内存中提取它们。使用procdump来dump 进程的内存-p参数后面的是目标进程的pidprocdump.exe -mp 1234得到的是.dmp后缀的文件。For CS v3.X Beacon对于3.X版本的Beacon未加密的 metadata 可以通过直接从内存中检索字节 0x0000BEEF 搜出。这个字节是 unencrypted metadata 的 header在进程生命周期的早期阶段捕获进程转储该转储包含未加密 metadata 的可能性就越大。4.X 版本开始引入sleepmask 如果dump内存的时机不好有可能是dump到的beacon已经被加密有可能检索不到这些特征字符串。并且sleepmask在后续不断完善和加强。早期的版本小于或等于4.5版本并没有对Beacon 堆中的配置文件进行加密而内存中的配置文件是使用一字节XOR进行加密的。因此从内存dump基本上都能够解密还原metadata。Tool cs-extract-key.py 这个工具可以从内存dump文件中提取metadataMetadata中包含有这些重要信息16 bytes 随机的 raw keyAES 和 HMAC 密钥是通过计算 raw key 的 SHA256 值从该 raw key 派生而来的。SHA256 值的前半部分是 HMAC 密钥而后半部分是 AES 密钥。随后可使用工具 cs-parse-http-traffic.py 解密捕获的网络流量具体方法如 Part 2 中所述。请注意工具 cs-extract-key.py 可能会产生误报即以 0x0000BEEF 开头但并非实际 metadata 的字节序列。Figure 2 中的示例即属此类情况第一个实例确实是有效的 metadata因为它包含可识别的计算机名称和用户名参见“Field”条目。此外从该 metadata 中提取的 AES 和 HMAC 密钥也在进程内存的其他位置被发现。但第二个实例并非如此没有可识别的名称且在其他位置未发现 AES 和 HMAC 密钥。因此这是一个必须忽略的FP。For CS v4.X Beacon对于 Cobalt Strike 4.0 版本的Beacon从进程内存中恢复未加密 metadata 的情况非常罕见。对于此类Beacon可以采用另一种方法。AES 和 HMAC 密钥位于可写进程内存中(Writable)但没有明确标识这些密钥的头部信息。它们只是长度为 16 字节的序列没有任何可辨识的特征。提取这些密钥的方法本质上是一种字典攻击。将利用进程内存中所有可能的、长度为 16 Bytes 且不为空的序列尝试解密一段加密的 C2 通信。若解密成功则表明已找到有效的密钥。此方法依赖于内存dump以及加密的通信流量数据两部分。可以使用工具cs-parse-http-traffic.py按如下方式提取这些加密数据cs-parse-http-traffic.py -k unknown capture.pcapngWith an unknown key (-k unknown), the tool will extract the encrypted data from the capture file, like this:我们只需要指定参数-k为unknown它就会帮助我们从 pcap 中提取符合模式 encrypted data数据包 103 是对 GET 请求数据包 97的 HTTP 响应。该响应的加密数据长度为 64 字节 d12c14aa698a6b85a8ed3c3c33774fe79acadd0e95fa88f45b66d8751682db734472b2c9c874ccc70afa426fb2f510654df7042aa7d2384229 518f26d1e044bd这是由TeamServer发送至Beacon的加密数据其中包含需由Beacon行的任务请注意在这些示例中我们分析的是未经变换的加密流量关于经 malleable C2 指令变换后的流量我们将在后续博文中进行探讨。我们可以尝试使用工具 cs-extract-key.py 解密这些数据方法是向该工具提供加密任务-t 选项和进程内存转储cs-extract-key.py -t d12c14aa698a6b85a8ed3c3c33774fe79acadd0e95fa88f45b66d8751682db734472b2c9c874ccc70afa426fb2f510654df7042aa7d2384229518f26d1e044bd rundll32.exe_211028_205047.dmpcs-extract-key.py 我们只需要提供 encrypted data 以及dmp文件他可以自动帮我们去爆破。随后可使用恢复出来的AES和HMAC密钥对流量进行解密-k HMACkey:AESkey如图 5 所示的解密出来看到Task是“DATA_JITTER”。数据抖动是 Cobalt Strike 的一项功能用于向Beacon发送随机数据这些随机数据会被Beacon忽略。而在默认的 Cobalt Strike Beacon 配置文件中Beacon不会发送随机数据数据也不会通过Malleable指令进行转换。这意味着使用这样的Beacon配置文件时只要Beacon没有待执行的任务就不会向TeamServer发送数据HTTP响应的Content-Length字段值为0。由于Beacon没有任务意味着没有加密数据被传输因此即使流量经过加密也很容易判断信标是否接收到了任务。没有加密数据即表示未发送任何任务。为掩盖这种命令任务缺失的情况Cobalt Strike 支持修改配置为交换随机数据使每个数据包都具有唯一性。但在本例中这些随机数据对蓝队成员却大有裨益它使我们能够从进程内存中恢复加密密钥。如果既不发送随机数据也不发送实际任务我们就永远无法看到加密数据从而无法识别进程内存中的加密密钥。Beacon发送到TeamServer的数据包含Beacon执行的任务结果。这些数据通过 POST 请求默认发送被称为callback。这些数据同样可用于查找解密密钥。在这种情况下操作流程与上文所述相同但应使用 -ccallback选项代替 -ttask选项。选项之所以不同是因为TeamServer对数据的加密方式与Beacon对数据的加密方式略有差异因此必须告知工具所使用的加密方式。关于进程内存转储的一些注意事项对于10MB 以内的进程内存转储字典攻击通常只需几分钟。虽然也可以使用完整的进程转储但由于转储文件体积较大字典攻击可能需要更长时间。工具 cs-extract-key.py 将进程内存转储作为一个 flat 文件读取因此文件越大需要处理的数据量就越多。不过我们正在开发一个工具 https://github.com/DidierStevens/Beta/blob/master/cs-analyze-processdump.py它能够解析转储文件的数据结构并提取/解码最有可能包含密钥的内存区域从而加快密钥恢复过程。请注意Beacon可配置为在非活动Sleep状态下对可写内存进行加密Sleepmask在此情况下AES 和 HMAC 密钥也会被加密无法通过本文所述方法进行恢复。我们正在开发的转储解析工具也将处理这种情况。最后如果本文针对 v3.X 版Beacon的方法对您的特定内存转储文件无效请尝试 v4.X 版的方法。该方法同样适用于 v3.X 版Beacon。如果你已经读到这里了记得点赞关注收藏。欢迎评论区交流探讨我们下期再见 参考链接https://blog.nviso.eu/2021/10/21/cobalt-strike-using-known-private-keys-to-decrypt-traffic-part-1/