java反射
0x00 java反射簡介
反射機制是在運行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個對象,都能夠調用它的任意一個方法和屬性,這種動態獲取的信息以及動態調用對象的方法的功能稱為java語言的反射機制
將類的各個部分封裝為其他對象
反射是框架設計的靈魂
Java反射不但可以獲取類所有的成員變量名稱,還可以無視權限修飾符實現修改對應的值
Java 反射主要提供以下功能:
- 在運行時判斷任意一個對象所屬的類;
- 在運行時構造任意一個類的對象;
- 在運行時判斷任意一個類所具有的成員變量和方法(通過反射甚至可以調用private方法);
- 在運行時調用任意一個對象的方法
反射的好處:
- 可以在程序運行過程中,操作這些對象
- 可以解耦,提高程序的可擴展性
0x01 獲取class對象
java反射操作的是java.lang.Class
對象,有一下方法獲取一個類的Class對象:
類名.class
Class.forName("com.demo.classloader.TestClass")
ClassLoader.loadClass("com.demo.classLoader.TestClass")
反射調用內部類的時候需要使用$
來代替.
,如com.org.test
類有一個叫做Hello
的內部類,則在調用它的時候要寫成:com.org.test$Hello
。
0x02 反射java.lang.Runtime
java.lang.Runtime
中有一個exec
方法可以執行本地命令,在很多payload中都能看見反射Runtime類來執行本地命令
不使用反射執行本地命令代碼片段:
// 輸出命令執行結果
System.out.println(IOUtils.toString(Runtime.getRuntime().exec("whoami").getInputStream(), "UTF-8"));
反射Runtime執行本地命令代碼片段:
這里的
IOUtils
是org.apache.commons.io.IOUtils
包下的,需要使用maven導入
import org.apache.commons.io.IOUtils;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class RunTimeTest {
public static void main(String[] args) throws IOException {
test();
}
public static void test() throws IOException {
try {
// 獲取Runtime類對象
Class runtimeClass = Class.forName("java.lang.Runtime");
// 獲取構造方法
Constructor constructor = runtimeClass.getDeclaredConstructor();
constructor.setAccessible(true);
// 創建Runtime類示例
Object runtimeInstance = constructor.newInstance();
// 獲取Runtime的exec(String cmd)方法
Method runtimeMethod = runtimeClass.getMethod("exec",String.class);
// 調用exec方法,等價於 rt.exec(cmd);
Process process = (Process) runtimeMethod.invoke(runtimeInstance,"whoami");
// 獲取命令執行結果
InputStream in = process.getInputStream();
// 輸出命令執行結果
System.out.println(IOUtils.toString(in,"UTF-8"));
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e){
e.printStackTrace();
}
}
public static void Method2(){
try {
// 獲取對象
Class cls = Class.forName("java.lang.Runtime");
// 獲取構造方法
Constructor constructor = cls.getDeclaredConstructor();
constructor.setAccessible(true);
// 實例化對象
Object ob = constructor.newInstance();
Method mt = cls.getMethod("exec", String.class);
mt.invoke(ob,"calc");
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e){
e.printStackTrace();
}
}
}
反射調用Runtime實現本地命令執行的流程如下:
- 反射獲取
Runtime
類對象(Class.forName("java.lang.Runtime")
)。 - 使用
Runtime
類的Class對象獲取Runtime
類的無參數構造方法(getDeclaredConstructor()
),因為Runtime的構造方法是private的我們無法直接調用,所以我們需要通過反射去修改方法的訪問權限(constructor.setAccessible(true)
)。 - 獲取
Runtime
類的exec(String)
方法(runtimeClass1.getMethod("exec", String.class);
)。 - 調用
exec(String)
方法(runtimeMethod.invoke(runtimeInstance, cmd)
)。
0x03 反射調用類方法
Class
對象提供了一個獲取某個類的所有的成員方法的方法,也可以通過方法名和方法參數類型來獲取指定成員方法
獲取當前類所有的成員方法:
Method[] methods = clazz.getDeclaredMethods();
獲取當前類的指定的成員方法:
Method method = clazz.getDeclaredMethod("方法名");
Method method = clazz.getDeclaredMethod("方法名",類型參數如String.class,多個參數用逗號隔開);
getMethod
和getDeclaredMethod
都能夠獲取到類成員方法,區別在於getMethod
只能獲取到當前類和父類的所有有權限的方法(如:public),而getDeclaredMethod
能獲取到當前類的所有成員方法(不包含父類)。
反射調用方法
獲取到java.lang.reflect.Method
對象以后我們可以通過Method
的invoke
方法來調用類方法
method.invoke
的第一個參數必須是類實例對象,如果調用的是static
方法那么第一個參數值可以傳null,因為在java中調用靜態方法是不需要有類實例的,因為可以直接類名.方法名(參數)的方式調用。
method.invoke
的第二個參數不是必須的,如果當前調用的方法沒有參數,那么第二個參數可以不傳,如果有參數那么就必須嚴格的依次傳入對應的參數類型。
0x04 反射獲取Runtime類執行命令
部分代碼:
Integer i = 1;
try {
Object obj = i.getClass().forName("java.lang.Runtime").
getMethod("getRuntime",new Class[]{}).invoke(null);
System.out.println(obj.getClass().getName());
i.getClass().forName("java.lang.Runtime").getMethod("exec", String.class).
invoke(obj,"calc");
} catch (Exception e) {
e.printStackTrace();
}
getMethod(方法名, 方法類型)
invoke(某個對象實例, 傳入參數)
invoke的作用是執行方法,如果這個參數是一個普通方法,那么第一個參數就是類對象;如果這個方法是一個靜態方法,那么第一個參數是類
0x05 反射小問題
5.1 當一個類沒有無參構造放法,也沒有類似單例模式里的靜態方法時,如何通過反射實例化該類?
在面對以上問題時,需要用到一個新的反射方法getConstructor
這個方法和getMethod
相似,getConstructor
接受的參數是構造函數列表類型,因為構造函數也支持重載, 所以必須使用參數列表類型才能唯一確定一個構造函數。
在獲取到構造函數后,使用newInstance
來執行
ProcessBuilder有兩個構造函數:
- public ProcessBuilder(List command)
- public ProcessBuilder(String... command)
Class clazz = Class.forName("java.lang.ProcessBuilder");
clazz.getMethod("start").invoke(clazz.getConstructor(List.class).newInstance( Arrays.asList("calc.exe") ));
上面是使用的第一種,所以在傳入的是LIst.class
,通過 getMethod("start")
獲取到start
方法,然后 invoke
執行, invoke
的第一個參數就是 ProcessBuilder Object了。
對於第二種構造函數,要怎么使用反射來執行呢?
這里就又涉及到java里的可變長參數(varargs)了;當定義函數的時候不確定參數數量時,就可以使用...
這樣的語法來表示這個函數的參數個數是可變的 ;對於可變參長數,java在編譯的時候會編譯成一個數組。
那么,對於反射來說,如果要獲取的目標函數里包含可變長參數,只要認為它是數組就行了。
所以,將字符串數組的類String[].class
傳給getConstructor
,就可以獲取ProcessBuilder
的第二種構造參數了
Class clazz =
Class.forName("java.lang.ProcessBuilder");
clazz.getMethod("start").invoke(clazz.getConstructor(String[].class).newInstance(new String[][]{{"calc.exe"}}));
在調用newInstance
的時候,因為這個函數本身接受的是一個可變長參數,我們在傳給ProcessBuilder
的也是一個可變長參數,二者疊加為一個二維數組
5.2 如果一個方法或構造方法是私有方法,我們是否能執行他
答案是可以,這里可以使用getDeclared
系列的反射,
getDeclaredMethod
系列的方法獲取的是當前類中聲明的方法, 是實在寫在這個類里的,包括私有方法, 但從父類繼承來得就不包含了getMethod
系列方法獲取的是當前類中所有公共方法, 包括從父類中繼承的方法
0x06 Java反射機制總結
java反射機制是Java動態性中最為重要的體現,利用反射機制我們可以輕松的實現Java類的動態調用。Java的大部分框架都是采用了反射機制來實現的(如:Spring MVC
、ORM
框架等),Java反射在編寫漏洞利用代碼、代碼審計、繞過RASP方法限制等中起到了至關重要的作用