Skip to content

Commit 535ca27

Browse files
committed
improve UselessPopCleanTransformer by expanding dups first, add grunt sample
1 parent e6a01e6 commit 535ca27

File tree

15 files changed

+522
-30
lines changed

15 files changed

+522
-30
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -643,7 +643,7 @@ public String namedOpcode() {
643643
public int getConsumedStackValuesCount(Frame<? extends Value> frame) {
644644
return switch (this.getOpcode()) {
645645
// Unary operations (one value)
646-
case ISTORE, LSTORE, FSTORE, DSTORE, ASTORE, POP, DUP, DUP_X1, SWAP, INEG,
646+
case ISTORE, LSTORE, FSTORE, DSTORE, ASTORE, POP, DUP, INEG,
647647
LNEG, FNEG, DNEG, I2L, I2F, I2D, L2I, L2F, L2D, F2I, F2L, F2D, D2I, D2L, D2F, I2B, I2C, I2S, IFEQ, IFNE,
648648
IFLT, IFGE, IFGT, IFLE, TABLESWITCH, LOOKUPSWITCH, IRETURN, LRETURN, FRETURN, DRETURN, ARETURN, PUTSTATIC,
649649
GETFIELD, NEWARRAY, ANEWARRAY, ARRAYLENGTH, ATHROW, CHECKCAST, INSTANCEOF, MONITORENTER, MONITOREXIT, IFNULL,
@@ -652,7 +652,7 @@ public int getConsumedStackValuesCount(Frame<? extends Value> frame) {
652652
case IALOAD, LALOAD, FALOAD, DALOAD, AALOAD, BALOAD, CALOAD, SALOAD, IADD, LADD, FADD, DADD, ISUB, LSUB, FSUB,
653653
DSUB, IMUL, LMUL, FMUL, DMUL, IDIV, LDIV, FDIV, DDIV, IREM, LREM, FREM, DREM, ISHL, LSHL, ISHR, LSHR, IUSHR,
654654
LUSHR, IAND, LAND, IOR, LOR, IXOR, LXOR, LCMP, FCMPL, FCMPG, DCMPL, DCMPG, IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT,
655-
IF_ICMPGE, IF_ICMPGT, IF_ICMPLE, IF_ACMPEQ, IF_ACMPNE, PUTFIELD -> 2;
655+
IF_ICMPGE, IF_ICMPGT, IF_ICMPLE, IF_ACMPEQ, IF_ACMPNE, PUTFIELD, SWAP, DUP_X1 -> 2;
656656
// Ternary operations (three values)
657657
case IASTORE, LASTORE, FASTORE, DASTORE, AASTORE, BASTORE, CASTORE, SASTORE -> 3;
658658

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

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

3+
import uwu.narumi.deobfuscator.api.transformer.ComposedTransformer;
34
import uwu.narumi.deobfuscator.core.other.composed.ComposedHP888Transformer;
45
import uwu.narumi.deobfuscator.core.other.composed.ComposedSuperblaubeereTransformer;
56
import uwu.narumi.deobfuscator.core.other.composed.ComposedUnknownObf1Transformer;
@@ -11,6 +12,7 @@
1112
import uwu.narumi.deobfuscator.core.other.impl.clean.peephole.UselessPopCleanTransformer;
1213
import uwu.narumi.deobfuscator.core.other.impl.pool.InlineLocalVariablesTransformer;
1314
import uwu.narumi.deobfuscator.core.other.impl.pool.InlineStaticFieldTransformer;
15+
import uwu.narumi.deobfuscator.core.other.impl.universal.AccessRepairTransformer;
1416
import uwu.narumi.deobfuscator.core.other.impl.universal.RemapperTransformer;
1517
import uwu.narumi.deobfuscator.core.other.impl.universal.StringBuilderTransformer;
1618
import uwu.narumi.deobfuscator.core.other.impl.universal.UniversalNumberTransformer;
@@ -171,6 +173,17 @@ protected void registerAll() {
171173
.input(OutputType.MULTIPLE_CLASSES, InputType.CUSTOM_CLASS, "sb27/sample1")
172174
.register();
173175

176+
// Grunt
177+
test("Grunt Sample 1")
178+
.transformers(
179+
AccessRepairTransformer::new,
180+
() -> new ComposedTransformer(true,
181+
ComposedGeneralFlowTransformer::new,
182+
InlineStaticFieldTransformer::new
183+
))
184+
.input(OutputType.MULTIPLE_CLASSES, InputType.CUSTOM_JAR, "grunt-obf.jar")
185+
.register();
186+
174187
test("POP2 Sample")
175188
.transformers(UselessPopCleanTransformer::new)
176189
.input(OutputType.SINGLE_CLASS, InputType.CUSTOM_CLASS, "Pop2Sample.class")
Lines changed: 309 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,309 @@
1+
package uwu.narumi.deobfuscator.core.other.impl.clean.peephole;
2+
3+
import org.jetbrains.annotations.Nullable;
4+
import org.objectweb.asm.tree.AbstractInsnNode;
5+
import org.objectweb.asm.tree.MethodNode;
6+
import org.objectweb.asm.tree.analysis.Frame;
7+
import org.objectweb.asm.tree.analysis.OriginalSourceValue;
8+
import uwu.narumi.deobfuscator.api.asm.InsnContext;
9+
import uwu.narumi.deobfuscator.api.asm.MethodContext;
10+
import uwu.narumi.deobfuscator.api.transformer.Transformer;
11+
12+
/**
13+
* This transformer expands DUP-related instructions (DUP, DUP_X1, DUP_X2, DUP2, DUP2_X1, DUP2_X2) and SWAP
14+
* by replacing them with clones of the instructions that produced the values on the stack.
15+
* This simplification is often effective after constant propagation or other optimizations have made
16+
* the producers constant values (e.g., LDC, BIPUSH).
17+
*
18+
* <p>Example 1:
19+
*
20+
* <p>Input:
21+
* <pre>
22+
* bipush 10
23+
* dup
24+
* invokestatic foo (I)V
25+
* invokestatic bar (I)V
26+
* </pre>
27+
*
28+
* <p>Output:
29+
* <pre>
30+
* bipush 10
31+
* bipush 10 // cloned value
32+
* invokestatic foo (I)V
33+
* invokestatic bar (I)V
34+
* </pre>
35+
*
36+
* Example 2 (DUP_X2 - Form 1: value1, value2, value3 -> value1, value3, value2, value1):
37+
*
38+
* <p>Input:
39+
* <pre>
40+
* bipush 10 // value3
41+
* bipush 20 // value2
42+
* bipush 30 // value1
43+
* dup_x2
44+
* invokestatic someMethod(III)V // Consumes value1, value2, value3
45+
* invokestatic anotherMethod(I)V // Consumes original value1
46+
* </pre>
47+
*
48+
* <p>Output:
49+
* <pre>
50+
* bipush 10 // value3
51+
* bipush 20 // value2
52+
* bipush 30 // value1
53+
* pop
54+
* pop
55+
* pop
56+
* bipush 30 // cloned value1
57+
* bipush 10 // cloned value3
58+
* bipush 20 // cloned value2
59+
* bipush 30 // cloned value1
60+
* invokestatic someMethod(III)V
61+
* invokestatic anotherMethod(I)V
62+
* </pre>
63+
*
64+
*/
65+
public class ExpandDupsTransformer extends Transformer {
66+
@Override
67+
protected void transform() throws Exception {
68+
scopedClasses().forEach(classWrapper -> classWrapper.methods().forEach(methodNode -> {
69+
MethodContext methodContext = MethodContext.of(classWrapper, methodNode);
70+
71+
for (AbstractInsnNode insn : methodNode.instructions.toArray()) {
72+
// Try to expand DUP-related instructions
73+
boolean expanded = expandDup(methodContext, insn);
74+
75+
if (expanded) {
76+
methodNode.instructions.remove(insn);
77+
markChange();
78+
}
79+
}
80+
}));
81+
}
82+
83+
/**
84+
* Expands DUP-related instructions in a method's bytecode.
85+
*
86+
* @return Is expanded
87+
*/
88+
// This is a mess XD
89+
private boolean expandDup(MethodContext methodContext, AbstractInsnNode insn) {
90+
MethodNode methodNode = methodContext.methodNode();
91+
InsnContext insnContext = methodContext.at(insn);
92+
Frame<OriginalSourceValue> frame = insnContext.frame();
93+
if (frame == null) return false;
94+
95+
if (insn.getOpcode() == DUP) {
96+
OriginalSourceValue value1 = frame.getStack(frame.getStackSize() - 1);
97+
AbstractInsnNode producer1 = checkAndGetProducer(value1);
98+
if (producer1 == null) return false;
99+
100+
// Expand
101+
methodNode.instructions.insert(insn, producer1.clone(null));
102+
103+
return true;
104+
} else if (insn.getOpcode() == DUP_X1) {
105+
OriginalSourceValue value1 = frame.getStack(frame.getStackSize() - 1);
106+
AbstractInsnNode producer1 = checkAndGetProducer(value1);
107+
if (producer1 == null) return false;
108+
109+
OriginalSourceValue value2 = frame.getStack(frame.getStackSize() - 2);
110+
AbstractInsnNode producer2 = checkAndGetProducer(value2);
111+
if (producer2 == null) return false;
112+
113+
insnContext.placePops();
114+
115+
// Expand
116+
methodNode.instructions.insert(insn, producer1.clone(null));
117+
methodNode.instructions.insert(insn, producer2.clone(null));
118+
methodNode.instructions.insert(insn, producer1.clone(null));
119+
120+
return true;
121+
} else if (insn.getOpcode() == DUP_X2) {
122+
OriginalSourceValue value1 = frame.getStack(frame.getStackSize() - 1);
123+
AbstractInsnNode producer1 = checkAndGetProducer(value1);
124+
if (producer1 == null) return false;
125+
126+
OriginalSourceValue value2 = frame.getStack(frame.getStackSize() - 2);
127+
AbstractInsnNode producer2 = checkAndGetProducer(value2);
128+
if (producer2 == null) return false;
129+
130+
// Forms
131+
if (value1.getSize() == 1 && value2.getSize() == 2) {
132+
insnContext.placePops();
133+
134+
// Expand
135+
methodNode.instructions.insert(insn, producer1.clone(null));
136+
methodNode.instructions.insert(insn, producer2.clone(null));
137+
methodNode.instructions.insert(insn, producer1.clone(null));
138+
139+
return true;
140+
} else {
141+
OriginalSourceValue value3 = frame.getStack(frame.getStackSize() - 3);
142+
AbstractInsnNode producer3 = checkAndGetProducer(value3);
143+
if (producer3 == null) return false;
144+
145+
if (value1.getSize() == 1 && value2.getSize() == 1 && value3.getSize() == 1) {
146+
insnContext.placePops();
147+
148+
// Expand
149+
methodNode.instructions.insert(insn, producer1.clone(null));
150+
methodNode.instructions.insert(insn, producer3.clone(null));
151+
methodNode.instructions.insert(insn, producer2.clone(null));
152+
methodNode.instructions.insert(insn, producer1.clone(null));
153+
154+
return true;
155+
}
156+
}
157+
} else if (insn.getOpcode() == DUP2) {
158+
OriginalSourceValue value1 = frame.getStack(frame.getStackSize() - 1);
159+
AbstractInsnNode producer1 = checkAndGetProducer(value1);
160+
if (producer1 == null) return false;
161+
162+
// Forms
163+
if (value1.getSize() == 2) {
164+
// Expand
165+
methodNode.instructions.insert(insn, producer1.clone(null));
166+
167+
return true;
168+
} else if (value1.getSize() == 1) {
169+
OriginalSourceValue value2 = frame.getStack(frame.getStackSize() - 2);
170+
AbstractInsnNode producer2 = checkAndGetProducer(value2);
171+
if (producer2 == null) return false;
172+
173+
// Expand
174+
methodNode.instructions.insert(insn, producer2.clone(null));
175+
methodNode.instructions.insert(insn, producer1.clone(null));
176+
177+
return true;
178+
}
179+
} else if (insn.getOpcode() == DUP2_X1) {
180+
OriginalSourceValue value1 = frame.getStack(frame.getStackSize() - 1);
181+
AbstractInsnNode producer1 = checkAndGetProducer(value1);
182+
if (producer1 == null) return false;
183+
184+
OriginalSourceValue value2 = frame.getStack(frame.getStackSize() - 2);
185+
AbstractInsnNode producer2 = checkAndGetProducer(value2);
186+
if (producer2 == null) return false;
187+
188+
// Forms
189+
if (value1.getSize() == 2 && value2.getSize() == 1) {
190+
insnContext.placePops();
191+
192+
// Expand
193+
methodNode.instructions.insert(insn, producer1.clone(null));
194+
methodNode.instructions.insert(insn, producer2.clone(null));
195+
methodNode.instructions.insert(insn, producer1.clone(null));
196+
197+
return true;
198+
} else {
199+
OriginalSourceValue value3 = frame.getStack(frame.getStackSize() - 3);
200+
AbstractInsnNode producer3 = checkAndGetProducer(value3);
201+
if (producer3 == null) return false;
202+
203+
if (value1.getSize() == 1 && value2.getSize() == 1 && value3.getSize() == 1) {
204+
insnContext.placePops();
205+
206+
// Expand
207+
methodNode.instructions.insert(insn, producer2.clone(null));
208+
methodNode.instructions.insert(insn, producer1.clone(null));
209+
methodNode.instructions.insert(insn, producer3.clone(null));
210+
methodNode.instructions.insert(insn, producer2.clone(null));
211+
methodNode.instructions.insert(insn, producer1.clone(null));
212+
213+
return true;
214+
}
215+
}
216+
} else if (insn.getOpcode() == DUP2_X2) {
217+
OriginalSourceValue value1 = frame.getStack(frame.getStackSize() - 1);
218+
AbstractInsnNode producer1 = checkAndGetProducer(value1);
219+
if (producer1 == null) return false;
220+
221+
OriginalSourceValue value2 = frame.getStack(frame.getStackSize() - 2);
222+
AbstractInsnNode producer2 = checkAndGetProducer(value2);
223+
if (producer2 == null) return false;
224+
225+
// Forms
226+
if (value1.getSize() == 2 && value2.getSize() == 2) {
227+
insnContext.placePops();
228+
229+
// Expand
230+
methodNode.instructions.insert(insn, producer1.clone(null));
231+
methodNode.instructions.insert(insn, producer2.clone(null));
232+
methodNode.instructions.insert(insn, producer1.clone(null));
233+
234+
return true;
235+
} else {
236+
OriginalSourceValue value3 = frame.getStack(frame.getStackSize() - 3);
237+
AbstractInsnNode producer3 = checkAndGetProducer(value3);
238+
if (producer3 == null) return false;
239+
240+
if (value1.getSize() == 1 && value2.getSize() == 1 && value3.getSize() == 2) {
241+
insnContext.placePops();
242+
243+
// Expand
244+
methodNode.instructions.insert(insn, producer2.clone(null));
245+
methodNode.instructions.insert(insn, producer1.clone(null));
246+
methodNode.instructions.insert(insn, producer3.clone(null));
247+
methodNode.instructions.insert(insn, producer2.clone(null));
248+
methodNode.instructions.insert(insn, producer1.clone(null));
249+
250+
return true;
251+
} else if (value1.getSize() == 2 && value2.getSize() == 1 && value3.getSize() == 1) {
252+
insnContext.placePops();
253+
254+
// Expand
255+
methodNode.instructions.insert(insn, producer1.clone(null));
256+
methodNode.instructions.insert(insn, producer3.clone(null));
257+
methodNode.instructions.insert(insn, producer2.clone(null));
258+
methodNode.instructions.insert(insn, producer1.clone(null));
259+
260+
return true;
261+
} else {
262+
OriginalSourceValue value4 = frame.getStack(frame.getStackSize() - 4);
263+
AbstractInsnNode producer4 = checkAndGetProducer(value4);
264+
if (producer4 == null) return false;
265+
266+
if (value1.getSize() == 1 && value2.getSize() == 2 && value3.getSize() == 1 && value4.getSize() == 1) {
267+
insnContext.placePops();
268+
269+
// Expand
270+
methodNode.instructions.insert(insn, producer2.clone(null));
271+
methodNode.instructions.insert(insn, producer1.clone(null));
272+
methodNode.instructions.insert(insn, producer4.clone(null));
273+
methodNode.instructions.insert(insn, producer3.clone(null));
274+
methodNode.instructions.insert(insn, producer2.clone(null));
275+
methodNode.instructions.insert(insn, producer1.clone(null));
276+
277+
return true;
278+
}
279+
}
280+
}
281+
} else if (insn.getOpcode() == SWAP) {
282+
OriginalSourceValue value1 = frame.getStack(frame.getStackSize() - 1);
283+
AbstractInsnNode producer1 = checkAndGetProducer(value1);
284+
if (producer1 == null) return false;
285+
286+
OriginalSourceValue value2 = frame.getStack(frame.getStackSize() - 2);
287+
AbstractInsnNode producer2 = checkAndGetProducer(value2);
288+
if (producer2 == null) return false;
289+
290+
insnContext.placePops();
291+
292+
// Swap
293+
methodNode.instructions.insert(insn, producer2.clone(null));
294+
methodNode.instructions.insert(insn, producer1.clone(null));
295+
296+
return true;
297+
}
298+
299+
return false;
300+
}
301+
302+
@Nullable
303+
private AbstractInsnNode checkAndGetProducer(OriginalSourceValue sourceValue) {
304+
if (sourceValue.originalSource.isOneWayProduced() && sourceValue.originalSource.getProducer().isConstant()) {
305+
return sourceValue.originalSource.getProducer();
306+
}
307+
return null;
308+
}
309+
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ public UselessPopCleanTransformer() {
2323

2424
@Override
2525
protected void transform() throws Exception {
26+
// Expand DUPs first
27+
Transformer.transform(ExpandDupsTransformer::new, scope(), context());
28+
2629
FramedInstructionsStream.of(this)
2730
.editInstructionsStream(stream -> stream.filter(insn -> insn.getOpcode() == POP || insn.getOpcode() == POP2))
2831
.forEach(insnContext -> {
56 KB
Binary file not shown.

0 commit comments

Comments
 (0)