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


0 從零開始的Java RASP實現(一)

本科畢設做過Python的RASP之后,對這項技術很有興趣,當時OpenRASP開始出現,並且Java的實現非常接近真正的運行時防御的概念。一直沒有時間和足夠的動力學習Java,最近一口氣學了不少Java相關的東西,准備從反序列化和RASP兩個方向繼續深入學一下Java。這邊筆記主要也是記錄整個學習過程,目標是仿照OpenRASP的java實現,完成一個Java的RASP。

1 javaagent

1.1 Main方法啟動前

概念介紹:

javaagent是java命令提供的一個參數,這個參數可以指定一個jar包,在真正的程序沒有運行之前先運行指定的jar包。並且對jar包有兩個要求:

  • jar包的MANIFEST.MF文件必須指定Premain-Class
  • Premain-Class指定的類必須實現premain()方法。

這個premain方法會在java命令行指定的main函數之前運行。

在java命令參數種,還可以看到其它參數,例如

-agentlib:<libname>[=<選項>]
                  加載本機代理庫 <libname>, 例如 -agentlib:hprof
                  另請參閱 -agentlib:jdwp=help 和 -agentlib:hprof=help
-agentpath:<pathname>[=<選項>]
                  按完整路徑名加載本機代理庫
-javaagent:<jarpath>[=<選項>]
                  加載 Java 編程語言代理, 請參閱 java.lang.instrument

javaagent可以指定很多個,jvm會依次執行不同的jar。前面提到的premain方法有兩種定義方式

public static void premain(String agentArgs, Instrumentation inst)

public static void premain(String agentArgs)

根據sun.instrument.InstrumentationImpl 的源代碼,可以知道,會優先調用第一種寫法。這種方法可以在JDK1.5及之后的版本使用

如何使用

使用javaagent需要幾個步驟:

  • 定義一個MANIFEST.MF文件,必須包含Premain-Class選項,也需要加入Can-Redefine-Classes和Can-Retransform-Classes選項,后面兩個選項看名字就知道意思
  • 創建Premain-Class指定的類,類中包含premain方法,該方法可以進一步加載RASP實現原理
  • 將MANIFEST.MF和寫好的各種類打包成jar
  • 啟動java時,添加-javaagent:xx.jar,即可讓java先自動執行寫好的premain方法

在premain方法執行時,獲取的Instrumentation對象還會加載大部分類,包括后面main方法執行時需要加載的各種類,但是抓不到系統類。也就是說,在這個位置在main方法執行前,就可以攔截或者重寫類,結合ASM、javassist、cglib方式就可以實現對類的改寫或者插樁。

創建agent

現在來實現一下,目錄結構如下

-java-agent
----src
----|----main
----|----------java
----|--------------com
----|-------------------bitterz
----|----------------------PreMain
----|pom.xml

pom.xml使用idea創建maven項目自帶的pom.xml即可,然后加入如下配置

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-jar-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>
            </configuration>
        </plugin>
    </plugins>
</build>

修改里面的premain即可,這樣配置之后,直接使用idea的maven->package就可以打包成一個可以使用的agent.jar。

com.bitterz.PreMain如下:

package com.bitterz;

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){
            System.out.println("agentArgs:" + agentArgs);
            inst.addTransformer(new DefineTransformer(), true);
        }

        static class DefineTransformer implements ClassFileTransformer{
            @Override
            public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
                System.out.println("premain load Class: " + className);  // 注意這里的輸出
                return classfileBuffer;
            }
        }
}

創建main

然后再創建一個要執行的main

package com.bitterz;

public class Main {
    public static void main(String[] args) {
        System.out.println("Main.main() in test project");
    }
}

同樣修改pom.xml配置,只需要再<manifestEntries>標簽下添加一個<Main-Class>即可用idea的maven打包成一個jar,並且指定了main類。

兩個項目用maven打包后,在命令行執行一下

可以看到premain先輸出了加載的各種類,包括com.bitterz.Main,然后才是真正的main執行,最后結束時,premain還加載了一些結束時需要的類。到此,在啟動main方法前,實現對后續類的加載或進一步修改操作,已經有了雛形

1.2 JVM啟動后

前面的方法需要在main函數啟動前,執行agent,但有些時候,jvm已經啟動了,而且服務不能輕易暫停,但這個時候還是想對jvm中的類做一些修改應該怎么辦呢?

attach機制

這就要引入attch機制了,jdk1.6之后在Instrumentation中添加了一種agentmain的代理方法,可以在main函數執行之后再運行。和premain函數一樣,開發者可以編寫一個包含agentmain函數的Java類,它也有兩種寫法:

public static void agentmain (String agentArgs, Instrumentation inst)

public static void agentmain (String agentArgs)

同樣的,帶有Instrumentation的方法會被優先調用,開發者必須再MANIFEST.MF文件中設置Agent-Class來指定包含agentmain函數的類。

這種attach機制的具體實現在com.sun.tools.attach中,有如下兩個類:

  • VirtualMachine 字面意義表示一個Java 虛擬機,也就是程序需要監控的目標虛擬機,提供了獲取系統信息(比如獲取內存dump、線程dump,類信息統計(比如已加載的類以及實例個數等), loadAgent,Attach 和 Detach (Attach 動作的相反行為,從 JVM 上面解除一個代理)等方法,可以實現的功能可以說非常之強大 。該類允許我們通過給attach方法傳入一個jvm的pid(進程id),遠程連接到jvm上

    代理類注入操作只是它眾多功能中的一個,通過loadAgent方法向jvm注冊一個代理程序agent,在該agent的代理程序中會得到一個Instrumentation實例,該實例可以 在class加載前改變class的字節碼,也可以在class加載后重新加載。在調用Instrumentation實例的方法時,這些方法會使用ClassFileTransformer接口中提供的方法進行處理。

  • VirtualMachineDescriptor 則是一個描述虛擬機的容器類,配合 VirtualMachine 類完成各種功能

具體實現過程:通過VirtualMachine類的attach(pid)方法,便可以attach到一個運行中的java進程上,之后便可以通過loadAgent(agentJarPath)來將agent的jar包注入到對應的進程,然后對應的進程會調用agentmain方法。

從jdk根目錄的lib/tools.jar源碼一路追蹤了一下VirtualMachine.attach方法,發現傳入一個pid之后,在Windows下會通過WindowsVirtualMachine這個類的構造函數,調用native方法,實現對jvm進程的attach

啟動一個長時間運行的jvm

底層方法還得靠c/c++來實現(滑稽),了解這個機制之后,回到前面的agentmain的實現流程。首先啟動一個一直運行的jvm

package com.bitterz;

public class Main {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("Main.main() in test project start!!");
        Thread.sleep(300000000);
        System.out.println("Main.main() in test project end!!");
    }
}

打包一個agentmain代理jar

啟動之后,把再寫一個agentmain,並且還要寫好MANIFEST.MF文件配置,代碼和pom.xml如下:

package com.bitterz;

import java.lang.instrument.Instrumentation;

public class AgentMain {
    public static void agentmain(String agentArgs, Instrumentation instrumentation) {
        System.out.println("agentmain start!");
        System.out.println(instrumentation.toString());
    }
}
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-jar-plugin</artifactId>
            <configuration>
                <archive>
                    <manifestEntries>
                        <Agent-Class>com.bitterz.AgentMain</Agent-Class>
                        <Can-Redefine-Classes>true</Can-Redefine-Classes>
                        <Can-Retransform-Classes>true</Can-Retransform-Classes>
                    </manifestEntries>
                </archive>
            </configuration>
        </plugin>
    </plugins>
</build>

使用maven package打包成一個jar文件即可

運用attach

再開啟動另一個java程序,使用進程號attach到前面一直運行的jvm,並使用loadAgent給這個jvm添加代理jar:

package com.bitterz.attach;

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;

public class AttachTest {
    public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException {
        VirtualMachine attach = VirtualMachine.attach("12244");  // 命令行找到這個jvm的進程號
        attach.loadAgent("C:\\Users\\helloworld\\Desktop\\java learn\\java-attach\\target\\java-attach-1.0-SNAPSHOT.jar");
        attach.detach();
    }
}

運行這個java文件,會看到前面一直sleep運行的jvm會輸出agentmain里面給定的輸出!

這里也就說明,通過attach機制,我們可以對指定運行中的jvm添加agent,而在premain方法中獲取到Instrumentation對象,通過對Instrumentation對象添加transformer類,可以實現類轉換(Class Transform),也就是在transform函數中結合修改字節碼的方法(ASM、Javassist、cglib等)可以進一步實現RASP!

后續的文章將會寫一寫如何實現這些對指定類方法的Hook,以及如何運行時對底層函數的參數進行過濾。

參考

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

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

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


免責聲明!

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



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