Inner classes

How to work with inner classes

**** NOTE: BLOG MOVED TO https://cmrodriguez.me/ ****

In Java nested classes are classes that are defined inside another class. The purpose of a nested class is to clearly group the nested class with its surrounding class, signaling that these two classes are to be used together. Or perhaps that the nested class is only to be used from inside its enclosing (owning) class.

As an example the following are nested classes:

public class OuterClass {

    ...
    class InnerClass {

        public char getInnerChar() {
            return 'i';
        }

    }
}

In order to get with Frida to the InnerClass we have to use the following code:

var InnerClass = Java.use("com.blog.testfrida.innerclasses.OuterClass$InnerClass");
var OuterClass = Java.use("com.blog.testfrida.innerclasses.OuterClass");

If the inner class is instantiated as a static public class, it can be used in a code as a common class (besides the OuterClass, that always has access to the InnerClass). But if the class is configured as shown in the example the following code will generate an error:

var instanceInnerClass = InnerClass.$new();

It will fail because the InnerClass will have by default a constructor that receives an instance from the OuterClass (as the declaration of the class is not static).

So to create an instance we need to execute the following code in Frida:

var instanceOuterClass = OuterClass.$new();
var instanceInnerClass = InnerClass.$new(instanceOuterClass);

So the reference to the OuterClass is stored in an internal attribute. In order to ge acces to it from a reimplementation of a method we can use the this$0 reference as in the following example:

InnerClass.getInnerChar.implementation = function () {
    //reference to outer class, and call a method
    console.log(this.this$0.value.getIdOuterClass());
    //reference to outer class, and call an attribute
    console.log(this.this$0.value.val.value);
    return 'j';
}

There is another type of innerclass called Anonymous Inner Class. It is an inner class without a name and for which only a single object is created. An anonymous inner class can be useful when making an instance of an object with certain “extras” such as overloading methods of a class or interface, without having to actually subclass a class. The following is an example of an anonymous inner class:

public class AnonymousInnerClass {

    public String getMessageAnonymous() {
        InnerInterface inner = new InnerInterface() {
            @Override
            public String getMessage() {
                return "getMessage";
            }
        };

        return inner.getMessage();
    }
}

Even when the anonmyous class seems not to have a name, Java internally at compile type generates a common nested class with a numeric name (starting in 1). In order to abstract ourselves from the name Java assigns to the class we can use the following code in Frida:

Java.enumerateLoadedClasses(
  {
  "onMatch": function(className){
        if(className.includes("com.blog.testfrida.innerclasses.AnonymousInnerClass$")){
            console.log(className);
        }            
    },
  "onComplete":function(){}
  }
);

This code snippet lists all the classes loaded in the VM by name. As we know that the anonymous class is created as an inner class, we know that the full classpath will be $. So we filter all the classes that starts with that name.

As a caveat, the first time I listed all the inner classes from AnonymousInnerClass, I could find none. After some minutes of analyzing the reason I realized that it has not been loaded by a ClassLoader yet, as it was never used in the application. So I forced the VM to load it basically instantiating it:

var AnonymousInnerClass = Java.use("com.blog.testfrida.innerclasses.AnonymousInnerClass");
var instanceAnonymous = AnonymousInnerClass.$new();

And after that I could achieve the dynamic instrumentation of the anonymous inner class:

Java.enumerateLoadedClasses(
  {
  "onMatch": function(className){
        if(className.includes("com.blog.testfrida.innerclasses.AnonymousInnerClass")){
            if (className.includes("$")) {
                var anonymousClass = Java.use(className);
                anonymousClass.getMessage.implementation = function () {
                    return "it works well :)";
                }
            }
        }            
    },
  "onComplete":function(){}
  }
);

Last updated