Skip to content

Commit 0fa7abd

Browse files
committed
qprotect transformers 😎
1 parent a516024 commit 0fa7abd

27 files changed

Lines changed: 2185 additions & 25 deletions

File tree

‎deobfuscator-api/src/main/java/org/objectweb/asm/tree/AbstractInsnNode.java‎

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@
3838
import org.jetbrains.annotations.Nullable;
3939
import org.objectweb.asm.MethodVisitor;
4040
import org.objectweb.asm.Opcodes;
41+
import org.objectweb.asm.tree.analysis.Frame;
42+
import org.objectweb.asm.tree.analysis.OriginalSourceValue;
43+
import org.objectweb.asm.tree.analysis.SourceValue;
44+
import org.objectweb.asm.tree.analysis.Value;
4145
import uwu.narumi.deobfuscator.api.asm.NamedOpcodes;
4246
import org.objectweb.asm.Type;
4347

@@ -652,10 +656,10 @@ public String namedOpcode() {
652656
/**
653657
* Returns the number of stack values required by this instruction.
654658
*/
655-
public int getRequiredStackValuesCount() {
656-
return switch (this.opcode) {
659+
public int getRequiredStackValuesCount(Frame<? extends Value> frame) {
660+
return switch (this.getOpcode()) {
657661
// Unary operations (one value)
658-
case ISTORE, LSTORE, FSTORE, DSTORE, ASTORE, POP, POP2, DUP, DUP_X1, DUP_X2, DUP2, DUP2_X1, DUP2_X2, SWAP, INEG,
662+
case ISTORE, LSTORE, FSTORE, DSTORE, ASTORE, POP, DUP, DUP_X1, SWAP, INEG,
659663
LNEG, FNEG, DNEG, I2L, I2F, I2D, L2I, L2F, L2D, F2I, F2L, F2D, D2I, D2L, D2F, I2B, I2C, I2S, IFEQ, IFNE,
660664
IFLT, IFGE, IFGT, IFLE, TABLESWITCH, LOOKUPSWITCH, IRETURN, LRETURN, FRETURN, DRETURN, ARETURN, PUTSTATIC,
661665
GETFIELD, NEWARRAY, ANEWARRAY, ARRAYLENGTH, ATHROW, CHECKCAST, INSTANCEOF, MONITORENTER, MONITOREXIT, IFNULL,
@@ -673,6 +677,32 @@ public int getRequiredStackValuesCount() {
673677
// Multi-dimensional array creation
674678
case MULTIANEWARRAY -> ((MultiANewArrayInsnNode) this).dims;
675679

680+
// Dynamic forms (uses frame)
681+
case POP2, DUP2 -> {
682+
Value sourceValue = frame.getStack(frame.getStackSize() - 1);
683+
yield sourceValue.getSize() == 2 ? 1 : 2;
684+
}
685+
case DUP_X2 -> {
686+
Value sourceValue2 = frame.getStack(frame.getStackSize() - 2);
687+
yield sourceValue2.getSize() == 2 ? 2 : 3;
688+
}
689+
case DUP2_X1 -> {
690+
Value sourceValue1 = frame.getStack(frame.getStackSize() - 1);
691+
yield sourceValue1.getSize() == 2 ? 2 : 3;
692+
}
693+
case DUP2_X2 -> {
694+
Value sourceValue1 = frame.getStack(frame.getStackSize() - 1);
695+
Value sourceValue2 = frame.getStack(frame.getStackSize() - 2);
696+
if (sourceValue1.getSize() == 2 && sourceValue2.getSize() == 2) {
697+
yield 2;
698+
}
699+
Value sourceValue3 = frame.getStack(frame.getStackSize() - 2);
700+
if (sourceValue1.getSize() == 2 || sourceValue3.getSize() == 2) {
701+
yield 3;
702+
}
703+
yield 4;
704+
}
705+
676706
// No values required
677707
default -> 0;
678708
};

‎deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/asm/InsnContext.java‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,16 @@ public MethodContext methodContext() {
4747
return methodContext;
4848
}
4949

50+
public int getRequiredStackValuesCount() {
51+
return this.insn.getRequiredStackValuesCount(this.frame());
52+
}
53+
5054
/**
5155
* Places POPs instructions before current instruction to remove source values from the stack.
5256
* This method automatically calculates how many stack values to pop.
5357
*/
5458
public void placePops() {
55-
for (int i = 0; i < this.insn.getRequiredStackValuesCount(); i++) {
59+
for (int i = 0; i < this.getRequiredStackValuesCount(); i++) {
5660
int stackValueIdx = frame().getStackSize() - (i + 1);
5761
OriginalSourceValue sourceValue = frame().getStack(stackValueIdx);
5862

‎deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/asm/matcher/Match.java‎

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

33
import org.jetbrains.annotations.ApiStatus;
4+
import org.jetbrains.annotations.Nullable;
45
import org.objectweb.asm.tree.AbstractInsnNode;
56
import uwu.narumi.deobfuscator.api.asm.InsnContext;
67
import uwu.narumi.deobfuscator.api.asm.MethodContext;
@@ -30,10 +31,6 @@ public boolean matches(InsnContext insnContext) {
3031
return this.matchResult(insnContext) != null;
3132
}
3233

33-
public boolean matches(MethodContext methodContext) {
34-
return !this.findAllMatches(methodContext).isEmpty();
35-
}
36-
3734
/**
3835
* Matches the instruction and merges if successful
3936
*
@@ -70,6 +67,7 @@ public List<MatchContext> findAllMatches(MethodContext methodContext) {
7067
return allMatches;
7168
}
7269

70+
@Nullable
7371
public MatchContext findFirstMatch(MethodContext methodContext) {
7472
return this.findAllMatches(methodContext).stream().findFirst().orElse(null);
7573
}

‎deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/asm/matcher/impl/FrameMatch.java‎

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -55,22 +55,12 @@ protected boolean test(MatchContext context) {
5555
return false;
5656
}
5757

58-
if (this.match instanceof SkipMatch) {
59-
// Skip match earlier
60-
return true;
61-
}
62-
6358
Frame<OriginalSourceValue> frame = context.frame();
6459
sourceValue = frame.getStack(index);
6560
if (stackFrameOptions.originalValue) {
6661
sourceValue = sourceValue.originalSource;
6762
}
6863
} else if (this.options instanceof LocalVariableFrameOptions lvFrameOptions) {
69-
if (this.match instanceof SkipMatch) {
70-
// Skip match earlier
71-
return true;
72-
}
73-
7464
Frame<OriginalSourceValue> frame = context.frame();
7565
sourceValue = frame.getLocal(lvFrameOptions.localVariableIdx);
7666
} else {

‎deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/helper/MethodHelper.java‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ public static Map<AbstractInsnNode, Frame<BasicValue>> analyzeBasic(
110110
* Computes a map which corresponds to: source value producer -> consumers
111111
*
112112
* @param frames Frames of the method
113+
* @return A map where keys are instructions that produce values and values are
114+
* * sets of instructions that consume those produced values
113115
*/
114116
public static Map<AbstractInsnNode, Set<AbstractInsnNode>> computeConsumersMap(Map<AbstractInsnNode, Frame<OriginalSourceValue>> frames) {
115117
Map<AbstractInsnNode, Set<AbstractInsnNode>> consumers = new HashMap<>();
@@ -119,9 +121,12 @@ public static Map<AbstractInsnNode, Set<AbstractInsnNode>> computeConsumersMap(M
119121
if (frame == null) continue;
120122

121123
// Loop through stack values and add consumer to them
122-
for (int i = 0; i < consumer.getRequiredStackValuesCount(); i++) {
124+
for (int i = 0; i < consumer.getRequiredStackValuesCount(frame); i++) {
125+
// Get the value from the stack (first consumed value is at the top)
123126
OriginalSourceValue sourceValue = frame.getStack(frame.getStackSize() - (i + 1));
127+
124128
for (AbstractInsnNode producer : sourceValue.insns) {
129+
// Add this consumer to the set of consumers for the producer instruction
125130
consumers.computeIfAbsent(producer, k -> new HashSet<>()).add(consumer);
126131
}
127132
}

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import uwu.narumi.deobfuscator.core.other.composed.ComposedHP888Transformer;
44
import uwu.narumi.deobfuscator.core.other.composed.ComposedUnknownObf1Transformer;
55
import uwu.narumi.deobfuscator.core.other.composed.ComposedZelixTransformer;
6+
import uwu.narumi.deobfuscator.core.other.composed.Composed_qProtectTransformer;
67
import uwu.narumi.deobfuscator.core.other.composed.general.ComposedGeneralFlowTransformer;
78
import uwu.narumi.deobfuscator.core.other.composed.general.ComposedPeepholeCleanTransformer;
89
import uwu.narumi.deobfuscator.core.other.impl.clean.peephole.JsrInlinerTransformer;
@@ -152,6 +153,12 @@ protected void registerAll() {
152153
.input(OutputType.MULTIPLE_CLASSES, InputType.CUSTOM_CLASS, "hp888")
153154
.register();
154155

156+
// qProtect
157+
test("qProtect Sample 1")
158+
.transformers(Composed_qProtectTransformer::new)
159+
.input(OutputType.MULTIPLE_CLASSES, InputType.CUSTOM_CLASS, "qprotect/sample1")
160+
.register();
161+
155162
test("POP2 Sample")
156163
.transformers(UselessPopCleanTransformer::new)
157164
.input(OutputType.SINGLE_CLASS, InputType.CUSTOM_CLASS, "Pop2Sample.class")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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.pool.InlineStaticFieldTransformer;
7+
import uwu.narumi.deobfuscator.core.other.impl.qprotect.qProtectStringPoolTransformer;
8+
import uwu.narumi.deobfuscator.core.other.impl.qprotect.qProtectStringTransformer;
9+
import uwu.narumi.deobfuscator.core.other.impl.qprotect.qProtectTryCatchTransformer;
10+
import uwu.narumi.deobfuscator.core.other.impl.qprotect.qProtectInvokeDynamicTransformer;
11+
import uwu.narumi.deobfuscator.core.other.impl.qprotect.qProtectNumberPoolTransformer;
12+
import uwu.narumi.deobfuscator.core.other.impl.universal.TryCatchRepairTransformer;
13+
import uwu.narumi.deobfuscator.core.other.impl.universal.UniversalFlowTransformer;
14+
import uwu.narumi.deobfuscator.core.other.impl.universal.UniversalNumberTransformer;
15+
16+
public class Composed_qProtectTransformer extends ComposedTransformer {
17+
public Composed_qProtectTransformer() {
18+
super(
19+
// This fixes some weird issues where "this" is used as a local variable name.
20+
LocalVariableNamesCleanTransformer::new,
21+
22+
// Initial cleaning code from garbage
23+
UniversalNumberTransformer::new,
24+
InlineStaticFieldTransformer::new,
25+
26+
// Inline number pools
27+
qProtectNumberPoolTransformer::new,
28+
// Decrypt method invocation
29+
qProtectInvokeDynamicTransformer::new,
30+
31+
// Resolve qProtect flow that uses try-catches
32+
qProtectTryCatchTransformer::new,
33+
TryCatchRepairTransformer::new,
34+
UniversalFlowTransformer::new,
35+
36+
// Decrypt strings
37+
qProtectStringTransformer::new,
38+
// Inline string pools
39+
qProtectStringPoolTransformer::new,
40+
41+
// Inline fields again
42+
InlineStaticFieldTransformer::new,
43+
44+
// Cleanup
45+
ComposedPeepholeCleanTransformer::new
46+
);
47+
}
48+
}

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

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package uwu.narumi.deobfuscator.core.other.impl.clean.peephole;
22

3+
import org.jetbrains.annotations.Nullable;
34
import org.objectweb.asm.tree.AbstractInsnNode;
45
import org.objectweb.asm.tree.MethodNode;
56
import org.objectweb.asm.tree.analysis.OriginalSourceValue;
@@ -43,7 +44,7 @@ private boolean tryRemovePop(InsnContext insnContext) {
4344
AbstractInsnNode insn = insnContext.insn();
4445
OriginalSourceValue firstValue = insnContext.frame().getStack(insnContext.frame().getStackSize() - 1);
4546

46-
if (!canPop(firstValue, insnContext.methodContext())) return false;
47+
if (!canPop(firstValue, insnContext.methodContext(), null)) return false;
4748

4849
if (insn.getOpcode() == POP) {
4950
// Pop the value from the stack
@@ -65,7 +66,7 @@ private boolean tryRemovePop(InsnContext insnContext) {
6566
}
6667

6768
// Return if we can't remove the source value
68-
if (!canPop(secondValue, insnContext.methodContext())) return false;
69+
if (!canPop(secondValue, insnContext.methodContext(), firstValue)) return false;
6970

7071
// Pop
7172
popSourceValue(firstValue, insnContext.methodNode());
@@ -79,8 +80,12 @@ private boolean tryRemovePop(InsnContext insnContext) {
7980

8081
/**
8182
* Checks if source value can be popped
83+
*
84+
* @param sourceValue Source value
85+
* @param methodContext Method context
86+
* @param poppedInPair Source value that was popped in pair with this value
8287
*/
83-
private boolean canPop(OriginalSourceValue sourceValue, MethodContext methodContext) {
88+
private boolean canPop(OriginalSourceValue sourceValue, MethodContext methodContext, @Nullable OriginalSourceValue poppedInPair) {
8489
if (sourceValue.insns.isEmpty()) {
8590
// Nothing to remove. Probably a local variable
8691
return false;
@@ -92,7 +97,9 @@ private boolean canPop(OriginalSourceValue sourceValue, MethodContext methodCont
9297
if (poppedInsns.contains(producer)) return false;
9398

9499
Set<AbstractInsnNode> consumers = methodContext.getConsumersMap().get(producer);
95-
if (consumers.stream().anyMatch(insn -> insn.getOpcode() != POP && insn.getOpcode() != POP2)) {
100+
if (consumers.stream().anyMatch(insn -> insn.getOpcode() != POP &&
101+
insn.getOpcode() != POP2 && (poppedInPair == null || !poppedInPair.insns.contains(insn)))
102+
) {
96103
// If the value is consumed by another instruction, we can't remove it
97104
return false;
98105
}

0 commit comments

Comments
 (0)