Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -172,8 +172,8 @@ protected void registerAll() {
.inputClassesDir(InputType.CUSTOM_CLASS, "qprotect/sample2")
.register();

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

Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,15 @@
package uwu.narumi.deobfuscator.core.other.composed;

import uwu.narumi.deobfuscator.api.transformer.ComposedTransformer;
import uwu.narumi.deobfuscator.core.other.composed.general.ComposedGeneralFlowTransformer;
import uwu.narumi.deobfuscator.core.other.composed.general.ComposedPeepholeCleanTransformer;
import uwu.narumi.deobfuscator.core.other.impl.clean.LocalVariableNamesCleanTransformer;
import uwu.narumi.deobfuscator.core.other.impl.pool.InlineLocalVariablesTransformer;
import uwu.narumi.deobfuscator.core.other.impl.pool.InlineStaticFieldTransformer;
import uwu.narumi.deobfuscator.core.other.impl.qprotect.qProtectAESStringEncryptionTransformer;
import uwu.narumi.deobfuscator.core.other.impl.qprotect.qProtectFieldFlowTransformer;
import uwu.narumi.deobfuscator.core.other.impl.qprotect.qProtectInvokeDynamicStrongTransformer;
import uwu.narumi.deobfuscator.core.other.impl.universal.SourceFileNameRecoverTransformer;
import uwu.narumi.deobfuscator.core.other.impl.qprotect.*;
import uwu.narumi.deobfuscator.core.other.impl.universal.*;
import uwu.narumi.deobfuscator.core.other.impl.universal.pool.UniversalStringPoolTransformer;
import uwu.narumi.deobfuscator.core.other.impl.qprotect.qProtectStringTransformer;
import uwu.narumi.deobfuscator.core.other.impl.qprotect.qProtectTryCatchTransformer;
import uwu.narumi.deobfuscator.core.other.impl.qprotect.qProtectInvokeDynamicTransformer;
import uwu.narumi.deobfuscator.core.other.impl.universal.pool.UniversalNumberPoolTransformer;
import uwu.narumi.deobfuscator.core.other.impl.universal.TryCatchRepairTransformer;
import uwu.narumi.deobfuscator.core.other.impl.universal.UniversalFlowTransformer;
import uwu.narumi.deobfuscator.core.other.impl.universal.UniversalNumberTransformer;

/**
* https://qtechnologies.dev/
Expand All @@ -33,7 +26,7 @@ public Composed_qProtectTransformer() {
InlineStaticFieldTransformer::new,
InlineLocalVariablesTransformer::new,

// Inline number pools
// Inline number pools, but keep byte arrays for StrongStringEncryption
UniversalNumberPoolTransformer::new,
// Decrypt method invocation
qProtectInvokeDynamicTransformer::new,
Expand All @@ -42,9 +35,10 @@ public Composed_qProtectTransformer() {
// Fix flow
UniversalFlowTransformer::new,

// Decrypt strings
// Strings
qProtectStringTransformer::new,
qProtectAESStringEncryptionTransformer::new,

// Inline string pools
UniversalStringPoolTransformer::new,

Expand All @@ -63,4 +57,48 @@ public Composed_qProtectTransformer() {
qProtectFieldFlowTransformer::new
);
}

public Composed_qProtectTransformer(boolean strongStrings) {
super(
// This fixes some weird issues where "this" is used as a local variable name.
LocalVariableNamesCleanTransformer::new,

// Initial cleaning code from garbage
UniversalNumberTransformer::new,
InlineStaticFieldTransformer::new,
InlineLocalVariablesTransformer::new,

// Inline number pools, but keep byte arrays for StrongStringEncryption
UniversalNumberPoolTransformer::new,
// Decrypt method invocation
qProtectInvokeDynamicTransformer::new,
qProtectInvokeDynamicStrongTransformer::new,

// Fix flow
UniversalFlowTransformer::new,

// Inline fields again
InlineStaticFieldTransformer::new,
InlineLocalVariablesTransformer::new,

// Cleanup
ComposedPeepholeCleanTransformer::new,

// Resolve qProtect flow that uses try-catches
qProtectTryCatchTransformer::new,
TryCatchRepairTransformer::new,

// String Strings
qProtectStrongStringTransformer::new,
qProtectAESStringEncryptionTransformer::new,
StringBuilderTransformer::new,

// Inline string pools
UniversalStringPoolTransformer::new,
StringBuilderTransformer::new,

// Remove field flow after cleaning code from garbage, so we can do pattern matching
qProtectFieldFlowTransformer::new
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import uwu.narumi.deobfuscator.api.asm.MethodContext;
import uwu.narumi.deobfuscator.api.asm.MethodRef;
import uwu.narumi.deobfuscator.api.asm.matcher.Match;
import uwu.narumi.deobfuscator.api.asm.matcher.group.AnyMatch;
import uwu.narumi.deobfuscator.api.asm.matcher.impl.FrameMatch;
import uwu.narumi.deobfuscator.api.asm.matcher.impl.MethodMatch;
import uwu.narumi.deobfuscator.api.asm.matcher.impl.NewArrayMatch;
Expand All @@ -32,9 +33,10 @@ public class qProtectInvokeDynamicStrongTransformer extends Transformer {

private static final int XOR_KEY_ARRAY_SIZE = 16;

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

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;");

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
package uwu.narumi.deobfuscator.core.other.impl.qprotect;

import org.objectweb.asm.tree.LdcInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.TableSwitchInsnNode;
import uwu.narumi.deobfuscator.api.asm.MethodContext;
import uwu.narumi.deobfuscator.api.asm.MethodRef;
import uwu.narumi.deobfuscator.api.asm.matcher.Match;
import uwu.narumi.deobfuscator.api.asm.matcher.MatchContext;
import uwu.narumi.deobfuscator.api.asm.matcher.group.SequenceMatch;
import uwu.narumi.deobfuscator.api.asm.matcher.impl.*;
import uwu.narumi.deobfuscator.api.transformer.Transformer;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;

/**
* Transforms encrypted strings in qProtect obfuscated code. Example here: {@link qprotect.StringStrongEncryption}
*/
public class qProtectStrongStringTransformer extends Transformer {

private static final Match DECRYPT_STRING_MATCH = MethodMatch.invokeStatic().desc("(II)Ljava/lang/String;")
.and(FrameMatch.stack(0, NumberMatch.numInteger().capture("salt2")))
.and(FrameMatch.stack(1, NumberMatch.numInteger().capture("salt1")));

@Override
protected void transform() throws Exception {
scopedClasses().forEach(classWrapper -> {
AtomicReference<MethodRef> decryptionMethodRef = new AtomicReference<>();

Map<Integer, String> encryptedArray = new HashMap<>();

classWrapper.findClInit().ifPresent(clinit -> {
if (clinit.instructions.size() > 3 && clinit.instructions.get(0).isNumber()) {
MethodContext clinitContext = MethodContext.of(classWrapper, clinit);
SequenceMatch.of(OpcodeMatch.of(DUP), NumberMatch.of().capture("array-index"), StringMatch.of().capture("encrypted-value"), OpcodeMatch.of(AASTORE)).findAllMatches(clinitContext).forEach(matchContext -> {
encryptedArray.put(matchContext.captures().get("array-index").insn().asInteger(), matchContext.captures().get("encrypted-value").insn().asString());
matchContext.removeAll();
});
}
});
if (encryptedArray.isEmpty()) return;

String[] decryptedArray = new String[encryptedArray.size()];

classWrapper.methods().forEach(methodNode -> {
DECRYPT_STRING_MATCH.findAllMatches(MethodContext.of(classWrapper, methodNode)).forEach(matchContext -> {
int salt1 = matchContext.captures().get("salt1").insn().asInteger();
int salt2 = matchContext.captures().get("salt2").insn().asInteger();

MethodInsnNode decryptMethodInsn = (MethodInsnNode) matchContext.insn();
decryptionMethodRef.set(MethodRef.of(decryptMethodInsn));

MethodNode decryptionMethod = findMethod(classWrapper.classNode(), MethodRef.of(decryptMethodInsn)).orElseThrow();

MethodContext decryptedMethodContext = MethodContext.of(classWrapper, decryptionMethod);
List<MatchContext> numbers = NumberMatch.of().findAllMatches(decryptedMethodContext);

int swappedKey = -1;

int[] numbersArray = new int[2];
for (int i = 0, hit = 0; i < numbers.size() && hit < 2; i++) {
if (numbers.get(i).insn().asInteger() != -1) {
numbersArray[hit] = numbers.get(i).insn().asInteger();
hit++;
}
}
int number1 = numbersArray[0];
int number2 = numbersArray[1];

if (!encryptedArray.containsKey(swappedKey)) {
int key = salt1;
swappedKey = (~(key = (key | number1) & (~key | number2)) | 0xFFFF) - ~key;
}
if (!encryptedArray.containsKey(swappedKey)) {
int key = salt1;
swappedKey = (~(key = key & number1 | ~key & number2) | 0xFFFF) - ~key;
}
if (!encryptedArray.containsKey(swappedKey)) {
int key = salt1;
swappedKey = (~(key = (key | number1) & ~(key & number2)) | 0xFFFF) - ~key;
}

if (!encryptedArray.containsKey(swappedKey)) {
int key = salt1;
swappedKey = (~(key = (key | number1) - (key & number1)) | 0xFFFF) - ~key;
}


if (!encryptedArray.containsKey(swappedKey)) {
System.out.println(swappedKey);
return;
}


int n3 = encryptedArray.get(swappedKey).toCharArray()[0];
int switchCaseKey = (~n3 | 0xFF) - ~n3;
int switchReturnKey;
TableSwitchInsnNode table = (TableSwitchInsnNode) Match.of(ctx -> ctx.insn() instanceof TableSwitchInsnNode).findFirstMatch(decryptedMethodContext).insn();
if (switchCaseKey >= table.min && switchCaseKey <= table.max) {
switchReturnKey = table.labels.get(switchCaseKey).getNext().asNumber().intValue();
} else {
switchReturnKey = table.dflt.getNext().asNumber().intValue();
}

String decryptedString = decrypt(encryptedArray.get(swappedKey), decryptedArray, salt1, salt2, swappedKey, switchReturnKey);
methodNode.instructions.insert(matchContext.insn(), new LdcInsnNode(decryptedString));
matchContext.removeAll();

markChange();
});
});

// Remove decryption method
if (decryptionMethodRef.get() != null) {
classWrapper.methods().removeIf(methodNode -> methodNode.name.equals(decryptionMethodRef.get().name()) && methodNode.desc.equals(decryptionMethodRef.get().desc()));
}
});
}
/*
* n pierwszy klucz
* n2 drugi klucz
* n4 klucz rozszyfrowany po pierwszej matematyce losowej
* n5 zwrocony przez switch klucz
* */
private static String decrypt(String encryptedString, String[] decryptedArray, int n, int n2, int n4, int n5) {
int n3 = n;
if (decryptedArray[n4] == null) {
char[] cArray = encryptedString.toCharArray();

n3 = (short)n2;
int n6 = (~n3 | 0xFF) - ~n3 + ~n5 + 1;
if (n6 < 0) {
n6 += 256;
}
n3 = (short)n2;
int n7 = n5;
int n8 = ((n3 = (~n3 | 0xFFFF) - ~n3 >>> 8) & ~n7) - (~n3 & n7);
if (n8 < 0) {
n8 += 256;
}
int n9 = 0;
while (n9 < cArray.length) {
int n10 = n9 % 2;
int n11 = n9;
char[] cArray2 = cArray;
int n12 = cArray[n11];
if (n10 == 0) {
n7 = n6;
n3 = n12;
cArray2[n11] = (char)(n3 & ~n7 | ~n3 & n7);
n7 = n6 << 5;
n3 = n6 >>> 3;
int n13 = (n3 & ~n7) + n7;
n7 = cArray[n9];
n3 = n13;
n3 = (n3 | n7) & (~n3 | ~n7);
n6 = (~n3 | 0xFF) - ~n3;
} else {
n7 = n8;
n3 = n12;
cArray2[n11] = (char)(n3 & ~n7 | ~n3 & n7);
n7 = n8 << 5;
n3 = n8 >>> 3;
int n14 = (n3 & ~n7) + n7;
n7 = cArray[n9];
n3 = n14;
n3 = (n3 | n7) - (n3 & n7);
n8 = (~n3 | 0xFF) - ~n3;
}
++n9;
}
decryptedArray[n4] = new String(cArray).intern();
}
return decryptedArray[n4];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import uwu.narumi.deobfuscator.api.asm.matcher.Match;
import uwu.narumi.deobfuscator.api.asm.matcher.MatchContext;
import uwu.narumi.deobfuscator.api.asm.matcher.group.AnyMatch;
import uwu.narumi.deobfuscator.api.asm.matcher.group.SequenceMatch;
import uwu.narumi.deobfuscator.api.asm.matcher.impl.FieldMatch;
import uwu.narumi.deobfuscator.api.asm.matcher.impl.FrameMatch;
import uwu.narumi.deobfuscator.api.asm.matcher.impl.InsnMatch;
Expand All @@ -24,6 +25,7 @@

import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;

/**
* Transforms number pool references to actual values.
Expand Down
Binary file not shown.
Loading
Loading