Skip to content

Commit 4e5fbdd

Browse files
committed
superblaubeere transformer
1 parent bdf8a2c commit 4e5fbdd

10 files changed

Lines changed: 343 additions & 27 deletions

File tree

deobfuscator-impl/src/test/java/uwu/narumi/deobfuscator/TestDeobfuscation.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package uwu.narumi.deobfuscator;
22

33
import uwu.narumi.deobfuscator.core.other.composed.ComposedHP888Transformer;
4+
import uwu.narumi.deobfuscator.core.other.composed.ComposedSuperblaubeereTransformer;
45
import uwu.narumi.deobfuscator.core.other.composed.ComposedUnknownObf1Transformer;
56
import uwu.narumi.deobfuscator.core.other.composed.ComposedZelixTransformer;
67
import uwu.narumi.deobfuscator.core.other.composed.Composed_qProtectTransformer;
@@ -164,6 +165,12 @@ protected void registerAll() {
164165
.input(OutputType.MULTIPLE_CLASSES, InputType.CUSTOM_CLASS, "qprotect/sample1")
165166
.register();
166167

168+
// Superblaubeere
169+
test("Superblaubeere Sample 1")
170+
.transformers(ComposedSuperblaubeereTransformer::new)
171+
.input(OutputType.MULTIPLE_CLASSES, InputType.CUSTOM_CLASS, "sb27/sample1")
172+
.register();
173+
167174
test("POP2 Sample")
168175
.transformers(UselessPopCleanTransformer::new)
169176
.input(OutputType.SINGLE_CLASS, InputType.CUSTOM_CLASS, "Pop2Sample.class")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package uwu.narumi.deobfuscator.core.other.composed;
2+
3+
import uwu.narumi.deobfuscator.api.transformer.ComposedTransformer;
4+
import uwu.narumi.deobfuscator.core.other.composed.general.ComposedPeepholeCleanTransformer;
5+
import uwu.narumi.deobfuscator.core.other.impl.clean.LocalVariableNamesCleanTransformer;
6+
import uwu.narumi.deobfuscator.core.other.impl.universal.pool.UniversalNumberPoolTransformer;
7+
import uwu.narumi.deobfuscator.core.other.impl.universal.pool.UniversalStringPoolTransformer;
8+
import uwu.narumi.deobfuscator.core.other.impl.sb27.SuperblaubeereStringTransformer;
9+
import uwu.narumi.deobfuscator.core.other.impl.universal.UniversalFlowTransformer;
10+
11+
/**
12+
* https://github.com/superblaubeere27/obfuscator
13+
*/
14+
// TODO: Transform comparison methods
15+
public class ComposedSuperblaubeereTransformer extends ComposedTransformer {
16+
public ComposedSuperblaubeereTransformer() {
17+
super(
18+
// Remove var names as they are obfuscated and names are useless
19+
LocalVariableNamesCleanTransformer::new,
20+
21+
// Fix flow
22+
UniversalFlowTransformer::new,
23+
24+
// Inline number pools
25+
UniversalNumberPoolTransformer::new,
26+
// Decrypt strings
27+
SuperblaubeereStringTransformer::new,
28+
UniversalStringPoolTransformer::new,
29+
30+
// Cleanup
31+
ComposedPeepholeCleanTransformer::new
32+
);
33+
}
34+
}

deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/composed/ComposedZelixTransformer.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@
1414
import java.util.Map;
1515

1616
/**
17-
* Work in progress
17+
* https://www.zelix.com/
1818
*/
19+
// TODO: Work in progress...
1920
public class ComposedZelixTransformer extends ComposedTransformer {
2021
public ComposedZelixTransformer() {
2122
this(false);

deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/composed/Composed_qProtectTransformer.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,18 @@
55
import uwu.narumi.deobfuscator.core.other.impl.clean.LocalVariableNamesCleanTransformer;
66
import uwu.narumi.deobfuscator.core.other.impl.pool.InlineStaticFieldTransformer;
77
import uwu.narumi.deobfuscator.core.other.impl.qprotect.qProtectFieldFlowTransformer;
8-
import uwu.narumi.deobfuscator.core.other.impl.qprotect.qProtectStringPoolTransformer;
8+
import uwu.narumi.deobfuscator.core.other.impl.universal.pool.UniversalStringPoolTransformer;
99
import uwu.narumi.deobfuscator.core.other.impl.qprotect.qProtectStringTransformer;
1010
import uwu.narumi.deobfuscator.core.other.impl.qprotect.qProtectTryCatchTransformer;
1111
import uwu.narumi.deobfuscator.core.other.impl.qprotect.qProtectInvokeDynamicTransformer;
12-
import uwu.narumi.deobfuscator.core.other.impl.qprotect.qProtectNumberPoolTransformer;
12+
import uwu.narumi.deobfuscator.core.other.impl.universal.pool.UniversalNumberPoolTransformer;
1313
import uwu.narumi.deobfuscator.core.other.impl.universal.TryCatchRepairTransformer;
1414
import uwu.narumi.deobfuscator.core.other.impl.universal.UniversalFlowTransformer;
1515
import uwu.narumi.deobfuscator.core.other.impl.universal.UniversalNumberTransformer;
1616

17+
/**
18+
* https://qtechnologies.dev/
19+
*/
1720
public class Composed_qProtectTransformer extends ComposedTransformer {
1821
public Composed_qProtectTransformer() {
1922
super(
@@ -25,7 +28,7 @@ public Composed_qProtectTransformer() {
2528
InlineStaticFieldTransformer::new,
2629

2730
// Inline number pools
28-
qProtectNumberPoolTransformer::new,
31+
UniversalNumberPoolTransformer::new,
2932
// Decrypt method invocation
3033
qProtectInvokeDynamicTransformer::new,
3134

@@ -37,7 +40,7 @@ public Composed_qProtectTransformer() {
3740
// Decrypt strings
3841
qProtectStringTransformer::new,
3942
// Inline string pools
40-
qProtectStringPoolTransformer::new,
43+
UniversalStringPoolTransformer::new,
4144

4245
// Inline fields again
4346
InlineStaticFieldTransformer::new,

deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/clean/peephole/UselessPopCleanTransformer.java

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import org.jetbrains.annotations.Nullable;
44
import org.objectweb.asm.tree.AbstractInsnNode;
5+
import org.objectweb.asm.tree.InsnNode;
56
import org.objectweb.asm.tree.MethodNode;
67
import org.objectweb.asm.tree.analysis.OriginalSourceValue;
78
import uwu.narumi.deobfuscator.api.asm.InsnContext;
@@ -28,7 +29,6 @@ protected void transform() throws Exception {
2829
boolean success = tryRemovePop(insnContext);
2930

3031
if (success) {
31-
insnContext.methodNode().instructions.remove(insnContext.insn());
3232
markChange();
3333
}
3434
});
@@ -44,16 +44,27 @@ private boolean tryRemovePop(InsnContext insnContext) {
4444
AbstractInsnNode insn = insnContext.insn();
4545
OriginalSourceValue firstValue = insnContext.frame().getStack(insnContext.frame().getStackSize() - 1);
4646

47-
if (!canPop(firstValue, insnContext.methodContext(), null)) return false;
47+
boolean canPopFirstValue = canPop(firstValue, insnContext.methodContext(), null);
4848

4949
if (insn.getOpcode() == POP) {
50+
if (!canPopFirstValue) return false;
51+
5052
// Pop the value from the stack
5153
popSourceValue(firstValue, insnContext.methodNode());
54+
55+
// Remove POP
56+
insnContext.methodNode().instructions.remove(insn);
5257
return true;
5358
} else if (insn.getOpcode() == POP2) {
5459
if (firstValue.getSize() == 2) {
60+
if (!canPopFirstValue) return false;
61+
5562
// Pop 2-sized value from the stack
5663
popSourceValue(firstValue, insnContext.methodNode());
64+
65+
// Remove POP
66+
insnContext.methodNode().instructions.remove(insn);
67+
return true;
5768
} else {
5869
// Pop two values from the stack
5970

@@ -65,14 +76,30 @@ private boolean tryRemovePop(InsnContext insnContext) {
6576
secondValue = Objects.requireNonNull(secondValue.copiedFrom);
6677
}
6778

68-
// Return if we can't remove the source value
69-
if (!canPop(secondValue, insnContext.methodContext(), firstValue)) return false;
79+
boolean canPopSecondValue = canPop(secondValue, insnContext.methodContext(), firstValue);
80+
if (canPopFirstValue && canPopSecondValue) {
81+
// Pop
82+
popSourceValue(firstValue, insnContext.methodNode());
83+
popSourceValue(secondValue, insnContext.methodNode());
84+
85+
// Remove POP2
86+
insnContext.methodNode().instructions.remove(insn);
87+
88+
return true;
89+
} else if (canPopFirstValue || canPopSecondValue) {
90+
// Only pop one value and replace POP2 with POP
91+
if (canPopFirstValue) {
92+
popSourceValue(firstValue, insnContext.methodNode());
93+
} else {
94+
popSourceValue(secondValue, insnContext.methodNode());
95+
}
7096

71-
// Pop
72-
popSourceValue(firstValue, insnContext.methodNode());
73-
popSourceValue(secondValue, insnContext.methodNode());
97+
// Replace POP2 with POP
98+
insnContext.methodNode().instructions.set(insn, new InsnNode(POP));
99+
100+
return true;
101+
}
74102
}
75-
return true;
76103
}
77104

78105
return false;
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
package uwu.narumi.deobfuscator.core.other.impl.sb27;
2+
3+
import org.objectweb.asm.tree.AbstractInsnNode;
4+
import org.objectweb.asm.tree.LdcInsnNode;
5+
import org.objectweb.asm.tree.MethodInsnNode;
6+
import uwu.narumi.deobfuscator.api.asm.MethodContext;
7+
import uwu.narumi.deobfuscator.api.asm.MethodRef;
8+
import uwu.narumi.deobfuscator.api.asm.matcher.Match;
9+
import uwu.narumi.deobfuscator.api.asm.matcher.group.SequenceMatch;
10+
import uwu.narumi.deobfuscator.api.asm.matcher.impl.FieldMatch;
11+
import uwu.narumi.deobfuscator.api.asm.matcher.impl.FrameMatch;
12+
import uwu.narumi.deobfuscator.api.asm.matcher.impl.MethodMatch;
13+
import uwu.narumi.deobfuscator.api.asm.matcher.impl.NumberMatch;
14+
import uwu.narumi.deobfuscator.api.asm.matcher.impl.OpcodeMatch;
15+
import uwu.narumi.deobfuscator.api.asm.matcher.impl.StringMatch;
16+
import uwu.narumi.deobfuscator.api.transformer.Transformer;
17+
18+
import javax.crypto.BadPaddingException;
19+
import javax.crypto.Cipher;
20+
import javax.crypto.IllegalBlockSizeException;
21+
import javax.crypto.NoSuchPaddingException;
22+
import javax.crypto.spec.SecretKeySpec;
23+
import java.nio.charset.StandardCharsets;
24+
import java.security.InvalidKeyException;
25+
import java.security.MessageDigest;
26+
import java.security.NoSuchAlgorithmException;
27+
import java.util.Arrays;
28+
import java.util.Base64;
29+
import java.util.HashMap;
30+
import java.util.HashSet;
31+
import java.util.Map;
32+
import java.util.Set;
33+
34+
/**
35+
* Transformer that decrypts strings obfuscated by Superblaubeere27.
36+
*/
37+
public class SuperblaubeereStringTransformer extends Transformer {
38+
/*
39+
invokevirtual java/lang/String.getBytes (Ljava/nio/charset/Charset;)[B
40+
invokevirtual java/security/MessageDigest.digest ([B)[B
41+
ldc "Blowfish"
42+
invokespecial javax/crypto/spec/SecretKeySpec.<init> ([BLjava/lang/String;)V
43+
astore v2
44+
ldc "Blowfish"
45+
invokestatic javax/crypto/Cipher.getInstance (Ljava/lang/String;)Ljavax/crypto/Cipher;
46+
*/
47+
private static final Match STRING_DECRYPT_BLOWFISH_MATCH = SequenceMatch.of(
48+
MethodMatch.invokeVirtual().owner("java/lang/String").name("getBytes").desc("(Ljava/nio/charset/Charset;)[B"),
49+
MethodMatch.invokeVirtual().owner("java/security/MessageDigest").name("digest").desc("([B)[B"),
50+
StringMatch.of("Blowfish"),
51+
MethodMatch.invokeSpecial().owner("javax/crypto/spec/SecretKeySpec").name("<init>").desc("([BLjava/lang/String;)V")
52+
);
53+
54+
/*
55+
invokevirtual java/lang/String.getBytes (Ljava/nio/charset/Charset;)[B
56+
invokevirtual java/security/MessageDigest.digest ([B)[B
57+
bipush 8
58+
invokestatic java/util/Arrays.copyOf ([BI)[B
59+
ldc "DES"
60+
invokespecial javax/crypto/spec/SecretKeySpec.<init> ([BLjava/lang/String;)V
61+
astore v2
62+
ldc "DES"
63+
invokestatic javax/crypto/Cipher.getInstance (Ljava/lang/String;)Ljavax/crypto/Cipher;
64+
*/
65+
private static final Match STRING_DECRYPT_DES_MATCH = SequenceMatch.of(
66+
MethodMatch.invokeVirtual().owner("java/lang/String").name("getBytes").desc("(Ljava/nio/charset/Charset;)[B"),
67+
MethodMatch.invokeVirtual().owner("java/security/MessageDigest").name("digest").desc("([B)[B"),
68+
NumberMatch.of(8),
69+
MethodMatch.invokeStatic().owner("java/util/Arrays").name("copyOf").desc("([BI)[B"),
70+
StringMatch.of("DES"),
71+
MethodMatch.invokeSpecial().owner("javax/crypto/spec/SecretKeySpec").name("<init>").desc("([BLjava/lang/String;)V")
72+
);
73+
74+
/*
75+
new java/lang/String
76+
dup
77+
invokestatic java/util/Base64.getDecoder ()Ljava/util/Base64$Decoder;
78+
aload p0
79+
getstatic java/nio/charset/StandardCharsets.UTF_8 Ljava/nio/charset/Charset;
80+
invokevirtual java/lang/String.getBytes (Ljava/nio/charset/Charset;)[B
81+
invokevirtual java/util/Base64$Decoder.decode ([B)[B
82+
getstatic java/nio/charset/StandardCharsets.UTF_8 Ljava/nio/charset/Charset;
83+
invokespecial java/lang/String.<init> ([BLjava/nio/charset/Charset;)V
84+
*/
85+
private static final Match STRING_DECRYPT_XOR_MATCH = SequenceMatch.of(
86+
OpcodeMatch.of(NEW),
87+
OpcodeMatch.of(DUP),
88+
MethodMatch.invokeStatic().owner("java/util/Base64").name("getDecoder").desc("()Ljava/util/Base64$Decoder;"),
89+
OpcodeMatch.of(ALOAD),
90+
FieldMatch.getStatic(),
91+
MethodMatch.invokeVirtual().owner("java/lang/String").name("getBytes").desc("(Ljava/nio/charset/Charset;)[B"),
92+
MethodMatch.invokeVirtual().owner("java/util/Base64$Decoder").name("decode").desc("([B)[B"),
93+
FieldMatch.getStatic(),
94+
MethodMatch.invokeSpecial().owner("java/lang/String").name("<init>").desc("([BLjava/nio/charset/Charset;)V")
95+
);
96+
97+
@Override
98+
protected void transform() throws Exception {
99+
scopedClasses().forEach(classWrapper -> {
100+
Map<MethodRef, Decryptor> decryptMethods = new HashMap<>();
101+
102+
// Find decrypt methods
103+
classWrapper.methods().forEach(methodNode -> {
104+
if (!methodNode.desc.equals("(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;")) return;
105+
106+
MethodContext methodContext = MethodContext.of(classWrapper, methodNode);
107+
if (STRING_DECRYPT_BLOWFISH_MATCH.findFirstMatch(methodContext) != null) {
108+
decryptMethods.put(MethodRef.of(classWrapper.classNode(), methodNode), SuperblaubeereStringTransformer::decryptStringBlowfish);
109+
} else if (STRING_DECRYPT_DES_MATCH.findFirstMatch(methodContext) != null) {
110+
decryptMethods.put(MethodRef.of(classWrapper.classNode(), methodNode), SuperblaubeereStringTransformer::decryptStringDES);
111+
} else if (STRING_DECRYPT_XOR_MATCH.findFirstMatch(methodContext) != null) {
112+
decryptMethods.put(MethodRef.of(classWrapper.classNode(), methodNode), SuperblaubeereStringTransformer::decryptXOR);
113+
}
114+
});
115+
116+
if (decryptMethods.isEmpty()) {
117+
// No decrypt method found
118+
return;
119+
}
120+
121+
Match STRING_DECRYPT_MATCH = MethodMatch.invokeStatic().and(Match.of(ctx -> decryptMethods.containsKey(MethodRef.of((MethodInsnNode)ctx.insn()))))
122+
.and(FrameMatch.stack(0, StringMatch.of().capture("key")))
123+
.and(FrameMatch.stack(1, StringMatch.of().capture("encryptedString")));
124+
125+
// Decrypt all strings
126+
classWrapper.methods().forEach(methodNode -> STRING_DECRYPT_MATCH.findAllMatches(MethodContext.of(classWrapper, methodNode)).forEach(matchCtx -> {
127+
String encryptedString = matchCtx.captures().get("encryptedString").insn().asString();
128+
String key = matchCtx.captures().get("key").insn().asString();
129+
MethodRef decryptMethodRef = MethodRef.of((MethodInsnNode) matchCtx.insn());
130+
131+
//System.out.println("Found encrypted string: " + encryptedString);
132+
//System.out.println("Key: " + key);
133+
134+
Decryptor decryptor = decryptMethods.get(decryptMethodRef);
135+
136+
String decryptedString = decryptor.decrypt(encryptedString, key);
137+
//System.out.println(decryptedString);
138+
139+
methodNode.instructions.set(matchCtx.insn(), new LdcInsnNode(decryptedString));
140+
markChange();
141+
142+
// Cleanup
143+
Set<AbstractInsnNode> toRemove = new HashSet<>(matchCtx.collectedInsns());
144+
toRemove.remove(matchCtx.insn()); // Remove self as it is already replaced
145+
toRemove.forEach(methodNode.instructions::remove);
146+
}));
147+
148+
// Remove decrypt methods
149+
classWrapper.methods().removeIf(methodNode -> decryptMethods.containsKey(MethodRef.of(classWrapper.classNode(), methodNode)));
150+
});
151+
}
152+
153+
private static String decryptStringBlowfish(String encryptedString, String key) {
154+
try {
155+
SecretKeySpec secretKeySpec = new SecretKeySpec(MessageDigest.getInstance("MD5").digest(key.getBytes(StandardCharsets.UTF_8)), "Blowfish");
156+
Cipher cipher = Cipher.getInstance("Blowfish");
157+
cipher.init(2, secretKeySpec);
158+
return new String(cipher.doFinal(Base64.getDecoder().decode(encryptedString.getBytes(StandardCharsets.UTF_8))), StandardCharsets.UTF_8);
159+
} catch (NoSuchAlgorithmException | NoSuchPaddingException | IllegalBlockSizeException | BadPaddingException |
160+
InvalidKeyException e) {
161+
throw new RuntimeException(e);
162+
}
163+
}
164+
165+
private static String decryptStringDES(String encryptedString, String key) {
166+
try {
167+
SecretKeySpec secretKeySpec = new SecretKeySpec(Arrays.copyOf(MessageDigest.getInstance("MD5").digest(key.getBytes(StandardCharsets.UTF_8)), 8), "DES");
168+
Cipher cipher = Cipher.getInstance("DES");
169+
cipher.init(2, secretKeySpec);
170+
return new String(cipher.doFinal(Base64.getDecoder().decode(encryptedString.getBytes(StandardCharsets.UTF_8))), StandardCharsets.UTF_8);
171+
} catch (Exception ex) {
172+
throw new RuntimeException(ex);
173+
}
174+
}
175+
176+
private static String decryptXOR(String encryptedString, String key) {
177+
encryptedString = new String(Base64.getDecoder().decode(encryptedString.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8);
178+
StringBuilder builder = new StringBuilder();
179+
char[] keyChars = key.toCharArray();
180+
int keyIndex = 0;
181+
182+
for (char c : encryptedString.toCharArray()) {
183+
builder.append((char)(c ^ keyChars[keyIndex % keyChars.length]));
184+
keyIndex++;
185+
}
186+
187+
return String.valueOf(builder);
188+
}
189+
190+
@FunctionalInterface
191+
interface Decryptor {
192+
String decrypt(String encryptedString, String key);
193+
}
194+
}

0 commit comments

Comments
 (0)