在前面發現不少技術都會去採用Java Agent該技術去作實現,比分說RASP和內存馬(其中一種方式)、包括IDEA的這些破解都是基於Java Agent去作實現。下面來領略該技術的微妙所在。html
在JDK1.5版本開始,Java增長了Instrumentation(Java Agent API)
和JVMTI(JVM Tool Interface)
功能,該功能能夠實現JVM再加載某個class文件對其字節碼進行修改,也能夠對已經加載的字節碼進行一個從新的加載。Java Agent能夠去實現字節碼插樁、動態跟蹤分析等。java
啓動Java程序的時候添加-javaagent(Instrumentation API實現方式)
或-agentpath/-agentlib(JVMTI的實現方式)
參數shell
在1.6版本新增了attach(附加方式)方式,能夠對運行中的Java進程
插入Agent
apache
方式一中只能在啓動前去指定須要加載的Agent文件,而方式二能夠在Java程序運行後根據進程ID進行動態注入Agent到JVM裏面去。編程
Java Agent是一個Java裏面命令的參數該參數內容能夠指定一個jar包,該jar包內容有必定的規範windows
上面說到的這個premain方法會在運行main方法前被調用,也就是說在運行main方法前會去加載-javaagent指定的jar包裏面的Premain-Class類中的premain方法。那麼其實Java agent本質上就是一個Java的類,可是普通的Java類是以main方法做爲程序入口點,而Java Agent則將premain
(Agent模式)和agentmain
(Attach模式)做爲了Agent程序的入口。api
若是須要修改已經被JVM加載過的類的字節碼,那麼還須要設置在MANIFEST.MF
中添加Can-Retransform-Classes: true
或Can-Redefine-Classes: true
。數組
先來看看命令參數tomcat
-agentlib:<libname>[=<選項>] 加載本機代理庫 <libname>, 例如 -agentlib:hprof 另請參閱 -agentlib:jdwp=help 和 -agentlib:hprof=help -agentpath:<pathname>[=<選項>] 按完整路徑名加載本機代理庫 -javaagent:<jarpath>[=<選項>] 加載 Java 編程語言代理, 請參閱 java.lang.instrument
上面說到的 java.lang.instrument
提供容許 Java 編程語言代理監測運行在 JVM 上的程序的服務。監測的機制是對方法的字節碼的修改,在啓動 JVM 時,經過指示代理類 及其代理選項 啓動一個代理程序。安全
該代理類必須實現公共的靜態 premain
方法,該方法原理上相似於 main 應用程序入口點,而且premain
方法的前面也會有必定的要求,簽名必須知足一下兩種格式:
public static void premain(String agentArgs, Instrumentation inst) public static void premain(String agentArgs)
JVM會去優先加載帶 Instrumentation
簽名的方法,加載成功忽略第二種,若是第一種沒有,則加載第二種方法。這個邏輯在sun.instrument.InstrumentationImpl
類中實現,能夠來審計一下該代碼
例:
public static void premain(String agentArgs, Instrumentation inst);
-javaagent:jarpath[=options] 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 文件附加到類路徑以後。
在JDK裏面有個rt.jar包中存在一個java.lang.instrument
的包,這個包提供了Java運行時,動態修改系統中的Class類型的功能。但最關鍵的仍是javaagent 。它能夠在運行時從新接收外部請求,對class類型進行一個修改。
這裏面有2個重要的接口 Instrumentation
和 ClassFileTransformer
先來看看Instrumentation接口中的內容
來看到上圖,這是java.lang.instrument.Instrumentation
中的一些方法。借鑑一下javasec裏面的一張圖,該圖片描述了各類方法的一個做用
java.lang.instrument.Instrumentation
的做用是用來監測運行在JVM中的Java API,利用該類能夠實現以下功能:
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方法。retransformClasses
)。這裏已經代表各大實現功能所對應的方法了。
java.lang.instrument.ClassFileTransformer
是一個轉換類文件的代理接口,咱們能夠在獲取到Instrumentation
對象後經過addTransformer
方法添加自定義類文件轉換器。
示例中咱們使用了addTransformer
註冊了一個咱們自定義的Transformer
到Java Agent
,當有新的類被JVM
加載時JVM
會自動回調用咱們自定義的Transformer
類的transform
方法,傳入該類的transform
信息(類名、類加載器、類字節碼
等),咱們能夠根據傳入的類信息決定是否須要修改類字節碼,修改完字節碼後咱們將新的類字節碼返回給JVM
,JVM
會驗證類和相應的修改是否合法,若是符合類加載要求JVM
會加載咱們修改後的類字節碼。
查看一下該接口
該接口中有隻有一個transform方法,裏面的參數內容對應的信息分別是:
ClassLoader loader 定義要轉換的類加載器;若是是引導加載器,則爲 null String className 加載的類名,如:java/lang/Runtime Class<?> classBeingRedefined 若是是被重定義或重轉換觸發,則爲重定義或重轉換的類;若是是類加載,則爲 null 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
實例。上面說的都是一些概念性的問題,如今去作一個Java agent的實現
來看一下實現的大體幾個步驟
Premain-Class
類,而且裏面包含premain
方法,方法邏輯由用戶本身肯定premain
和 MANIFEST.MF
文件打包成一個jar包-javaagent: jar
參數包路徑 啓動要代理的方法。完成以上步驟後,啓動程序的時候會去執行premain
方法,固然這個確定是優先於main方法執行的。可是難免會有一些系統類優先於javaagent進行執行。可是用戶類這些確定是會被javaagent給攔截下來的。這麼這時候攔截下來後就能夠進行一個重寫類等操做,例如使用ASM、javassist,cglib等等來改寫實現類。在實現裏面須要去些2個項目,一個是javaAgent的類,一個是須要JavaAagent須要去代理的類。在mian方法執行前去執行的一些代碼。
建立一個Agent類,裏面須要包含premain方法:
package com.nice0e3; 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類:
package com.nice0e3; 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]; } }
這裏須要重寫transform方法。也就是在加載的時候須要執行操做都會在該方法中進行實現。
SRC\META-INF\MANIFEST.MF文件中添加內容:
Manifest-Version: 1.0 Can-Redefine-Classes: true Can-Retransform-Classes: true Premain-Class: com.nice0e3.Agent
我這裏用的是maven去作一個配置
pom.xml:
<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.nice0e3.Agent</Premain-Class> <Agent-Class>com.nice0e3.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包後,再創建一個項目,配置加入-javaagent參數,-javaagent:out\Agent1-1.0-SNAPSHOT.jar
後面不能有多餘的空格。
編寫一個main方法
package com.test; import java.io.IOException; import java.io.InputStream; public class test { public static void main(String[] args) throws IOException { System.out.println("main"); } }
這裏能夠看到打印了JVM加載的全部類。而main這個字符再Shutdown以前被打印了,最後面纔去加載Shutdown這個也是比較重要的一個點,可是在這裏不作贅述。
前面說過transform方法,也就是在加載的時候須要執行其餘的操做都會在該方法中進行實現。這是由於ClassFileTransformer
中會去攔截系統類和本身實現的類對象,若是須要對某個類進行改寫,就能夠在攔截的時候抓住這個類使用字節碼編譯工具去實現。
這裏來複制一個小案例
import javassist.*; import java.io.IOException; import java.lang.instrument.ClassFileTransformer; import java.security.ProtectionDomain; /** * @author rickiyang * @date 2019-08-06 * @Desc */ public class MyClassTransformer implements ClassFileTransformer { @Override public byte[] transform(final ClassLoader loader, final String className, final Class<?> classBeingRedefined,final ProtectionDomain protectionDomain, final byte[] classfileBuffer) { // 操做Date類 if ("java/util/Date".equals(className)) { try { // 從ClassPool得到CtClass對象 final ClassPool classPool = ClassPool.getDefault(); final CtClass clazz = classPool.get("java.util.Date"); CtMethod convertToAbbr = clazz.getDeclaredMethod("convertToAbbr"); //這裏對 java.util.Date.convertToAbbr() 方法進行了改寫,在 return以前增長了一個 打印操做 String methodBody = "{sb.append(Character.toUpperCase(name.charAt(0)));" + "sb.append(name.charAt(1)).append(name.charAt(2));" + "System.out.println(\"sb.toString()\");" + "return sb;}"; convertToAbbr.setBody(methodBody); // 返回字節碼,而且detachCtClass對象 byte[] byteCode = clazz.toBytecode(); //detach的意思是將內存中曾經被javassist加載過的Date對象移除,若是下次有須要在內存中找不到會從新走javassist加載 clazz.detach(); return byteCode; } catch (Exception ex) { ex.printStackTrace(); } } // 若是返回null則字節碼不會被修改 return null; } }
這裏是使用javassist
去動態建立一個類,而且對java.util.Date
的convertToAbbr
方法去作一個改寫使用setBody
插入新的內容,而後轉換成字節碼進行返回。
前面是使用在main方法運行以前,執行Instrument
。而在JDK1.6之後新增的agentmain
方法,能夠實如今main方法執行之後進行插入執行。
該方法和前面的permain
相似,須要定義一個agentmain
方法的類。
public static void agentmain (String agentArgs, Instrumentation inst) public static void agentmain (String agentArgs)
這個也是和前面的同樣,有Instrumentation
類型參數的運行優先級也是會比沒有該參數的高。
在Java JDK6之後實現啓動後加載Instrument
的是Attach api
。存在於com.sun.tools.attach
裏面有兩個重要的類。
來查看一下該包中的內容,這裏有兩個比較重要的類,分別是VirtualMachine
和VirtualMachineDescriptor
VirtualMachine
能夠來實現獲取系統信息,內存dump、現成dump、類信息統計(例如JVM加載的類)。裏面配備有幾個方法LoadAgent,Attach 和 Detach 。下面來看看這幾個方法的做用
Attach :從 JVM 上面解除一個代理等方法,能夠實現的功能能夠說很是之強大 。該類容許咱們經過給attach方法傳入一個jvm的pid(進程id),遠程鏈接到jvm上
loadAgent:向jvm註冊一個代理程序agent,在該agent的代理程序中會獲得一個Instrumentation實例,該實例能夠 在class加載前改變class的字節碼,也能夠在class加載後從新加載。在調用Instrumentation實例的方法時,這些方法會使用ClassFileTransformer接口中提供的方法進行處理。
Detach:從 JVM 上面解除一個代理(agent)
Attach模式須要知道咱們運行的Java程序進程ID,經過Java虛擬機的進程注入方式實現能夠將咱們的Agent程序動態的注入到一個已在運行中的Java程序中。咱們也可使用自帶的Jps -l
命令去查看。
看到第一個16320進程估計就是IDEA的破解插件,使用的Java agent技術進行一個實現破解。
attach實現動態注入的原理以下:
VirtualMachine類的attach(pid)
方法,即可以attach到一個運行中的java進程上,以後即可以經過loadAgent(agentJarPath)
來將agent的jar包注入到對應的進程,而後對應的進程會調用agentmain方法。
package com.nice0e3; import com.sun.tools.attach.VirtualMachine; import com.sun.tools.attach.VirtualMachineDescriptor; import java.util.List; public class test { public static void main(String[] args) { List<VirtualMachineDescriptor> list = VirtualMachine.list(); for (VirtualMachineDescriptor virtualMachineDescriptor : list) { System.out.println(virtualMachineDescriptor+"\n"+virtualMachineDescriptor.id()); } } }
有了進程ID後就可使用Attach API注入Agent了。
編輯pom.xml文件
<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> <Agent-Class>com.nice0e3.Agent</Agent-Class> <Can-Redefine-Classes>true</Can-Redefine-Classes> <Can-Retransform-Classes>true</Can-Retransform-Classes> </manifestEntries> </archive> </configuration> </plugin> </plugins> </build>
Agent類:
package com.nice0e3; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.IllegalClassFormatException; import java.lang.instrument.Instrumentation; import java.security.ProtectionDomain; public class Agent { public static void agentmain(String agentArgs, Instrumentation instrumentation) { instrumentation.addTransformer(new DefineTransformer(), true); } }
DefineTransformer類:
package com.nice0e3; 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 classfileBuffer; } }
編譯成jar包後,編寫一個main方法來進行測試
main方法類:
package com.test; import com.sun.tools.attach.*; import java.io.IOException; import java.io.InputStream; import java.util.List; public class test { public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException { System.out.println("main running"); List<VirtualMachineDescriptor> list = VirtualMachine.list(); for (VirtualMachineDescriptor vir : list) { System.out.println(vir.displayName());//打印JVM加載類名 if (vir.displayName().endsWith("com.test.test")){ VirtualMachine attach = VirtualMachine.attach(vir.id()); //attach注入一個jvm id注入進去 attach.loadAgent("out\\Agent1-1.0-SNAPSHOT.jar");//加載agent attach.detach(); } } } }
執行結果:
instrumentation.redefineClasses
,讓JVM從新該Java類,這樣咱們就可使用Agent機制修改該類的字節碼了。下面拿一個Javasec的裏面的案例來作一個測試,複製該代碼
package com.test; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.TimeUnit; /** * Creator: yz * Date: 2020/10/29 */ public class CrackLicenseTest { private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); private static boolean checkExpiry(String expireDate) { try { Date date = DATE_FORMAT.parse(expireDate); // 檢測當前系統時間早於License受權截至時間 if (new Date().before(date)) { return false; } } catch (ParseException e) { e.printStackTrace(); } return true; } public static void main(String[] args) { // 設置一個已通過期的License時間 final String expireDate = "2020-10-01 00:00:00"; new Thread(new Runnable() { @Override public void run() { while (true) { try { String time = "[" + DATE_FORMAT.format(new Date()) + "] "; // 檢測license是否已通過期 if (checkExpiry(expireDate)) { System.err.println(time + "您的受權已過時,請從新購買受權!"); } else { System.out.println(time + "您的受權正常,截止時間爲:" + expireDate); } // sleep 1秒 TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); } }
這裏是模擬了一個IDEA的檢測激活功能。
執行以下
如今須要的就是將這個檢測的激活的CrackLicenseTest
這個類給HOOK掉。
下面來編寫一下代碼。
package com.nice0e3; import com.sun.tools.attach.VirtualMachine; import com.sun.tools.attach.VirtualMachineDescriptor; import javassist.ClassPool; import javassist.CtClass; import javassist.CtMethod; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileOutputStream; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.Instrumentation; import java.lang.instrument.UnmodifiableClassException; import java.net.URL; import java.security.ProtectionDomain; import java.util.List; /** * Creator: yz * Date: 2020/1/2 */ public class CrackLicenseAgent { /** * 須要被Hook的類 */ private static final String HOOK_CLASS = "com.anbai.sec.agent.CrackLicenseTest"; /** * Java Agent模式入口 * * @param args 命令參數 * @param inst Instrumentation */ public static void premain(String args, final Instrumentation inst) { loadAgent(args, inst); } /** * Java Attach模式入口 * * @param args 命令參數 * @param inst Instrumentation */ public static void agentmain(String args, final Instrumentation inst) { loadAgent(args, inst); } public static void main(String[] args) { if (args.length == 0) { List<VirtualMachineDescriptor> list = VirtualMachine.list(); for (VirtualMachineDescriptor desc : list) { System.out.println("進程ID:" + desc.id() + ",進程名稱:" + desc.displayName()); } return; } // Java進程ID String pid = args[0]; try { // 注入到JVM虛擬機進程 VirtualMachine vm = VirtualMachine.attach(pid); // 獲取當前Agent的jar包路徑 URL agentURL = CrackLicenseAgent.class.getProtectionDomain().getCodeSource().getLocation(); String agentPath = new File(agentURL.toURI()).getAbsolutePath(); // 注入Agent到目標JVM vm.loadAgent(agentPath); vm.detach(); } catch (Exception e) { e.printStackTrace(); } } /** * 加載Agent * * @param arg 命令參數 * @param inst Instrumentation */ private static void loadAgent(String arg, final Instrumentation inst) { // 建立ClassFileTransformer對象 ClassFileTransformer classFileTransformer = createClassFileTransformer(); // 添加自定義的Transformer,第二個參數true表示是否容許Agent Retransform, // 需配合MANIFEST.MF中的Can-Retransform-Classes: true配置 inst.addTransformer(classFileTransformer, true); // 獲取全部已經被JVM加載的類對象 Class[] loadedClass = inst.getAllLoadedClasses(); for (Class clazz : loadedClass) { String className = clazz.getName(); if (inst.isModifiableClass(clazz)) { // 使用Agent從新加載HelloWorld類的字節碼 if (className.equals(HOOK_CLASS)) { try { inst.retransformClasses(clazz); } catch (UnmodifiableClassException e) { e.printStackTrace(); } } } } } private static ClassFileTransformer createClassFileTransformer() { return new ClassFileTransformer() { /** * 類文件轉換方法,重寫transform方法可獲取到待加載的類相關信息 * * @param loader 定義要轉換的類加載器;若是是引導加載器,則爲 null * @param className 類名,如:java/lang/Runtime * @param classBeingRedefined 若是是被重定義或重轉換觸發,則爲重定義或重轉換的類;若是是類加載,則爲 null * @param protectionDomain 要定義或重定義的類的保護域 * @param classfileBuffer 類文件格式的輸入字節緩衝區(不得修改) * @return 字節碼byte數組。 */ @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) { // 將目錄路徑替換成Java類名 className = className.replace("/", "."); // 只處理com.anbai.sec.agent.CrackLicenseTest類的字節碼 if (className.equals(HOOK_CLASS)) { try { ClassPool classPool = ClassPool.getDefault(); // 使用javassist將類二進制解析成CtClass對象 CtClass ctClass = classPool.makeClass(new ByteArrayInputStream(classfileBuffer)); // 使用CtClass對象獲取checkExpiry方法,相似於Java反射機制的clazz.getDeclaredMethod(xxx) CtMethod ctMethod = ctClass.getDeclaredMethod( "checkExpiry", new CtClass[]{classPool.getCtClass("java.lang.String")} ); // 在checkExpiry方法執行前插入輸出License到期時間代碼 ctMethod.insertBefore("System.out.println(\"License到期時間:\" + $1);"); // 修改checkExpiry方法的返回值,將受權過時改成未過時 ctMethod.insertAfter("return false;"); // 修改後的類字節碼 classfileBuffer = ctClass.toBytecode(); File classFilePath = new File(new File(System.getProperty("user.dir"), "src\\main\\java\\com\\nice0e3\\"), "CrackLicenseTest.class"); // 寫入修改後的字節碼到class文件 FileOutputStream fos = new FileOutputStream(classFilePath); fos.write(classfileBuffer); fos.flush(); fos.close(); } catch (Exception e) { e.printStackTrace(); } } return classfileBuffer; } }; } }
這個不知道爲啥本身作的時候沒有成功,貼一張成功的圖過來。
https://www.cnblogs.com/rickiyang/p/11368932.html https://javasec.org/javase/JavaAgent/JavaAgent.html https://y4er.com/post/javaagent-tomcat-memshell/
在中途中會遇到不少坑,好比tools.jar的jar包在windows下找不到,須要手工去Java jdk的lib目錄下而後將該包手工進行添加進去。學習就是一個排坑的過程。假設用Java agent 須要在反序列化或者是直接打入內存馬該怎麼去實現?其實y4er師傅文中有提到過一些須要注意點和考慮到的點。這個後面再去作實現。