Skip to content

Commit 7fb937c

Browse files
authored
qProtect Strong Strings (#126)
* qProtect Strong String Transformer * qProtect Indy fix * qProtect Tests * qProtect Indy's * I did many things wrong, fixes * Tests * Valid tests
1 parent 592d6de commit 7fb937c

File tree

7 files changed

+257
-360
lines changed

7 files changed

+257
-360
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,8 +172,8 @@ protected void registerAll() {
172172
.inputClassesDir(InputType.CUSTOM_CLASS, "qprotect/sample2")
173173
.register();
174174

175-
test("qProtect Sample 3")
176-
.transformers(Composed_qProtectTransformer::new)
175+
test("qProtect Strong String Sample 3")
176+
.transformers(() -> new Composed_qProtectTransformer(true))
177177
.inputClassesDir(InputType.CUSTOM_CLASS, "qprotect/sample3")
178178
.register();
179179

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

Lines changed: 50 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,15 @@
11
package uwu.narumi.deobfuscator.core.other.composed;
22

33
import uwu.narumi.deobfuscator.api.transformer.ComposedTransformer;
4+
import uwu.narumi.deobfuscator.core.other.composed.general.ComposedGeneralFlowTransformer;
45
import uwu.narumi.deobfuscator.core.other.composed.general.ComposedPeepholeCleanTransformer;
56
import uwu.narumi.deobfuscator.core.other.impl.clean.LocalVariableNamesCleanTransformer;
67
import uwu.narumi.deobfuscator.core.other.impl.pool.InlineLocalVariablesTransformer;
78
import uwu.narumi.deobfuscator.core.other.impl.pool.InlineStaticFieldTransformer;
8-
import uwu.narumi.deobfuscator.core.other.impl.qprotect.qProtectAESStringEncryptionTransformer;
9-
import uwu.narumi.deobfuscator.core.other.impl.qprotect.qProtectFieldFlowTransformer;
10-
import uwu.narumi.deobfuscator.core.other.impl.qprotect.qProtectInvokeDynamicStrongTransformer;
11-
import uwu.narumi.deobfuscator.core.other.impl.universal.SourceFileNameRecoverTransformer;
9+
import uwu.narumi.deobfuscator.core.other.impl.qprotect.*;
10+
import uwu.narumi.deobfuscator.core.other.impl.universal.*;
1211
import uwu.narumi.deobfuscator.core.other.impl.universal.pool.UniversalStringPoolTransformer;
13-
import uwu.narumi.deobfuscator.core.other.impl.qprotect.qProtectStringTransformer;
14-
import uwu.narumi.deobfuscator.core.other.impl.qprotect.qProtectTryCatchTransformer;
15-
import uwu.narumi.deobfuscator.core.other.impl.qprotect.qProtectInvokeDynamicTransformer;
1612
import uwu.narumi.deobfuscator.core.other.impl.universal.pool.UniversalNumberPoolTransformer;
17-
import uwu.narumi.deobfuscator.core.other.impl.universal.TryCatchRepairTransformer;
18-
import uwu.narumi.deobfuscator.core.other.impl.universal.UniversalFlowTransformer;
19-
import uwu.narumi.deobfuscator.core.other.impl.universal.UniversalNumberTransformer;
2013

2114
/**
2215
* https://qtechnologies.dev/
@@ -33,7 +26,7 @@ public Composed_qProtectTransformer() {
3326
InlineStaticFieldTransformer::new,
3427
InlineLocalVariablesTransformer::new,
3528

36-
// Inline number pools
29+
// Inline number pools, but keep byte arrays for StrongStringEncryption
3730
UniversalNumberPoolTransformer::new,
3831
// Decrypt method invocation
3932
qProtectInvokeDynamicTransformer::new,
@@ -42,9 +35,10 @@ public Composed_qProtectTransformer() {
4235
// Fix flow
4336
UniversalFlowTransformer::new,
4437

45-
// Decrypt strings
38+
// Strings
4639
qProtectStringTransformer::new,
4740
qProtectAESStringEncryptionTransformer::new,
41+
4842
// Inline string pools
4943
UniversalStringPoolTransformer::new,
5044

@@ -63,4 +57,48 @@ public Composed_qProtectTransformer() {
6357
qProtectFieldFlowTransformer::new
6458
);
6559
}
60+
61+
public Composed_qProtectTransformer(boolean strongStrings) {
62+
super(
63+
// This fixes some weird issues where "this" is used as a local variable name.
64+
LocalVariableNamesCleanTransformer::new,
65+
66+
// Initial cleaning code from garbage
67+
UniversalNumberTransformer::new,
68+
InlineStaticFieldTransformer::new,
69+
InlineLocalVariablesTransformer::new,
70+
71+
// Inline number pools, but keep byte arrays for StrongStringEncryption
72+
UniversalNumberPoolTransformer::new,
73+
// Decrypt method invocation
74+
qProtectInvokeDynamicTransformer::new,
75+
qProtectInvokeDynamicStrongTransformer::new,
76+
77+
// Fix flow
78+
UniversalFlowTransformer::new,
79+
80+
// Inline fields again
81+
InlineStaticFieldTransformer::new,
82+
InlineLocalVariablesTransformer::new,
83+
84+
// Cleanup
85+
ComposedPeepholeCleanTransformer::new,
86+
87+
// Resolve qProtect flow that uses try-catches
88+
qProtectTryCatchTransformer::new,
89+
TryCatchRepairTransformer::new,
90+
91+
// String Strings
92+
qProtectStrongStringTransformer::new,
93+
qProtectAESStringEncryptionTransformer::new,
94+
StringBuilderTransformer::new,
95+
96+
// Inline string pools
97+
UniversalStringPoolTransformer::new,
98+
StringBuilderTransformer::new,
99+
100+
// Remove field flow after cleaning code from garbage, so we can do pattern matching
101+
qProtectFieldFlowTransformer::new
102+
);
103+
}
66104
}

deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/qprotect/qProtectInvokeDynamicStrongTransformer.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import uwu.narumi.deobfuscator.api.asm.MethodContext;
1212
import uwu.narumi.deobfuscator.api.asm.MethodRef;
1313
import uwu.narumi.deobfuscator.api.asm.matcher.Match;
14+
import uwu.narumi.deobfuscator.api.asm.matcher.group.AnyMatch;
1415
import uwu.narumi.deobfuscator.api.asm.matcher.impl.FrameMatch;
1516
import uwu.narumi.deobfuscator.api.asm.matcher.impl.MethodMatch;
1617
import uwu.narumi.deobfuscator.api.asm.matcher.impl.NewArrayMatch;
@@ -32,9 +33,10 @@ public class qProtectInvokeDynamicStrongTransformer extends Transformer {
3233

3334
private static final int XOR_KEY_ARRAY_SIZE = 16;
3435

35-
private static final Match RESOLVE_HANDLE_MATCH = MethodMatch.invokeSpecial().owner("java/lang/invoke/ConstantCallSite").name("<init>").desc("(Ljava/lang/invoke/MethodHandle;)V")
36-
.and(FrameMatch.stack(0, OpcodeMatch.of(CHECKCAST)
37-
.and(FrameMatch.stack(0, MethodMatch.invokeStatic().desc("([Ljava/lang/Object;)Ljava/lang/Object;").capture("resolveHandleMethod")))));
36+
private static final Match RESOLVE_HANDLE_MATCH = AnyMatch.of(MethodMatch.invokeSpecial().owner("java/lang/invoke/ConstantCallSite").name("<init>").desc("(Ljava/lang/invoke/MethodHandle;)V")
37+
.and(FrameMatch.stack(0, OpcodeMatch.of(CHECKCAST)
38+
.and(FrameMatch.stack(0, MethodMatch.invokeStatic().desc("([Ljava/lang/Object;)Ljava/lang/Object;").capture("resolveHandleMethod"))))),
39+
MethodMatch.invokeStatic().desc("([Ljava/lang/Object;)Ljava/lang/Object;").capture("resolveHandleMethod"));
3840

3941
private static final Match FIND_METHOD_HANDLE_MATCH = MethodMatch.invokeStatic().desc("(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
4042

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
package uwu.narumi.deobfuscator.core.other.impl.qprotect;
2+
3+
import org.objectweb.asm.tree.LdcInsnNode;
4+
import org.objectweb.asm.tree.MethodInsnNode;
5+
import org.objectweb.asm.tree.MethodNode;
6+
import org.objectweb.asm.tree.TableSwitchInsnNode;
7+
import uwu.narumi.deobfuscator.api.asm.MethodContext;
8+
import uwu.narumi.deobfuscator.api.asm.MethodRef;
9+
import uwu.narumi.deobfuscator.api.asm.matcher.Match;
10+
import uwu.narumi.deobfuscator.api.asm.matcher.MatchContext;
11+
import uwu.narumi.deobfuscator.api.asm.matcher.group.SequenceMatch;
12+
import uwu.narumi.deobfuscator.api.asm.matcher.impl.*;
13+
import uwu.narumi.deobfuscator.api.transformer.Transformer;
14+
15+
import java.util.ArrayList;
16+
import java.util.HashMap;
17+
import java.util.List;
18+
import java.util.Map;
19+
import java.util.concurrent.atomic.AtomicReference;
20+
21+
/**
22+
* Transforms encrypted strings in qProtect obfuscated code. Example here: {@link qprotect.StringStrongEncryption}
23+
*/
24+
public class qProtectStrongStringTransformer extends Transformer {
25+
26+
private static final Match DECRYPT_STRING_MATCH = MethodMatch.invokeStatic().desc("(II)Ljava/lang/String;")
27+
.and(FrameMatch.stack(0, NumberMatch.numInteger().capture("salt2")))
28+
.and(FrameMatch.stack(1, NumberMatch.numInteger().capture("salt1")));
29+
30+
@Override
31+
protected void transform() throws Exception {
32+
scopedClasses().forEach(classWrapper -> {
33+
AtomicReference<MethodRef> decryptionMethodRef = new AtomicReference<>();
34+
35+
Map<Integer, String> encryptedArray = new HashMap<>();
36+
37+
classWrapper.findClInit().ifPresent(clinit -> {
38+
if (clinit.instructions.size() > 3 && clinit.instructions.get(0).isNumber()) {
39+
MethodContext clinitContext = MethodContext.of(classWrapper, clinit);
40+
SequenceMatch.of(OpcodeMatch.of(DUP), NumberMatch.of().capture("array-index"), StringMatch.of().capture("encrypted-value"), OpcodeMatch.of(AASTORE)).findAllMatches(clinitContext).forEach(matchContext -> {
41+
encryptedArray.put(matchContext.captures().get("array-index").insn().asInteger(), matchContext.captures().get("encrypted-value").insn().asString());
42+
matchContext.removeAll();
43+
});
44+
}
45+
});
46+
if (encryptedArray.isEmpty()) return;
47+
48+
String[] decryptedArray = new String[encryptedArray.size()];
49+
50+
classWrapper.methods().forEach(methodNode -> {
51+
DECRYPT_STRING_MATCH.findAllMatches(MethodContext.of(classWrapper, methodNode)).forEach(matchContext -> {
52+
int salt1 = matchContext.captures().get("salt1").insn().asInteger();
53+
int salt2 = matchContext.captures().get("salt2").insn().asInteger();
54+
55+
MethodInsnNode decryptMethodInsn = (MethodInsnNode) matchContext.insn();
56+
decryptionMethodRef.set(MethodRef.of(decryptMethodInsn));
57+
58+
MethodNode decryptionMethod = findMethod(classWrapper.classNode(), MethodRef.of(decryptMethodInsn)).orElseThrow();
59+
60+
MethodContext decryptedMethodContext = MethodContext.of(classWrapper, decryptionMethod);
61+
List<MatchContext> numbers = NumberMatch.of().findAllMatches(decryptedMethodContext);
62+
63+
int swappedKey = -1;
64+
65+
int[] numbersArray = new int[2];
66+
for (int i = 0, hit = 0; i < numbers.size() && hit < 2; i++) {
67+
if (numbers.get(i).insn().asInteger() != -1) {
68+
numbersArray[hit] = numbers.get(i).insn().asInteger();
69+
hit++;
70+
}
71+
}
72+
int number1 = numbersArray[0];
73+
int number2 = numbersArray[1];
74+
75+
if (!encryptedArray.containsKey(swappedKey)) {
76+
int key = salt1;
77+
swappedKey = (~(key = (key | number1) & (~key | number2)) | 0xFFFF) - ~key;
78+
}
79+
if (!encryptedArray.containsKey(swappedKey)) {
80+
int key = salt1;
81+
swappedKey = (~(key = key & number1 | ~key & number2) | 0xFFFF) - ~key;
82+
}
83+
if (!encryptedArray.containsKey(swappedKey)) {
84+
int key = salt1;
85+
swappedKey = (~(key = (key | number1) & ~(key & number2)) | 0xFFFF) - ~key;
86+
}
87+
88+
if (!encryptedArray.containsKey(swappedKey)) {
89+
int key = salt1;
90+
swappedKey = (~(key = (key | number1) - (key & number1)) | 0xFFFF) - ~key;
91+
}
92+
93+
94+
if (!encryptedArray.containsKey(swappedKey)) {
95+
System.out.println(swappedKey);
96+
return;
97+
}
98+
99+
100+
int n3 = encryptedArray.get(swappedKey).toCharArray()[0];
101+
int switchCaseKey = (~n3 | 0xFF) - ~n3;
102+
int switchReturnKey;
103+
TableSwitchInsnNode table = (TableSwitchInsnNode) Match.of(ctx -> ctx.insn() instanceof TableSwitchInsnNode).findFirstMatch(decryptedMethodContext).insn();
104+
if (switchCaseKey >= table.min && switchCaseKey <= table.max) {
105+
switchReturnKey = table.labels.get(switchCaseKey).getNext().asNumber().intValue();
106+
} else {
107+
switchReturnKey = table.dflt.getNext().asNumber().intValue();
108+
}
109+
110+
String decryptedString = decrypt(encryptedArray.get(swappedKey), decryptedArray, salt1, salt2, swappedKey, switchReturnKey);
111+
methodNode.instructions.insert(matchContext.insn(), new LdcInsnNode(decryptedString));
112+
matchContext.removeAll();
113+
114+
markChange();
115+
});
116+
});
117+
118+
// Remove decryption method
119+
if (decryptionMethodRef.get() != null) {
120+
classWrapper.methods().removeIf(methodNode -> methodNode.name.equals(decryptionMethodRef.get().name()) && methodNode.desc.equals(decryptionMethodRef.get().desc()));
121+
}
122+
});
123+
}
124+
/*
125+
* n pierwszy klucz
126+
* n2 drugi klucz
127+
* n4 klucz rozszyfrowany po pierwszej matematyce losowej
128+
* n5 zwrocony przez switch klucz
129+
* */
130+
private static String decrypt(String encryptedString, String[] decryptedArray, int n, int n2, int n4, int n5) {
131+
int n3 = n;
132+
if (decryptedArray[n4] == null) {
133+
char[] cArray = encryptedString.toCharArray();
134+
135+
n3 = (short)n2;
136+
int n6 = (~n3 | 0xFF) - ~n3 + ~n5 + 1;
137+
if (n6 < 0) {
138+
n6 += 256;
139+
}
140+
n3 = (short)n2;
141+
int n7 = n5;
142+
int n8 = ((n3 = (~n3 | 0xFFFF) - ~n3 >>> 8) & ~n7) - (~n3 & n7);
143+
if (n8 < 0) {
144+
n8 += 256;
145+
}
146+
int n9 = 0;
147+
while (n9 < cArray.length) {
148+
int n10 = n9 % 2;
149+
int n11 = n9;
150+
char[] cArray2 = cArray;
151+
int n12 = cArray[n11];
152+
if (n10 == 0) {
153+
n7 = n6;
154+
n3 = n12;
155+
cArray2[n11] = (char)(n3 & ~n7 | ~n3 & n7);
156+
n7 = n6 << 5;
157+
n3 = n6 >>> 3;
158+
int n13 = (n3 & ~n7) + n7;
159+
n7 = cArray[n9];
160+
n3 = n13;
161+
n3 = (n3 | n7) & (~n3 | ~n7);
162+
n6 = (~n3 | 0xFF) - ~n3;
163+
} else {
164+
n7 = n8;
165+
n3 = n12;
166+
cArray2[n11] = (char)(n3 & ~n7 | ~n3 & n7);
167+
n7 = n8 << 5;
168+
n3 = n8 >>> 3;
169+
int n14 = (n3 & ~n7) + n7;
170+
n7 = cArray[n9];
171+
n3 = n14;
172+
n3 = (n3 | n7) - (n3 & n7);
173+
n8 = (~n3 | 0xFF) - ~n3;
174+
}
175+
++n9;
176+
}
177+
decryptedArray[n4] = new String(cArray).intern();
178+
}
179+
return decryptedArray[n4];
180+
}
181+
}

deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/universal/pool/UniversalNumberPoolTransformer.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import uwu.narumi.deobfuscator.api.asm.matcher.Match;
1212
import uwu.narumi.deobfuscator.api.asm.matcher.MatchContext;
1313
import uwu.narumi.deobfuscator.api.asm.matcher.group.AnyMatch;
14+
import uwu.narumi.deobfuscator.api.asm.matcher.group.SequenceMatch;
1415
import uwu.narumi.deobfuscator.api.asm.matcher.impl.FieldMatch;
1516
import uwu.narumi.deobfuscator.api.asm.matcher.impl.FrameMatch;
1617
import uwu.narumi.deobfuscator.api.asm.matcher.impl.InsnMatch;
@@ -24,6 +25,7 @@
2425

2526
import java.util.Objects;
2627
import java.util.Set;
28+
import java.util.concurrent.atomic.AtomicBoolean;
2729

2830
/**
2931
* Transforms number pool references to actual values.
Binary file not shown.

0 commit comments

Comments
 (0)