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

什麼是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文件)的一部分,用於描述歸檔文件的元數據信息和配置。它通常位於歸檔文件的根目錄下。

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

  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包的絕對路徑

運行結果如圖:

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

【---- 幫助網安學習,以下所有學習資料免費領!領取資料加 we~@x: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

    虛擬機代理應該從哪個類的 premain  agentmain方法開始執行。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。

運行結果:

爲什麼結果中有多個helloworld

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

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

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

我們可以修改一下代碼做測試,這裏我在每個helloworld後添加了被加載類的名字

修改後的輸出結果:

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

第一步

首先我們寫出我們正在執行的程序:循環打印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!");
}
}

第二步

準備我們的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中的內容。

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程序,後運行注入程序)

  

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章