java内存马检测基础方法
字数 1312 2025-08-24 07:48:22
Java内存马检测基础方法
核心思路
- 利用JDK提供的sa-jdi API基于黑名单dump存在于JVM中的真实字节码
- 基于ASM框架进行分析
- 反编译检测
sa-jdi的基本使用
1. 通过命令行dump JVM中的class类
ClassDump工具可以设置两个System properties:
sun.jvm.hotspot.tools.jcore.filter: Filter的类名sun.jvm.hotspot.tools.jcore.outputDir: 输出的目录
sa-jdi.jar中提供了sun.jvm.hotspot.tools.jcore.PackageNameFilter,可以指定dump哪些包里的类。PackageNameFilter中有一个System property可以指定过滤哪些包:
sun.jvm.hotspot.tools.jcore.PackageNameFilter.pkgList
使用示例:
java -classpath "D:\env\Java\jdk1.8.0_65\lib\sa-jdi.jar"
-Dsun.jvm.hotspot.tools.jcore.filter=sun.jvm.hotspot.tools.jcore.PackageNameFilter
-Dsun.jvm.hotspot.tools.jcore.PackageNameFilter.pkgList=com.example.filter
-Dsun.jvm.hotspot.tools.jcore.outputDir=D:\ClassOut
sun.jvm.hotspot.tools.jcore.ClassDump 16992
其中:
com.example.filter表示需要dump的包D:\ClassOut表示输出文件的路径16992表示需要dump的JVM进程pid
2. 通过API dump JVM中的class类
代码示例:
import sun.jvm.hotspot.HotSpotAgent;
import sun.jvm.hotspot.oops.InstanceKlass;
import sun.jvm.hotspot.tools.jcore.ClassDump;
import sun.jvm.hotspot.tools.jcore.ClassFilter;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class JdiDemo {
static class MyFilter implements ClassFilter {
@Override
public boolean canInclude(InstanceKlass kls) {
String klassName = kls.getName().asString();
return klassName.equals("com/example/filter/Myfilter"); //指定dump的类
}
}
public static void main(String[] args) throws Exception {
int pid = 17072; //要dump的JVM的pid
ClassFilter filter = new MyFilter();
ClassDump classDump = new ClassDump();
classDump.setClassFilter(filter);
classDump.setOutputDirectory("D:\\ClassOut"); //输出位置
Class<?> toolClass = Class.forName("sun.jvm.hotspot.tools.Tool");
Method method = toolClass.getDeclaredMethod("start", String[].class);
method.setAccessible(true);
String[] params = new String[]{String.valueOf(pid)};
try {
method.invoke(classDump, (Object)params); //通过反射调用start方法
} catch (Exception ignored) {
System.out.println(ignored);
return;
}
System.out.println("dump class finish");
Field field = toolClass.getDeclaredField("agent");
field.setAccessible(true);
HotSpotAgent agent = (HotSpotAgent)field.get(classDump);
agent.detach();
}
}
3. 选择dump的类
一个JVM中包含了很多类,但不需要全部dump下来。可以按照内存马的特征点有选择性地dump类。
示例筛选器:
static class MyFilter implements ClassFilter {
private static String[] dginterfaces = {
"javax/servlet/Filter",
"javax/servlet/Servlet",
"javax/servlet/ServletRequestListener"
};
private static String[] riskPackage = {
"net/rebeyond/",
"com/metasploit/"
};
private static String[] riskSuperClassesName = {
"javax/servlet/http/HttpServlet"
};
@Override
public boolean canInclude(InstanceKlass kls) {
String klassName = kls.getName().asString();
//类名黑名单判断
if (klassName.equals("org/springframework/web/servlet/handler/AbstractHandlerMapping")){
System.out.println(klassName);
return true;
}
for (String rp : riskPackage) {
if (klassName.startsWith(rp)){
System.out.println(klassName);
return true;
}
}
//接口黑名单
KlassArray interfaces = kls.getTransitiveInterfaces();
int len = interfaces.length();
for (int i = 0; i < len; ++i) {
for (String df : dginterfaces){
if (interfaces.getAt(i).getName().asString().equals(df)){
System.out.println(klassName);
return true;
}
}
}
//父类黑名单
Klass spuperklass = kls.getSuper();
if (spuperklass != null){
for (String rsk : riskSuperClassesName) {
if (rsk.equals(spuperklass.getName().asString())){
System.out.println(klassName);
return true;
}
}
}
return false;
}
}
4. 类的筛选优化
对于Spring的controller内存马,由于不需要继承特定类或实现特定接口,可以通过javaagent技术获取类名。controller内存马需要将类注册到RequestMappingHandlerMapping类对象中,因此可以通过RequestMappingHandlerMapping获取要dump的类名。
示例代码:
@RequestMapping("/mappings")
@ResponseBody
public String mappings(){
try {
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes()
.getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
RequestMappingHandlerMapping rmhMapping = context.getBean(RequestMappingHandlerMapping.class);
Field _mappingRegistry = AbstractHandlerMethodMapping.class.getDeclaredField("mappingRegistry");
_mappingRegistry.setAccessible(true);
Object mappingRegistry = _mappingRegistry.get(rmhMapping);
Field _registry = mappingRegistry.getClass().getDeclaredField("registry");
_registry.setAccessible(true);
HashMap<Object, Object> registry = (HashMap<Object, Object>)_registry.get(mappingRegistry);
Class<?>[] tempArray = AbstractHandlerMethodMapping.class.getDeclaredClasses();
Class<?> mappingRegistrationClazz = null;
for (Class<?> item : tempArray) {
if (item.getName().equals("org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$MappingRegistration")) {
mappingRegistrationClazz = item;
}
}
StringBuilder sb = new StringBuilder();
sb.append("<pre>");
sb.append("| path |").append("\t").append("\t").append("| info |").append("\n");
for (Map.Entry<Object, Object> entry : registry.entrySet()){
sb.append("-");
sb.append("\n");
RequestMappingInfo key = (RequestMappingInfo)entry.getKey();
if (key.getPatternsCondition()!=null){
List<String> tempList = new ArrayList<>(key.getPatternsCondition().getPatterns());
sb.append(tempList.get(0)).append("\t").append("-->").append("\t");
}
if (key.getPathPatternsCondition()!=null){
List<PathPattern> tempList = new ArrayList<>(key.getPathPatternsCondition().getPatterns());
sb.append(tempList.get(0).toString()).append("\t").append("-->").append("\t");
}
Field _handlerMethod = mappingRegistrationClazz.getDeclaredField("handlerMethod");
_handlerMethod.setAccessible(true);
HandlerMethod handlerMethod = (HandlerMethod)_handlerMethod.get(entry.getValue());
Field _desc = handlerMethod.getClass().getDeclaredField("description");
_desc.setAccessible(true);
String desc = (String)_desc.get(handlerMethod);
sb.append(desc);
sb.append("\n");
}
sb.append("</pre>");
return sb.toString();
} catch (Exception e){
e.printStackTrace();
}
return "";
}
5. sa-jdi VS javaagent
| 比较点 | sa-jdi | javaagent |
|---|---|---|
| 兼容性 | 只能在同jdk版本中使用 | 可以在不同jdk版本下使用 |
| 类的类型 | 获取的是InstanceKlass类 | 获取的是Class类 |
| dump的字节码 | 直接从JVM获取真实字节码 | 可能被攻击者影响 |
| 准确度 | 更高 | 较低 |
基于ASM进行分析
ASM是一种通用Java字节码操作和分析框架,可用于修改现有class文件或动态生成class文件。
1. 基本使用
添加依赖:
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>9.7</version>
</dependency>
示例代码:
import org.objectweb.asm.*;
import java.io.File;
import java.io.FileInputStream;
class ClassPrinter extends ClassVisitor {
public ClassPrinter() {
super(Opcodes.ASM7);
}
//访问类的基本结构,如类名、访问修饰符、父类和接口
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
System.out.println(name + " extends " + superName + " {");
}
//访问类的源文件信息
public void visitSource(String source, String debug) {}
//访问当前类是另一个类的成员类的情况
public void visitOuterClass(String owner, String name, String desc) {}
//访问类级别的注解
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
return null;
}
//访问类级别的其他属性
public void visitAttribute(Attribute attr) {}
//访问内部类信息
public void visitInnerClass(String name, String outerName, String innerName, int access) {}
//访问类的字段(属性)
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
System.out.println(" " + desc + " " + name);
return null;
}
//访问类的方法
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
System.out.println(" " + name + desc);
return null;
}
//表示类的访问结束
public void visitEnd() {
System.out.println("}");
}
}
public class Analysis {
public static void doAnalysis(String classpath) throws Exception {
File classFile = new File(classpath);
ClassReader cr = new ClassReader(new FileInputStream(classFile));
ClassPrinter cp = new ClassPrinter();
cr.accept(cp, ClassReader.SKIP_DEBUG);
}
}
2. Runtime.getRuntime().exec()检测
通过MethodVisitor类的visitMethodInsn方法可以获取分析的类方法中所调用的类方法。
示例代码:
class ExecClassVisitor extends ClassVisitor {
public ExecClassVisitor(int api) {
super(api);
}
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
System.out.println(name + desc);
super.visitMethod(access, name, desc, signature, exceptions);
return new ExecMethodVisitor(this.api);
}
}
class ExecMethodVisitor extends MethodVisitor {
public ExecMethodVisitor(int api) {
super(api);
}
@Override
public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface){
System.out.println("owner:" + owner + " name:" + name + " descriptor:" + descriptor);
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
}
}
检测Runtime.getRuntime().exec()的代码:
class ExecClassVisitor extends ClassVisitor {
public ExecClassVisitor(int api) {
super(api);
}
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
super.visitMethod(access, name, desc, signature, exceptions);
return new ExecMethodVisitor(this.api);
}
}
class ExecMethodVisitor extends MethodVisitor {
public ExecMethodVisitor(int api) {
super(api);
}
@Override
public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface){
boolean runtimeCondition = owner.equals("java/lang/Runtime")
&& name.equals("exec")
&& descriptor.equals("([Ljava/lang/String;)Ljava/lang/Process;");
if (runtimeCondition) {
System.out.println("owner:" + owner + " name:" + name + " descriptor:" + descriptor);
System.out.println("The class has used Runtime.getRuntime().exec()");
}
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
}
}
反编译分析
可以使用CFR工具将class文件反编译后进行分析。
添加依赖:
<dependency>
<groupId>org.benf</groupId>
<artifactId>cfr</artifactId>
<version>0.152</version>
</dependency>
反编译代码示例:
import org.benf.cfr.reader.api.CfrDriver;
import org.benf.cfr.reader.util.getopt.OptionsImpl;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class CFRer {
public static Long cfr(String source, String targetPath) throws IOException {
Long start = System.currentTimeMillis();
// source jar
List<String> files = new ArrayList<>();
files.add(source);
// target dir
HashMap<String, String> outputMap = new HashMap<>();
outputMap.put("outputdir", targetPath);
OptionsImpl options = new OptionsImpl(outputMap);
CfrDriver cfrDriver = new CfrDriver.Builder().withBuiltOptions(options).build();
cfrDriver.analyse(files);
Long end = System.currentTimeMillis();
return end - start;
}
}
通过判断反编译后的java文件中的字符串,也可以作为判断内存马的一种方法。