Agent内存马的自动分析与查杀(一)

简介: Agent内存马的自动分析与查杀

出发点是Java Agent内存马的自动分析与查杀,实际上其他内存马都可以通过这种方式查杀

本文主要的难点主要是以下三个,我会在文中逐个解答

  1. 如何dumpJVM真正的当前的字节码
  2. 如何解决由于LAMBDA表达式导致非法字节码无法分析的问题
  3. 如何对字节码进行分析以确定某个类是内存马

基于静态分析动态,打破规则之道                            -- Java King 对本文的评价

背景

对于Java内存马的攻防一直没有停止,是Java安全领域的重点

回顾TomcatSpring内存马:FilterController等都需要注册新的组件

针对于需要注册新组件的内存马查杀起来比较容易:

例如c0ny1师傅的java-memshell-scanner项目,利用了Tomcat API删除添加的组件。优点在于一个简单的JSP文件即可查看所有的组件信息,结合人工审查(类名和ClassLoader等信息)对内存马进行查杀,也可以对有风险的Class进行dump后反编译分析

或者LandGrey师傅基于Alibaba Arthas编写的copagent项目,分析JVM中所有的Class,根据危险注解和类名等信息dump可疑的组件,结合人工反编译后进行分析


但实战中,可能并不是以上这种注册新组件的内存马

例如师傅们常用的冰蝎内存马,是Java Agent内存马。以下这段是冰蝎内存马一段代码,简单分析后可以发现冰蝎内存马是利用Java Agent注入到javax.servlet.http.HttpServletservice方法中,这是JavaEE的规范,理论上部署在Tomcat的都要符合这个规范,简单来理解这是Tomcat处理请求最先且总是经过的地方,在该类加入内存马的逻辑,可以保证稳定触发

0720a98a5016990af0494aea8a84c82d_640_wx_fmt=png&wxfrom=5&wx_lazy=1&wx_co=1.png

类似的逻辑,可以使用Java Agent将内存马注入org.apache.catalina.core.ApplicationFilterChain类中,该类位于Filter链头部,也就是说经过Tomcat的请求都会交经过该类的doFilter方法处理,所以在该方法中加入内存马逻辑,也是一种稳定触发的方式(据说这是老版本冰蝎内存马的方式)

还可以对类似的类进行注入,例如org.springframework.web.servlet.DispatcherServlet类,针对于Spring框架的底层进行注入。或者一些巧妙的思路,比如注入Tomcat自带的Filter之一org.apache.tomcat.websocket.server.WsFilter类,这也是Java Agent内存马可以做到的


上文简单地介绍了各种内存马的利用方式与普通内存马的查杀,之所以最后介绍Java Agent内存马的查杀,是因为比较困难。宽字节安全的师傅提出查杀思路:基于javaAgent内存马检测查杀指南

引用文章讲到Java Agent内存马检测的难点:

调用retransformClass方法的时候参数中的字节码并不是调用redefineClass后被修改的类的字节码。对于冰蝎来讲,根本无法获取被冰蝎修改后的字节码。我们自己写Java Agent清除内存马的时候,同样也是无法获取到被redefineClass修改后的字节码,只能获取到被retransformClass修改后的字节码。通过JavaassistASM工具获取到类的字节码,也只是读取磁盘上响应类的字节码,而不是JVM中的字节码

宽字节安全的师傅找到了一种检测手段:sa-jdi.jar

借用公众号师傅的图片,这是一个GUI工具,可以查看JVM中所有已加载的类。区别在于这里获取到的是真正的当前的字节码,而不是获取到原始的,本地的字节码,所以是可以查看被Java Agent调用redefineClass后被修改的类的字节码。进一步可以dump下来认为存在风险的类然后反编译人工审核

f438bb946a834eaf9896fff648e7cd5c_640_wx_fmt=other&wxfrom=5&wx_lazy=1&wx_co=1.jpg


介绍

以上是背景,接下来介绍我做了些什么,能够实现怎样的效果

不难看出,以上内存马查杀手段都是半自动结合人工审核的方式,当检测出内存马后

是否可以找到一种方式,做到一条龙式服务:

  • 检测(同时支持普通内存马和Java Agent内存马的检测)
  • 分析(如何确定该类是内存马,仅根据恶意类名和注解等信息不完善)
  • 查杀(当确定内存马存在,如何自动地删除内存马并恢复正常业务逻辑)

大致看来,实现起来似乎不难,然而实际中遇到了很多坑,接下来我会逐个介绍


SA-JDI分析

我尝试通过Java Agent技术来获取当前的字节码,发现如师傅所说拿不到被修改的字节码

所以为了可以检测Agent马需要从sa-jdi.jar本身入手,想办法dump得到当前字节码(这样不止可以分析被修改了字节码的Agent马也可以分析普通类型的内存马)

注意到其中一个类:sun.jvm.hotspot.tools.jcore.ClassDump并通过查资料发现该类功能正是dump当前的Class(根据类名也可猜测出)其中的main方法提供一个dump class的命令行工具

于是我想了一些办法,用代码实现了命令行工具的功能,并可以设置一个Filter

ClassDump classDump = new ClassDump();
// my filter
classDump.setClassFilter(filter);
classDump.setOutputDirectory("out");
// protected start method
Class<?> toolClass = Class.forName("sun.jvm.hotspot.tools.Tool");
Method method = toolClass.getDeclaredMethod("start", String[].class);
method.setAccessible(true);
// jvm pid
String[] params = new String[]{String.valueOf(pid)};
try {
   method.invoke(classDump, (Object) params);
} catch (Exception ignored) {
   logger.error("unknown error");
   return;
}
logger.info("dump class finish");
// detach
Field field = toolClass.getDeclaredField("agent");
field.setAccessible(true);
HotSpotAgent agent = (HotSpotAgent) field.get(classDump);
agent.detach();

上文提到设置一个Filter是用于确定需要对哪些类进行dump操作(dump过多会导致性能等问题)

public class NameFilter implements ClassFilter {
   @Override
   public boolean canInclude(InstanceKlass instanceKlass) {
       String klassName = instanceKlass.getName().asString();
       // 在黑名单中的类需要dump
       if (blackList.contains(klassName)) {
           return true;
      }
       // 包含了关键字的类也需要dump
       for (String k : Constant.keyword) {
           if (klassName.contains(k)) {
               return true;
          }
      }
       return false;
  }
}

以上包含了类的黑名单和关键字:

  • 黑名单:Java Agent内存马通常会Hook的地方,需要dump下来进行分析
  • 关键字:类名如果出现memshellshell等关键字认为可能是普通内存马,需要分析
public class Constant {
   // BLACKLIST (Analysis Target)
   // CLASS_NAME#METHOD_NAME
   public static List<String> blackList = new ArrayList<>();
   // SHELL KEYWORD
   public static List<String> keyword = new ArrayList<>();
   static {
       blackList.add("javax/servlet/http/HttpServlet#service");
       blackList.add("org/apache/catalina/core/ApplicationFilterChain#doFilter");
       blackList.add("org/springframework/web/servlet/DispatcherServlet#doService");
       blackList.add("org/apache/tomcat/websocket/server/WsFilter#doFilter");
       keyword.add("shell");
       keyword.add("memshell");
       keyword.add("agentshell");
       keyword.add("exploit");
       keyword.add("payload");
       keyword.add("rebeyond");
       keyword.add("metasploit");
  }
}

另外如果想在Maven项目中加入JDK/lib下的依赖,需要特殊配置

<dependency>
   <groupId>sun.jvm.hotspot</groupId>
   <artifactId>sa-jdi</artifactId>
   <version>jdk-8</version>
   <scope>system</scope>
   <systemPath>${env.JAVA_HOME}/lib/sa-jdi.jar</systemPath>
</dependency>

在打包成工具Jar包时默认情况下不会加入system scope的依赖,所以需要特殊处理

<artifactId>maven-assembly-plugin</artifactId>
<configuration>
   <appendAssemblyId>false</appendAssemblyId>
   <descriptors>
       <descriptor>assembly.xml</descriptor>
   </descriptors>
   <archive>
       <manifest>
           <mainClass>org.sec.Main</mainClass>
       </manifest>
   </archive>
</configuration>

编写assembly.xml文件

<!-- 省略部分 -->
<dependencySets>
   <dependencySet>
       <outputDirectory>/</outputDirectory>
       <unpack>true</unpack>
       <scope>system</scope>
   </dependencySet>
</dependencySets>

接着就可以通过代码的方式,根据黑名单和关键字来确定需要dump哪些类然后进行dump操作了

我在测试中遇到一个小问题,值得分享:HttpServlet是正常可以dump的但是ApplicationFilterChain类没有找到。这是因为SpringBoot的懒加载问题,需要手动请求下某个接口就可以了


相关文章
|
11天前
|
编译器 C语言
动态内存分配与管理详解(附加笔试题分析)(上)
动态内存分配与管理详解(附加笔试题分析)
34 1
|
1月前
|
程序员 编译器 C++
【C++核心】C++内存分区模型分析
这篇文章详细解释了C++程序执行时内存的四个区域:代码区、全局区、栈区和堆区,以及如何在这些区域中分配和释放内存。
46 2
|
11天前
|
程序员 编译器 C语言
动态内存分配与管理详解(附加笔试题分析)(下)
动态内存分配与管理详解(附加笔试题分析)(下)
25 2
|
14天前
|
SQL 分布式计算 Hadoop
Hadoop-19 Flume Agent批量采集数据到HDFS集群 监听Hive的日志 操作则把记录写入到HDFS 方便后续分析
Hadoop-19 Flume Agent批量采集数据到HDFS集群 监听Hive的日志 操作则把记录写入到HDFS 方便后续分析
36 2
|
1月前
|
算法 程序员 Python
程序员必看!Python复杂度分析全攻略,让你的算法设计既快又省内存!
在编程领域,Python以简洁的语法和强大的库支持成为众多程序员的首选语言。然而,性能优化仍是挑战。本文将带你深入了解Python算法的复杂度分析,从时间与空间复杂度入手,分享四大最佳实践:选择合适算法、优化实现、利用Python特性减少空间消耗及定期评估调整,助你写出高效且节省内存的代码,轻松应对各种编程挑战。
29 1
|
1月前
|
存储 Prometheus NoSQL
Redis 内存突增时,如何定量分析其内存使用情况
【9月更文挑战第21天】当Redis内存突增时,可采用多种方法分析内存使用情况:1)使用`INFO memory`命令查看详细内存信息;2)借助`redis-cli --bigkeys`和RMA工具定位大键;3)利用Prometheus和Grafana监控内存变化;4)优化数据类型和存储结构;5)检查并调整内存碎片率。通过这些方法,可有效定位并解决内存问题,保障Redis稳定运行。
|
16天前
|
SQL 安全 算法
ChatGPT高效提问—prompt实践(漏洞风险分析-重构建议-识别内存泄漏)
ChatGPT高效提问—prompt实践(漏洞风险分析-重构建议-识别内存泄漏)
26 0
|
1月前
|
NoSQL 程序员 Linux
轻踩一下就崩溃吗——踩内存案例分析
踩内存问题分析成本较高,尤其是低概率问题困难更大。本文详细分析并还原了两个由于动态库全局符号介入机制(it's a feature, not a bug)触发的踩内存案例。
|
1月前
|
存储 运维
.NET开发必备技巧:使用Visual Studio分析.NET Dump,快速查找程序内存泄漏问题!
.NET开发必备技巧:使用Visual Studio分析.NET Dump,快速查找程序内存泄漏问题!
|
1月前
使用qemu来dump虚拟机的内存,然后用crash来分析
使用qemu来dump虚拟机的内存,然后用crash来分析