從零開始的Java RASP實現(二)



最近面試有點多,停更了兩周,接着 上一篇繼續寫java RASP的實現過程。

2 RASP-demo

通過前面的例子已經可以看到,通過Instrumentation對象添加Transformer,在transform方法中可以做到對加載的類進行動態修改,如果transform方法可以獲取到所有系統類的加載,豈不是就可以有針對性的對有風險的類進行修改,在其危險方法執行前后獲取參數加以判斷,從而實現RASP。但事情不是那么美好的

以前面javaagent使用流程舉例,對主要方法進一些修改,看看效果:

package com.bitterz;

import java.io.IOException;
import java.lang.Runtime;
import java.lang.String;

public class Main {
    public static class A{
        public void t(){System.out.println("Main$A.t()");}
    }

    public static void main(String[] args) throws InterruptedException, IOException {
        System.out.println("-------Main.main() start-------");
        Runtime.getRuntime().exec("calc");
        String a = "a";
        System.out.println(a);
        A a1 = new A();
        a1.t();
        System.out.println("-------Main.main() end-------");
    }
}
package com.bitterz;

import java.io.IOException;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;


public class PreMain {
        public static void premain(String agentArgs, Instrumentation inst) throws IOException {
            System.out.println("++++++++Premain start++++++++");
            System.out.println(ClassLoader.getSystemClassLoader().toString());  // 查看當前代理類是被哪個類加載器加載的
            inst.addTransformer(new DefineTransformer(), true);
            System.out.println("++++++++Premain end++++++++");
        }

    public static class DefineTransformer implements ClassFileTransformer {
        @Override  // 添加override會讓transform只接收到appClassLoader加載的類,去掉就可以接收重要的系統類
        public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
            System.out.println(className.toString() + "    " + loader.toString());  // 類名 和 類加載器
            System.out.println("");
            return classfileBuffer;
        }
    }
}

輸出結果如下

這里注意兩個細節

  • premain和main都是被同一個類加載器鎖加載的
  • main方法要new一個A類對象時,會先進入transform函數,且類加載器和前面一樣,然后再執行A類中的方法

除了這兩個細節外,我們應該注意到,像String、Runtime這些類,也被調用了,transform函數卻獲取不到!這里就跟類加載機制有關了!

2.1 類加載機制

雙親委派

首先要理解一下類加載的雙親委派機制,每一個類加載器創建時都要指定一個父加載器,當類加載器A要加載B類時,會先由其父加載器對B類進行加載,如果父加載器無法加載,則由它自己進行加載。看一下ClassLoader的源代碼就很好理解了

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    synchronized (getClassLoadingLock(name)) {
        // First, check if the class has already been loaded
        Class<?> c = findLoadedClass(name);  // 查看是不是本身已經加載過的類
        if (c == null) {
            long t0 = System.nanoTime();
            try {  // 父類先嘗試加載
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }

            if (c == null) {  // 父類加載失敗,自己加載
                // If still not found, then invoke findClass in order
                // to find the class.
                long t1 = System.nanoTime();
                c = findClass(name);

                // this is the defining class loader; record the stats
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

結合源代碼也就很好理解。特別需要指出的是Class<?> c = findLoadedClass(name);這一行可以看出,每個ClassLoader都記錄着一個自己加載過的類。

BootStrap ClassLoader

jvm中所有的類都是由類加載器加載的,那類加載器又是誰加載的呢?首先,jvm啟動之后,會執行一段機器碼,也就是C寫好的程序,這段程序被成為Bootstrap ClassLoader,注意它不是可以訪問的java對象。由它去加載系統類,以及擴展類加載器(extClassLoader)和應用程序類加載器(AppClassLoader)。

  • 其中extClassLoader負責加載<JAVA_HOME>/lib/ext目錄下或者由系統變量-Djava.ext.dir指定位路徑中的類庫
  • AppClassLoader負責加載系統類路徑java -classpath-D java.class.path 指定路徑下的類庫,也就是我們經常用到的classpath路徑

他們的關系如下:

  • 啟動類加載器,由C++實現,沒有父類。

  • 拓展類加載器(ExtClassLoader),由Java語言實現,父類加載器為null

  • 系統類加載器(AppClassLoader),由Java語言實現,父類加載器為ExtClassLoader

另外需要指出的是,一個Java類,由它的全限定名稱和加載它的classloader兩者確定。

說了這么多,回到2.1章節最開始的問題,agentmain和main都是同一個appClassLoader加載的,並且我們寫好的各種類都是AppClassLoader加載的,那BootstrapClassLoader和extClassLoader加載的類調用我們寫好的代理方法,這些類加載器向上委派尋找類時,擴展類加載器和引導類加載器都沒有加過,直接違背雙親委派原則!舉個例子,因為我們可以在transform函數里面獲取到類字節碼,並加以修改,如果我們在系統類方法前面插了代理方法,由於這些系統類是被Bootstrap ClassLoader加載的,當BootstrapClassLoader檢查這些代理方法是否被加載時,直接就報錯了,因為代理類是appClassLoader加載的(見2.1 類加載機制這個章節上面的圖),要解決這個問題,我們就應該想辦法把代理類通過BootstrapClassLoader進行加載,從百度的OpenRASP可以學到解決方案:

// localJarPath為代理jar包的絕對路徑
inst.appendToBootstrapClassLoaderSearch(new JarFile(localJarPath))

通過appendToBootstrapClassLoaderSearch方法,可以把一個jar包放到Bootstrap ClassLoader的搜索路徑,也就是說,當Bootstrap ClassLoader檢查自身加載過的類,發現沒有找到目標類時,會在指定的jar文件中搜索,從而避免前面提到的違背雙親委派問題。參考官方文檔翻譯文檔

2.2 Instrumentation介紹

為了方便用戶對JVM進行操作,JDK1.5之后引入了這個Instrumentation特性,通過Instrumentation的實例對象,可以對jvm進行一定的操作,例如修改字節碼、插樁等等。它的實現原理是JVMTI(JVM Tool Interface),也就是JVM向用戶提供的操作jvm的接口。JVMTI是事件驅動的,當發生一定的處理邏輯時,才會調用回調接口,而這些接口可以讓用戶擴展一些邏輯。例如前面的transform函數調用,就是JVMTI監聽到類加載,就會基於這個事件,回調instrumentation中的所有ClassTransformer.transform函數,進行類轉換(Class transform)。所以我們可以理解為獲得instrumentation對象,就可以實現對一個jvm的一定操作,獲取的這個對象的方法就是前文提到的javaagent和attach方法。

Instrumentation類中常用方法

void addTransformer(ClassFileTransformer transformer, boolean canRetransform)//注冊ClassFileTransformer實例,注冊多個會按照注冊順序進行調用。所有的類被加載完畢之后會調用ClassFileTransformer實例,相當於它們通過了redefineClasses方法進行重定義。布爾值參數canRetransform決定這里被重定義的類是否能夠通過retransformClasses方法進行回滾。

void addTransformer(ClassFileTransformer transformer)//相當於addTransformer(transformer, false),也就是通過ClassFileTransformer實例重定義的類不能進行回滾。

boolean removeTransformer(ClassFileTransformer transformer)//移除(反注冊)ClassFileTransformer實例。

void retransformClasses(Class<?>... classes)//已加載類進行重新轉換的方法,重新轉換的類會被回調到ClassFileTransformer的列表中進行處理。

void appendToBootstrapClassLoaderSearch(JarFile jarfile)//指定 JAR 文件,放到Bootstrap ClassLoader搜索路徑

void appendToSystemClassLoaderSearch(JarFile jarfile)//將某個jar加入到Classpath里供AppClassloard去加載。

Class[] getAllLoadedClasses()//返回 JVM 當前加載的所有類的數組

Class[] getInitiatedClasses(ClassLoader loader)//獲取所有已經被初始化過了的類。

boolean isModifiableClass(Class<?> theClass)//確定一個類是否可以被 retransformation 或 redefinition 修改

void redefineClasses(ClassDefinition... definitions)//重定義類,也就是對已經加載的類進行重定義,ClassDefinition類型的入參包括了對應的類型Class<?>對象和字節碼文件對應的字節數組。

Instrumentation觸發流程

摘自https://www.cnblogs.com/rickiyang/p/11368932.html

JVMTIAgent是一個利用JVMTI暴露出來的接口提供了代理啟動時加載(agent on load)、代理通過attach形式加載(agent on attach)和代理卸載(agent on unload)功能的動態庫。instrument agent可以理解為一類JVMTIAgent動態庫,別名是JPLISAgent(Java Programming Language Instrumentation Services Agent),也就是專門為java語言編寫的插樁服務提供支持的代理

啟動時加載instrument agent過程:

  1. 創建並初始化 JPLISAgent;
  2. 監聽 VMInit 事件,在 JVM 初始化完成之后做下面的事情:
    1. 創建 InstrumentationImpl 對象 ;
    2. 監聽 ClassFileLoadHook 事件 ;
    3. 調用 InstrumentationImpl 的loadClassAndCallPremain方法,在這個方法里會去調用 javaagent 中 MANIFEST.MF 里指定的Premain-Class 類的 premain 方法 ;
  3. 解析 javaagent 中 MANIFEST.MF 文件的參數,並根據這些參數來設置 JPLISAgent 里的一些內容。

運行時加載instrument agent過程:

通過 JVM 的attach機制來請求目標 JVM 加載對應的agent,過程大致如下:

  1. 創建並初始化JPLISAgent;
  2. 解析 javaagent 里 MANIFEST.MF 里的參數;
  3. 創建 InstrumentationImpl 對象;
  4. 監聽 ClassFileLoadHook 事件;
  5. 調用 InstrumentationImpl 的loadClassAndCallAgentmain方法,在這個方法里會去調用javaagent里 MANIFEST.MF 里指定的Agent-Class類的agentmain方法。

Instrumentation的局限性

摘自https://www.cnblogs.com/rickiyang/p/11368932.html

  1. premain和agentmain兩種方式修改字節碼的時機都是類文件加載之后,也就是說必須要帶有Class類型的參數,不能通過字節碼文件和自定義的類名重新定義一個本來不存在的類。
  2. 類的字節碼修改稱為類轉換(Class Transform),類轉換其實最終都回歸到類重定義Instrumentation#redefineClasses()方法,此方法有以下限制:
    1. 新類和老類的父類必須相同;
    2. 新類和老類實現的接口數也要相同,並且是相同的接口;
    3. 新類和老類訪問符必須一致。 新類和老類字段數和字段名要一致;
    4. 新類和老類新增或刪除的方法必須是private static/final修飾的;
    5. 可以修改方法體。

除了上面的方式,如果想要重新定義一個類,可以考慮基於類加載器隔離的方式:創建一個新的自定義類加載器去通過新的字節碼去定義一個全新的類,不過也存在只能通過反射調用該全新類的局限性。

另外由於JVM維護運行線程和邏輯的安全,禁止對運行時的類新加方法並重新定義該方法,只允許修改方法體中的邏輯,如果多次attach時,顯然就會出現重復插樁,造成重復警告等,影響業務線程。

2.3 javassist

前面提到修改字節碼以實現在系統類或其它重要類執行前后插入代碼,運行時防御惡意攻擊,基於javassist容易上手的特點,所以直接用javassist修改字節碼。javassist有幾個重要的類及其方法:

ClassPool

  • getDefault:返回一個ClassPool對象,ClassPool是單例模式,保存了當前運行環境中創建過的CtClass
  • get/getCtClass:根據類名獲取該類的CtClass對象,而后進一步操作CtClass對象

CtClass:對class文件的解析,將其描述為一個java對象

  • detach,將一個class從ClassPool中刪除,減小內存消耗

  • getDeclaredMethod(String arg), 獲取一個CtMethod對象

  • getDeclaredMethods,獲取所有的這個類中所有的方法,並返回CtMethod數組

  • getConstructor(String arg),獲取一個指定的構造方法,返回CtConstructor對象

  • getConstructors,獲取所有的構造方法,返回CtConstructor數組

  • toBytecode,將ctClass對象的字節碼轉換成字節數組,這個方法在rasp中非常需要

  • writeFile,將ctClass對象的修改保存到文件中。

CtMethod:對類中的方法的描述,可以通過這個對象,在一個方法前后添加代碼

  • insertBefore,很明顯,在給定的方法前插入一段代碼
  • insertAfter,也很明顯,在給定方法的所有return前,插入一段代碼,報錯會掠過這些代碼
  • insertAt,在指定位置插入代碼,一般不這么操作,改變系統類中方法的邏輯,可能會引發更多的問題
  • setBody,將方法的內容設置為指定的代碼,如果是abstrict修飾的方法,該修飾符將被移除。指定的代碼用$1,$2代表實際傳入的參數
ctMethod.setBody("{$1='a';if ($2==1) return 0;.....}")

問題來了:如何修改jvm中的字節碼

根據前面提到的這些方法,我們可以思考這樣一個問題,就算用了javassist提供的各種方法,給字節碼中添加了各類方法,但也只能保存到class文件中,而這個類jvm在運行時,我們修改class文件並不會影響jvm中的字節碼,想要執行插入的代碼,只能重新運行class文件,能不能動態修改jvm中的字節碼,改變程序運行結果呢??

方法1,通過ClassLoader#defineClass方法覆蓋jvm中的字節碼

package com.bitterz.assist;
import javassist.*;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.ProtectionDomain;

public class Test {
    private final String name="laowang";

    @Override
    public boolean equals(Object o){
        System.out.println(this.name);
        return false;
    }

    public static void main(String[] args) throws NotFoundException, CannotCompileException, IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
        // 先測試一次equals方法的輸出
        Test test1 = new Test();
        test1.equals("a");
        System.out.println("+++++++++++++++++分割+++++++++++++++++");
        // 獲取當前class文件位置
        Process chdir = Runtime.getRuntime().exec("cmd /c chdir");
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(chdir.getInputStream(), "gbk"));
        String baseDir = bufferedReader.readLine();
        String classDir = baseDir + "/target/classes".replace("/", "\\");
        // 添加class路徑並找到Test類的字節碼
        ClassPool classPool = new ClassPool(true);
        classPool.appendClassPath(classDir);
        CtClass ctClass = classPool.get("com.bitterz.assist.Test");
        CtMethod[] methods = ctClass.getMethods();
        String src = "System.out.println("+ "\" insert by javassist \"" +");";

        // 找到對應的method,並在前后插入代碼
        for (CtMethod method : methods) {
            if (method.getName().contains("equals")){
                method.insertBefore(src);
            }
        }
        // 獲取字節碼
        byte[] bytes = ctClass.toBytecode();

        // 反射調用defineClass方法,將字節碼覆蓋到jvm中
        ClassLoader classLoader = new ClassLoader() {
        };

        String name = Test.class.getName();
        Method defineClass = Class.forName(ClassLoader.class.getName()).getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class, ProtectionDomain.class);
        defineClass.setAccessible(true);
        Class<?> test = (Class<?>) defineClass.invoke(classLoader,  name, bytes, 0, bytes.length, null);
        // 這里取巧用了equals方法,是因為所有類都繼承自Object類,因此能過夠編譯通過,前面重寫了equals方法,所以會調用到重寫的equals方法中
        test.newInstance().equals("a");
    }
}

輸出結果如下:

可見,通過javassist修改字節碼后,利用ClassLoader#defineClass方法可以覆蓋jvm中的字節碼,實現對jvm中已加載類的動態修改。

  • 不過這種方式受限於類加載機制和jvm對重復類定義的檢查,每個類最好用新的類加載器去加載,否則在defineClass時會出現報錯
  • 並且,受限於jvm的安全機制,無法修改系統類,例如java.lang下的類

方法2,通過ClassFileTransformer#transform方法修改字節碼

在前一篇筆記中,記錄了通過javaagent機制可以對jvm加載過的類進行類轉換(Class Transform),也就是在ClassFileTransformer#transform中返回新的字節碼,會覆蓋原有的字節碼,下面來試試對java.lang.ProcessBuilder的字節碼進行修改,實現hook

首先時需要執行的main方法

// 項目名為test,跟后面的agent不在一個項目
package com.bitterz;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.String;

public class Main {
    public static void main(String[] args) throws InterruptedException, IOException {
        System.out.println("main start!");
        ProcessBuilder processBuilder = new ProcessBuilder();
        processBuilder.command("cmd", "/c", "chdir");
        Process process = processBuilder.start();
        InputStream inputStream =  process.getInputStream();
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "gbk"));
        System.out.println(bufferedReader.readLine());
    }
}

test項目對應的pom.xml文件,可以直接打包成jar,並且指定了啟動類
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.bitterz</groupId>
    <artifactId>test</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <configuration>
                    <archive>
                        <manifestEntries>
                            <Main-Class>com.bitterz.Main</Main-Class>
                        </manifestEntries>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

接下來是agent項目

// premain,這個類會在前面那個項目的main方法啟動前執行
package com.bitterz;

import com.bitterz.hook.ProcessBuilderHook;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;

public class PreMain {
    public static void premain(String agentArgs, Instrumentation inst) throws IOException, UnmodifiableClassException {
        // 先測試一次使用ProcessBuilder獲取當前路徑
        System.out.println("\n");
        ProcessBuilder processBuilder = new ProcessBuilder();
        processBuilder.command("cmd", "/c", "chdir");
        Process process = processBuilder.start();
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream(), "gbk"));
        System.out.println(bufferedReader.readLine());

        // 添加ClassFileTransformer類
        ProcessBuilderHook processBuilderHook = new ProcessBuilderHook(inst);
        inst.addTransformer(processBuilderHook, true);

        // 獲取所有jvm中加載過的類
        Class[] allLoadedClasses = inst.getAllLoadedClasses();
        for (Class aClass : allLoadedClasses) {
            if (inst.isModifiableClass(aClass) && !aClass.getName().startsWith("java.lang.invoke.LambdaForm")){
                // 調用instrumentation中所有的ClassFileTransformer#transform方法,實現類字節碼修改
                inst.retransformClasses(new Class[]{aClass});
            }
        }
        System.out.println("++++++++++++++++++hook finished++++++++++++++++++\n");
    }
}
// 在transform中執行類轉換的邏輯,插入過濾代碼
package com.bitterz.hook;

import java.io.IOException;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
import javassist.*;

public class ProcessBuilderHook implements ClassFileTransformer {
    private Instrumentation inst;
    private ClassPool classPool;
    public ProcessBuilderHook(Instrumentation inst){
        this.inst = inst;
        this.classPool = new ClassPool(true);
    }

    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) {
        if (className.equals("java/lang/ProcessBuilder")){
            CtClass ctClass = null;
            try {
                // 找到ProcessBuilder對應的字節碼
                ctClass = this.classPool.get("java.lang.ProcessBuilder");
                // 獲取所有method
                CtMethod[] methods = ctClass.getMethods();
                // $0代表this,這里this = 用戶創建的ProcessBuilder實例對象
                String src = "if ($0.command.get(0).equals(\"cmd\"))" +
                        "{System.out.println(\"危險!\");" +
                        "System.out.println();"+
                        "return null;}";
                for (CtMethod method : methods) {
                    // 找到start方法,並插入攔截代碼
                    if (method.getName().equals("start")){
                        method.insertBefore(src);
                        break;
                    }
                }
                classfileBuffer = ctClass.toBytecode();
            }
            catch (NotFoundException | CannotCompileException | IOException e) {
                e.printStackTrace();
            }
            finally {
                if (ctClass != null){
                    ctClass.detach();
                }
            }
        }
        return classfileBuffer;
    }
}

然后是agent項目對應的pom.xml文件,因為要用javassist包,所以把依賴也打包進去了

premain對應的pom.xml,可以直接用maven打包成jar,並且自動填寫了MANIFEST.MF中需要的字段
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.bitterz</groupId>
    <artifactId>java-agent</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>javassist</groupId>
            <artifactId>javassist</artifactId>
            <version>3.12.0.GA</version>
        </dependency>
    </dependencies>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
            </plugin>

            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <archive>
                        <manifestEntries>
                            <Premain-Class>com.bitterz.PreMain</Premain-Class>
                            <Can-Redefine-Classes>true</Can-Redefine-Classes>
                            <Can-Retransform-Classes>true</Can-Retransform-Classes>
                        </manifestEntries>
                    </archive>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>
                <executions>
                    <execution>
                        <id>make-assembly</id>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

做好前面的准備工作之后,執行一下,看看效果

  • 很明顯,agent項目中使用ProcessBuilder執行系統命令時,因為還沒有插入檢測邏輯,所以沒有被攔截,成果返回當前路徑
  • hook完成后,main方法啟動,執行start后,觸發插入的檢測代碼,並返回一個null
  • Main.java的15行報錯,出現了null值,看看15行是啥InputStream inputStream = process.getInputStream();,很明顯了,hook成功,返回一個null,所以15行這里會報錯

2.4 總結

到此java RASP的雛形就出來了,通過javaagent機制,在項目啟動前,添加類轉換方法,利用javassist、ASM這些修改字節碼的工具對關鍵系統類,在類轉換方法中修改字節碼,並返回給jvm,這樣可以繞過defineClass對系統類的保護,完成對系統類和基礎類的字節碼修改,實現對這些類的hook。

針對不同類,還需要進一步實現不同的方法的hook以及檢測邏輯,考慮到通用性,或許可以用json、Yaml這類規則,以便於在php、python、go等語言中能夠通用規則。或者使用OpenRASP的思路,java負責hook方法,把參數交給js來實現檢測邏輯,同時也能兼顧php等語言的規則通用。或許,使用機器學習或深度學習模型,也能對參數進行檢測,這就涉及到很廣闊的思路了。

另外就是各種心跳、雲控的設計了,不是RASP的重點,所以沒有進一步實現,java RASP的研究就到這里了(好像有點淺嘗則止呢?不過結合OpenRASP的源碼,很容易就能理解java RASP的實現思路了)

參考

https://www.cnblogs.com/rickiyang/p/11368932.html

https://www.cnblogs.com/kendoziyu/p/maven-auto-build-javaagent-jar.html

Java底層防護 - OpenRASP核心源碼淺析

Java RASP淺析——以百度OpenRASP為例

淺談 RASP

appendToBootstrapClassLoaderSearch方法說明

Instrumentation中文文檔

Instrumentation官方文檔

JVMTI解讀

深入理解Java類加載器(ClassLoader)


免責聲明!

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



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