Java Agent在中介軟體安全領域的應用(文末附詳細程式碼)
寫作背景主要是調研agent在zookeeper安全性方面的調研,本文主要記錄java agent相關的技術。
1、什麼是java agent
Java Agent又叫做Java探針,是在JDK1.5引入的一種可以動態修改Java位元組碼的技術,是依附於java應用程式(JVM)Instrumentation API與虛擬機器互動。
Java類編譯之後形成位元組碼被JVM執行,在JVM在執行這些位元組碼之前獲取這些位元組碼資訊,並且透過位元組碼轉換器對這些位元組碼進行修改,來完成一些額外的功能
底層的具體由JVMTI機制實現。
JVM tool interface(JVMTI)是供工具使用的本機程式設計介面。它提供了一種檢查狀態和控制Java虛擬機器(JVM)中執行的應用程式執行的方法。JVMTI支援需要訪問JVM狀態的各種工具,包括但不限於:分析、除錯、監視、執行緒分析和覆蓋率分析工具。
2、使用場景
java agent技術的一些常用使用場景:
對class檔案加密 實現應用效能監控(APM) JAVA程式的除錯 熱載入 啟動方式
在java主程式中,透過-javaagent指定代理jar包,來實現程式代理。**-javaagent:jarpath[=**options]
jarpath是代理JAR檔案的路徑。options是代理選項。此開關可以在同一命令列上多次使用,從而建立多個代理。多個代理可以使用同一jarpath。代理JAR檔案必須符合JAR檔案規範。
3、啟動方式
掛載agent的方式主要包含靜態啟動、動態啟動兩種方式。
3.1 靜態啟動(Agent模式)
當在JVM啟動時,來指定代理。需要實現的方法premain()
//優先執行
public static void premain(String agentArgs, Instrumentation inst)
public static void premain(String agentArgs)
啟動agent
-javaagent:/data/gitlab/test-demo/agent/target/agent.jar -jar example-1.0-SNAPSHOT.jar
3.2 動態啟動(Attach模式)
當在JVM啟動一段時間後,來指定代理。基於動態attach模式,需要實現方法 agentmain。
//優先執行
public static void agentmain(String agentArgs, Instrumentation inst);
public static void agentmain(String agentArgs)
需要透過VirtualMachine將載入agent的jar,插裝到需要代理的JVM程式中,來實現代理操作。
public class AttachAgent {
public static void main(String[] args) {
String pid = args[0];
String agentJar = args[1];
VirtualMachine virtualMachine = VirtualMachine.attach(pid);
virtualMachine.loadAgent(agentJar);
virtualMachine.detach();
}
}
啟動agent命令,因為要依賴java toos.jar,需要顯示指定classpath。具體做法如下所示:
java -classpath .:$CLASS_PATH:/data/gitlab/test-demo/agent/target/agent.jar com.vhicool.demo.AttachAgent 65887 /data/gitlab/test-demo/agent/target/agent.jar
命令的具體引數說明如下:
agentArgs: java agent啟動指定的傳入的引數,如:java -agentlib:/data/agent.jar=opt1,opt2 inst:它提供了向現有編譯的Java類新增位元組碼的能力
編寫完AttachAgent,還不能直接被jvm載入,首先需要在 在resources/META-INF/MANIFREST.MF檔案加入如下配置
Manifest-Version: 1.0
Premain-Class: com.vhicoo.HelloAgent
Agent-Class: com.vhicoo.HelloAgent
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Build-Jdk-Spec: 1.8
Created-By: Maven Jar Plugin 3.2.0
然後要在maven中定義agent配置:
<build>
<finalName>HelloAgent</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<archive>
<!--META-INF/MANIFEST.MF -->
<manifest>
<addClasspath>true</addClasspath>
</manifest>
<manifestEntries>
<Premain-Class>com.vhicoo.HelloAgent</Premain-Class>
<Agent-Class>com.vhicoo.HelloAgent</Agent-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>
對這些配置項說明如下:
Premain-Class:包含premain方法的類名,當在JVM靜態啟動時指定代理時,需要此屬性 Agent-Class:包含agentmain方法的類。當在JVM動態啟動時指定代理時,需要此屬性 Boot-Class-Path:引導類載入器要搜尋的路徑列表。路徑表示目錄或庫(在許多平臺上通常稱為JAR或zip庫)。在查詢類的平臺特定機制失敗後,引導類載入器會搜尋這些路徑。列表中的路徑由一個或多個空格分隔。路徑採用層次URI的路徑元件的語法。如果路徑以斜槓字元(“/”)開頭,則路徑為絕對路徑,否則路徑為相對路徑。根據代理JAR檔案的絕對路徑解析相對路徑。忽略格式不正確和不存在的路徑。當在VM啟動後某個時間啟動代理時,將忽略不表示JAR檔案的路徑。此屬性是可選的。 Can-Redefine-Classes:重新定義此代理所需的類的能力。返回:true/false,預設false Can-Retransform-Classes:是否能夠重新轉換此代理所需的類。返回:true/false,預設false Can-Set-Native-Method-Prefix:是否設定此代理所需的本機方法字首的能力。返回:true/false,預設false
接下來我們再來看一下java agent技術中一個非常重要的類:Instrumentation,是實現動態修改位元組碼的主力軍,對其中非常重要的方法一一介紹下作用。
transformer 所有滿足ClassFileTransformers的類,在初始化過程中,都會呼叫轉換器(除了任何註冊的轉換器所依賴的類的定義)。轉換器可以修改目標類的位元組碼,從而達到重新定義類功能的能力。對於已經載入的類,需要藉助retransform來實現重新觸發類轉換
retransform 註冊的Transformer會在類初始化過程中,一個一個去將滿足條件的類進行轉換。retransfrom是JVM重複執行這個過程的能力。Instrumentation的retransformClasses方法只提供類,不透過類位元組碼,它是透過將已經註冊的具有轉換能力的ClassFileTransformers,提供實際位元組碼,來達到修改目標類的能力。使用場景是在目標類已經載入(如agent attach模式),對目標類進行修改。
redefine 代理類可以在任何時候,透過指定目標類和類位元組碼,將來達到修改目標類,redefineClasses更改現有(和已載入)類的實際定義
4、解除安裝Agent
在javaagent不管是透過agent模式還是透過attach模式,都將本身agent程式碼也載入到目標JVM中。
在我們不需要對目標JVM代理,想要使目標class恢復到代理之前應該如果去操作呢?
首先解除安裝javaagent包括如下途徑。當然最快的解除安裝agent方式是直接將目標JVM程式重啟,但是這種方式對於高可用業務場景,可能會帶來風險。
我們通常有如下兩種方式進行類的解除安裝。
4.1 解除安裝Transform
第一種是解除安裝Transform。
如果我們是透過定義Transformer來實現javaagent,那麼可以使用retransform將Transformer移除,並且將被代理class恢復。
具體的實施步驟如下:
在premain或者agentmain中,註冊Transformer
HelloTransformer helloTransformer = new HelloTransformer();
inst.addTransformer(helloTransformer, true);
觸發retransform使類重新載入,從而修改目標class位元組碼,生成新的被代理class。其中clazz變數是被代理類物件
inst.retransformClasses(clazz);
透過removeTransformer移除已經註冊的Transformer,並且再次觸發retransform,使被代理class位元組碼還原到代理之前。其中helloTransformer必須是第一步定義的Transformer物件引用。
inst.removeTransformer(helloTransformer);
inst.retransformClasses(clazz);
4.2 透過熱部署能力實現還原目標代理類
透過redefine,直接將程式碼進行重新載入,達到類覆蓋的目的。實現方式為Instrumentation#redefineClasses(ClassDefinition classDefinition)方法,透過提供的類位元組碼,重新定義類。
這種方法不僅能夠還原被代理的類,同時也能實現代理依賴的類,根據指定的類直接嗎,重新載入為新的class,這樣就可以實現agent.jar的熱部署。
5、完整Demo示範
5.1 定義Agent代理類:HelloAgent
透過mainAgent代理類,實現類擴充套件功能,預設只能生效修改還未載入的類。
但是一般我們在對程式類進行代理時,大多已經執行一段時間,處理程式碼已經完成載入。在這個時候,需要將已經載入的類進行替換,在attach到目標程式後,透過呼叫Instrumentation#redefineClasses讓jvm重新載入指定類,這樣就完成已載入型別位元組碼修改。
package com.vhicool.demo.agent;
import java.lang.instrument.Instrumentation;
/**
* <p> agent代理類入口,同時支援靜態、動態啟動 </p>
*
* @author vhicool
**/
public class HelloAgent {
public static void premain(String agentArgs, Instrumentation inst){
inst.addTransformer(new HelloTransformer(),true);
System.out.println("Premain HelloAgent success");
}
public static void agentmain(String agentArgs, Instrumentation inst) {
inst.addTransformer(new HelloTransformer());
for (String clazz : HelloTransformer.INJECTED_CLASS) {
System.out.println("Retransform classes : " + clazz);
inst.retransformClasses(Class.forName(clazz));
}
System.out.println("Attach HelloAgent success");
}
}
5.2 定義類轉換器:Transformer
package com.vhicool.demo.agent;
import javassist.*;
import java.io.IOException;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.List;
/**
* <p> 定義轉換器,在代理的方法執行前,輸出日誌 </p>
*
* @author vhicool
**/
public class HelloTransformer implements ClassFileTransformer {
public static final List<String> INJECTED_CLASS = new ArrayList<String>();
static {
//被代理的類
INJECTED_CLASS.add("com.vhicool.test.example.ProcessTest");
}
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
String realClassName = className.replace("/", ".");
try {
if (INJECTED_CLASS.contains(realClassName)) {
System.out.println("Agent class : " + realClassName);
ClassPool classPool = ClassPool.getDefault();
CtClass ctClass = classPool.get(realClassName);
CtMethod[] declaredMethods = ctClass.getDeclaredMethods();
for (CtMethod ctMethod : declaredMethods) {
String method = ctMethod.getName();
ctMethod.insertBefore(String.format("System.out.println(\"Process method : + %s\");", method));
}
return ctClass.toBytecode();
}
} catch (NotFoundException | CannotCompileException | IOException e) {
e.printStackTrace();
}
return classfileBuffer;
}
}
5.3 定義Agent注入入口
這一步,只有動態啟動(attatch)模式需要,靜態啟動不需要定義此類。
package com.vhicool.demo;
import com.sun.tools.attach.AgentInitializationException;
import com.sun.tools.attach.AgentLoadException;
import com.sun.tools.attach.AttachNotSupportedException;
import com.sun.tools.attach.VirtualMachine;
import java.io.IOException;
/**
* <p> 這個類是將代理程式插入到目標代理程式程式 </p>
*
* @author vhicool
* @date 2023/1/16
* @since 1.0.0
**/
public class AttachAgent {
public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException {
String pid = args[0];
String jvmJar = args.length > 1 ? args[1] : System.getProperty("user.dir") + "/agent.jar";
System.out.println("Agent :" + jvmJar + " ,attach Pid :" + pid);
VirtualMachine virtualMachine = VirtualMachine.attach(pid);
virtualMachine.loadAgent(jvmJar);
virtualMachine.detach();
}
}
5.4 pom依賴
javassist是一個用來 處理 Java 位元組碼的類庫。它可以在一個已經編譯好的類中新增新的方法,或者是修改已有的方法。
maven-shade-plugin會將專案依賴打到jar中
<dependencies>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.28.0-GA</version>
</dependency>
<dependency>
<groupId>jdk.tools</groupId>
<artifactId>jdk.tools</artifactId>
<version>1.8</version>
<scope>system</scope>
<systemPath>${env.JAVA_HOME}/lib/tools.jar</systemPath>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<archive>
<!--META-INF/MANIFEST.MF -->
<manifest>
<addClasspath>true</addClasspath>
</manifest>
<manifestEntries>
<Agent-Class>com.vhicool.demo.agent.HelloAgent</Agent-Class>
<Premain-Class>com.vhicool.demo.agent.HelloAgent</Premain-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-shade-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration combine.self="override">
<createDependencyReducedPom>false</createDependencyReducedPom>
<shadedArtifactAttached>false</shadedArtifactAttached>
<finalName>agent</finalName>
<transformers>
<transformer
<resource>reference.conf</resource>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
<transformer implementation="org.apache.maven.plugins.shade.resource.ApacheNoticeResourceTransformer">
<projectName>agent</projectName>
<encoding>UTF-8</encoding>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
5.5 定義一個需要被注入的程式
package com.vhicool.test.example;
/**
*
* @author vhicool
**/
public class HelloMain {
public static void main(String[] args) throws InterruptedException {
ProcessTest helloMain = new ProcessTest();
for (int i = 0; ; i++) {
helloMain.process(i + "");
Thread.sleep(1000);
}
}
}
public class ProcessTest {
public void process(String s) {
System.out.println("輸出 :" + s);
}
}
5.6 分別使用兩種方式啟動
javaagent(靜態啟動方式) 靜態啟動是隨著java主程式一起啟動,伴隨jvm的生命週期
java -javaagent:/data/gitlab/test-demo/agent/target/agent.jar -jar example-1.0-SNAPSHOT.jar
輸出:
Process method : + process
輸出 :0
Process method : + process
輸出 :1
Process method : + process
輸出 :2
javaagent(動態啟動方式)
啟動主程式,此時程式正常輸出結果
java -jar example.jar
其中需要先檢視example.jar的pid。com.vhicool.demo.AttachAgent是attach模式啟動程式,agent.jar 是代理程式
java -classpath .:$CLASS_PATH:/data/gitlab/test-demo/agent/target/agent.jar com.vhicool.demo.AttachAgent 57092 /data/gitlab/test-demo/agent/target/agent.jar
輸出結果如下所示:
輸出 :39
Transform classes : com.vhicool.test.example.ProcessTest
Agent class : com.vhicool.test.example.ProcessTest
Attach HelloAgent success
Process method : + process
輸出 :40
Process method : + process
輸出 :41
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70024922/viewspace-2937144/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 是否可以建個關於Java在應用軟體領域的論壇Java
- Photoshop軟體的應用領域介紹
- 六西格瑪設計在軟體研發領域的應用
- websphere如何部署應用程式_中介軟體Web
- 大模型在程式碼缺陷檢測領域的應用實踐大模型
- Python的優缺點和應用領域有哪些? 【詳細】Python
- 低程式碼的主要應用領域以及目標群體分析
- 網路安全應用領域有哪些?常見應用領域總結!
- 網路安全的應用領域有哪些?
- Java中的Unsafe在安全領域的一些應用總結和復現Java
- 移動物聯網NB-IoT在細分領域的應用
- 行業應用軟體領域的問題是什麼?行業
- 車牌識別一體機在智慧領域的應用
- 淺談人工智慧在流媒體領域的應用人工智慧
- StarRocks在支付對賬領域的應用
- ChatGPT在資訊保安領域的應用前景ChatGPT
- 大資料文摘:細數機器學習在金融領域的七大應用大資料機器學習
- 深度學習影象演算法在內容安全領域的應用深度學習演算法
- 分享5款在各自領域遙遙領先的軟體
- 訊息中介軟體的應用場景
- 軟體測試與程式碼安全詳解
- Mock技術在測試領域的應用Mock
- 深度學習在醫療領域的應用深度學習
- 漫談 C# 在遊戲領域的應用C#遊戲
- 社交資料在徵信領域的應用探索
- Laravel 跨域功能中介軟體Laravel跨域
- 平臺安全之中介軟體安全
- IBM:人工智慧在人力資源領域的應用案例(附下載)IBM人工智慧
- 網路安全有哪些細分領域?
- Graph Embedding在人力資本領域的應用
- 區塊鏈技術在金融領域的應用區塊鏈
- 人工智慧在材料領域的應用有哪些?人工智慧
- 串聯諧振在各個領域的應用
- 【AI in 美團】深度學習在文字領域的應用AI深度學習
- 調製技術在通訊領域有哪些具體應用?
- laravel 區域性排除中介軟體Laravel
- Linux最常見的三個應用領域詳解!Linux
- 人工智慧技術在軟體安全漏洞檢測領域有哪些作用人工智慧