这篇文章发表于 986 天前,可能其部分内容已经发生变化,如有疑问可询问作者。
之前,对于cobaltstrike破解版使用的解决方案是白嫖来的cobaltstrike原版包(记得检查hash值)结合cobaltstrike破解器。这样能够相对轻松地使用破解版却用着相对踏实。
但目前仅仅是破解已经满足不了自己的需要(主要是被不断更新的杀软搞得心烦,太卷了太卷了),想从源码层面对其特征和功能等进行修改,于是开始写这篇文章。本文参考了许多文章,自己也提出些许见解,仅供学习与思考,不提供任何成品。
环境搭建
我用到的cobaltstrike原版本是4.4,因此其环境需要有一定的要求,经过对几种JAVA环境的一番测试及其他考虑,个人建议使用oracle JDK 8,下载好相应压缩包之后就是环境变量的配置。
1 | sudo tar -zxf jdk-8u321-linux-x64.tar.gz -C /usr/local # 解压至/usr/local文件夹 |
反编译与编译
该部分主要参考这篇文章。
远控主要的部分就在原版包里面的cobaltstrike.jar
包里面,我们首先需要对它进行反编译,试了几个工具和方法之后,本人推荐使用Luyten
,将这个jar包反编译并得到一个zip压缩包。
使用ideaj创建一个全新的项目,随后改动相关文件夹结构如下截图,其中的lib/cobaltstrike.jar
文件就是原版包,而decompiled_src_luyten
文件夹中的就是先前压缩包解压后的结果,src
文件夹暂时为空,凡是之后有所修改的.java文件都要放在这里面。
接着就是修改ideaj里面的Project Structure
的配置,可以比较清楚地用如下两张图表达清楚:
这里的Main Class
选项也许可以会被自动找到合适的类,也可以通过寻找META-INF/MANIFEST.MF
中的配置找到,就是aggressor.Aggressor
。
接下来就是一个Hello World的例子。将decompiled_src_luyten/aggressor/Aggressor.java
文件复制至src/aggressor/Aggressor.java
,可以适当修改下相关的代码,比如在主函数最开始的时候弹窗,只要在主函数最开始增加如下代码:
1 | JOptionPane.showMessageDialog(null, "Hello world"); |
然后就是编译jar包,点击Build->Build Artifacts...
,选择build即可。我们能够在out
文件夹里面找到新打包的jar。
再稍微配置一下运行jar包的命令即可,这些参数可以通过运行报错信息所给的提示进行添加。
点击ideaj里面的运行键,发现它能够运行helloworld弹窗,但是我们得不到那个登录的弹窗。
去暗桩
简单试了下,新编译得到的jar包似乎和CSAgent没法直接联动破解运行。不过都到这一步了,其实只要去掉几个暗桩即可。
这部分主要参考这篇文章。
这里直接采取添加硬编码密钥的方法,修改common/Authorization.java
里面的Authorization
函数:
1 | final AuthCrypto authCrypto = new AuthCrypto(); |
那个Authorization
函数最好再修改下,建议把读取cobaltstrike.auth文件的代码全部注释掉。接下来就是注释掉common/Stater.java
里面的initializeStarter
函数内容和common/Stater2.java
里面的initialize
函数内容,并且注释掉common/Helper.java
里面的startHelper
函数除返回真以外的所有内容。
以上是修改后的文件结构,再编译运行下,发现登录界面的弹窗也出来了。
剩下的把beacon中途退出的暗桩也去掉,即把beacon/BeaconData.java
文件中的shouldPad
函数里面的this.shouldPad
值改成恒为false。
TIPS:也许会遇到编译错误,一般按照ideaj的提示自动进行修改或者对比其他反编译结果修改即可!后不赘述。
OK了,可以先简单测试一下能不能进行远控,基本是没什么问题了。
teamserver端配置修改
首先必须保证这个teamserver团队服务器的相关端口的特征不会被扫描出来。
teamserver端口及证书信息修改
这个脚本除去对环境变量的判定,剩下的就是真正在起着作用的内容:
1 | # generate a certificate |
这里可能产生的特征一个是50050端口,其次就是这个默认ssl证书的基本信息,把这些都改掉即可(如果您不懂证书相关配置的话可以查阅这个回答)。比如像下面这样:
1 | if [ -e ./cobaltstrike.store ]; then |
删除原先的cobaltstrike.store
文件,再运行teamserver
生成一个新的,用keytool -list -v -keystore cobaltstrike.store
命令查看下即可。
teamserver防爆破
即便你修改了端口和证书特征,但还是可能被人找上门来然后不停爆破(爆破脚本可参考这个)。您当然可以指定团队上线的ip段或者设个非常复杂的密码,但其实这还是没能解决根本问题。
想从根源上解决的话,是需要修改jar包源代码的(原谅我安排目录的时候将它放在这),只要做到魔改后的程序和大众的有所不同就好了。以下内容参考这篇文章。
找到ssl/SecureServerSocket.java
中的authenticate
函数,可以发现这里有个比较有趣的东西:
1 | if (dataInputStream.readInt() != 48879) { |
接下来的事就比较好办了,只要全局搜索这个48879
数然后同时替换成某个int即可。但是要注意不要修改dns/AsymmetricCrypto.java
里面的(这个和beacon回连认证有关)!修改之后编译下,您的cobaltstrike就已经异于常人了,即便是弱口令也不会被爆破出来。
TeamServer.prop配置
teamserver运行的时候可能会报错说缺失TeamServer.prop
文件,但其实这是个无伤大雅的小问题,相关的配置文件可以在这里找到。
您如果是很讲究的人可以改改。
profile配置修改
这部分的流量其实和前面的teamserver连接的流量毫无关系。而且profile的配置相对来说比较简单,github上面很多,目前个人比较喜欢的配置文件是在伪装成jquery这样
的基础上进行一定的修改。其中配置选项的具体意思可以参见官网、这篇文章和这篇,本文仅挑选一些重点。
Staging process
:里面的host_stage
设置为true,不要担心,我们接下来会对stager可能导致的问题进行较为全面的修复。Post Exploitation
:对于免杀而言,这部分是重点!Memory Indicators
:这部分也是重点!Process Injection
:这部分也是重点!这些和dll相关的代码块信息可使用cobaltstrike文件夹里自带的peclone
来生成。SSL CERTIFICATE
:这部分是配置ssl证书,上线https必须要干的事情(想改默认配置的话也可以通过替换resources/ssl.store
并编译),否则就是默认的ssl证书。
好了,重点说完了,自学自改即可。最后使用c2lint
检测一下其正确性。不过对于上线https,这里更为详细地说明一下。
https监听配置
自签名证书不好,这里就简单介绍下有效的ssl证书的配置方法,顺带套上CDN以隐藏IP,以cloudflare为例。这部分内容参考了这篇文章。
- 去申请一个不暴露任何敏感信息的域名(比如godaddy),然后将修改namespace将其转入cloudflare下。
- 修改该域名控制台的
SSL/TLS encryption mode
选项为Full (strict)
。 - 根据该域名申请相应的证书,下载
server.pem
和server.key
。
然后在本机上利用这两个文件生成符合格式的证书,可使用类似下面这样的命令:
1 | # generate file "*.p12" |
最后只要修改一下SSL CERTIFICATE
里面的配置即可。当然,这部分建议在搞动作前再弄,因为免杀最好先在断网的环境下完成。
JA3|JA3S/JARM 指纹修改
由于这些指纹和JAVA运行环境、网页服务器等都有关系(其实主要是在验证TLS的配置特征),这个方法其实不能够作为直接判断cobaltstrike服务器的依据,但它的确是可以作为一种非常强力的辅助判别手段——个人感觉它的准确率高得吓人。
可以用这个工具来生成一下你那个服务器的JARM指纹,得到07d14d16d21d21d00042d41d00041d47e4e0ae17960b2a5b4fd6107fbb0926
。发现它确实是属于有嫌疑的值,虽然这不能完全判别为cobaltstrike服务器,但如果有一丢丢其他特征,那么再结合这个特点就很可能把这个服务器给抓出来了。比如下面这位裸奔的老哥……
这也许可以更换为ECC证书(我之前申请的是RSA证书,不过换了还是在特征里面)或者通过nginx来根据一些特征对扫描流量和beacon回连流量分别进行一定程度的处理,但这些手段都不治本,一旦流量被抓照样是原型毕露(JARM是主动探测的,但JA3和JA3S是分析流量的),所以本文不采用这种。目前有研究尝试伪造相关的指纹,比如这个项目,然而个人感觉其效果不好,不利于beacon回连。倒不如直接架设个External C2,但我们其实可以省事且有效地改掉带特征的指纹,只要修改和JAVA TLS相关的文件。
举个例子,从JA3|JA3S / JARM的原理中我们可以得知该指纹和JAVA所使用的CipherSuites套件有关。关于这套件,从这手册中可知,我们能够禁用或启用JAVA运行环境中一些CipherSuites套件。所以我们可以尝试修改${JAVA_HOME}/jre/lib/security/java.security
文件中的Legacy algorithms for Secure Socket Layer/Transport Layer Security (SSL/TLS)
或者Algorithm restrictions for Secure Socket Layer/Transport Layer Security
里面的一些内容(请仔细阅读配置文件中注释)。
说具体点的话你可以在jdk.tls.disabledAlgorithms
里面加上TLSv1.3
试一试。
之后运行相关服务,再进行JARM指纹的比对,发现该特征已被消除。接着使用这个改进过的项目来对抓取的攻击流量进行JA3|JA3S测试,发现JA3=72a589da586844d7f0818ce684948eea
但JA3S不满足特征。而对于JA3来说,毕竟Windows上的其他合法应用程序也使用相同的套接字,所以综合来看这个TLS指纹是没有说服力的。
当然!事实上我们还可以很轻松地修改配置文件里的其他内容让这个指纹判定变得毫无意义。
源码层面特征修改
4.4版本已经修复了一个老版本的DOS漏洞,即Hotcobalt(CVE-2021-36798),但它还是有很多可以被发现的特征,先不论那些流量配置文件,目前得看一下其代码本身的默认配置有无可改进的地方。为方便找到需要弥补的弱点,接下来各个标题都是对进攻方不利的概括描述。
首先明确一点:接下来的任何和URL有关的部分其实大多可以通过设置UA头等信息结合nginx方便而且有效地补上缺陷,您当然可以选择这种好办法,但本文就不多说了。
stager导致配置泄漏
不要以为简单配置下profile就能够逃过一劫,默认stager开启的话建议一定要修改。
由于cobaltstrike监听器默认会开启stager以便分批传输payload,而这个stager监听端口正是我们在Listeners中设定的,因此这个监听端口就非常容易被找出特征,所以有人就建议手动在不用的时候kill掉stager,好吧这个解决办法非常好用但可以有更高的追求 :D
这个nmap脚本就利用到了该端口的一些特点,不过它似乎会误报。可以随便看下代码,发现了下面这两个东西:
1 | repacked = repacked .. string.pack(endian,z ~ 0x2e2e2e2e) --version 4 |
这个显然是一个特征(其实这个脚本里面还有用来判断的特征,比如checksum8的值92或者93),2e是4.x版本的,而69是3.x版本的。经过网上的一番搜索很容易发现这个是一个异或加密用的数。
另外,在刚刚这个特征的基础之上,用这个工具导致的大量虚假上线问题。
其原理大概如下:由于cobaltstrike监听器默认会开启stager以便分批传输payload,访问相应的路径即可发现payload文件。比如监听8001端口,那么访问 http://ip:8001/ab2g 或者 http://ip:8001/ab2h 就能够得到一个文件(这些后缀可以通过爆破得到),若能够被解密(比如使用CobaltStrikeParser
工具),那么基本上对比下所得的信息就能被锁定这是个跑着stager的服务器,更糟糕的是,这些信息足以构造大量的包来为造出大批量上线的效果,详情请看这篇文章。
绝大多数被提及的方法就是修改checksum8算法或者那个xor用的密钥。这篇文章已经提到了相关的操作流程,我接下来详细说说。
修改xor密钥
修改beacon/BeaconPayload.java
中的beacon_obfuscate
函数里面的0x2E为一个你想的数字即可,比如0x3E。既然这里的密钥变了,那么我们也需要改掉远控客户端那里的密钥。
但cobaltstrike的各种dll文件其实是有加密的(我们能够在sleeve
文件夹中找到),所以要先解密。我找到了一个很有意思的项目,它不仅能够解密,而且相比原项目似乎解决了一个可能的问题。注意,下载后的东西未必能直接使用,对于4.4而言,还需要修改CrackSleeve.java
内置的密钥:
1 | private static byte[] OriginKey = {94, -104, 25, 74, 1, -58, -76, -113, -91, -126, -90, -87, -4, -69, -110, -42}; |
将CrackSleeve.java
文件置于与原版cobaltstrike包同一目录下,运行如下命令:
1 | javac -encoding UTF-8 -classpath cobaltstrike.jar CrackSleeve.java |
需要修改的dll文件有beacon.dll
、beacon.x64.dll
、dnsb.dll
、dnsb.x64.dll
、pivot.dll
、pivot.x64.dll
、extc2.dll
、extc2.x64.dll
这些,直接找到相关的0x2E的值替换为0x3E即可(搜索关键字符串为2E和xor)。可以用各种反编译软件改,甚至用16进制编辑器。
然后我们使用如下命令将这些修改后的dll文件加密回去:
1 | java -cp cobaltstrike.jar:. CrackSleeve encode 5e98194a01c6b48fa582a6a9fcbb92d6 |
最后将Encode
文件夹里面的所有dll文件放在sleeve
文件夹中进行jar包的编译,但不知道为什么,一开始总是没办法将原版jar包里的dll文件用修改后的dll文件覆写。也许你会选择投机取巧——直接解压后修改原版jar包的sleeve
目录:
1 | mkdir tmp && mv cobaltstrike.jar tmp && cd tmp |
其实没必要大费周章用解压这样的操作,只要调整一下文件夹结构,设置resources目录便可,如下图:
测试了一下,发现能够运行且对躲避检测有一定效果,但checksum8依旧和92或者93这两个特征值关系密切。但访问相关路径我们依然能够下载这个分段payload,这时候如果我们稍微修改一下CobaltStrikeParser
工具的代码——只需要加个爆破xor key的循环,依然会被轻松发现。于是在这个基础上,最好还要对路径进行一定的隐藏。
修改stage下载路径
这部分内容和网上一些处理有所不同,我不太能接受修改checksum8
搞个完全固定路径的方法,这大可直接在profile里面修改(其实改profile基本就够了)。在处理之前,需要大致了解下这篇文章中提到的一些概念区分:
- PS:stage set uri指的是c2Profile中,stage配置中的set uri参数
- PS:checkSum uri指的是beacon内部通过checkSum8算法自动生成的URL值
- stager默认自带一个生成符合checkSum8算法URI的的模块,这使得在没有Profile文件时也能够上线
- stager可以通过Profile文件中的stage set uri字段下载stage,该URL不用经历checkSum8算法校验
- host_stage设置false后,无法通过Profile中的stage set uri与checkSum uri上线
- Kill stage后无法通过checkSum uri上线,但能够通过stage set uri上线
个人觉得较为合适的修改需要在默认生成与接收的地方做下手脚,最好profile文件也能兼顾,却不需要大量的代码修改。这之前应该先理下代码的逻辑。
全局搜索checksum8
函数,能够在找到common/CommonUtils.java
和cloudstrike/WebServer.java
里面找到:
1 | public static long checksum8(String replace) { |
这里的内容很简单,不是重点。我们应当对这两个文件中引用它的地方感兴趣,很快就能够发觉这个92和93数值其实可以较为随意的修改为任何小于256的数,但这个强度肯定是不够的,对路径进行爆破的话照样原型毕露。而且这个checksum8
函数在整个项目中有不少地方进行了调用,这是一件非常让人头大的事,如果贸然修改checksum8
算法的话,就可能导致其他功能出现意料外的错误。但很多文章都是这么干的,我不知道会不会有什么后果。
算了,懒得看其他地方的调用。换个角度考虑,有没有直接在路径生成上下工夫的可能性呢,比如让路径的长度不为4?按照这个想法,可以修改如下两处地方:
其一,common/CommonUtils.java
这部分其实是控制beacon来寻找URL的代码。我们像下面这样进行修改:
1 | public static String MSFURI(final int n) { |
其二,cloudstrike/WebServer.java
这部分其实是服务端接收URL的代码。同样也是延长URL的路径长度:
1 | public static boolean isStager(final String uri) { |
然后你还可以修改92和93为其他值、修改URL的长度、修改URL的前后缀……嗯,不多说了,可见花样也是很多的 :)
以上两者结合起来,就相对简单地实现了攻击方的防御,让防御方成本增加。
修复新出的漏洞
这个利用很简单,修复也比较简单。像这篇文章这样即可,也就是在cloudstrike/WebServer.java
里面的_serve
函数中合适位置加上对uri开头字符的判断。
1 | if (!uri.startsWith("/")) { |
beacon内存特征修改
这部分其实已经在对抗主机上的杀软了,但这里主要对抗的是cobaltstrike原本内存方面的明显特点而非一些可疑的加载手法这种。
消除obfuscate-and-sleep加解密代码特征1 - 绕过内存签名检测
Beacon通常是反射加载到内存中,为掩盖这些特征,cobaltstrike自带了obfuscate-and-sleep选项(启用它只需要在Profile里面配置stage->sleep_mask
即可),它会在Beacon睡眠的时候将自身绝大部分加密,以专门避开基于签名的内存扫描。
但这个即便是开启这个选项,Beacon也会被发现。因为该选项并不会对负责加解密的代码进行混淆,于是这些就会直接暴露在内存中,虽然目前是4.4版本,但依旧可以使用如下yara规则进行扫描:
1 | rule cobaltstrike_beacon_4_2_decrypt |
可以在Beacon运行时候利用Process Hacker
尝试右键并dump下其运行时候的内存信息,随后使用yara规则进行分析。
1 | D:\share\yara-v4.2.0-1885-win64>yara64.exe --print-strings beacon_4.2.yar rundll32.sleep60.dmp |
经过测试,发现cobaltstrike生成的exe、注入的进程、spawn的进程和静态混淆shellcode加载器都会被检测到,无论有没有开启obfuscate-and-sleep选项。因此这个特征是非常致命的,不过对于那些真正有授权的只要像这篇文章那样介绍的稍作修改编译即可获得。
好吧,貌似这玩意国内都不是什么正品。所以接下来我们直接修改那个解密后的dll文件吧,因为是4.4版本,所以和这篇文章的操作有所不同,这位师傅版本应该是4.3,找的是beacon.x64.dll
这种文件;而4.4版本已经将其分离到sleepmask.x64.o
、sleepmask.x86.o
这两个文件中。
可以看到第二三行汇编代码的顺序根本不影响其效果,所以我们可以对换来改变其特征,比如可以这样:
1 | 0000236d 4c 8b 53 08 MOV R10,qword ptr [RBX + 0x8] |
其实只要汇编的逻辑不变就好了,完全可以比较随意地发挥。剩下的那个文件也是同理。不过即使修改了这个特征,beacon还是存在着非常明显的特点。
消除obfuscate-and-sleep加解密代码特征2 - 绕过beaconeye检测
在这方面很难绕过一座大山即beaconeye的扫描。当然我们首先得试一试它的效果,发现那种exe运行的、spawn出来的这种基本都会被发现,但如果inject到其他进程后的话就不会被发现。还试了下用加密的shellcode绕过静态查杀的个人方案,但结果照样会被beaconeye发现(所以啊不要有侥幸心理 X)
beaconeye的原理这篇文章已经非常详细地描述了——尽管beacon已经采用了obfuscate-and-sleep选项来保护,但由于这个Beacon config结构体信息是一直暴露在堆内存中,故非常容易就被发现,而且不好修改(不像刚刚的内存签名那样只要dll中那串特征消失即可绕过)。只要就是针对beacon的结构体进行规则扫描,其规则如下:
1 | public static string cobaltStrikeRule64 = "rule CobaltStrike { " + |
针对这种情况,有两种较简单的解决办法:1、不让该部分堆内存被扫描;2、即便堆被扫描,其特征也无法对应上。 个人想了一下,可能还是采用后者较为合适。
想要了解这个结构体的大致格式的话,可以在beacon/Settings.java
文件中找到相关函数,结合这篇文章理解,我这里就不多说了。总之,它指出了一种简单有效的办法,就是修改堆块初始化的值。
对于beacon.x64.dll
,我们找到了下面这样的内容,很显然这里对分配的堆块进行了初始化:
1 | 180018791 8b cb MOV param_1,EBX |
只要将XOR EDX,EDX
这个对EDX的赋值语句改一改即可,注意不要破坏结构。beacon.dll
同理。
但这里其实还是没有完全结束的,如果profile里面没有指定set cleanup "true";
的话(参见该文章),使用目前的CobaltStrikeScan
的规则还是可以检测出来的(虽然和beaconeye采用的特征有所不同,却也是堆中提取的特征),稍微分析一下就会发现这些规则是分析了刚刚那段代码附近的代码情况,而且爆破了异或密钥(就是beacon/BeaconPayload.java
中的beacon_obfuscate
函数里面的密钥,原先是0x2E)。
可见这里的处理方式较为tricky,事实上想要继续修改其特征的话已经相当有难度了,最好是直接重写dll,但这工程量有点浩大,所以点到为止(其实是菜)。
虽然现在魔改得到的东西基本上差不多了,但其实最好的办法是学习里面的技术然后自己写一个。因为即便是能免杀上线,在后续操作的时候还是可能会报一些特征,比如微软自带的杀软似乎会在beacon进行和post-ex
相关的操作(比如spawn
、keylogger
等等)时报毒。我怀疑是命名管道的特征,但目前使用sysmon并没有观察到,所以暂时不清楚到底是哪个行为特征被查杀了。这想要修改似乎过于困难了对我来说,不过这些应该可以通过添加一些插件或通过奇怪的操作解决(算是勉强能用吧QAQ)。最近有点忙,以后有想法了再填坑。
对于后续免杀思考的参考文章:
https://www.anquanke.com/post/id/262742
https://labs.f-secure.com/blog/detecting-cobalt-strike-default-modules-via-named-pipe-analysis/
https://guage.cool/cobaltstrike-detect/
6月24日更新:发现了一个beacon项目,也许不久就会被和谐…?