Деобфускация с помощью vlx-vmengine

vlx-vmengine-jvm — это движок выполнения байт-кода Java, реализованный на Java. Инструкции по использованию см. на https://github.com/vlinx-io/vlx-vmengine-jvm.

Ниже приведён простой код на Java:

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();

    }

}

После компиляции в файлы классов и обфускации с помощью определённого обфускатора получены следующие файлы:

a.class

При открытии в jadx обнаружилось, что помимо функции main вся остальная информация стала нечитаемой, а строки были зашифрованы.

image-20230521195209691

Однако даже после обфускации базовая структура класса и информация о байт-коде по-прежнему сохраняются. При открытии a.class с помощью ClassViewer можно увидеть методы и информацию о байт-коде класса. image-20230521200149593

Независимо от метода обфускации, она может запутать код только на статическом уровне и увеличить сложность анализа. При динамическом выполнении программа по-прежнему должна восстанавливать оригинальную логику работы. При использовании vlx-vmengine-jvm для выполнения обфусцированного кода в методе main мы получаем следующий результат:

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"

Из вывода в консоль видно, что программа восстановила своё оригинальное поведение — вывод Hi, George. Одновременно по выводу можно определить, что функция расшифровки строк находится в private static java.lang.String a.a(int,int) с параметрами 7144 и -13249. Если продолжить отладку метода a.a(int,int) с помощью vmengine, можно будет обнаружить метод шифрования строк, используемый этим обфускатором.