Sử dụng vlx-vmengine để giải rối mã

vlx-vmengine-jvm là công cụ thực thi bytecode Java được triển khai bằng Java. Vui lòng tham khảo https://github.com/vlinx-io/vlx-vmengine-jvm để biết cách sử dụng.

Có một đoạn mã Java đơn giản như sau:

class HelloWorld {

    private String name = "";

    public HelloWorld(String name){
        this.name = name;
    }

    public void sayHi(){
        System.out.println("Hi, " + name);
    }

    public static void main(String[] args){
        
        String name = "George";
        HelloWorld hello = new HelloWorld(name);

        hello.sayHi();

    }

}

Sau khi biên dịch thành tệp class và làm rối bằng một công cụ làm rối nhất định, các tệp sau được thu được:

a.class

Sau khi mở bằng jadx, phát hiện ngoài hàm main, tất cả các thông tin khác đều không thể nhận dạng và các chuỗi đã bị mã hóa.

image-20230521195209691

Tuy nhiên, ngay cả khi đã bị làm rối, cấu trúc cơ bản của class và thông tin bytecode vẫn tồn tại. Sử dụng ClassViewer để mở a.class, bạn có thể thấy các phương thức và thông tin bytecode của class. image-20230521200149593

Dù làm rối như thế nào, nó chỉ có thể làm rối mã ở cấp độ tĩnh và tăng độ phức tạp của phân tích. Trong quá trình thực thi động, nó vẫn cần khôi phục logic chạy gốc của chương trình. Sử dụng vlx-vmengine-jvm để chạy mã đã bị làm rối trong phương thức main, chúng tôi nhận được đầu ra sau

2023-05-21 18:19:05 [DEBUG] LocalVars: [kotlin.Unit, kotlin.Unit, kotlin.Unit]
2023-05-21 18:19:05 [DEBUG] "L0: SIPUSH"
2023-05-21 18:19:05 [DEBUG] "push" 7144
2023-05-21 18:19:05 [DEBUG] "L3: SIPUSH"
2023-05-21 18:19:05 [DEBUG] "push" -13249
2023-05-21 18:19:05 [DEBUG] "L6: INVOKESTATIC"
2023-05-21 18:19:05 [DEBUG] "#20"
2023-05-21 18:19:05 [DEBUG] "class a, NameAndType(name='a', type='(II)Ljava/lang/String;')"
2023-05-21 18:19:05 [DEBUG] private static java.lang.String a.a(int,int)
2023-05-21 18:19:05 [DEBUG] "pop" -13249
2023-05-21 18:19:05 [DEBUG] "pop" 7144
2023-05-21 18:19:05 [DEBUG] 	Execute method: private static java.lang.String a.a(int,int)
2023-05-21 18:19:05 [DEBUG] 	Args: [7144, -13249]
2023-05-21 18:19:05 [DEBUG] "push" "George"
2023-05-21 18:19:05 [DEBUG] "L9: ASTORE_1"
2023-05-21 18:19:05 [DEBUG] "pop" "George"
2023-05-21 18:19:05 [DEBUG] "localVars[1] = George"
2023-05-21 18:19:05 [DEBUG] "L10: NEW"
2023-05-21 18:19:05 [DEBUG] class a
2023-05-21 18:19:05 [DEBUG] "push" InstanceToCreate(clazz=class a)
2023-05-21 18:19:05 [DEBUG] "L13: DUP"
2023-05-21 18:19:05 [DEBUG] "pop" InstanceToCreate(clazz=class a)
2023-05-21 18:19:05 [DEBUG] "push" InstanceToCreate(clazz=class a)
2023-05-21 18:19:05 [DEBUG] "push" InstanceToCreate(clazz=class a)
2023-05-21 18:19:05 [DEBUG] "L14: ALOAD_1"
2023-05-21 18:19:05 [DEBUG] "#1"
2023-05-21 18:19:05 [DEBUG] "push" "George"
2023-05-21 18:19:05 [DEBUG] "L15: INVOKESPECIAL"
2023-05-21 18:19:05 [DEBUG] "#47"
2023-05-21 18:19:05 [DEBUG] "class a, NameAndType(name='<init>', type='(Ljava/lang/String;)V')"
2023-05-21 18:19:05 [DEBUG] public a(java.lang.String)
2023-05-21 18:19:05 [DEBUG] "pop" "George"
2023-05-21 18:19:05 [DEBUG] "Execute new instance: public a(java.lang.String)"
2023-05-21 18:19:05 [DEBUG] "Args: [George]"
2023-05-21 18:19:05 [DEBUG] "pop" InstanceToCreate(clazz=class a)
2023-05-21 18:19:05 [DEBUG] "L18: ASTORE_2"
2023-05-21 18:19:05 [DEBUG] "pop" a@4612b856
2023-05-21 18:19:05 [DEBUG] "localVars[2] = a@4612b856"
2023-05-21 18:19:05 [DEBUG] "L19: ALOAD_2"
2023-05-21 18:19:05 [DEBUG] "#2"
2023-05-21 18:19:05 [DEBUG] "push" a@4612b856
2023-05-21 18:19:05 [DEBUG] "L20: INVOKEVIRTUAL"
2023-05-21 18:19:05 [DEBUG] "#54"
2023-05-21 18:19:05 [DEBUG] "class a, NameAndType(name='a', type='()V')"
2023-05-21 18:19:05 [DEBUG] public void a.a()
2023-05-21 18:19:05 [DEBUG] "pop" a@4612b856
2023-05-21 18:19:05 [DEBUG] 	Execute method: public void a.a()
2023-05-21 18:19:05 [DEBUG] 	Receiver: a@4612b856
2023-05-21 18:19:05 [DEBUG] 	Args: [a@4612b856]
Hi, George
2023-05-21 18:19:05 [DEBUG] "L23: RETURN"

Từ đầu ra console, chúng ta có thể thấy rằng chương trình đã khôi phục hành vi gốc, đó là in Hi, George. Đồng thời, từ đầu ra, chúng ta cũng có thể xác định rằng hàm giải mã chuỗi nằm trong private static java.lang.String a.a(int,int), với các tham số 7144-13249. Nếu chúng ta tiếp tục sử dụng vmengine để gỡ lỗi phương thức a.a(int,int), chúng ta có thể phát hiện phương pháp mã hóa chuỗi được sử dụng bởi công cụ làm rối này.