Search code examples
javajna

Is possible to annotate the exported function name?


I have the following interface, mapping an exported function of a DLL:

interface Foo extends Library {
    int ProcessBytes(byte[] bytes, int size);
}

I'm using Proguard to obfuscate my application. And, for now I'm using the -keep configuration to maintain the code of the interface above. So, JNA can find the function and call it.

I would like to obfuscate the interface too. If I remove the -keep configuration, my interface will look like this:

interface a extends Library {
    int a(byte[] a, int b);
}

And JNA can't handle it, because a function does not exist in the exported functions. So, my question is: Is possible to annotate the exported function name? Something like this:

interface Foo extends Library {  
   @Function("ProcessBytes")
   int anyname(byte[] bytes, int size);
}

Like "I dont care the name of your method, I'll call the "ProcessBytes" function when the method anyname is called.


Solution

  • You can create a custom annotation and use a FunctionMapper to resolve the function name. This class can be passed to JNA when loading a library with loadLibrary.

    @Target(value = ElementType.METHOD)
    @Retention(value = RetentionPolicy.RUNTIME)
    @interface NativeFunctionName {
        String name() default "";
    }
    
    class FunctionNameAnnotationMapper implements FunctionMapper {
        @Override
        public String getFunctionName(NativeLibrary nativeLibrary, Method method) {
            NativeFunctionName annotation = method.getAnnotation(NativeFunctionName.class);
            // just return the function's name if the annotation is not applied
            return annotation == null ? method.getName() : annotation.name();
        }
    }
    

    Usage would be as follows:

    public class Program {
        public static void main(String... args) {
            Map<String, Object> opts = new HashMap<>();
            opts.put(Library.OPTION_FUNCTION_MAPPER, new FunctionNameAnnotationMapper());
    
            Obfuscated lib = Native.loadLibrary("my-library", Obfuscated.class, opts);
            lib.obfuscated1(0, null);
        }
    }
    
    interface Obfuscated extends Library {
        @NativeFunctionName(name = "main")
        public int obfuscated1(int argc, Pointer argv);
    }
    

    Note that the JVM will need to access these annotation values during runtime, so they will be included in the output. Make sure your obfuscator keeps these annotations.

    If you're already using a function mapper, consider creating an "aggregate" function mapper class which allows you to pass multiple mappers.

    More about function mapping here: http://java-native-access.github.io/jna/4.5.0/javadoc/overview-summary.html#function-mapping