benf.org : other : cfr : multi version jars |
JEP 238 describes new support in JRE 9 to allow multiple versions of the same classfile to be included in the same jar. The JRE will then use the 'latest appropriate' version. (A nice description is here.)
While this is kind of nice, it does seem like another lovely way to obfuscate behaviour!
Consider a jar with the following content (you can get the jar from github, here)
lee@rick:/tmp$ unzip ~/code/cfr_tests/hardcoded/multijar2.jar Archive: /mnt/c/Users/Lee/code/cfr_tests/hardcoded/multijar2.jar creating: META-INF/ inflating: META-INF/MANIFEST.MF creating: META-INF/versions/ creating: META-INF/versions/11/ creating: META-INF/versions/11/org/ creating: META-INF/versions/11/org/benf/ creating: META-INF/versions/11/org/benf/multijar2/ inflating: META-INF/versions/11/org/benf/multijar2/Main$Fred.class creating: org/benf/multijar2/ inflating: org/benf/multijar2/Main$1.class inflating: org/benf/multijar2/Main$Fred.class inflating: org/benf/multijar2/Main.class
Hopefully it's pretty obvious that there's an inner class ('Fred') which has a potential change in behaviour from java 10 to 11.
We can test this....
C:\code\cfr_tests\hardcoded>"c:\Program Files\Java\jdk1.6\bin"\java -jar multijar2.jar not secret C:\code\cfr_tests\hardcoded>"c:\Program Files\Java\jdk1.8\bin"\java -jar multijar2.jar not secret C:\code\cfr_tests\hardcoded>"c:\Program Files\Java\jdk-12\bin"\java -jar multijar2.jar BIG secret
Prior to 0.145, CFR would process class files inside the META-INF directory as if they were normal class files, which means they would be processed, but they might collide with other class files with identical full names.
As of 0.145, CFR properly handles these, and warns you if there is alternate behaviour
If we decompile this into a temp directory using cfr-0.145+, we'll get:
lee@rick:~/code/cfr/target$ java -jar cfr-0.145.jar /tmp/multijar2.jar --outputdir /tmp/multisrc Processing /tmp/multijar2.jar (use silent to silence) Processing org.benf.multijar2.Main Processing org.benf.multijar2.Main
then we can see that we've generated an extra file.
lee@rick:/tmp/multisrc$ find . . ./META-INF ./META-INF/versions ./META-INF/versions/11 ./META-INF/versions/11/org ./META-INF/versions/11/org/benf ./META-INF/versions/11/org/benf/multijar2 ./META-INF/versions/11/org/benf/multijar2/Main.java ./org ./org/benf ./org/benf/multijar2 ./org/benf/multijar2/Main.java ./summary.txt
If we look at the source we've output, we see two versions of Main.java. The 'normal', unversioned one looks like this:
public class Main { public static void main(String ... args) { Main main = new Main(); main.new Fred().doIt(); } /* * Multiple versions of this class in jar - see https://www.benf.org/other/cfr/multi-version-jar.html */ private class Fred { private Fred() { } public void doIt() { System.out.println("not secret"); } } }
.... and the one in META-INF/versions/11 looks like this....
public class Main { public static void main(String ... args) { Main main = new Main(); main.new Fred().doIt(); } /* * Multiple versions of this class in jar - see https://www.benf.org/other/cfr/multi-version-jar.html */ private class Fred { private Fred() { } public void doIt() { System.out.println("BIG secret"); } } }
This is a deliberate decision - while the jar we're reversing only overrode the inner class Fred, we don't have separate source for this. As such, CFR will regenerate the outermost class of any inner classes that have version specific class files.
You'll note that each class (including inner classes) that have version specific implementations have a comment, so it's possible to tell directly from the above that only Fred was provided.
Just specify that you can handle DECOMPILED_MULTIJAR - example here.
Last updated 05/2019 |