Try   HackMD

[Learning] Java Reflection

Mục đích của mình khi viết series Java Sec là để lưu lại các kiến thức trong quá trình tìm hiểu và học JAVA của mình.

Java reflection mechanism

  • Java reflection là một tính năng trong ngôn ngữ Java. Trước tiên cần hiểu 2 định nghĩa về compile timeruntime

  • Compile Time:
    Compile time refers to the phase of program development when the source code is translated into bytecode by the Java compiler (javac). During this phase, the compiler checks the syntax, type correctness, and other static aspects of the code. If there are any syntax errors, type mismatches, or other issues that violate the language rules, the compiler will generate error messages, and the code won't be compiled until these issues are resolved.
    During compilation, the code is converted into an intermediate form called bytecode, which is a platform-independent representation of the source code. This bytecode is then executed by the Java Virtual Machine (JVM) during runtime.

    Là giai đoạn mà source code được compiler biên dịch thành code mà máy tính có thể thực thi (executable code).

    Trong Java, đó là quá trình biên dịch file .java thành file .class.

    Trong compile time, chỉ một số chức năng biên dịch được thực hiện và code không được đưa vào bộ nhớ để chạy mà chỉ hoạt động dưới dạng văn bản, chẳng hạn như kiểm tra lỗi (errors).

  • Run Time:
    Run time, also known as execution time, is the phase when the compiled bytecode is executed by the Java Virtual Machine. The JVM loads the classes and executes the methods as needed. During runtime, dynamic aspects of the program are evaluated, such as user inputs, calculations, and interactions with the environment.

    During runtime, if the program encounters runtime errors, such as division by zero, null pointer dereference, or exceptions that are not caught, the program may terminate abruptly or handle these errors based on exception handling mechanisms. These runtime errors are not detected by the compiler during compilation because they depend on the actual execution flow and data values.
    Là giai đoạn mà executable code bắt đầu chạy cho đến khi kết thúc chương trình.

    Là giai đoạn mà code trên đĩa (disk) được đưa vào bộ nhớ và thực thi (execute).

=> In summary, compile time is the phase when the code is translated into bytecode and checked for syntactic and type-related issues, while run time is the phase when the compiled bytecode is executed by the JVM, and dynamic aspects of the program are evaluated.

Ví dụ về 2 định nghĩa trên

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

  • Khi syntax ở dòng 7 bị sai, thì compile sẽ bắt lỗi ở đây.
    Image Not Showing Possible Reasons
    • The image was uploaded to a note which you don't have access to
    • The note which the image was originally uploaded to has been deleted
    Learn More →
  • Còn khi dùng phép chia cho 0 thì sẽ phát hiện bởi runtime

In this example, the missing semicolon is a compile-time error because it violates the syntax rules of Java. The division by zero, on the other hand, is a run-time error because it involves a specific computation that leads to an exceptional situation.

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Tiếp tục quay lại với reflection:

  • Java Reflection là một tính năng trong Java. Java Reflection cho phép truy cập các thông tin của đối tượng (tên class, các field, các method) và chỉnh sửa các field của đối tượng (kể cả các field private) trong quá trình runtime.

Ví dụ cụ thể chúng ta có thể sửa private property của class:

import java.lang.reflect.Field; class MyClass { private int privateField = 42; } public class Main { public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException { MyClass instance = new MyClass(); // Get the class of the instance Class<?> myClass = instance.getClass(); // Get the field you want to access (even if it's private) Field privateField = myClass.getDeclaredField("privateField"); // Make the private field accessible privateField.setAccessible(true); // Set a new value for the private field in the instance privateField.set(instance, 99); // Get the updated value of the private field from the instance int fieldValue = (int) privateField.get(instance); System.out.println("Updated Private Field Value: " + fieldValue); } }

Hình dung hệ thống phân cấp sẽ như sau:

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

  • Các class được dùng trong Reflection được nằm trong package java.lang.reflect

Object
Class Object là class gốc trong hệ thống phân lớp các class.

Mọi class đều là con của class Object, hay có thể nói class Object là class cha của toàn bộ các class.

Class

Class là class được cung cấp bởi package java.lang.Class.

Một instance của class Class đại diện cho toàn bộ các kiểu dữ liệu trong Java, bao gồm: các kiểu dữ liệu cơ bản (boolean, byte, char, short, int, long), void, array, class, interface, enumeration, annotation.

Class không có public constructor. Thay vào đó object của Class được tạo ra tự động bởi JVM trong quả trình tải class.

Khi sử dụng java reflection thì việc đầu tiên thường phải làm đó là có được một đối tượng kiểu Class, từ các đối tượng kiểu Class chúng ta có thể lấy được các thông tin về:

  • Class Name
  • Class Modifies (public, private, synchronized etc.)
  • Package Info
  • Superclass
  • Implemented Interfaces
  • Constructors
  • Methods
  • Fields
  • Annotations

Có 3 cách để lấy object Class

  1. getClass()
  2. <class name>.class
  3. forName()

Code minh hoạ:

package org.example; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Constructor; class Persontest{ private String name = "Onsra"; public int age = 21; public Persontest(){} private void say(){ System.out.println("private say()"); } public void work(){ System.out.println("public work()"); } } public class Main { public static void main(String[] args) throws Exception { Persontest p1 = new Persontest(); Class c1 = p1.getClass(); Class c2 = Persontest.class; Class c3 = Class.forName("org.example.Persontest"); String className = c2.getName(); System.out.println(className); //output: org.example.Persontest Field[] fields = c2.getFields(); for (Field field: fields){ System.out.println(field.getName()); // output: age } Field[] allFields = c2.getDeclaredFields(); for (Field field: allFields){ System.out.println(field.getName()); // output: name,age Method [] methods = c2.getMethods(); for(Method method: methods){ System.out.println(method.getName()); } Method [] allmethods = c2.getDeclaredMethods(); for(Method method: allmethods){ System.out.println(method.getName()); } Field f1 = c2.getField("age"); System.out.println(f1); Field f2 = c2.getDeclaredField("name"); f2.setAccessible(true); System.out.println(f2); Object p2 = c2.newInstance(); f2.set(p2, "xOnrax"); System.out.println(f2.get(p2)); Constructor [] constructors = c2.getConstructors(); for (Constructor constructor : constructors){ System.out.println(constructor.toString()); } } }

Code minh hoạ đi lượm nhặt :v :

package org.example; class Persontest{ private String name = "Onsra"; public int age = 21; public Persontest(){} private void say(){ System.out.println("private say()"); } public void work(){ System.out.println("public work()"); } } public class Main { public static void main(String[] args) throws Exception { Persontest p1 = new Persontest(); Class c1 = p1.getClass(); Class c2 = Persontest.class; Class c3 = Class.forName("org.example.Persontest"); } }

Sau đây là code minh hoạ về cách lấy field (set,get,..) và method,
Cách set up và tạo class


File Main:

package org.example; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Constructor; public class Main { public static void main(String[] args) throws Exception { // Sử dụng Class.forName() để lấy ra object Class Class cls = Class.forName("org.example.Person"); // Sử dụng getField() để lấy field cụ thể (có modifier public) Field a = cls.getField("a"); System.out.println("(+) Demo getField(): "); System.out.println(a + "\n"); // "a" ko public nên sẽ trả về null Person person = new Person(); Object o = a.get(person); System.out.println("(+) Demo <Field lấy được>.get():"); System.out.println(o + "\n"); a.set(person, "abc"); System.out.println("(+) Demo <Field lấy được>.set():"); System.out.println(person + "\n"); // Sử dụng getDeclaredFields() để lấy toàn bộ Field System.out.println("(+) Demo getDeclaredFields():"); Field[] declaredFields = cls.getDeclaredFields(); for (Field declaredField : declaredFields) { System.out.println(declaredField); } System.out.println("\n"); // Sử dụng setAccessible() để truy cập vào field bất kể access modifier, // ở đây a là 1 field private a.setAccessible(true); Object o1 = a.get(person); System.out.println("(+) Demo <Field lấy được>.setAccessible(): "); System.out.println(o1 + "\n"); // Sử dụng getMethods() để lấy ra toàn bộ Method System.out.println("(+) Demo getMethods(): "); Method[] methods = cls.getMethods(); for (Method method : methods) { System.out.println(method); } System.out.println("\n"); // Lấy ra tên class System.out.println("(+) Demo getName(): "); String name = cls.getName(); System.out.println(name + "\n"); Constructor constructor = cls.getConstructor(String.class,int.class); System.out.println("(+) Demo getConstructor(): "); System.out.println(constructor + "\n"); // Construct có param System.out.println("(+) Demo <Constructor lấy được>.getConstructor() khi có param: "); Object o2 = constructor.newInstance("xxxxx", 19); System.out.println(o); // Construct không có param System.out.println("(+) Demo <Constructor lấy được>.getConstructor() khi không có param: "); Object o3 = constructor.newInstance(); System.out.println(o3); } }

File Person:

package org.example; public class Person { private String name = "onsra" ; private int age = 21; public String a = "test"; public Person() { } public void eat(){ System.out.println("eat"); } public void eat(String food){ System.out.println("eat "+food); } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + ", a='" + a + '\'' + '}'; } public Person(String name, int age) { this.name = name; this.age = age; } }

Output:

(+) Demo getField(): public java.lang.String org.example.Person.a (+) Demo <Field lấy được>.get(): test (+) Demo <Field lấy được>.set(): Person{name='onsra', age=21, a='abc'} (+) Demo getDeclaredFields(): private java.lang.String org.example.Person.name private int org.example.Person.age public java.lang.String org.example.Person.a (+) Demo <Field lấy được>.setAccessible(): abc (+) Demo getMethods(): public java.lang.String org.example.Person.toString() public void org.example.Person.eat(java.lang.String) public void org.example.Person.eat() public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException public final void java.lang.Object.wait() throws java.lang.InterruptedException public boolean java.lang.Object.equals(java.lang.Object) public native int java.lang.Object.hashCode() public final native java.lang.Class java.lang.Object.getClass() public final native void java.lang.Object.notify() public final native void java.lang.Object.notifyAll() (+) Demo getName(): org.example.Person (+) Demo getConstructor(): public org.example.Person(java.lang.String,int) (+) Demo <Constructor lấy được>.getConstructor() khi có param: test (+) Demo <Constructor lấy được>.getConstructor() khi không có param: Exception in thread "main" java.lang.IllegalArgumentException: wrong number of arguments at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:490) at org.example.Main.main(Main.java:65) Process finished with exit code 1

Vậy tóm lại Java reflection sẽ làm được gì trong websec, đặc biệt là để áp dụng trong phần deserialize

Lý thuyết vậy đủ rồi chúng ta sẽ đi qua ví dụ về các bài CTF ở phần 2.

Tham khảo:

​​​​- https://tsublogs.wordpress.com
​​​​- https://sio.hashnode.dev/java-learning-6-reflection-api