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

bitterz發表於2021-08-17


最近面試有點多,停更了兩週,接著上一篇繼續寫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)

相關文章