Author: lz520520@深蓝攻防实验室
0x00 前言
为什么会有修改蚁剑这个念头呢,之前有朋友对冰蝎做了改造,本来冰蝎就是加密传输,可对抗大多数WAF,但因为有一些弱特征、强特征等,通过流量分析转换成自动化检测,也可以做到冰蝎流量检测的,比如密钥交互特征,但改造之后,基本是无法检测的,跳过了密钥交互这个步骤,将AES密钥写死,还有其他新增的机制不仅仅对抗WAF,还可以对抗人工分析。
冰蝎改造需要反编译源码,然后先修bug,再开始改造,比较麻烦,成本也比较高,而蚁剑作为一款开源的webshell管理工具,让二次开发更容易,并且使用自带的加载器就可以调试;有编码器和解码器模块,可以让使用者很容易做到流量混淆,所以我也在想蚁剑是否也可以达到冰蝎一样的效果。
第一篇我会介绍一下后续改造所涉及到的蚁剑模块,这样让大家能更容易理解,后续也方便自己去动手开发属于自己的蚁剑。
0x01 蚁剑介绍
我不会长篇大论蚁剑各个组成部分,只是挑重点讲,想了解详情的可以看下蚁剑官方文档(https://doc.u0u.us/zh-hans/index.html) ,这个文档需要代理访问。
首先了解下蚁剑分为两部分,加载器和源代码,源代码是nodejs,使用加载器就可以让你不用安装nodejs的环境即可运行和调试蚁剑,后续关于蚁剑的调试工作基本使用加载器antsword.exe来完成,而我们改造的部分只有antsword-master(源代码)。
如果有用过蚁剑的,都会知道他的编码器和解码器,这个功能也是蚁剑爱好者最喜欢的部分。
总所周知,蚁剑所有脚本的源代码(php/asp/aspx等)均来自菜刀,所以流量特征也和菜刀差不多,过WAF基本不用想了。
所以有了编码器和解码器,进行流量混淆可绕过WAF,并且编码器和解码器可以自定义,使用nodejs编写即可,类似于插件很容易上手,先简单说明下编码器和解码器的作用。
编码器:对发送流量进行编码,服务端进行解码。
解码器:服务端对返回流量编码,需要客户端通过解码器解码还原流量接收。
可以看到一个对发送加密,一个对返回加密,这样才能达到完美过WAF的目的,但原版蚁剑会有一个问题,只支持php的编码解码,所以需要改造蚁剑支持asp、aspx。
而jsp这个比较麻烦,他不是脚本语言,没有eval/execute等函数可以用来直接执行代码,使用过菜刀的都知道,jsp的"一句话"非常大,如果要达到冰蝎那样短小的webshell,并且实现加密,得使用冰蝎的方式来实现,类加载,将命令执行、文件管理等操作编译成class,硬编码到蚁剑里,然后传给服务端解析,工作量比较大,当然有人已经实现了蚁剑版的jsp一句话,但只做到了base64和hex编码发送,有人特意分析还是有比较明显特征的,而且返回包也没有做加密处理,这个后续会说到。
这边和大家分享几篇文章,有涉及到蚁剑的流量分析和编码器编写等,并且官方github上也分享了几个编码器,包括AES/RSA加密的。
蚁剑流量分析到改装蚁剑之waf绕过 https://www.t00ls.net/articles-50892.html
从静态到动态打造一款免杀的antSword(蚁剑) https://xz.aliyun.com/t/4000
编码器、解码器 https://github.com/AntSwordProject/AwesomeEncoder
除此之外,蚁剑自带的编码器其实过WAF还是比较勉强的,因为他的webshell脚本是最原始的一句话,如
<?php eval($_POST["test"]);?>
根据上面提到的编码器作用,可以知道,服务端得进行解码才能正常接收,那像这种一句话是没法解码的,所以实际上是将解码函数一同发送到服务端,那几个解码函数是没法加密的,就是一个很明显的特征。
0x02 编码模块
这里使用base64编码器举例,通过流量分析可以看到,将原本传输的代码做了base64编码,然后调用eval对前面的参数进行解码还原。
我们再来看看他的编码器是怎么写的。在讲内容之前,我先跟大家讲下编码器的输入输出是怎样的。
如下
输入三个参数
pwd:连接密码,类型string。
data:传输的数据数组,类型string数组,。
ext:一些扩展选项,在一些场景可能会用到。
输出一个参数
data: 编码器处理后的数组,这个你可以通过代理查看post提交了哪些数据,和这里的data是完全一样的。
这里使用console.log来打印查看下编码之前的这些参数具体数值。
data数组实际上只有一对键值,data['_'] = "……";
ext有一些选项,比如ext.opts里包含编解码器名称、当前编码语言、密码、url等等。
所以base64编码器在data[pwd]设置为base64解码代码,data[randomID]设置为原代码的base64编码数据,最后删除掉data里的原有payload data['_']
实际上你编码器选择default也是会做处理的,只是这个写死在源码里,sourcecorease.js
也是会将data['_']删除。
通过上面例子应该对编码器的原理有所了解了。
到此,我相信大家应该会有思路怎么让发送流量完全加密了,原本自带编码器是通过再传递解码代码来实现无需服务端修改也能编码传输原始数据的目的。
那么我们只需要将解码代码放到webshell里,不就可以实现完全编码传输了吗。
我们对一句话改造下
<?php eval(base64_decode($_POST["test"]));?>
最后就可以实现如下的效果,省去解码代码传递的这一步,当然base64只是举个例子,你可以DIY加密方式,webshell里写好相应的解码函数即可。
0x03 解码模块
我们仔细观察可以看到返回数据还是明文,所以就需要使用解码器来对返回加密。
先看下解码器的组成,这里导出了两个方法,asoutput及decode_buff。
asoutput无需传入参数,返回一段php代码字符串,名称为asenc的函数,这个函数会放在请求包里,用于在服务端执行完代码后,再回显部分调用该函数asenc来编码处理,所以服务端无需针对解码做改动。
先看下原始发送的php代码是啥样的,第一个红框就是我们可控的部分,在传输之前,蚁剑会将该部分替换成我们编写的解码器里的php编码函数,然后再第二个红框处调用asenc函数进行编码。
decode_buff 是在接收时进行解码操作,传入的data为buffer数组,返回也是buffer数组,这里一般不需要做编码猜解,因为后续处理代码会执行该操作,ext是一个扩展选项,有包括密码等选项,可扩展操作,比如需要有key进行解码。
如上编写完毕后,并选择好解码器,进行测试,效果如下,可以看到返回流量也进行了编码加密,并且返回部分有前后分界字符串,只有客户端知道分界位置,WAF基本上是没法定位位置进行解码的。
大家可以看到简单的设置编码器和解码器,就可以实现个加密传输的效果,如果一些WAF无法有效识别base64解码,基本上就可以绕过了,当然我们要保证万无一失,就需要使用复杂点的函数加密。
上面这个看起来都加密了,实际上并不是,这只是一个探针式的请求,来校验webshell是否连接成功。
在实际文件管理和命令执行过程中,会提交更多参数,这也就是为啥编码器里的data是一个数组,菜刀本身也是这么设计的。
比如我执行了一个命令,可以看到多传输了两个参数,这两个参数base64编码了,但这个编码是固定不变的。
base64解码如下
所以如果WAF支持base64解码,我相信大多数WAF是做得到的,那你传输了执行命令直接都被检测到了。
那么解决思路有几种,
一、在编码器里将其他参数也进行相应编码(这里传递到编码器过来的时候已经是预处理base64编码了),服务端对这些参数进行解码,然而这些参数值实际上被服务端取出来的方式是通过一个获取请求参数列表的函数,比如PHP里的$_POST,asp里的request,aspx里的Request.Item,这个提取的代码是放在test参数值里的,那么解码函数得通过编码器,将test传参里的"请求获取函数"替换成对请求值额外解码的函数。是不是听着有点绕。
看图说话,也就是需要将下面这个base64解码之前先对POST提交数据进行解码。
修改成这样
如果按照原来的编码器方式,就是需要进行正则替换这部分,而且不同语言函数不一样,所以需要不同的操作,这个通用性不强,每次新的编码器都需要做这么一步,当然你如果打造一个像冰蝎那样固定的webshell,编码器固定不变也是没问题的。
这里发散下,如果像解码器那样,有一个asenc一样的asdec的函数,你去设置一个解码函数,来解码其他被编码传输的参数。这就需要改动他请求模块代码,这是一个思路。
另一个思路,对请求代码改造,不要分多个参数传递,都是一个参数提交,就像返回值就一串,这个工作量就会比较大,需要修改请求代码,并且各个语言的代码模板也得都换成新的了。
还有一个思路,沿着第一方式,设置一个asdec的解码函数,但既不需要自己去每次编写一个解码函数,又可控调整传参混淆的目的,这个就是后面实现的方式,达到所有传参都混淆的,这里简单提一下,就是为每个参数值添加一个随机长度前缀,在解码的时候去掉前缀即可,而我们可控的选项就是前缀的长度,在UI上添加一个前缀长度设置项,可填写自定义长度。这个方法既简单又好用,因为加随机前缀,就可以导致WAF无法正常解码,他也不可能知道从哪分割获取正常的参数值。这个相较其他方法,改造起来不要太简单,并且我在这个基础上,再进行了一次参数值倒序,使用一个倒序函数就可以还原了。
为啥这么做是防止意外,因为比如base64,你如果填充是4的整数倍,是可以正常解码的,如果是hex编码,你填充偶数也是可以正常解码的,这里不细讲,没明白的可以琢磨一下。
除了上面这种解决思路
对其他参数编码后,在不改动test的值的情况下,服务端可以提前解码替换掉"请求参数获取"的列表,这个在PHP里还是比较好处理的,就是将$_POST里的值进行解码然后替换$_POST,这样后续主代码使用$_POST也就能获取正常的值了。但对asp/aspx不适用,并没有像PHP这样的预定义变量,无法覆盖request函数里获取到的值,这个我没有细究,可能也有方法,有人知道的话也可以一起讨论下。
0x04 编码模块小结
到这里,我们对编码器和解码器有了一定了解,可以上手改造自己的蚁剑了。
编码器里,使用nodejs对发送数据进行编码,webshell在服务端进行解码。
解码器里,使用nodejs对接收数据进行解码,传递脚本代码使得webshell在服务端进行回显数据编码。
所以,编码与解码都需要客户端和服务端的配合,最终实现加密传输的效果。
冰蝎改造将动态的AES密钥写死在webshell里,可以减少很多特征。蚁剑就是将解码函数写死在webshell里,也可以有效绕过WAF检测。
这个部分其实算是插件改造,核心代码部分还没动,比如上面说的随机前缀设置,以及asp、aspx的解码模块支持,本篇先编解码器,让大家可以先动起手来,后面再进行源码分析,进一步改造蚁剑。