前言
随着近几年 HW 实战的开展所有人都对 Cobalstrike 不再陌生,相关安防设备一直在思考如何在流量侧 / 终端侧识别出 CS 的行踪。攻击者除了使用习惯上基于插件进行开发提效外,还有一些需要关注对抗的点来提升 CS 的隐藏性,我们会分两篇文章将 CS 在流量层面、内存层面对抗侧二次开发做出的思考实践与大家分享交流。
流量层面的思考
从流量整体伪装的角度来看,最好的效果就是和正常流量混为一体,以此为出发点来构思流量隐蔽方案。
-
有些思路会说改变CS原有的加密方式,自定义编码/加密的算法,不局限于C2 profile里面的几种算法。不管是哪种算法,我觉得目前基于HTTPS流量的通信方式是最好的,对于中间流量设备来说所观测到的https流量也是最多的,我们的c2混在其中让其无法区分。
-
当然即使是完全加密的流量,也能使用其他方式进行特征抽象。比如 SPLUNK 和 RITA(一款用于网络流量分析的开源框架)都曾实践过对通信频率,发送/接收平均字节数,连接数进行分析,使用数学的方式提取检测特征。当然我们也可以做出应对,增加流量发送的随机性,对于Beacon在静默状态和使用状态设置不同的通信频率,达到在时序性上的对抗。
在实践中我们更关注第一点。传统的域前置方式利用HTTPs隐藏真实的请求Host,来绕过安全设备的流量检测。从中间设备观察到的DNS和TLS sni均是可信域名。但随着云服务厂商进一步限制,域前置的使用范围进一步缩小。我们使用了Shadow-TLS这一方式,不再依赖于云厂商的实现缺陷,也能达到隐蔽流量的效果。
Cobalt Strike 流程概述
cs.auth 认证流程
cs 的 license
核心是非对称加密算法,对于关键的 beacon 相关文件使用 rsa 公钥加密,license
文件则是包含了 cobalt stirke 版本、watermark 标志、license 过期时间以及 beacon 解密私钥等信息。
主函数
客户端主函数 agressor
, server 端主函数 server.TeamServer
,其中客户端主要是启动之前校验一下 license,然后是启动图形界面,数据初始化。客户端这块不是主要关注的点,我们主要关注的点在服务端。也就是 TeamServer
类。
TeamServer
类也继承于 Starter
启动器父类,也会检测 license 合法性,获取过期时间水印等参数,解析 c2profile
配置文件,拉起 server 服务、listener,等待客户端连接上来,其中 listener 是由 nanohttpd 服务承载。
beacon 的生成
当选择一种生成模式之后从通用模板 artifact.exe 当中填充 beacon.dll 的 shellcode,profile 配置也是在这个时候经过 xor 加密填充到预留的空间。
beacon 的上线流程
收集一波主机信息之后向 server 发送一个上线包,此时产生一个 session id 作为这台肉鸡的标识,后续则不停发送心跳包(结合随机垃圾数据以免成为固定特征)等待下一次指令。
第一步明显特征规避
二阶段加载特征
二开第一步先把常规的 cs 特征规避了,像是 profile 配置、端口证书这些都不需要代码层面的改动,github 上比较火的开源 profile 配置也已经被检测为特征了。我们更关注的是需要代码改动的特征,cs 的 stage 二阶段远程加载需要下载 beacon 的 shellcode,当访问 url 路径符合 checksum8 计算的值,listener 就会返回二阶段所需要运行的真正执行 beacon 的 shellcode。使用二阶段加载 beacon 的功能会增加资产测绘被发现的风险,而且 beacon 的一阶段也不具备什么特殊的功能,不太符合实际场景的需求,大家可能更倾向于 stageless 或者定制化二阶段加载。这里的做法是直接删除该服务,毕竟怎么也不会用上单一的二阶段加载 beacon。
ja3/ja3s | jarm 指纹修改
JA3 是由 John Althouse、Jeff Atkinson 和 Josh Atkins 创建的开源项目。JA3/JA3S 可以为客户端和服务器之间的通信创建 SSL 指纹。唯一签名可以表示从 Client Hello 数据包中的字段收集的几个值:
- SSL Version
- Accepted Ciphers
- List of Extensions
- Elliptic Curves
- Elliptic Curve Formats
JA3 工具用于为与服务器的客户端信标连接生成签名。另一方面,JA3S 能够生成服务器指纹。两者的结合可以产生最准确的结果。JA3S 可以捕获完整的加密通信并结合 JA3 的发现来生成签名。更详细的信息可以直接查看官方 github 仓库:https://github.com/salesforce/ja3。
本来 ja3/ja3s 指纹不算是什么很强的特征,因为它只是一个 tls 握手包的 hash 值,ja3s 是被动流量,jarm 是主动探测,这个指纹取决于你的 tls 所使用的参数,也就是说完全有可能别人写的 https 服务也和 cs 的 listener 撞上了,但是我们不能不修改,因为放着这样一个特征不去管也会成为可能突破口,修改的办法也很简单,在 nanohttpd 服务中,修改 SSL 相关的任意参数就能达到效果。
这里列出几个已知的 ja3/ja3s 指纹信息:
JA3
-
72a589da586844d7f0818ce684948eea -
a0e9f5d64349fb13191bc781f81f42e1
JA3s
-
b742b407517bac9536a77a7b0fee28e9 -
ae4edc6faf64d08308082ad26be60767
可以看到修改过后的值:
Sigma 检测规则
根据开源的 sigma 检测规则,上面公开了一些关于 cs beacon 的特征,像是 cs 默认证书以及检测被公开的一些 profile 配置,这里提几个实战中可能遇到的:
-
查看 DNS 日志基于 dns_stager_subhost 的默认 DNS c2 行为
-
基于对单个域的大量 DNS 查询检测
-
还有一分钟内从单一来源查找多条 TXT 记录
如果实战中遇到只有 DNS 出网的上线需求的话可以考虑使用 DNS beacon, 注意一下默认的 DNS 行为就行,使得 DNS 请求趋于正常流量,根据配置就可以做到。只是需要注意一下一些通用的检测手段。
第二步隐蔽通信流量
接下来是本次二开 CS 中的重点,传统的 CDN 域前置隐藏 c2 的方式大家早已熟知了,CDN 厂商也做了限制手段,想要再做出提升,唯有另辟蹊径,因此我们将目标对准了 Shadow-TLS,以此达到更好的隐蔽流量的效果。
一般使用域前置时,把 CDN 作为白名单域名,当作一个靶子,中间设备所观测到的是一直与 CDN 通信:
现在 CDN 厂商使用应对手段禁止域前置被恶意使用:
-
对比 SNI 和 Host 是否相等,如果是 HTTPS 流量需要解密 -
禁止未验证的域名加速
传统 CDN 域前置遇到了些许阻碍,但有另一种思路,shadow-tls 将任意白名单域名作为影子域名,借用 TLS 连接建立起伪装,这或许是个不错的选择。
在讲 shadow-tls 之前我们先简单回顾下 TLS 握手的流程:
客户端发送握手,服务端返回握手、证书,双方协商密钥,之后统一用一个密钥对称加密之后的内容。那么怎么在这上面做文章呢?shadowtls 的实现方式:
客户端请求与中间人服务器建立连接,握手阶段服务端将客户端的请求转发到⼀个可信域名上(顾名思义 Shadow 域名),这样保证流量侧看到的服务端证书是⼀个可信域名的证书,在握手结束后,client 和 server 切换模式,利用已建立的连接传输自定义数据即可。这样做其实相当于一次 “TLS 表演”,给中间设备看的。并且在 client Response 中使用预定共享 key 返回 8 字节的 hmac 值,用于区分客户端流量和主动探测流量,正确响应主动探测流量。
snowc2 的实现方式
我们如何在 CS 中实现呢?这里我们用了更为直接的办法,在接收到影子域名的 server hello 握手包之后替换掉证书部分,使用 c2 server 的证书协商出密钥完成握手,接着就是正常的 https 流量 application data,同样给中间设备做了一次 “TLS 表演” 。
对于 CS Server 来说想要实现这部分功能,需要覆盖 JDK 类,这里用到了 java 提供的 endorsed 技术,可以简单理解为 -Djava.endorsed.dirs
指定的目录面放置的 jar 文件,将有覆盖系统 API 的功能。我们找到 CertificateMessage.java
文件中 T12CertificateProducer
类的 onProduceCertificate
方法,这个方法是处理 tls 握手时返回 sever hello 消息中的证书数据部分的,我们在其发送证书信息之前,替换成白名单域名的证书。这里给出演示 DEMO,实际项目实现需完成更多细节。
既然服务端使用了白名单域名证书,客户端也要写一个替换证书,只不过要替换成原来的证书,写一个简单判断即可。
在 tls 握手阶段,判断 server hello 消息头,识别出证书消息,将其替换为我们原来的自签名证书。
最终效果
当我们自建域前置并结合 shadow tls 时:
client -> tls -> cdn -> c2 server
tls 请求
SNI: allow.com
http 请求
Host: allow.com
中间设备所观测到的将会是,客户端向 allow.com 发起了请求,经过 TLS 握手进行后续的通信,证书也是 allow.com,从明面上来讲,已经找不出毛病。
可以看到客户端和 cs server 的 tls 握手通信变成了与 www.baidu.com
域名证书的握手。此外除了 https 连接请求之外,我们也加入了 dns 请求的流量,以让其看起来更像正常的通信流量。
这次二开我们专注在 C2 流量层面所做的事就告一段落,后续会更新内存层面的去特征化实践,请大家继续关注雪诺凛冬实验室。
Reference
-
https://www.ihcblog.com/a-better-tls-obfs-proxy/ -
https://thedfirreport.com/2022/01/24/cobalt-strike-a-defenders-guide-part-2/ -
https://github.com/salesforce/ja3 -
https://github.com/snowtech-cn/shadow-tls-client -
https://github.com/snowtech-cn/serverhelloEndorsed
关于雪诺凛冬实验室
原文始发于微信公众号(雪诺凛冬实验室):实战场景中 Cobalstrike 的二次开发