Java agent技術的注入利用與避坑點

合天网安实验室發表於2024-03-06

什麼是Java agent技術?

Java代理(Java agent)是一種Java技術,它允許開發人員在執行時以某種方式修改或增強Java應用程式的行為。Java代理透過在Java虛擬機器(JVM)啟動時以"代理"(agent)的形式載入到JVM中,以監視、修改或甚至完全改變目標應用程式的行為。

Java agent 可以做什麼?

  1. 安全監控和審計:

    透過Java代理,可以在應用程式中注入程式碼以監視其行為並記錄關鍵事件。這可以用於安全審計目的,以確保應用程式不受到惡意行為或違規操作的影響。

  2. 安全驗證和授權:

    Java代理可以攔截對受保護資源的訪問,並執行安全驗證和授權操作。透過代理,可以實現訪問控制策略,確保只有經過授權的使用者或系統可以訪問特定資源。

  3. 安全加固:

    透過Java代理,可以對應用程式進行安全加固,例如實時檢測和防禦攻擊,包括程式碼注入、SQL隱碼攻擊、跨站點指令碼攻擊等。代理可以攔截請求,並根據安全策略進行處理,從而提高應用程式的安全性。

  4. 加密和解密:

    Java代理可以用於實現端到端的資料加密和解密,保護敏感資料在傳輸過程中的安全性。代理可以攔截資料流,對資料進行加密或解密操作,以確保資料在傳輸過程中不會被竊取或篡改。

  5. 安全日誌記錄:

    Java代理可以用於記錄應用程式的安全日誌,包括使用者操作、異常事件、安全警報等。透過代理,可以將安全日誌傳送到中央日誌伺服器進行集中管理和分析,以便及時發現和應對安全威脅。

靜態Agent使用

建立Maven專案,寫一個類PreMainTraceAgent,使用Maven編譯並打成jar包。

package com.example;
​
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
​
public class PreMainTraceAgent {
​
public static void premain(String agentArgs, Instrumentation inst) {
System.out.println("agentArgs : " + agentArgs);
inst.addTransformer(new DefineTransformer(), true);
}
​
static class DefineTransformer implements ClassFileTransformer {
static int counts=0;
@Override
public byte[] transform(
ClassLoader loader,
String className,
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer
) throws IllegalClassFormatException {
System.out.println("premain load Class:" + className);
System.out.println("filter "+(counts++)+" class");
return classfileBuffer;
}
}
}

打成jar包之後我們要注意META-INF目錄下的MSNIFEST.MF檔案,MANIFEST.MF檔案是 Java 歸檔檔案(如 JAR檔案)的一部分,用於描述歸檔檔案的後設資料資訊和配置。它通常位於歸檔檔案的根目錄下。

Java agent技術的注入利用與避坑點

Java agent技術的注入利用與避坑點

一些常見的屬性我們需要了解

  1. Manifest-Version: 描述了 MANIFEST.MF 檔案的版本。

  2. Created-By: 描述了建立該歸檔檔案的工具名稱和版本。

  3. Main-Class: 描述了可執行 JAR 檔案的入口類(Main類),當您執行 JAR

    檔案時,Java虛擬機器會自動尋找並執行該類中的main方法。

  4. Class-Path: 描述了歸檔檔案中包含的依賴項 JAR 檔案的路徑,以便 Java

    虛擬機器在執行時能夠找到並載入這些依賴項。

在構建和部署 Java 應用程式時,MANIFEST.MF檔案可以幫助指定各種後設資料資訊,使得應用程式可以更好地被管理和執行。例如,當您建立一個可執行的JAR 檔案時,透過指定 Main-Class 屬性,可以告訴 Java 虛擬機器該 JAR檔案的入口點是哪個類。

另外建立一個專案,寫一個主函式,內容隨意,配置虛擬機器選項。這裡-javaagent:後面跟上上面專案jar包的絕對路徑

Java agent技術的注入利用與避坑點

執行結果如圖:

Java agent技術的注入利用與避坑點

可以看到premain方法中的程式碼成功的執行在了Main函式之前。這種使用premain方法在Main函式前執行的也被成為靜態agent

【----幫助網安學習,以下所有學習資料免費領!加vx:dctintin,備註 “部落格園” 獲取!】

 ① 網安學習成長路徑思維導圖
 ② 60+網安經典常用工具包
 ③ 100+SRC漏洞分析報告
 ④ 150+網安攻防實戰技術電子書
 ⑤ 最權威CISSP 認證考試指南+題庫
 ⑥ 超1800頁CTF實戰技巧手冊
 ⑦ 最新網安大廠面試題合集(含答案)
 ⑧ APP客戶端安全檢測指南(安卓+IOS)

動態Agent使用

首先是被代理部分(單獨的專案)

package com.example;
​
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
​
public class AgentMain {
public static void agentmain(String agentArgs, Instrumentation
instrumentation) {
instrumentation.addTransformer(new MyTransformer(),true);
​
}
public static class MyTransformer implements ClassFileTransformer {
static int count = 0;
​
@Override
public byte[] transform(
ClassLoader loader,
String className,
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer) throws IllegalClassFormatException {
System.out.println("hello world");//這裡就是我們能看到的輸出。
return classfileBuffer;
}
}
}

接下來就是使用Maven打成jar包

預設情況下META-INFMANIFEST.MF檔案中有這些內容

Manifest-Version: 1.0

Created-By: Maven JAR Plugin 3.3.0

Build-Jdk-Spec: 11

但是這些是不夠的,我們需要指出被代理的類。

Manifest-Version: 1.0

Agent-Class: com.example.AgentMain

Can-Redefine-Classes: true

Can-Retransform-Classes: true

  1. Agent-Class:指定了代理的入口類。這個屬性告訴 Java

    虛擬機器代理應該從哪個類的 premainagentmain方法開始執行。premain 方法用於靜態代理(在 JVM啟動時載入),而 agentmain 方法用於動態代理(在 JVM執行時載入)。代理的入口類必須包含其中一個方法。

  2. Can-Redefine-Classes:指定了代理是否可以重新定義類。如果設定為

    true,代理將允許重新定義已經載入的類,這意味著你可以修改已經載入的類的位元組碼。這對於某些代理操作,如熱程式碼替換,非常有用。

  3. Can-Retransform-Classes:指定了代理是否可以重新轉換類。如果設定為

    true,代理將允許重新轉換已經載入的類,這意味著你可以多次修改已經載入的類的位元組碼。這對於一些特定的代理操作也是非常有用的,如AOP(面向切面程式設計)。

因為是動態載入所以我們不需要在虛擬機器啟動選項中指定jar包的路徑。

接下來寫主程式的測試類

package org.example;
​
import com.sun.tools.attach.VirtualMachine;
​
import java.io.File;
import java.lang.management.ManagementFactory;
​
public class TestMain {
public static void main(String[] args) {
String agentJarPath =
"C:Users86186DesktopstudyJavauntitledtargetuntitled-1.0-SNAPSHOT.jar";
File agentJarFile = new File(agentJarPath);
if (!agentJarFile.exists()) {
System.err.println("Agent JAR file not found.");
return;
}
String name = ManagementFactory.getRuntimeMXBean().getName();
String pid = name.split("@")[0];
​
if (pid == null) {
System.err.println("Unable to find process ID.");
return;
}
String targetClassName = "AgentMain";
try {
VirtualMachine vm = VirtualMachine.attach(pid);
vm.loadAgent(agentJarPath,targetClassName);
vm.detach();
} catch (Exception e) {
e.printStackTrace();
}
}
​
}

這裡在獲取程序號的時候會因為版本的不同而出現錯誤,java9以下預設是正常的,java9以上會出現報錯,我們需要在虛擬機器啟動引數中加上-Djdk.attach.allowAttachSelf=true。

Java agent技術的注入利用與避坑點

執行結果:

Java agent技術的注入利用與避坑點

為什麼結果中有多個helloworld

這裡有講一下為什麼我們在程式碼中之用了一次sout,但是在結果中卻出現了多個helloworld。

MyTransformer類中的transform方法中的輸出語句只會在類被載入時執行一次,但是它會對每個類檔案呼叫一次。由於一個類可能會由多個ClassLoader載入,或者同一個ClassLoader可能會載入多次,因此會導致多次輸出。

這種情況通常在Java應用程式中使用了多個ClassLoader時發生,例如Web應用程式中的熱部署或者OSGi環境中。每次類被載入,transform方法都會被呼叫一次,因此會看到多次輸出。

我們可以修改一下程式碼做測試,這裡我在每個helloworld後新增了被載入類的名字

Java agent技術的注入利用與避坑點

修改後的輸出結果:

Java agent技術的注入利用與避坑點

實戰示例:修改目標虛擬機器中執行的程式

第一步

首先我們寫出我們正在執行的程式:迴圈列印helloworld。

package org.example;
​
import static java.lang.Thread.sleep;
​
public class Main {
public static void main(String[] args) throws InterruptedException {
while(true) {
hello();
sleep(1500);
}
}
public static void hello(){
System.out.println("Hello World!");
}
}

Java agent技術的注入利用與避坑點

第二步

準備我們的agentmain和ClassFileTransformer實現類。

package com.example;
​
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
import java.security.ProtectionDomain;
​
public class AgentMain {
public static void agentmain(String agentArgs, Instrumentation
instrumentation) throws UnmodifiableClassException {
Class [] classes = instrumentation.getAllLoadedClasses();
​
//獲取目標JVM載入的全部類
for(Class cls : classes){
​
if (cls.getName().equals("org.example.Main")){
​
instrumentation.addTransformer(new HackTransform(),true);
instrumentation.retransformClasses(cls);
}
// System.out.println(cls.getName());
}
​
}
​
}
​
package com.example;
​
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
​
import java.io.IOException;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
​
public class HackTransform implements ClassFileTransformer {
​
@Override
public byte[] transform(ClassLoader loader, String className,
Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
byte[] classfileBuffer) throws IllegalClassFormatException {
if (className.equals("org/example/Main")) {
try {
System.out.println(className);
​
ClassPool classPool = ClassPool.getDefault();
​
​
if (classBeingRedefined != null) {
ClassClassPath ccp = new ClassClassPath(classBeingRedefined);
classPool.insertClassPath(ccp);
}
​
CtClass ctClass = classPool.get("org.example.Main");
System.out.println(ctClass);
​
​
CtMethod ctMethod = ctClass.getDeclaredMethod("hello");
​
//設定方法體
String body = "{System.out.println("[+]Hacker!!");}";
ctMethod.setBody(body);
ctClass.defrost();
​
return ctClass.toBytecode();
​
} catch (Exception e) {
e.printStackTrace();
}
}
​
return null;
}
}

第三步

把第二步中的兩個類打成jar包。並修改其中MANIFEST.MF中的內容。

Java agent技術的注入利用與避坑點

MANIFEST.MF中的內容

Manifest-Version: 1.0Agent-Class:com.example.AgentMainCan-Redefine-Classes: trueCan-Retransform-Classes:true

第四步

寫我們的注入程式碼

package org.example;
​
import com.sun.tools.attach.*;
​
import java.io.IOException;
import java.util.List;
​
public class inject {
​
public static void main(String[] args) throws IOException,
AttachNotSupportedException, AgentLoadException,
AgentInitializationException {
//呼叫VirtualMachine.list()獲取正在執行的JVM列表
List<VirtualMachineDescriptor> list = VirtualMachine.list();
for (VirtualMachineDescriptor vmd : list) {
System.out.println(vmd.displayName());
​
if (vmd.displayName().equals("org.example.Main")) {
​
//連線指定JVM
VirtualMachine virtualMachine = VirtualMachine.attach(vmd.id());
String agentJarPath =
"C:Users86186DesktopstudyJavauntitledtargetuntitled-1.0-SNAPSHOT.jar";
//載入Agent
virtualMachine.loadAgent(agentJarPath,"com.example.AgentMain");
//斷開JVM連線
virtualMachine.detach();
}
​
}
​
}
}

第五步

執行即可(先執行主java程式,後執行注入程式)

Java agent技術的注入利用與避坑點

Java agent技術的注入利用與避坑點

Java agent技術的注入利用與避坑點

更多網安技能的線上實操練習,請點選這裡>>

相關文章