Decompiled Java Code Manipulation using JEB API – Part 3: Defeating Reflection

This is the third and final part of our series of blogs showing how to use JEB’s API to manipulate decompiled Java syntax trees. (See Part 1, Part 2.)

Today, we will show how to leverage the API to defeat the main obfuscation scheme used to protect the infamous OBad trojan: generalized reflection.

Want a visual TL;DR? Have a look at the following demo video.

Download the scripts: 1, 2, 3
Demo video

Reflection is a powerful feature used by interpreted languages to query information about what is currently loaded and executed by the virtual machine. Using reflection, a program can find references to classes and items within classes, and act on those items, for example, execute methods.

Example:

java.lang.System.exit(3);

can be rewritten to:

java.lang.Class.forName("java.lang.System")
        .getMethod("exit", Integer.TYPE).invoke(null, 3);

Combined with string encryption, reflection can seriously bloat and obscure a program, slowing down the reverse-engineering process.

Based on the above code example, it appears that a solution to remove reflection code automatically or semi-automatically is both useful and realistic. Let’s see how we can to achieve this goal using JEB’s AST API.

As said in the introduction, consider the Android trojan OBad. The video shows the use of a preliminary script to decrypt the scripts. Refer to the script ObadDecrypt.py linked above; we won’t cover the details of string decryption here, as they have been covered in a previous blog.

After string decryption, OclIIOlC.onCreate looks like the following piece of Java code:

    protected void onCreate(Bundle arg9) {
        Object v1_2;
        Object v9;
        super.onCreate(arg9);
        if(Class.forName("android.os.Build").getField("MODEL").get(null).equals(new String(CIOIIolc.IoOoOIOI(CIOIIolc.cOIcOOo(IoOoOIOI.cOIcOOo("cmA4"), CIOIIolc.cOIcOOo("ÞÜëÇØÚâØÞÜÄØåØÞÜéê×èêÉÛè".getBytes())))))) {
            System.exit(0);
        }

        Context v0 = OcooIclc.cOIcOOo;
        Class v2 = MainService.class;
        try {
            v9 = Class.forName("android.content.Intent").getDeclaredConstructor(Class.forName("android.content.Context"), Class.class).newInstance(v0, v2);
        }
        catch(Throwable v0_1) {
            throw v0_1.getCause();
        }

        try {
            Class.forName("android.content.Intent").getMethod("addFlags", Integer.TYPE).invoke(v9, Integer.valueOf(268435456));
        }
        catch(Throwable v0_1) {
            throw v0_1.getCause();
        }

        Context v1_1 = OcooIclc.cOIcOOo;
        try {
            Class.forName("android.content.Context").getMethod("startService", Class.forName("android.content.Intent")).invoke(v1_1, v9);
        }
        catch(Throwable v0_1) {
            throw v0_1.getCause();
        }

        v1_1 = this.getApplicationContext();
        try {
            v1_2 = Class.forName("android.content.Context").getMethod("getPackageManager", null).invoke(v1_1, null);
        }
        catch(Throwable v0_1) {
            throw v0_1.getCause();
        }

        ComponentName v0_2 = this.getComponentName();
        try {
            Class.forName("android.content.pm.PackageManager").getMethod("setComponentEnabledSetting", Class.forName("android.content.ComponentName"), Integer.TYPE, Integer.TYPE).invoke(v1_2, v0_2, Integer.valueOf(2), Integer.valueOf(1));
        }
        catch(Throwable v0_1) {
            throw v0_1.getCause();
        }

        this.finish();
    }

Let’s remove reflection. The steps are:

  1. Recursively walk the AST and check the statements
    Note: we could enumerate all elements instead, however, the script is intended as a demo, not an exhaustive solution to this problem
  2. Look for Call statements (eg, foo()), or Assignments whose right part is a Call (eg, r = bar())
  3. Check if the Call matches the following nested Call pattern:
    Class.forName(…).getMethod(…).invoke(…)
    Note: the script does not take care of class instantiation or field access through reflection, for the same reasons stated above.
  4. Extract the reflection information:
    1. Fully-qualified name of the class
    2. Method name and prototype
    3. Invocation arguments
  5. Create a new method, register it to the DEX object
  6. Create a Call element that accurately mirrors the reflection calls
    Note: There are limitations, especially considering the return type
  7. Finally, replace the reflection call by the direct method call

A deobfuscated / “unreflected” version of the source code above looks like:

    protected void onCreate(Bundle arg9) {
        // ...
        // ...

        try {
            v9.addFlags(Integer.valueOf(268435456));
        }
        catch(Throwable v0_1) {
            throw v0_1.getCause();
        }

        Context v1_1 = OcooIclc.cOIcOOo;
        try {
            v1_1.startService(v9);
        }
        catch(Throwable v0_1) {
            throw v0_1.getCause();
        }

        v1_1 = this.getApplicationContext();
        try {
            v1_2 = v1_1.getPackageManager();
        }
        catch(Throwable v0_1) {
            throw v0_1.getCause();
        }

        ComponentName v0_2 = this.getComponentName();
        try {
            v1_2.setComponentEnabledSetting(v0_2, Integer.valueOf(2),
                    Integer.valueOf(1));
        }
        catch(Throwable v0_1) {
            throw v0_1.getCause();
        }

        this.finish();
    }

We can now remove the try-catchall. This is what the third script is doing.

At this stage, the value and potential of the AST API package should have been clearly demonstrated. We encourage users of JEB to experiment with it, tweak our sample scripts, create their own, and eventually contribute by sending us their useful deobfuscation and/or optimization scripts. We will be happy to make them available to our user base through the Resources page.

Published by

Nicolas Falliere

Author of JEB.

One thought on “Decompiled Java Code Manipulation using JEB API – Part 3: Defeating Reflection”

Leave a Reply

Your email address will not be published. Required fields are marked *


The reCAPTCHA verification period has expired. Please reload the page.

*