前言
前段时间参与了一场内存马的应急,以前打内存马这种攻击操作做的很多,但实际从防守角度去找别人的内存马这种操作基本没做过。试用了网上的很多工具,引发了我对如何能在内存中找到他人内存马的思考。由于网上的实操工具很多,我就不做演示,后面只从纯原理角度探讨Java内存马的查杀理论思路和demo代码
查杀手段
Jsp查杀
https://github.com/c0ny1/java-memshell-scanner/blob/master/tomcat-memshell-scanner.jsp
列出中间件中的所有Filter/Listener/Sevlet,从该类是否实际存在于物理路径上来判断是否为内存马
Java Agent查杀
https://github.com/LandGrey/copagent/tree/master
从文件内容、父类信息、注解信息、包名信息、接口信息等维度通过是否匹配到黑名单来判断目标是否为内存马
进程内存Dump
内存马查杀的暴力解法,直接dump制定进程的内存马,然后通过二进制编辑器查询
1 | gcore <PID> |
综合上面三种方式,从通用和可自动化角度考虑,Java Agent查杀是最好的方式。因此后面我们主要从Java Agent角度出发来讨论如何辨别内存马
查杀方式
获取JVM中所有的类
Java Agent方式查杀的基础是首先获取当前JVM中所有的类
1 | Class allClasses=inst.getAllLoadedClasses() |
物理文件和内存文件的比对
内存马是一种无文件攻击,根据此概念,内存马在磁盘上不会存在对应文件。因此可以遍历
1 | for (Class class:allClasses){ |
此方式优点为:如果目标是类似Filter/Listener/Servlet这种添加“路由”的内存马,很轻松的可以被此种方式识别出来
此方式缺点为:1、如果攻击方不惜破坏内存马的无文件特性,直接在对应的classpath位置写一个内存马物理文件,那么这种检测方式就会失效 2、如果目标使用Java Agent技术,篡改了某些中间件的Filter实现内存马,那么此种方式也无法检测到
异常父类/异常接口/异常注解识别
实现Filter/Listener/Servlet接口或通过注解实现该Filter/Lisener/Servlet的类,拉出标记为高风险类。查看高风险类是否继承了ClassLoader,如果继承了CLassLoader可以直接判断为内存马,因为正常代码不会把这两种功能混在一个类中,而内存马为了开发简单可能会把ClassLoader和实现Filter等写在一个类中
1 | while (class != null && !class.getName().equals("java.lang.Object")){ |
内容识别
对上种方式提取的高风险类进行反编译,然后进行黑名单匹配
1 | javax.crypto. |
类篡改识别
前面提到Java Agent类型的内存马无法通过物理文件和内存文件的比对检测出来,针对这种篡改性质的内存马,我们可以通过技术手段将其识别出来。原理为,我们通过Java Agent技术添加一个ClassFileTransformer,把当前内存中的所有类的字节码保存下来。然后再读区该类对应的物理路径处的文件,再获得一个字节码。这两个字节码中,前者表示当前类在内存中的字节码,后者表示该类原本的内存马。两者比较字节码的hash,如果一致表示类没有被篡改过,如果不一致表示类已被篡改。
1 | import java.io.InputStream; |
对抗
最后分享一种对抗场景。现在有很多的内存马会删除/tmp/.java_pid文件,这样会导致后续的java agent注入不上。前段时间应急就遇到这种情况,最后dump了进程全部内存,而全部内存的无效数据非常多,给分析造成了很大的阻力。最近研究发现,java自带的工具可以破解/tmp/.java_pid的情况,这里分享出来
1 | sudo java -classpath "$JAVA_HOME/lib/sa-jdi.jar" -Dsun.jvm.hotspot.tools.jcore.filter=sun.jvm.hotspot.tools.jcore.PackageNameFilter -Dsun.jvm.hotspot.tools.jcore.PackageNameFilter.pkgList=com sun.jvm.hotspot.tools.jcore.ClassDump 414703 |
该命令会dump所有com包名开头的class
