Java中動態載入位元組碼的那些方法
阿新 • • 發佈:2021-08-11
持續補充 ...
URLCLassLoader
ClassLoader#loader(URL[])遠端載入class
URLCLassLoader
實際上是我們平時預設使用的AppClassLoader
的父類,所以,我們解釋URLClassLoader
的工作過程實際上就是在解釋預設的Java類載入器的工作流程。
java 對路徑的處理:
- URL未以斜槓 / 結尾,則認為是一個JAR檔案,使用 JarLoader 來尋找類,即為在Jar包中尋 找.class檔案
- URL以斜槓 / 結尾,且協議名是 file ,則使用 FileLoader 來尋找類,即為在本地檔案系統中尋 找.class檔案
- URL以斜槓 / 結尾,且協議名不是 file ,則使用最基礎的 Loader 來尋找類
也就是協議不是file且以 / 結尾,會使用Loader尋找類,最常見的是http協議。可以通過這種方法直接載入遠端的class檔案,所以如果我們控制了目標Java ClassLoader的基礎路徑為一個http伺服器,即可RCE
CLassLoader#definClass 直接載入位元組碼
無論載入遠端class還是本地class或者jar檔案,呼叫過程都是下面三個方法:
- loadClass 的作用是從已載入的類快取、父載入器等位置尋找類(這裡實際上是雙親委派機 制),在前面沒有找到的情況下,執行 findClass
- findClass 的作用是根據基礎URL指定的方式來載入類的位元組碼,就像上一節中說到的,可能會在 本地檔案系統、jar包或遠端http伺服器上讀取位元組碼,然後交給
defineClass
- defineClass 的作用是處理前面傳入的位元組碼,將其處理成真正的Java類
核心在defineClass,決定了如何將一段位元組流轉變成一個Java類,Java預設的ClassLoader#defineClass
是一個native方法,邏輯在JVM的C語言程式碼中。
我們無法直接外部呼叫這些方法來載入位元組碼,但是有一些庫中包含了部分程式碼完成了這個過程,我們可以利用這些庫達到目的。
載入方式一: TemplatesImpl
非常常用的Java反序列化利用鏈組成部分,poc:
public static void main(String[] args) throws Exception {
BASE64Decoder base64Decoder = new BASE64Decoder();
byte[] code = base64Decoder.decodeBuffer("");
TemplatesImpl obj = new TemplatesImpl();
Field f1 = obj.getClass().getDeclaredField("__bytecodes");
f1.setAccessible(true);
f1.set(obj, new byte[][]{code});
Field f2 = obj.getClass().getDeclaredField("__name");
f2.setAccessible(true);
f2.set(obj, "HelloTemplatesImpl");
Field f3 = obj.getClass().getDeclaredField("__tfactory");
f3.setAccessible(true);
f3.set(obj, new TransformerFactoryImpl());
obj.newTransformer();
}
注意:
設定了三個屬性: _bytecodes 、 _name 和 _tfactory 。
- _bytecodes 是由位元組碼組成的陣列;
- _name 可以是任意字串,只要不為null即可;
- _tfactory 需要是一個 TransformerFactoryImpl 物件,因為
emplatesImpl#defineTransletClasses()
方法裡有呼叫到tfactory.getExternalExtensionsMap()
,如果是null會出錯。
另外,TemplatesImpl 中對載入的位元組碼是有一定要求的:這個位元組碼對應的類必須是 com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet
的子類。因此需要構造一個特殊的類。
載入方式二: BCELClassLoader
BCEL Classloader去哪了
在java 8u251前可用。
使用的是com.sun.org.apache.bcel.internal.util.ClassLoader;
// 原生位元組碼轉BCEL
JavaClass cls = Repository.lookupClass(exp.class);
String code = Utility.encode(cls.getBytes(), true);
System.out.println(code);
// 載入
new ClassLoader().loadClass("exp").newInstance();
待補充...