Java Sec - Javasisst

5.1k words

I. Lời nói đầu

Trong chain CC2, Javasisst được sử dụng để xây dựng gadget chain. Do đó, trước khi debug chain CC2 tôi muốn hiểu Javasisst thật sự làm gì.

II. Giới thiệu về Javasisst

2.1. Javassist là gì?

Bytecode Java được lưu trữ dưới dạng nhị phân trong các file .class. Mỗi file .class chứa một class hoặc một interface Java.

Javassist (Java Programming Assistant) là thư viện Java dùng để đọc, tạo và chỉnh sửa bytecode của class Java ngay trong lúc chạy hoặc trước khi chạy ứng dụng.

Tóm gọn: thay vì sửa file .java rồi compile lại bằng javac, Javassist cho phép bạn can thiệp trực tiếp vào file .class.

Javasisst được ứng dụng trong:

  • AOP, proxy và framework runtime.
  • Instrumentation / Java Agent.
  • Hook method để logging, profiling hoặc tracing.
  • Sinh class động.

Trang chủ của Javasisst: https://www.javassist.org/

2.2. Một số concept quan trọng trong Javasisst

Javassist làm việc với các đối tượng biểu diễn cấu trúc class:

Thành phần Ý nghĩa
ClassPool Kho chứa metadata của các class
CtClass Đại diện cho một class
CtMethod Đại diện cho một method
CtField Đại diện cho một field
CtConstructor Đại diện cho constructor

Ct là viết tắt của Compile Time.

III. Cách sử dụng Javasisst

Ở đây tôi đi sâu hơn 1 chút về các concept quan trọng trong Javasisst

3.1. Một số concept quan trọng

a. ClassPool

ClassPool: Là một container chứa các đối tượng CtClass, được cài đặt bằng Hashtable.

  • Key: tên class
  • Value: đối tượng CtClass đại diện cho class đó
  • HashtableHashMap đều là các implementation của Map. HashMap có thể chứa giá trị null, trong khi Hashtable thì không

Các method thường dùng:

  • static ClassPool getDefault(): Trả về class pool mặc định.
  • ClassPath insertClassPath(java.lang.String pathname): Thêm một thư mục hoặc file JAR (hoặc ZIP) vào đầu classpath tìm kiếm.
  • ClassPath insertClassPath(ClassPath cp): Thêm một đối tượng ClassPath vào đầu classpath.
  • java.lang.ClassLoader getClassLoader(): Lấy ClassLoader được sử dụng cho các thao tác như toClass(), getAnnotations() trong CtClass, v.v.
  • CtClass get(java.lang.String classname): Đọc file class từ nguồn và trả về một đối tượng CtClass đại diện cho class đó.
  • ClassPath appendClassPath(ClassPath cp): Thêm một ClassPath vào cuối classpath.
  • CtClass makeClass(java.lang.String classname): Tạo một class public mới.

b. CtClass

CtClass đại diện cho một class.

  • Một đối tượng CtClass (compile-time class) có thể thao tác trực tiếp trên file .class
  • Các đối tượng CtClass này được lấy thông qua các method của ClassPool.

Các method thường dùng:

  • void setSuperclass(CtClass clazz): Thay đổi superclass của class này (trừ khi nó là interface).
  • java.lang.Class<?> toClass(java.lang.invoke.MethodHandles.Lookup lookup): Chuyển CtClass thành một đối tượng java.lang.Class.
  • byte[] toBytecode(): Chuyển CtClass thành mảng bytecode của file class.
  • void writeFile(): Ghi file class do CtClass đại diện ra thư mục hiện tại.
  • void writeFile(java.lang.String directoryName): Ghi file class ra thư mục được chỉ định.
  • CtConstructor makeClassInitializer(): Tạo một class initializer rỗng (static constructor).

c. CtMethod

CtMethod: Đại diện cho một method trong class.

d. CtConstructor

Một instance của CtConstructor đại diện cho một constructor. Nó cũng có thể đại diện cho static constructor (class initializer).

Các method thường dùng:

  • void setBody(java.lang.String src): Thiết lập phần thân (body) của constructor.
  • void setBody(CtConstructor src, ClassMap map): Sao chép phần thân của một constructor khác.
  • CtMethod toMethod(java.lang.String name, CtClass declaring): Sao chép constructor này và chuyển nó thành một method.

e. ClassClassPath

Class này được sử dụng như một search path để lấy file class thông qua getResourceAsStream() trong java.lang.Class.

Constructor của nó:

  • ClassClassPath(java.lang.Class<?> c)
    Tạo một search path.

Các method thường dùng:

  • java.net.URL find(java.lang.String classname)
    Lấy URL của file class được chỉ định.
  • java.io.InputStream openClassfile(java.lang.String classname)
    Lấy file class thông qua getResourceAsStream().

3.2. Ví dụ

1
ClassPool pool = ClassPool.getDefault();

Lấy một đối tượng ClassPool bằng system search path mặc định.

Nếu cần chỉnh sửa class search path, sử dụng insertClassPath().

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

Chèn đường dẫn chứa class hiện tại vào class search path.

toByCode

toBytecode() dùng khi muốn lấy raw bytecode,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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

toClass dùng khi muốn load class đã bị chỉnh sửa vào JVM và thực thi luôn.

Class Hello:

1
2
3
4
5
public class Hello {
public void say() {
System.out.println("Hello");
}
}

Test class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Test {
public static void main(String[] args) throws Exception {
ClassPool cp = ClassPool.getDefault();
// Lấy ClassPool từ system search path mặc định
CtClass cc = cp.get("com.demo.Hello");
// Lấy class Hello
CtMethod m = cc.getDeclaredMethod("say");
// Lấy method say() trong Hello
m.insertBefore("{ System.out.println(\"Hello.say():\"); }");
// Chèn bytecode vào đầu thân method
Class c = cc.toClass();
// Chuyển CtClass thành java.lang.Class
Hello h = (Hello) c.newInstance();
// Tạo instance thông qua reflection và ép kiểu
h.say();
// Gọi method say()
}
}