Java安全之Java Agent


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方法需要注意以下事項:

  1. ClassLoader如果是被Bootstrap ClassLoader(引導類加載器)所加載那么loader參數的值是空。
  2. 修改類字節碼時需要特別注意插入的代碼在對應的ClassLoader中可以正確的獲取到,否則會報ClassNotFoundException,比如修改java.io.FileInputStream(該類由Bootstrap ClassLoader加載)時插入了我們檢測代碼,那么我們將必須保證FileInputStream能夠獲取到我們的檢測代碼類。
  3. JVM類名的書寫方式路徑方式:java/lang/String而不是我們常用的類名方式:java.lang.String。
  4. 類字節必須符合JVM校驗要求,如果無法驗證類字節碼會導致JVM崩潰或者VerifyError(類驗證錯誤)。
  5. 如果修改的是retransform類(修改已被JVM加載的類),修改后的類字節碼不得新增方法、修改方法參數、類成員變量。
  6. addTransformer時如果沒有傳入retransform參數(默認是false)就算MANIFEST.MF中配置了Can-Redefine-Classes: true而且手動調用了retransformClasses方法也一樣無法retransform。
  7. 卸載transform時需要使用創建時的Instrumentation實例。

Instrumentation

java.lang.instrument.Instrumentation是監測運行在JVM程序的Java API,利用Instrumentation我們可以實現如下功能:

  1. 動態添加或移除自定義的ClassFileTransformer(addTransformer/removeTransformer),JVM會在類加載時調用Agent中注冊的ClassFileTransformer;
  2. 動態修改classpath(appendToBootstrapClassLoaderSearch、appendToSystemClassLoaderSearch),將Agent程序添加到BootstrapClassLoader和SystemClassLoaderSearch(對應的是ClassLoader類的getSystemClassLoader方法,默認是sun.misc.Launcher$AppClassLoader)中搜索;
  3. 動態獲取所有JVM已加載的類(getAllLoadedClasses);
  4. 動態獲取某個類加載器已實例化的所有類(getInitiatedClasses)。
  5. 重定義某個已加載的類的字節碼(redefineClasses)。
  6. 動態設置JNI前綴(setNativeMethodPrefix),可以實現Hook native方法。
  7. 重新加載某個已經被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模式為例):

  1. 編寫一個Agent類,其中定義premain方法並調用Instrumentation#addTransformer方法添加一個自定義的Transformer
  2. 自定義一個Transformer類,實現Instrumentation接口,在transform方法中寫入自己想要的AOP邏輯
  3. 創建MANIFEST.MF文件,可以手動寫也可以通過Maven的插件(pom.xml)
  4. 打包Agent的jar包
  5. 在需要使用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里我們自定義的Transformerttransform添加一個邏輯,使用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 包里面:

  1. VirtualMachine 字面意義表示一個Java 虛擬機,也就是程序需要監控的目標虛擬機,提供了獲取系統信息(比如獲取內存dump、線程dump,類信息統計(比如已加載的類以及實例個數等), loadAgent,Attach 和 Detach (Attach 動作的相反行為,從 JVM 上面解除一個代理)等方法,可以實現的功能可以說非常之強大 。該類允許我們通過給attach方法傳入一個jvm的pid(進程id),遠程連接到jvm上 。代理類注入操作只是它眾多功能中的一個,通過loadAgent方法向jvm注冊一個代理程序agent,在該agent的代理程序中會得到一個Instrumentation實例,該實例可以 在class加載前改變class的字節碼,也可以在class加載后重新加載。在調用Instrumentation實例的方法時,這些方法會使用ClassFileTransformer接口中提供的方法進行處理。

  2. 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模式小結:

  1. 上面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"));
     }
    
  2. premain和agentmain兩種方式修改字節碼的時機都是類文件加載之后,也就是說必須要帶有Class類型的參數,不能通過字節碼文件和自定義的類名重新定義一個本來不存在的類。
  3. 類的字節碼修改稱為類轉換(Class Transform),類轉換其實最終都回歸到類重定義Instrumentation#redefineClasses()方法,此方法有以下限制:
    • 新類和老類的父類必須相同;
    • 新類和老類實現的接口數也要相同,並且是相同的接口;
    • 新類和老類訪問符必須一致。 新類和老類字段數和字段名要一致;
    • 新類和老類新增或刪除的方法必須是private static/final修飾的;
    • 可以修改方法體。
  4. java agent 中的所有依賴,在原進程中的 classpath 中都要能找到,否則在注入時原進程會報錯NoClassDefFoundError。
  5. 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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM