I've got a jarfile from a friend, he told me to put it thru an decompiler. So I did, but:

  • JD-GUI basically spit out just the imports and didn't show any class data
  • CFR dumped out stack trace on certain and created basically unusable Java code
  • I didn't even try using JAD for it because it's using pattern matching for decompilation
  • The bytecode, according to the specification, is invalid because it contains incorrect entry for constant table
  • JBE spit out a couple of errors and crashed, the same goes for fernflower

Albeit all of that, I tried to execute it on a virtual machine - and it works! I'm literally swept off my knees looking at what happened there, and I believe this may be a fragment of code that is simply un-decompilable. I also did analyze the class file by hand, but everything seemed so contradictionary I eventually gave up. And, what is more interesting, I tried to use it along with multiple JRE versions starting with Java 13 and ending with Java 8 it worked in practice everywhere and I can't believe it.

The jar file.

I've also uploaded the CFR output (it's hillarious). I don't know really much about the obfuscator and the person who sent me the jar refuses to provide the further information, but I've got permission for redistributing and obviously decompiling it. Last bit of information I've got is the fact that the obfuscator is supposedly public and free-to-use, but I refuse to believe it (it could be though, unless I've been a victim of bluffing).

How do I even tackle that? How is this, that the jar-file is almost completely obfuscated up until this point all the tooling that exists nowadays has severe problems with even outputting the bytecode assembly? It's not a very confidental piece of code and I'd most probably throw it away and forget about it, but I'm so curious on how it works, I've spent around two days trying to figure it out.

Thanks in advance for any help.

  • Try Jasmin jasmin.sourceforge.net – Axifive Jan 17 at 17:40
  • javap or Krakatau. Also Procyon as an alternative to CFR – Axifive Jan 17 at 18:06
  • javap doesn't work. Could you please check your suggestions before actually submitting them? – Kamila Szewczyk Jan 17 at 18:11
  • The output of Procyon: pastebin.com/V2i3EXuu – Kamila Szewczyk Jan 17 at 18:14
  • javap is not designed to handle obfuscated code at all. Luckily, the Krakatau disassembler is designed to work with obfuscated bytecode. Anyway, I'll try decompiling this myself when I get home. I suspect that OP just isn't using the right tools. – Antimony Jan 17 at 19:09

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 later 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, so this is what is left after Krakatau's simplification passes. 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();
| improve this answer | |
  • Very good explanation! I've gone thru the code before and managed to get it sorted out. My main interest is now finding out the mechanisms the code actually secured itself from classic deobfuscators. I think I'll figure it out myself though. Thanks for the answer! – Kamila Szewczyk Jan 18 at 15:40
  • PS: Are you saying that javap may be vulnerable? – Kamila Szewczyk Jan 18 at 15:45
  • 1
    @KrzysztofSzewczyk I doubt there's remote code execution, but javap can easily be DOSed or made to output useless or misleading results. – Antimony Jan 18 at 15:46
  • Alright, that's what I wanted to know. Thank you very much! – Kamila Szewczyk Jan 18 at 15:47

Before tossing this at a decompiler you should use automated tools like java-deobfuscator to clean up the illegal names and remove some of the control-flow obfuscation.

This can be accomplished with this config (Assuming you have a bukkit jar handy):

input: unnamedObf.jar
output: unnamedObf-clean.jar
path: [rt.jar, bukkit.jar]
  - com.javadeobfuscator.deobfuscator.transformers.general.removers.IllegalSignatureRemover
  - com.javadeobfuscator.deobfuscator.transformers.general.removers.LineNumberRemover
  - com.javadeobfuscator.deobfuscator.transformers.general.removers.LocalVariableRemover
  - com.javadeobfuscator.deobfuscator.transformers.general.removers.SyntheticBridgeRemover
  - com.javadeobfuscator.deobfuscator.transformers.normalizer.PackageNormalizer
  - com.javadeobfuscator.deobfuscator.transformers.normalizer.ClassNormalizer
  - com.javadeobfuscator.deobfuscator.transformers.normalizer.FieldNormalizer
  - com.javadeobfuscator.deobfuscator.transformers.normalizer.MethodNormalizer
  - com.javadeobfuscator.deobfuscator.transformers.general.peephole.PeepholeOptimizer

Here's a comparsion for before & after using CFR. decompile comparison before and after applying java-deobfuscator

It doesn't clear everything up, there's still plenty of obfuscator-generated invoke-dynamic calls and opaque predicates, but its definitely a better starting point than the original jar.

| improve this answer | |
  • 1
    The tool doesn't really help that much, because CFR still fails to decompile certain methods. – Kamila Szewczyk Jan 18 at 11:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.