Search code examples
javaandroidenumshookxposed

XPosed: Hooking a function overridden in an enum


Say I have an enum class defined like this (adapted from the java documentation)

package com.example.planetExample;

public enum Planet {
    MERCURY (3.303e+23, 2.4397e6){
        public double surfaceGravity() {
            return 42;
        }
    },
    VENUS   (4.869e+24, 6.0518e6);

    private final double mass;   // in kilograms
    private final double radius; // in meters
    Planet(double mass, double radius) {
        this.mass = mass;
        this.radius = radius;
    }

    // universal gravitational constant  (m3 kg-1 s-2)
    public static final double G = 6.67300E-11;

    double surfaceGravity() {
        return G * mass / (radius * radius);
    }
}

Now, I want to use XPosed hook the surfaceGravity function that has been overriden by MERCURY (and not the general one defined below). How can I get access to that function?

I tried findAndHookMethod("com.example.planetExample.Planet", lpparam.classLoader, "surfaceGravity", [etc]), but that one only hooks the general surfaceGravity defined by the Planet class, and not the one defined by MERCURY. If I try com.example.planetExample.Planet$MERCURY or com.example.planetExample.Planet.MERCURY, I'm getting errors from XPosed that the function surfaceGravity could not be found.

Is there a way to hook this function using XPosed?


Solution

  • MERCURY is a field of the Planet Enum. Since it has its own implementation, a class will be generated at compile time for it, unfortunately its name won't match the field's name (e.g., in your case it will likely be com.test.Planet$1).

    Consider the following example:

    public static void main(String[] args) {
    
        System.out.println("Mercury radius: " + Planet.MERCURY.surfaceGravity()); // 42
        System.out.println("Planet class: " + Planet.class.getName()); //prints "com.test.Planet"
    
        try {
            Class<?> planet_cls = Class.forName("com.test.Planet");
    
            System.out.println(Planet.class); // com.test.Planet
            System.out.println(Planet.MERCURY.getClass()); // com.test.Planet$1
            System.out.println(Planet.VENUS.getClass()); // com.test.Planet
    
            for(Class c: Planet.class.getDeclaredClasses())
                System.out.println("Name:" + c.getName()); // wont print
    
            for(Field c: Planet.class.getDeclaredFields())
                System.out.println("Field Name:" + c.getName()); // MERCURY & VENUS :)
    
            try {
                Field mercury_field = planet_cls.getDeclaredField("MERCURY");
    
                Object o = mercury_field.get(null);
    
                System.out.println("Field class name: " + o.getClass()); // com.test.Planet$1
    
                try {
                    Method surfaceGravity = o.getClass().getDeclaredMethod("surfaceGravity");
    
                    System.out.println("Confirm result: " + surfaceGravity.invoke(o)); // 42!
    
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
    
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
    

    So by retrieving the field by name, you can get its class and methods. Note that in Xposed you will have another hooking api that receives the method instead of looking for it by name, just pass it the method in the example.

    The output of that code to save you some time:

    Mercury radius: 42.0
    Planet class: com.test.Planet
    class com.test.Planet
    class com.test.Planet$1
    class com.test.Planet
    Field Name:MERCURY
    Field Name:VENUS
    Field Name:mass
    Field Name:radius
    Field Name:G
    Field Name:$VALUES
    Field class name: class com.test.Planet$1
    Confirm result: 42.0