First off, you should never use javap
when dealing with obfuscated applications, because javap
is not designed to handle malicious bytecode, and can easily be thwarted in a number of ways. Luckily, the Krakatau disassembler is specifically designed for working with obfuscated bytecode and can handle pretty much anything. (If you find anything that runs on the JVM and can't be disassembled, please file a bug). Luckily, in this case it disassembles without errors.
In addition to the bytecode disassembler and assembler, Krakatau also contains a Java decompiler which is specifically designed for decompiling obfuscated bytecode. It's not particularly user friendly, but it can often decompile stuff that no other decompiler can handle. In this case, Krakatau was able to decompile your application without errors after some minor fixes.
In order to decompile your app with Krakatau, I had to do two things:
- Fix a Python 2 unicode handling error (the
'ascii' codec can't decode byte 0xe2 in position 0: ordinal not in range(128)
message you got) by changing .decode()
to .decode('utf8')
.
- Supply the Spigot library with the
-path
option. I had to modify the Spigot jar slightly first, to move the bukkit part to the right path (it expects classes at org/bukkit/craftbukkit/entity/CraftPlayer
, but that class is located at bukkit/craftbukkit/v1_15_R1/entity/CraftPlayer
in the Jar I downloaded).
Anyway, once that was done, the obfuscated jar you provided decompiled without errors. Note that the jar makes use of invokedynamic
, which can't be decompiled in general, since it is a bytecode feature that has no Java equivalent. In order to warn you of this, Krakatau outputs comments in the decompiled source like this:
label12: {
try {
if (!/*invokedynamic*/false) {
break label12;
}
if (b) {
break label12;
In cases like this, you need to consult the disassembled bytecode to see exactly what it is doing:
L14: iload 6
L16: ifne L8
L19: invokedynamic InvokeDynamic invokeStatic Method [c24] '\u200c\u2001\u2005\u2001' [u26] : '\ufb05\u58d7\ufb06\u0226\udb3d\u78ef\ufb09\u0226\ufb00\u58d5\udb3d\u221f\udb32' '()L\u2004\u2006\u2007\u2007;'
L24: invokedynamic [id154]
L29: invokedynamic InvokeDynamic invokeStatic Method [c24] '\u200c\u2001\u2005\u2001' [u26] : [nat157]
L34: invokedynamic InvokeDynamic invokeStatic Method [c24] '\u200c\u2001\u2005\u2001' [u26] : '\ufb05\u58d7\ufb06\u0226\udb3d\u78ef\ufb09\u0226\ufb00\u58d5\udb3d\u221f\udb32' '()L\u2004\u2006\u2007\u2007;'
L39: new '\u200b\u2007\u2006\u200d'
L42: dup
L43: aload 4
As far as obfuscations go, the main obfuscations I noticed were a) renaming everything to unicode, and b) inserting dummy fields and a bunch of fake control flow branching on those fields. Here is an example of the latter in the decompiled output:
boolean b = \u200e\u200a;
label0: {
if (b) {
break label0;
}
if (b) {
break label0;
}
\u2005\u200c\u2005\u2009 = new java.util.HashMap();
if (!b) {
}
}
Note that the Krakatau decompiler performs a lot of analysis to simplify the control flow. It is no wonder that other decompilers would just give up when seeing complicated control flow like this.
It is also notable that this is a fairly weak obfuscation. In particular, it creates a static field and branches on the value, but it never actually assigns to that field, so the field can just be replaced by a constant to simplify the bytecode. This reminds me a bit of bytecode I've seen that was obfuscated by ZKM, so it is possible that your friend was using ZKM.
For example, after I patched the jar to replace the static fields with a constant false
and decompiled it again, Krakatau's constant propagation was able to completely simplify away the fake control flow. Here's the decompiled version of the same function as above after replacing the field with false
:
static {
\u2005\u200c\u2005\u2009 = new java.util.HashMap();
}