Java安全之Java Agent
文章首發: https://sec-in.com/article/1690
About Java Agent
Java Agent的出現
在JDK1.5版本開始,Java增加了Instrumentation(Java Agent API)和JVMTI(JVM Tool Interface)功能,該功能可以實現JVM再加載某個class文件對其字節碼進行修改,也可以對已經加載的字節碼進行一個重新的加載。而在1.6版本新增了attach(附加方式)方式,可以對運行中的Java進程插入Agent。Java Agent可以去實現字節碼插樁、動態跟蹤分析等,比如RASP產品和Java Agent內存馬。
Java Agent運行模式
有兩種模式:
1、啟動Java程序時添加-javaagent(Instrumentation API實現方式)或-agentpath/-agentlib(JVMTI的實現方式)參數,如java -javaagent:/data/XXX.jar LingXeTest。
2、JDK1.6新增了attach(附加方式)方式,可以對運行中的Java進程附加Agent。
這兩種運行方式的最大區別在於第一種方式只能在程序啟動時指定Agent文件,而attach方式可以在Java程序運行后根據進程ID動態注入Agent到JVM。
所以類似於想要注入Agent型內存馬,一般會用attach的方式。
Java Agent
Javaagent是java命令的一個參數。參數 javaagent 可以用於指定一個jar包
Java Agent和普通的Java類並沒有任何區別,普通的Java程序中規定了main方法為程序入口,而Java Agent則將premain(Agent模式)和agentmain(Attach模式)作為了Agent程序的入口,兩者所接受的參數是完全一致的,如下:
public static void premain(String args, Instrumentation inst) {}
public static void agentmain(String args, Instrumentation inst) {}
而在Attach模式下的premain()
方法有兩種寫法,如下:
public static void premain(String agentArgs, Instrumentation inst)
public static void premain(String agentArgs)
JVM會去優先加載帶 Instrumentation 簽名的方法,加載成功忽略第二種,如果第一種沒有,則加載第二種方法。
Java Agent還限制了我們必須以jar包的形式運行或加載,我們必須將編寫好的Agent程序打包成一個jar文件。除此之外,Java Agent還強制要求了所有的jar文件中必須包含/META-INF/MANIFEST.MF文件,且該文件中必須定義好Premain-Class(Agent模式)或Agent-Class:(Agent模式)配置,如:
Premain-Class: com.anbai.sec.agent.CrackLicenseAgent
Agent-Class: com.anbai.sec.agent.CrackLicenseAgent
如果我們需要修改已經被JVM加載過的類的字節碼,那么還需要設置在MANIFEST.MF中添加
Can-Retransform-Classes: true或Can-Redefine-Classes: true。
javaagent參數相關:
-agentlib:<libname>[=<選項>] 加載本機代理庫 <libname>, 例如 -agentlib:hprof
另請參閱 -agentlib:jdwp=help 和 -agentlib:hprof=help
-agentpath:<pathname>[=<選項>]
按完整路徑名加載本機代理庫
-javaagent:<jarpath>[=<選項>]
加載 Java 編程語言代理, 請參閱 java.lang.instrument
jarpath 是指向代理程序 JAR 文件的路徑。options 是代理選項。此開關可以在同一命令行上多次使用,從而創建多個代理程序。多個代理程序可以使用同一 jarpath。代理 JAR 文件必須符合 JAR 文件規范。下面的清單屬性是針對代理 JAR 文件定義的:
Premain-Class
代理類。即包含 premain 方法的類。此屬性是必需的,如果它不存在,JVM 將中止。注:這是類名,而不是文件名或路徑。
Boot-Class-Path
由引導類加載器搜索的路徑列表。路徑表示目錄或庫(在許多平台上通常作為 jar 或 zip 庫被引用)。查找類的特定於平台的機制出現故障之后,引導類加載器會搜索這些路徑。按列出的順序搜索路徑。列表中的路徑由一個或多個空格分開。路徑使用分層 URI 的路徑組件的語法。如果該路徑以斜杠字符(“/”)開頭,則為絕對路徑,否則為相對路徑。相對路徑根據代理 JAR 文件的絕對路徑解析。忽略格式不正確的路徑和不存在的路徑。此屬性是可選的。
Can-Redefine-Classes
布爾值(true 或 false,與大小寫無關)。能夠重定義此代理所需的類。值如果不是 true,則被認為是 false。此屬性是可選的,默認值為 false。
代理 JAR 文件附加到類路徑之后。
而關於java.lang.instrument
包位於rt.jar,一共有5個文件
源碼簡介
其實這一部分把注釋翻譯過來,有些類和某些方法依舊不理解是什么意思,也有些看懂了但不知道怎么用,先鴿着。
ClassDefinition
public final class ClassDefinition {
/**
* 要重定義的類
*/
private final Class<?> mClass;
/**
* 用於替換的本地 class ,為 byte 數組
*/
private final byte[] mClassFile;
/**
* 構造方法,使用提供的類和類文件字節創建一個新的 ClassDefinition 綁定
*/
public ClassDefinition( Class<?> theClass, byte[] theClassFile) {
if (theClass == null || theClassFile == null) {
throw new NullPointerException();
}
mClass = theClass;
mClassFile = theClassFile;
}
/**
* 以下為 getter 方法
*/
public Class<?> getDefinitionClass() {
return mClass;
}
public byte[] getDefinitionClassFile() {
return mClassFile;
}
}
ClassFileTransformer
ClassFileTransformer是一個轉換類文件的代理接口,我們可以在獲取到Instrumentation對象后通過addTransformer方法添加自定義類文件轉換器。
使用addTransformer
方法可以注冊一個我們自定義的Transformer到Java Agent,當有新的類被JVM加載時JVM會自動回調用我們自定義的Transformer類的transform方法,傳入該類的transform信息(類名、類加載器、類字節碼等),我們可以根據傳入的類信息決定是否需要修改類字節碼,修改完字節碼后我們將新的類字節碼返回給JVM,JVM會驗證類和相應的修改是否合法,如果符合類加載要求JVM會加載我們修改后的類字節碼。
package java.lang.instrument;
public interface ClassFileTransformer {
/**
* 類文件轉換方法,重寫transform方法可獲取到待加載的類相關信息
*
* @param loader 定義要轉換的類加載器;如果是引導加載器,則為 null
* @param className 類名,如:java/lang/Runtime
* @param classBeingRedefined 如果是被重定義或重轉換觸發,則為重定義或重轉換的類;如果是類加載,則為 null
* @param protectionDomain 要定義或重定義的類的保護域
* @param classfileBuffer 類文件格式的輸入字節緩沖區(不得修改)
* @return 字節碼byte數組。
*/
byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer);
}
重寫transform方法需要注意以下事項:
- ClassLoader如果是被Bootstrap ClassLoader(引導類加載器)所加載那么loader參數的值是空。
- 修改類字節碼時需要特別注意插入的代碼在對應的ClassLoader中可以正確的獲取到,否則會報ClassNotFoundException,比如修改java.io.FileInputStream(該類由Bootstrap ClassLoader加載)時插入了我們檢測代碼,那么我們將必須保證FileInputStream能夠獲取到我們的檢測代碼類。
- JVM類名的書寫方式路徑方式:java/lang/String而不是我們常用的類名方式:java.lang.String。
- 類字節必須符合JVM校驗要求,如果無法驗證類字節碼會導致JVM崩潰或者VerifyError(類驗證錯誤)。
- 如果修改的是retransform類(修改已被JVM加載的類),修改后的類字節碼不得新增方法、修改方法參數、類成員變量。
- addTransformer時如果沒有傳入retransform參數(默認是false)就算MANIFEST.MF中配置了Can-Redefine-Classes: true而且手動調用了retransformClasses方法也一樣無法retransform。
- 卸載transform時需要使用創建時的Instrumentation實例。
Instrumentation
java.lang.instrument.Instrumentation是監測運行在JVM程序的Java API,利用Instrumentation我們可以實現如下功能:
- 動態添加或移除自定義的ClassFileTransformer(addTransformer/removeTransformer),JVM會在類加載時調用Agent中注冊的ClassFileTransformer;
- 動態修改classpath(appendToBootstrapClassLoaderSearch、appendToSystemClassLoaderSearch),將Agent程序添加到BootstrapClassLoader和SystemClassLoaderSearch(對應的是ClassLoader類的getSystemClassLoader方法,默認是sun.misc.Launcher$AppClassLoader)中搜索;
- 動態獲取所有JVM已加載的類(getAllLoadedClasses);
- 動態獲取某個類加載器已實例化的所有類(getInitiatedClasses)。
- 重定義某個已加載的類的字節碼(redefineClasses)。
- 動態設置JNI前綴(setNativeMethodPrefix),可以實現Hook native方法。
- 重新加載某個已經被JVM加載過的類字節碼retransformClasses)。
源碼如下:
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);
boolean isRetransformClassesSupported();
//在類加載之后,重新定義 Class。這個很重要,該方法是1.6 之后加入的,事實上,該方法是 update 了一個類。
void retransformClasses(Class<?>... classes) throws UnmodifiableClassException;
boolean isRedefineClassesSupported();
void redefineClasses(ClassDefinition... definitions)
throws ClassNotFoundException, UnmodifiableClassException;
boolean isModifiableClass(Class<?> theClass);
@SuppressWarnings("rawtypes")
Class[] getAllLoadedClasses();
@SuppressWarnings("rawtypes")
Class[] getInitiatedClasses(ClassLoader loader);
//獲取一個對象的大小
long getObjectSize(Object objectToSize);
void appendToBootstrapClassLoaderSearch(JarFile jarfile);
void appendToSystemClassLoaderSearch(JarFile jarfile);
boolean isNativeMethodPrefixSupported();
void setNativeMethodPrefix(ClassFileTransformer transformer, String prefix);
}
Java Agent使用
前面都是理論,我們來簡單寫一個小Demo感受一下如何使用Java Agent技術。
Agent模式
大致分為以下流程(以-javaagent模式為例):
- 編寫一個Agent類,其中定義
premain
方法並調用Instrumentation#addTransformer
方法添加一個自定義的Transformer
- 自定義一個
Transformer
類,實現Instrumentation
接口,在transform
方法中寫入自己想要的AOP邏輯 - 創建MANIFEST.MF文件,可以手動寫也可以通過Maven的插件(pom.xml)
- 打包Agent的jar包
- 在需要使用JavaAgent的項目添加JVM啟動參數
-javaagent
並指定我們打包好的jar
這里需要2個項目,1個為javaagent的jar包,另1個為被javaagent代理的類。最終在被代理類的main方法執行前先執行我們Agent中的premain方法
0x01 編寫javaagent相關代碼
先創建一個Maven項目,其中創建一個Agent類,里面需要包含premain
方法
package com.zh1z3ven;
import java.lang.instrument.Instrumentation;
public class Agent {
public static void premain(String agentArgs, Instrumentation inst){
System.out.println("agentArgs"+agentArgs);
inst.addTransformer(new DefineTransformer(),true);//調用addTransformer添加一個Transformer
}
}
創建DefineTransformer
類,實現ClassFileTransformer
接口
package com.zh1z3ven;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
public class DefineTransformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
System.out.println("premain load class"+className); //打印加載的類
return new byte[0];
}
}
0x02 創建MANIFEST.MF文件
手動創建的話需要在resources/META-INF目錄下創建MANIFEST.MF文件,內容如下:注意多留一行空行
Manifest-Version: 1.0
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Premain-Class: com.zh1z3ven.Agent
通過pom.xml
中調用Maven的插件去創建該文件
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<archive>
<!--自動添加META-INF/MANIFEST.MF -->
<manifest>
<addClasspath>true</addClasspath>
</manifest>
<manifestEntries>
<Premain-Class>com.zh1z3ven.Agent</Premain-Class>
<Agent-Class>com.zh1z3ven.Agent</Agent-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
</manifestEntries>
</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>
打包好jar后,文件會在jar包中
一些可能會用到的參數說明:
Premain-Class :包含 premain 方法的類(類的全路徑名)
Agent-Class :包含 agentmain 方法的類(類的全路徑名)
Boot-Class-Path :設置引導類加載器搜索的路徑列表。查找類的特定於平台的機制失敗后,引導類加載器會搜索這些路徑。按列出的順序搜索路徑。列表中的路徑由一個或多個空格分開。路徑使用分層 URI 的路徑組件語法。如果該路徑以斜杠字符(“/”)開頭,則為絕對路徑,否則為相對路徑。相對路徑根據代理 JAR 文件的絕對路徑解析。忽略格式不正確的路徑和不存在的路徑。如果代理是在 VM 啟動之后某一時刻啟動的,則忽略不表示 JAR 文件的路徑。(可選)
Can-Redefine-Classes :true表示能重定義此代理所需的類,默認值為 false(可選)
Can-Retransform-Classes :true 表示能重轉換此代理所需的類,默認值為 false (可選)
Can-Set-Native-Method-Prefix: true表示能設置此代理所需的本機方法前綴,默認值為 false(可選)
0x03 編寫測試類
隨意寫一個
public class a {
public static void main(String[] args) {
System.out.println("main Method");
}
}
0x04 -javaagent模式啟動
JVM啟動參數添加
-javaagent:target/JavaAgent-1.0-SNAPSHOT.jar
執行main方法之前會加載所有的類,包括系統類和自定義類。而在ClassFileTransformer
中會去攔截系統類和自己實現的類對象,邏輯則是在ClassFileTransformer
實現類的transform
方法中定義。
而在這里transform
給我的感覺是類似於一個filter
會去攔截/遍歷一些要在JVM中加載的類,而在transform
方法中我們可以定義一些邏輯,比如if className== xxx
時走入一個邏輯去實現AOP。而其中就可以利用如javassist技術修改字節碼並作為transform
方法的返回值,這樣就在該類在JVM中加載前(-javaagent模式)修改了字節碼
使用javassist修改字節碼
這里在之前a類中新添加一個方法,並在Agent
里我們自定義的Transformert
中transform
添加一個邏輯,使用javassist
去修改我們a類中新添加的方法。
a類中新加一個call方法
package MemoryShell.JavaAgent;
public class a {
public static void main(String[] args) {
System.out.println("main Method");
call();
}
public static void call(){
System.out.println("say hello ...");
}
}
DefineTransformer
package com.zh1z3ven;
import javassist.*;
import java.io.IOException;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
public class DefineTransformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
// System.out.println("premain load class"+className); //打印加載的類
if ("MemoryShell/JavaAgent/a".equals(className)){
try {
ClassPool classPool = ClassPool.getDefault();
CtClass ctClass = classPool.get("MemoryShell.JavaAgent.a");
CtMethod call = ctClass.getDeclaredMethod("call");
// 打印后加了一個彈計算器的操作
String MethodBody = "{System.out.println(\"say hello ...\");" +
"java.lang.Runtime.getRuntime().exec(\"open -a Calculator\");}";
call.setBody(MethodBody);
byte[] bytes = ctClass.toBytecode();
//detach的意思是將內存中曾經被javassist加載過的a對象移除,如果下次有需要在內存中找不到會重新走javassist加載
ctClass.detach();
return bytes;
} catch (NotFoundException e) {
e.printStackTrace();
} catch (CannotCompileException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
return new byte[0];
}
}
打成jar包,指定JVM參數后運行a類
-javaagent:target/JavaAgent-1.1-SNAPSHOT.jar
Attach api
在Java SE 6 以后在Instrumentation
接口中提供了新的方法agentmain
可以在 main 函數開始運行之后再運行。
//采用attach機制,被代理的目標程序VM有可能很早之前已經啟動,當然其所有類已經被加載完成,這個時候需要借助Instrumentation#retransformClasses(Class<?>... classes)讓對應的類可以重新轉換,從而激活重新轉換的類執行ClassFileTransformer列表中的回調
public static void agentmain (String agentArgs, Instrumentation inst)
public static void agentmain (String agentArgs)
同樣,agentmain 方法中帶Instrumentation參數的方法也比不帶優先級更高。開發者必須在MANIFEST.MF文件里面設置“Agent-Class”來指定包含 agentmain 函數的類。
在Java6 以后實現啟動后加載的新實現是Attach api。Attach API 很簡單,只有 2 個主要的類,都在 com.sun.tools.attach 包里面:
-
VirtualMachine 字面意義表示一個Java 虛擬機,也就是程序需要監控的目標虛擬機,提供了獲取系統信息(比如獲取內存dump、線程dump,類信息統計(比如已加載的類以及實例個數等), loadAgent,Attach 和 Detach (Attach 動作的相反行為,從 JVM 上面解除一個代理)等方法,可以實現的功能可以說非常之強大 。該類允許我們通過給attach方法傳入一個jvm的pid(進程id),遠程連接到jvm上 。代理類注入操作只是它眾多功能中的一個,通過loadAgent方法向jvm注冊一個代理程序agent,在該agent的代理程序中會得到一個Instrumentation實例,該實例可以 在class加載前改變class的字節碼,也可以在class加載后重新加載。在調用Instrumentation實例的方法時,這些方法會使用ClassFileTransformer接口中提供的方法進行處理。
-
VirtualMachineDescriptor 則是一個描述虛擬機的容器類,配合 VirtualMachine 類完成各種功能。
attach實現動態注入的原理如下:
通過VirtualMachine類的attach(pid)方法,便可以attach到一個運行中的java進程上,之后便可以通過loadAgent(agentJarPath)來將agent的jar包注入到對應的進程,然后對應的進程會調用agentmain方法。
Attach模式使用
0x01 在JavaAgent項目中新編寫一個AgentMain類
package com.zh1z3ven;
import java.lang.instrument.Instrumentation;
public class AgentMain {
public static void agentmain(String agentArgs, Instrumentation instrumentation) {
instrumentation.addTransformer(new AgentMainTransformer(), true);
}
}
0x02 新建一個自定義的Transformer
transform方法中邏輯依舊是修改a類的call方法字節碼去彈calc
package com.zh1z3ven;
import javassist.*;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
public class AgentMainTransformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
if ("MemoryShell.JavaAgent.a".equals(className)) {
try {
ClassPool classPool = ClassPool.getDefault();
CtClass ctClass = classPool.get("MemoryShell.JavaAgent.a");
CtMethod call = ctClass.getDeclaredMethod("call");
// 打印后加了一個彈計算器的操作
String MethodBody = "{java.lang.Runtime.getRuntime().exec(\"open -a Calculator\");" +
"System.out.println(\"say hello ...\");}";
call.setBody(MethodBody);
byte[] bytes = ctClass.toBytecode();
return bytes;
//detach的意思是將內存中曾經被javassist加載過的a對象移除,如果下次有需要在內存中找不到會重新走javassist加載
// ctClass.detach();
} catch (Exception e) {
e.printStackTrace();
return classfileBuffer;
}
}else {
return classfileBuffer;
}
}
}
0x03 測試AgentMainTest類
將jar通過jvm pid注入進來,使其修改a類中call方法的字節碼
package MemoryShell.JavaAgent;
import com.sun.tools.attach.*;
import java.io.IOException;
import java.util.List;
public class AgentMainTest {
public static void main(String[] args) {
System.out.println("running JVM start ");
List<VirtualMachineDescriptor> list = VirtualMachine.list(); // 尋找當前系統中所有運行着的JVM進程
for (VirtualMachineDescriptor vmd : list) {
//如果虛擬機的名稱為 xxx 則 該虛擬機為目標虛擬機,獲取該虛擬機的 pid
//然后加載 agent.jar 發送給該虛擬機
System.out.println(vmd.displayName()); //vmd.displayName()看到當前系統都有哪些JVM進程在運行
if (vmd.displayName().endsWith("MemoryShell.JavaAgent.AgentMainTest")) {
VirtualMachine virtualMachine = null;
try {
virtualMachine = VirtualMachine.attach(vmd.id());
virtualMachine.loadAgent("/Users/xxxx/JavaSourceCode/JavaCode/JavaAgent/target/JavaAgent-1.0-SNAPSHOT.jar");
virtualMachine.detach();
} catch (AttachNotSupportedException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (AgentLoadException e) {
e.printStackTrace();
} catch (AgentInitializationException e) {
e.printStackTrace();
}
}
}
}
}
0x04 記得修改MANIFEST.MF或直接改pom.xml
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<archive>
<!--自動添加META-INF/MANIFEST.MF -->
<manifest>
<addClasspath>true</addClasspath>
</manifest>
<manifestEntries>
<Agent-Class>com.zh1z3ven.AgentMain</Agent-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
</manifestEntries>
</archive>
</configuration>
</plugin>
0x05 打包,先運行測試AgentMainTest類將jar注入進來使其修改a類的字節碼,之后運行a的main方法,調用到call方法時是我們修改過后的字節碼了,所以會彈calc
Agent模式與Attach模式小結:
- 上面Attach這種情況是修改的還沒被JVM加載的類,已加載的Java類是不會再被Agent處理的,這時候我們需要在Attach到目標進程后調用instrumentation.redefineClasses,讓JVM重新該Java類,這樣我們就可以使用Agent機制修改該類的字節碼了。
public static void agentmain(String agentArgs, Instrumentation inst) throws UnmodifiableClassException, ClassNotFoundException { inst.addTransformer(new Transformer(), true); inst.retransformClasses(Class.forName("com.example.xxxclass")); }
- premain和agentmain兩種方式修改字節碼的時機都是類文件加載之后,也就是說必須要帶有Class類型的參數,不能通過字節碼文件和自定義的類名重新定義一個本來不存在的類。
- 類的字節碼修改稱為類轉換(Class Transform),類轉換其實最終都回歸到類重定義Instrumentation#redefineClasses()方法,此方法有以下限制:
- 新類和老類的父類必須相同;
- 新類和老類實現的接口數也要相同,並且是相同的接口;
- 新類和老類訪問符必須一致。 新類和老類字段數和字段名要一致;
- 新類和老類新增或刪除的方法必須是private static/final修飾的;
- 可以修改方法體。
- java agent 中的所有依賴,在原進程中的 classpath 中都要能找到,否則在注入時原進程會報錯NoClassDefFoundError。
- agent 進程的 classpath 中必須有 tools.jar(提供 VirtualMachine attach api ),jdk 默認有 tools.jar,jre 默認沒有。並且Linux和Windows之間是存在一個tools.jar適配問題。
Reference
https://www.cnblogs.com/nice0e3/p/14086165.html
https://www.cnblogs.com/rickiyang/p/11368932.html
https://su18.org/post/irP0RsYK1/
javasec.org