Search code examples
javaclassreflectionobfuscationdeobfuscation

Java Class.forName and obfuscated names, java fail to find class


I have .jar file with not common obfuscated names, obfuscated class looks like:
nul.goto. ...\\.. .final. (NOTE: this first gap isn't normal space, it's no-break space)
I can't get this class by Class.forName

I make my own version of code with obfuscation like that, and it don't work even if I use Class.forName(SomeClass.class.getName()) (it's just code to test) after obfuscation java will throw error that it can't find this class.

How to get class like that?


Solution

  • So after some time I decided to go back to this, I used code like this for testing:
    pckg.Main:

    public static void main(String[] args) {
        Class<ObfuscateMe> obfuscateMeClass = ObfuscateMe.class;
        System.out.println("Class name: `" + obfuscateMeClass + "`");
        try {
            System.out.println("Class.forName: " + Class.forName(obfuscateMeClass.getName()));
        }
        catch (ClassNotFoundException exception) { System.out.println("Can't find class using Class.forName: `" + obfuscateMeClass.getName() + "`"); }
        try {
            System.out.println("ClassLoader.loadClass: " + Main.class.getClassLoader().loadClass(obfuscateMeClass.getName()));
        }
        catch (ClassNotFoundException exception) { System.out.println("Can't find class using ClassLoader.loadClass: `" + obfuscateMeClass.getName() + "`"); }
    }
    

    Obfuscated with ProGuard:

    -injars 'B:\Java\ProGuardTestApp\test.jar'
    -outjars 'B:\Java\ProGuardTestApp\testObf.jar'
    -dontshrink
    -dontoptimize
    -classobfuscationdictionary 'B:\Java\ProGuardTestApp\obfs.txt' # contains just one line "final"
    -repackageclasses 'nul.goto. ...\\.. .final.'
    -ignorewarnings
    -keepclasseswithmembers public class * {
        public static void main(java.lang.String[]);
    }
    

    First I noticed that this no longer is a problem in java 11: class like that will just fail to load with error:

    Error: LinkageError occurred while loading main class pckg.Main
        java.lang.ClassFormatError: Illegal class name "nul/goto/▒///\\// /final//final" in 
    class file pckg/Main
    

    But there is still inconsistency between forName and loadClass, forName would still throw ClassNotFoundException but loadClass with throw ClassFormatError instead. OpenJ9 throws ClassFormatError in both cases.

    On java 8 hotspot it works fine, and just like @Holger suggested in comment, version with ClassLoader works just fine. So I decided to dig a bit a find out why.
    In class loader it just goes to very simple piece of code inside URLClassLoader: name.replace('.', '/').concat(".class"); and just load that class from jar without any issues via native method defineClass.
    But Class.forName goes directly to native method and validates class name: http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/native/java/lang/Class.c

    if (VerifyFixClassname(clname) == JNI_TRUE) {
        /* slashes present in clname, use name b4 translation for exception */
        (*env)->GetStringUTFRegion(env, classname, 0, unicode_len, clname);
        JNU_ThrowClassNotFoundException(env, clname);
        goto done;
    }
    
    if (!VerifyClassname(clname, JNI_TRUE)) {  /* expects slashed name */
        JNU_ThrowClassNotFoundException(env, clname);
        goto done;
    }
    

    So all . are changed to / and then in VerifyClassname we can find this:

    if (slash_okay && ch == '/' && last_ch) {
        if (last_ch == '/') {
            return 0;       /* Don't permit consecutive slashes */
        }
    }  
    

    src: http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/native/common/check_format.c#l155

    And that prevents class from loading.