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