java-deobfuscator / deobfuscator

The real deal
https://javadeobfuscator.com
Apache License 2.0
1.55k stars 289 forks source link

help identifying obfuscator #930

Closed rm1410 closed 1 year ago

rm1410 commented 2 years ago

Hi, I'm trying to find out which obfuscator was used for an application and if there are transformers to deobfuscate it (especially the string encryption). It is possible for me to send parts of the obfuscated jar, if you want to have them please ask.

Name obfuscation

All class, package and variable names are replaced by chars from a-z. The detector suggested the SourceFileClassNormalizer, which was able to successfully recover all class names.

Integer replacement

Many(all) integers from 0-3 are replaced by a String of 0-3 spaces and the #length() method.

"".length() // -> replace 0
"  ".length() // -> replace 2

Example:

if (" ".length() <= -" ".length()) {
   return null;
}

Junk code

useless calls

There are useless "".length(); calls that seem to be randomly inserted into the code.

Example:

doSomething();
"".length();
doSomethingElse();
"".length();

redundant if statements

Furthermore, there are if statements, which are always "false" and usually return null.

Example:

 if ((147 ^ 196 ^ (187 ^ 146) << 1) == 0) { // 5 == 0 -> false
   return null;
} else { // else not not always present
   doSomething();
}

boolean operation obfuscation

Every boolean operation is replaced by a method requiring an integer to which the boolean is casted to.

Example of boolean operation methods:

private static boolean a(int var0) {
   return var0 != 0;
}

private static boolean b(int var0) {
   return var0 == 0;
}

Code example:

if (a((int)somBoolean)) {
   doSomething();
} else if (b((int)this.someOptional.equals(Optional.empty()))) {
   doSomethingElse();
}

String encryption

Most classes contain two static arrays. One int[ ] and one String[ ].

The values of the integer array contain most integers used by the class. The values are set staticly with a mathematical equation as value.

Example:

private static void b() { // called in static{} block
   i = new int[3];
   i[0] = (64 ^ 103) & ~(19 ^ 52); // -> 0
   i[1] = " ".length(); // -> 1
   i[2] = " ".length() << " ".length(); // -> 2
   i[3] =  " ".length() << "   ".length() // -> 8
}

The string array contains all strings used in the class. Strings are individually encrypted by either blowfish, Base64 or DES. The encryption method per string is seemingly random. Decryption method(s) are generated per class and require the encrypted String along with an MD5 hash.

Example:

private static void a() { // called in static{} block after b()
   d = new String[i[2]]; // i[2] = 2
   d[i[0]] = b("S45xmW0cZ80=", "gYruk"); // i[0] = 0 |  b() -> Blowfish decryptor |  -> "ACTIVE"
   d[i[1]] = b("ibd2AStnPYw=", "RVexm"); // i[1] = 1 |  could be some random decryptor |  -> "PENDING"
}

Blowfish decryptor:

private static String b(String var0, String var1) {
   try {
      SecretKeySpec var2 = new SecretKeySpec(MessageDigest.getInstance("MD5").digest(var1.getBytes(StandardCharsets.UTF_8)), "Blowfish");
      Cipher var3 = Cipher.getInstance("Blowfish");
      var3.init(i[2], var2); // i is the integer Array described above. i[2] = 2
      return new String(var3.doFinal(Base64.getDecoder().decode(var0.getBytes(StandardCharsets.UTF_8))), StandardCharsets.UTF_8);
   } catch (Exception var4) {
      var4.printStackTrace();
      return null;
   }
}

DES decryptor:

private static String d(String var0, String var1) {
   try {
      SecretKeySpec var2 = new SecretKeySpec(Arrays.copyOf(MessageDigest.getInstance("MD5").digest(var1.getBytes(StandardCharsets.UTF_8)), i[3]), "DES"); // i[3] = 8
      Cipher var3 = Cipher.getInstance("DES");
      var3.init(i[2], var2); // i[2] = 2
      return new String(var3.doFinal(Base64.getDecoder().decode(var0.getBytes(StandardCharsets.UTF_8))), StandardCharsets.UTF_8);
   } catch (Exception var4) {
      var4.printStackTrace();
      return null;
   }
}

Base64 decryptor:

private static String c(String var0, String var1) {
   var0 = new String(Base64.getDecoder().decode(var0.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8);
   StringBuilder var2 = new StringBuilder();
   char[] var3 = var1.toCharArray();
   int var4 = i[0]; // i[0] = 0
   char[] var5 = var0.toCharArray();
   int var6 = var5.length;
   int var7 = i[0]; // i[0] = 0

   do {
      if (!d(var7, var6)) { // d() -> var0 < var1
         return String.valueOf(var2);
      }

      char var8 = var5[var7];
      var2.append((char)(var8 ^ var3[var4 % var3.length]));

      ++var4;
      ++var7;

   } while(((177 ^ 170) & ~(76 ^ 87)) < 1 << (1 << 1)); // while(true)

   return null;
}
Janmm14 commented 2 years ago

looks a lot like superblaubeere. if it did not detect superblaubeere, its some obfuscator based on it.

I think it would not be that hard to create a new transformer to handle this kind of obfuscation. usually in this project its not that important what a method does, as it can be emulated with the MethodExecutor anyway. More important is what environment info it uses, and so far you seem to be lucky on this one as well as its none.

unfortunaly this project is quite dead and most people who maintained it have each create a personal fork where they put in fixes and new transformers so they work for a longer time.

rm1410 commented 2 years ago

Thank you for the reply :)

if it did not detect superblaubeere

There is a detector for superblaubeere? As far as I have seen, there is no detector for this obfuscator in the "rules" package.

I think it would not be that hard to create a new transformer to handle this kind of obfuscation.

So there is no existing transformer in this project or a known fork for the obfuscation techniques described above? (except the classname obfuscation)

More important is what environment info it uses

What is the environment info and why am I lucky not to have it?

unfortunaly this project is quite dead and most people who maintained it have each create a personal fork

This is really a pity. I don't quite understand why maintainers don't want to merge the code anymore. That would have only advantages for everyone.

Janmm14 commented 2 years ago

There is a detector for superblaubeere? As far as I have seen, there is no detector for this obfuscator in the "rules" package.

https://github.com/java-deobfuscator/deobfuscator/blob/master/src/main/java/com/javadeobfuscator/deobfuscator/rules/special/RuleSuperblaubeereObfuscation.java

What is the environment info and why am I lucky not to have it?

Some obfuscation code checks or uses parts of the stack trace (very nasty obfuscation might do even more such checks) as part of a decryption key. The code you've shown doesn't do that.

This is really a pity. I don't quite understand why maintainers don't want to merge the code anymore. That would have only advantages for everyone.

As I said above, thats not neccessarily the case that it has advantages for everyone. Showing how you attack obfuscation does motivate obfuscation creators. There are samples available for free and for commercial obfuscators who did this in the past.

Edit: The anti-emulation stringer says to have is a result of this project. While ZKM does not mention stuff this directly, they do have updated their obfuscation to detect bugs in the bytecode execution emulators of this project.

rm1410 commented 2 years ago

https://github.com/java-deobfuscator/deobfuscator/blob/master/src/main/java/com/javadeobfuscator/deobfuscator/rules/special/RuleSuperblaubeereObfuscation.java

Thank you very much. I used the associated transformer and removed the string encryption with it.

Some obfuscation code checks or uses parts of the stack trace (very nasty obfuscation might do even more such checks) as part of a decryption key. The code you've shown doesn't do that.

Oh yeah, that sounds really nasty. I'm glad I don't have anything like that.

Showing how you attack obfuscation does motivate obfuscation creators.

Oh, I hadn't thought of that. That makes sense, of course. I can totally understand that you'd rather keep your transformer to yourself if it's going to be bypassed right when you release it anyway.

The anti-emulation stringer says to have is a result of this project. While ZKM does not mention stuff this directly, they do have updated their obfuscation to detect bugs in the bytecode execution emulators of this project.

That's really interesting. But with something like this I would merge fixes, since it is almost a free bugreport XD

rm1410 commented 1 year ago

Thank you, I really appreciate the answers and the help.