GotoFinal
GotoFinal

Reputation: 3675

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?

Upvotes: 1

Views: 1552

Answers (1)

GotoFinal
GotoFinal

Reputation: 3675

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.

Upvotes: 1

Related Questions