didiernvisocyber threats, ForensicsNovember 17, 2021 6 Minutes
- Cobalt Strike: 使用已知的私钥解密流量 - Part 2
- Cobalt Strike: 使用进程内存解密流量 - Part 3
- Cobalt Strike:使用已知的私钥解密流量-Part 1
- Cobalt Steike: 解密被掩盖的流量 - Part 4
- Cobalt Strike: 解密DNS流量 - Part 5(当前部分)
Cobalt Strike的beacon可以通过DNS流量进行通信,本文展示如何解码并解密DNS流量
本系列博客文章描述使用不同的方法解密Cobalt Strike流量。在Part 1中,我们从Cobalt Strike的恶意流量包中提取到用于加密流量的私钥。在Part 2 中,我们从一个RSA私钥开始解密Cobalt Strike流量。在Part 3 中,解释了如何在没有找到RSA私钥,但是有一些进程内存转储文件的情况下解密Cobalt Strike流量。在Part 3 中,我们处理的是用可变的C2数据转换来混淆的流量。
在本系列的前四部分文章中,我们查看到的流量均是使用HTTP(或HTTPS)协议。beacon也可以被配置成使用DNS协议进行通信,通过进行DNS的A,AAAA和/或TXT查询记录。从Beacon发送到团队服务器的数据流通过十六进制编码,这些数字构成了被查询名称的标签,而从团队服务器发送到Beacon的数据就包含在这些A、AAAA和/或TXT查询记录中。将这些数据需要从DNS查询中提取出来,然后就可以被解密了(解密方法和通过HTTP传输的流量相同)
DNS C2协议
我们使用2021年版Cyber Security Rumble的一个挑战来讲解Cobalt Strike的DNS流量是什么样子的。
首先,我们使用1768.py这个工具查看下Beacon的配置内容:
图1:DNS Beacon的配置内容
“payload type”字段确认了这是一个DNS Beacon,字段“Server”告诉我们用来进行DNS查询的域名:
wallet[.]thedarkestside[.]org
图1中第三个标红块中的DNS配置参数:maxdns, DNS_idle等等,这些参数将会在DNS流量中出现的时候进行详细的讲解。
在Wirshark中,DNS流量看起来是如下图这样的:
图2:从Wireshark中看到的DNS流量
我们将这些信息(字段信息)整合为DNS查询和回复文本进行表述。
图3:以文本形式体现的Cobalt Strike的DNS流量
我们从第一组查询开始:
图4:DNS_beacon查询和回复
每隔一段时间(由睡眠设置决定),Beacon会对19997cf2[.]wallet[.]thedarkestside[.]org发出A记录DNS查询。wallet[.]thedarkestside[.]org是这个Beacon所有发出的查询的根标签,这是在配置中设置的。19997cf2是这个特定Beacon实例的Beacon ID(bid)的十六进制表示。每个运行中的Beacon都会产生一个32位的数字,用于在团队服务器中识别Beacon。它对每个运行中的Beacon都是不同的,即使同一个Beacon可执行文件被启动了几次。所有对这个特定信标的DNS请求,都有根标签19997cf2[.]wallet[.]thedarkestside[.]org。
为了了解像上面这样一组DNS查询的目的,我们需要查看下Beacon的配置:
图5:打开Beacon的DNS配置文件
如下的设置定义了每个类型的顶级标签所查询的内容:
DNS_beacon
DNS_A
DNS_AAAA
DNS_TXT
DNS_metadata
DNS_output
注意在图5中看到的设置的值是默认的Cobalt Strike 配置
例如,如果一个Beacon发起的DNS查询中有个域名是以 http://www开始的,那么我们就知道这个查询是将元数据发送到团队服务器的。
在我们的Beacon配置中,DNS_beacon的值是(NULL...):这是个空字符串,这意味着在根字段之前没有字段。因此,在这种情况下,我们就知道以19997cf2[.]wallet[.]thedarkestside[.]org 为域名的DNS查询是DNS_beacon查询。DNS_beacon查询是beacon用来查询团队服务器任务队列中是否有需要执行的任务。针对这个A查询的回复是一条IPv4地址,而这个IPv4地址则是发送给beacon的指令,让beacon去做些什么。为了了解着个指令到底是什么内容,我们首先需要对这个回复地址用DNS_idle的值进行异或操作。在beacon中,DNS_idle的值是8.8.4.4(默认的DNS_idle值为0.0.0.0)。
查看图4中的内容,对于第一个查询请求的回复是8.8.4.4,这些恢复信息需要用DNS_idle的值8.8.4.4来进行异或操作:这样得到的结果就是0.0.0.0。 一条回复的值为0.0.0.0就意味着团队服务器的任务队列中没有需要该beacon执行的任务,之后就可以进行休眠,过段时间再进行查询。所以再图4中的前5条查询,beacon没有需要执行的任务。
从第六条查询开始有了变化:回复内容是IPv4地址8.8.4.246,我们将该地址的值与8.8.4.4进行异或操作,我们得到的值是0.0.0.242. 而这个值是命令该beacon使用TXT查询去来检查需要执行的任务。
下面的值决定一个beacon如何与团队服务器进行交互:
图6:可能的DNS_Beacon回复
回复如果最后一位为1,意味着beacon要做一个检查(用DNS_metadata查询)。
如果第4到2位均设置为0,则通过一个A查询来进行通信
如果第2位为1,则是要通过TXT查询来进行通讯。
如果第3位为1,则是要通过AAAA查询来进行通讯。
值242的二进制表示位1111 0010,因此, 不需要进行签到,但应通过TXT记录检索任务
下一组的DNS查询是由beacon进行,因为收到的指令为(0.0.0.242):
图7:DNS_TXT查询
注意这些查询中的以api开始的域名,这些是DNS_TXT查询,根据配置内容(见图5),这是按照团队服务器的指示(0.0.0.242)。
虽然DNS_TXT查询应该使用TXT查询,在DNS_TXT查询中最开始的DNS查询是一个A查询。针对这个查询的回复,那个IPv4地址,需要拿来和DNS_Idle的值来进行异或操作。在我们的例子中,8.8.4.68 和 8.8.4.4进行异或操作,得到的值是0.0.0.64. 这个值就是指定将要通过TXT记录进行传送的的加密数据的长度。注意对于DNS_A 和DNS_AAAA查询,第一条查询也是一条A查询,也对将要收到的加密数据长度进行了编码。
接下来,该beacon会发送尽可能多的查询。每个TXT记录的值都是以BASE64编码的字符串,这些值需要被连接起来再进行解码。当解码数据达到了A记录回复中指定的长度(64字节)之后就不在发送TXT查询请求。
由于Beacon的TXT记录查询速度(决定于睡眠设置)非常的块,所以引入了一种避免DNS查询结果的缓存干扰通信的机制,该机制让每一次DNS查询的域名都不同,但是该机制会添加额外的十六进制标签
注意到在顶级标签(本例中api)和根标签(本例中19997cf2[.]wallet[.]thedarkestside[.]org )中间存在一段十六进制标签。该十六进制标签即为第一次DNS查询中的07311917 和第二次DNS查询中的17311917 。该十六进制标签中包含了一个计数器和一个随机数字:COUNTER + RANDOMNUMBER
在本例中,随机数字为7311917, 计数器是以0开始,增量为1的。也就是这样的机制让每次查询都不同,同时能够让每次查询的乱序回复能够有正确的顺序,
因此,当所有的DNS TXT回复都接收到之后(本例中就一条),对base64的字符串(本例中为ZUZBozZmBi10KvISBcqS0nxp32b7h6WxUBw4n70cOLP13eN7PgcnUVOWdO+tDCbeElzdrp0b0N5DIEhB7eQ9Yg==)进行解码和解密(我们将在本篇最后使用工具进行解密)
这也就是DNS Beacon如何从团队服务器接收到(任务)指令。加密的数据通过DNS A,DNS AAAA,或者是DNS TXT记录的回复进行传输。
当通过DNS A记录(0.0.0.240)进行数据通讯的时候,流量抓包看起来就是下面的样子:
图8:DNS_A查询
cdn.
是DNS_A查询的顶级标签(见图5配置)
第一个回复是8.8.4.116,和8.8.4.4进行异或操作,得到的结果是0.0.0.112. 因此意味着将要收到112字节的内容:也就是112/4=28次DNS A 查询。
从DNS A查询回复中的到的IPv4地址中获得的加密数据,在本例中为:19, 64, 240, 89, 241, 225, …
在DNS_AAAA查询中,方法是完全一样的,除了顶级标签是www6之外。在本例中(见图5配置)每个IPv6地址中包含16字节的加密数据。
通过DNS记录从团队服务器发送到beacon的加密数据和通过http或者https有着完全相同的格式,因此我们的解密进程也是完全一样的。
当Beacon需要将结果(任务输出)发送到团队服务器上的时候,会使用DNS_output查询。在我们的例子中,这些查询会以顶级标签开始,举例如下:
图9:Beacon通过DNS_output查询发送任务执行结果到团队服务器
DNS_output查询的每个名称,都有一个独特的十六进制计数器,就像DNS_A、DNS_AAAA和DNS_TXT查询一样。要传输的数据,在标签中用十六进制数字进行编码,并添加到名称中。
我们来看看第一个DNS查询(图9):post.140.09842910.19997cf2[.]wallet[.]thedarkestside.org。
这个名字细分为以下标签。
post: DNS_output 查询类型
140: 传输的数据
09842910: 计数器 + 随机数字
19997cf2: beacon ID
wallet[.]thedarkestside.org: 操作者选择的域名
第一次查询的传输的数据其实为之后需要传输的加密数据的长度值,该数据可以解码为如下:140 -> 1 40.
第一个十六进制的数据(本例中的1)是个计数器,指定用于包含十六进制数据的标签数量。由于一个DNS标签大小被限制为63个字符,当需要对大于32个字节的数据进行编码的时候就需要不止1个标签。这就解释了为何需要一个计数器。40是个十六进制数据,也就是说,加密的数据是64字节长。
第二个DNS查询(图9)是:
post.2942880f933a45cf2d048b0c14917493df0cd10a0de26ea103d0eb1b3.4adf28c63a97deb5cbe4e20b26902d1ef427957323967835f7d18a42.19842910.19997cf2[.]wallet[.]thedarkestside[.]org.
这个查询中的名字包含了(部分)加密数据,这些数据在标签里面使用了十六进制进行编码。
这些是传输的数据的标签:
2942880f933a45cf2d048b0c14917493df0cd10a0de26ea103d0eb1b3.4adf28c63a97deb5cbe4e20b26902d1ef427957323967835f7d18a42
第一个数字2,表示2标签是用来编码加密的数据的:942880f933a45cf2d048b0c14917493df0cd10a0de26ea103d0eb1b3和4adf28c63a97deb5cbe4e20b26902d1ef427957323967835f7d18a42。
第3个DNS查询(图9)是: post.1debfa06ab4786477.29842910.19997cf2[.]wallet[.]thedarkestside[.]org.
这个标签的计数器是1,传输的数据是debfa06ab4786477。
将这些标签以正确的顺序放到一起,得到如下的十六进制数据:942880f933a45cf2d048b0c14917493df0cd10a0de26ea103d0eb1b34adf28c63a97deb5cbe4e20b26902d1ef427957323967835f7d18a42debfa06ab4786477。这个128字的十六进制数,或者说64字节,与第一个查询中的长度(40个十六进制)所指定的完全一样。
The hexadecimal data above, is the encrypted data transmitted via DNS records from the beacon to the team server (e.g., the task results or output) and it has almost the same format as the encrypted output transmitted with http or https. The difference is the following: with http or https traffic, the format starts with an unencrypted size field (size of the encrypted data). That size field is not present in the format of the DNS_output data.
上面的十六进制数据是通过DNS记录从beacon发送到团队服务器的加密数据(例如,任务执行结果或者说是输出),而且这些数据的输出格式和通过http或者https传送的加密输出格式是一摸一样的。不一样的地方在于:通过http或https协议的数据流,数据格式上来说是以未加密的尺寸字段(加密数据的大小)开始。而这个字段大小没有在DNS_output形式表示的数据中出现。
解密
我们开发了个工具,cs-parse-traffic,能够解析DNS流量和HTTP(S)流量。类似于加密HTTP流量,我们使用该工具从DNS查询内容中解码加密的数据,查找beacon进程内存中的密钥,然后进行解密DNS流量。
首先,我们使用未知密钥选项(-k 未知)运行该工具去从DNS查询和回复的网络报文文件中提取加密的数据:
图10:从DNS查询内容中提取加密的数据
选项-f dns 是处理DNS流量所必需的,选项-i 8.8.4.4. 是用来提供DNS_Idle值。这个值是用来正确解码DNS回复(DNS查询不需要)。
(红色方框中)的加密数据能够用于从运行的beacon的进程内存转储文件中寻找AES和HMAC密钥。
图11:从进程内内存中提取密钥
这个密钥可以用于解密DNS流量:
Figure 12: decrypting DNS traffic
图12:加密DNS流量
This traffic was used in a CTF challenge of the Cyber Security Rumble 2021. To find the flag, grep for CSR in the decrypted traffic:
这个流量曾用于Cyber Security Rumble 2021CTF挑战。为了寻找flag,使用grep工具在解密流量中查找CSR。
图13:从解密的流量中寻找flag
结论
Cobalt Strike 的DNS流量和HTTP流量之间最大的区别是:加密数据的编码方式。一旦加密数据被恢复出来,DNS和HTTP之间的解密方法时非常相似的。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)