干货 | HW中盛行的Java内存马,如何全面检测?

admin 2024年10月29日13:24:36评论28 views字数 6209阅读20分41秒阅读模式

1. 背景

1.1. Java内存马是什么?

内存马是一种仅在内存中运行、没有文件落地的恶意程序,因此具有较强的隐蔽性,能够避开常规的基于文件系统的检测。Java内存马是针对Java语言的内存马,它利用Java语言的动态特性,如类加载机制、动态代理和反射技术等,在Java应用的内存中注入恶意代码,从而实现远程控制。

1.2. Java类加载机制

Java虚拟机(JVM)的类加载机制在程序运行时根据需要动态地加载类,而不是在启动时一次性加载所有类。而类可以在运行时从不同的来源,如本地文件系统、远程网络或者其他自定义位置加载。攻击者可以利用这一特性,在运行时加载恶意类,从而向内存中引入不受信任的代码。

1.3. 内存马检测的必要性

近年来,内存马因其持久性、隐蔽性、兼容性强等特性,已被攻击者广泛应用于各种复杂的攻击场景。内存马通过驻留在内存中,规避文件系统的监控,使得传统的杀毒软件和入侵检测系统难以发现其踪迹。因此,需要研究专门的内存马检测技术,以便及时发现并消除内存马带来的安全威胁,从而保障系统的安全性。

2. 检测

Java程序的内存检测一般以Class字节码为维度进行,简单分为两个步骤:

  1. 读取内存中的Class信息

  2. 对Class信息进行分析检测

检测大纲:

干货 | HW中盛行的Java内存马,如何全面检测?

2.1. Class信息获取

2.1.1. 获取方式

2.1.1.1. Serviceability Agent (SA)

SA是JDK提供的一个强大的调试工具集,适用于语言层和虚拟机层,支持调试运行着的Java进程、core文件和虚拟机crash之后的dump文件。SA运行在单独的进程中,和目标Java进程是隔离的,在使用SA工具时,不会在目标Java进程中执行任何代码,但是,在SA读取数据的过程中,目标Java进程会被挂起。

通过sa-jdi.jar中sun.jvm.hotspot.tools.jcore.ClassDump类的方法接收进程id,能够把对应进程已加载的类全部读取出来。

代码示例:

干货 | HW中盛行的Java内存马,如何全面检测?

2.1.1.2. Java Agent

Java Agent 是一种基于 Java Instrumentation API 的机制,可用于在 Java 应用程序运行时动态地修改其字节码。Java Agent 提供了两个主要方法:

  • premain:在 Java 进程启动前加载。这个方法可以在实际类加载之前插入自定义的字节码操作逻辑。

  • agentmain:在 Java 进程启动后加载。这个方法可以在进程运行时对已加载的类进行操作,如读取、修改类字节码等操作。

通过VirtualMachine.attach(pid)方法,可以将当前进程附加到目标 JVM 实例,实现动态代码注入。

PS:冰蝎工具的防检测功能会删除/tmp/.java_pid文件,导致无法使用attach进行注入,可用SA方式替代。

干货 | HW中盛行的Java内存马,如何全面检测?

正常进行attach以后,可使用agentmain方法注入到另一个进程中来实现内存信息获取。代码示例:

干货 | HW中盛行的Java内存马,如何全面检测?

2.1.2. 获取内容

获取Java类的详细信息,主要关注以下几个字段:

  • 类名(className):获取内存中加载的类的完整名称,有助于识别和追踪潜在的恶意类。

  • 类加载器(classLoader):获取负责加载该类的类加载器,以了解其加载来源,并识别异常或不寻常的加载行为。

  • 包名(packageName):获取类所属的包,以帮助判断类的组织结构和潜在的风险区域。

  • 父类(parentClass):获取该类的父类,分析其继承关系,以发现可能隐藏的恶意代码。

  • 类路径(classPath):获取类在文件系统中的位置,用于验证类的合法来源和检查潜在的篡改迹象。

  • 接口(interfaces):获取该类实现的所有接口,了解其功能和行为契约,检测异常接口实现或恶意接口。

  • 注解(annotations):获取类上的注解,识别可能影响类行为的元数据。

  • 类字节码(classBytes):获取类的字节码内容,深入分析其实际实现,识别可能的恶意代码或不符合预期的行为。

2.2. Class信息检测

对获取到的Class信息进行检测,可以分为两个方面:

  1. 首先对Class进行筛选,识别风险类。

  2. 根据需求对全部类或者风险类的Class字节码内容进行检测。

2.2.1. Class风险类检测

2.2.1.1. Web风险类检测

首先,根据不同内存马的分类来分析潜在的Web风险类

  • 组件型内存马:仅需要针对内存中对应的Servlet、filter、Listener等组件进行分析即可。

  • JSP内存马:仅针对内存中实现HttpServlet的类进行分析即可。

  • agent型内存马:由于agent机制几乎可以改写内存中的任何类,可以hook的位置非常多,具体特征需跟着技术发展不断迭代更新。hook点一般都在Web调用的关键链路上。

虽然有各种各样的内存马,但他们通常都作用在Web调用的关键链路上,通过Web请求触发内存马,通过请求参数执行逻辑。

1) Web调用链分析:

哪些调用链是Web调用的关键链路?

以springboot 的Web项目为例,处理Web请求的调用堆栈大概如下:

1、tomcat建立HTTP连接并添加读写事件的监听:

干货 | HW中盛行的Java内存马,如何全面检测?

2、tomcat 收到请求数据包并放到线程池中处理数据包:

干货 | HW中盛行的Java内存马,如何全面检测?

3、tomcat实际处理请求数据包:

干货 | HW中盛行的Java内存马,如何全面检测?

实际应用场景中,hook点大概率会发生在tomcat实际处理请求数据包转http请求request之后。

从堆栈信息可以看出来,调用链路会经过tomcat的Valve组件,servlet的FilterChain及Filter组件,Servlet组件,以及springmvc的相关处理,进而执行到Controller的helloworld方法。

那是不是只要关注上述调用链路就可以了?

当然不是,每个方法里都会有其他代码逻辑,实际Web调用过程中会涉及的调用类远比上述调用链更多。

故光靠分析调用链,无法圈定需要内存分析的所有类,只能找出比较高危的类做重点分析。

2) Web容器注册分析

为什么要做Web容器注册分析?

Web框架在进行Web调用处理时,一般分成两步:

  1. 程序启动时注册组件,即Web程序启动时定义好当收到请求时该交给哪个组件如何进行处理。

  2. 实际发生Web调用时,Web容器根据调用信息,找到对应的组件进行处理。

所以,我们可以通过读取Web容器的注册组件对应类进行内存分析,目标性更强,更精准。

此处介绍两种最常见的Web容器:tomcat和springmvc。

a) tomcat容器:

在使用tomcat作为Web容器时,可通过其核心对象StandardContext中获得filter、servlet、listener的注册信息。

获取的过程如下:

  • StandardContext --> filterConfigs,filterDefs --> filter

  • StandardContext --> servletMappings,children --> servlet

  • StandardContext --> applicationListeners,listeners --> listener

Valve也是tomcat中非常重要的组件,但valve是tomcat中内置的组件,正常情况下并不会进行自定义,只需要通过判断类实现Valve接口过滤即可。

而filter、servlet、listener是servlet规范中提供给开发者自定义的开放性组件,则更具有进一步通过容器注册分析的意义。

b) springmvc容器:

springmvc作为最常见的Web框架,其注册信息同样具备针对性分析的价值。

需要获取的注册信息:

springmvc中主要获取的是HandlerMethod对象和HandlerInterceptor对象信息。

  • 容器中注册的HandlerMethod对象表示请求的实际处理方法,表示springmvc中的controller对象和对应处理方法。

  • 容器中的HandlerInterceptor是开放的拦截器,springmvc提供了在请求处理前后进行自定义处理的机制。

如何获取注册信息:

先获取spring的容器组件ApplicationContext,再从中获取springmvc的核心组件DispatcherServlet,进而获取注册信息。获取过程如下:

  • ApplicationContext --> DispatcherServlet --> List<HandlerMapping> handlerMappings --> AbstractHandlerMethodMapping.getHandlerMethods()

  • ApplicationContext --> DispatcherServlet --> List<HandlerMapping> handlerMappings --> AbstractHandlerMapping.adaptedInterceptors

在实际按照上述方式获取springmvc的注册信息时,可能会用到反射获取信息,且可能会不知道从哪获取最源头的ApplicationContext对象,进而无从下手。

最源头的ApplicationContext对象,一般需要从类的静态变量中获取。可通过内存分析ApplicationContext对象的引用关系,找到获取的链路。

获取方式举例如下:

干货 | HW中盛行的Java内存马,如何全面检测?

2.2.1.2. Class基础信息检测

1) classPath为空

通过分析classPath为空的类,排除已知的正常情况,剩下的类大概率为风险类

1、getProtectionDomain().getCodeSource()为空

这意味着该类没有关联的代码源信息。可能的原因包括:

  • 该类是 JVM 自带的类(例如,java.* 中的类),因为这些类是由 JVM 自身加载的,它们没有显式的代码源。

  • 该类是通过字节码操作动态生成的,或者是以非标准的方式加载的(如通过某些代理类或字节码工具动态生成)。

  • 该类是在 Java 运行时生成的,例如使用 Proxy、ClassLoader.defineClass() 等方法生成的类。

2、getClassLoader().getResource(className.replace(".", "/") + ".class")为空

这意味着类加载器无法找到与指定类对应的资源路径。可能的原因包括:

  • 类是由一个特定的类加载器加载的,但该类加载器不使用标准的路径查找机制(如 URLClassLoader 或自定义的 ClassLoader)。

  • 该类是通过内存中直接加载的方式(如字节码生成工具)加载的,而不是从文件系统或网络资源中获取的,因此找不到相应的类文件资源。

  • 类加载器因为安全限制或者其他原因,无法访问该类的实际存储位置。

2) 关键字特征匹配

可以通过检测包名、类名、父类名、类加载器等类基础信息中是否包含诸如 shell、exploit 等可疑关键字,识别可能由内存马生成工具创建的恶意类。

  • 冰蝎的包名以net.rebeyond.开头或带有Behinder关键字

  • 哥斯拉的包名以core.shell.开头或带有Godzilla关键字

  • msf的包名以com.metasploit.开头

  • 蚁剑带有AntSword关键字

  • ...

2.2.2. Class字节码检测

通过检测Class基础信息方式识别内存马的方式仅对Class的一些表象特征进行分析,其优势在于消耗低,但容易被绕过。而内存马注入的恶意代码肯定是以Class字节码存在在内存之中,通过检查Class字节码内容是一种更直接的方式。

2.2.2.1. 内存与磁盘字节码比对

读取已加载的字节码信息,和磁盘中的字节码信息进行比对。如果不一致,则为可疑类。

如果直接对Class的字节流进行比对,可能经常会误报,可以使用ASM工具将Class内容解析成属性和方法,再对每个属性和方法进行比对。

代码示例:

干货 | HW中盛行的Java内存马,如何全面检测?

干货 | HW中盛行的Java内存马,如何全面检测?

2.2.2.2. 敏感代码调用检测

使用ASM工具解析Class字节码的方法,与敏感代码特征进行比对

高敏感的代码调用:

1、命令执行:

  • java.lang.Runtime#exec

  • java.lang.ProcessBuilder#start

  • java.lang.UNIXProcess#forkAndExec

  • ...

2、JNI加载动态库:

  • java.lang.System#load

  • java.lang.Runtime#load

  • ...

3、类加载:

  • java.lang.ClassLoader#defineClass

  • java.lang.reflect.Proxy#defineClass0

  • sun.misc.Unsafe#defineAnonymousClass

  • java.lang.invoke.MethodHandles.Lookup#defineHiddenClass

  • ...

其他敏感代码调用:

  • 获取系统信息

  • 字节码生成类库

  • 文件操作

  • 编码加密

  • ...

2.2.2.3. 反射调用检测

由于Java语言开放和动态的特性,可通过反射等方式动态地调用上述的敏感代码,进而绕过正常的检测规则。

反射调用方式:

  • java.lang.reflect.Method#invoke

  • sun.reflect.NativeMethodAccessorImpl#invoke

  • jdk.internal.reflect.NativeMethodAccessorImpl#invoke0

  • java.lang.invoke.LambdaMetafactory#metafactory

  • java.lang.invoke.MethodHandle#invoke

  • java.lang.invoke.MethodHandle#invokeExact

  • java.lang.invoke.MethodHandle#invokeWithArguments

  • ...

如何有效检测反射调用:

有师傅提出用模拟栈帧的方式进行检测,但是模拟栈帧的方式开销大,同时只能将检测范围圈定在单个方法内,而反射调用的入参可能在其他方法中定义,甚至在Web请求参数中定义,故有一定的局限性。

由于反射的运行时确定的特性,可以通过运行时检测,修改字节码,在反射代码前插桩,判断反射的参数是否命中敏感代码调用,进而告警。

2.2.2.4. Webshell引擎检测

将Class字节码反编译为Java文件后,交由成熟的Webshell检测引擎进行扫描。将反编译后的Class文件视作普通源代码进行检测,其核心依然是识别潜在的Webshell。通过这种方法,充分利用现有的检测技术,识别出隐藏在内存或代码中的恶意行为,进一步提升检测的准确性与效率。

3. 总结

本文系统性地列举了Java内存马的检测方法,涵盖了从类的基本信息获取到字节码的深入检测。通过这些方法,能够有效识别内存马攻击。可根据不同场景或需求,在检测率与性能开销之间进行平衡,选择适合的检测方式,以实现最佳的检测效果。

然而,攻防对抗本质上是动态的,随着内存马技术的不断发展,新的攻击手段和技术也会不断涌现,现有的检测手段也可能会失效。因此,需要持续更新和调整检测策略,跟踪最新的攻击趋势和技术进展,不断搜集和优化检测特征,以提升系统的防御能力,降低安全风险。

来源:【干货 | HW中盛行的Java内存马,如何全面检测?- FreeBuf网络安全行业门户】,感谢【网宿安全演武实验室

原文始发于微信公众号(船山信安):干货 | HW中盛行的Java内存马,如何全面检测?

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年10月29日13:24:36
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   干货 | HW中盛行的Java内存马,如何全面检测?https://cn-sec.com/archives/3327206.html

发表评论

匿名网友 填写信息