Skip to content

Commit d9de32d

Browse files
committed
inline pure functions transformer, sb27 complete deobf, partial grunt deobf
1 parent 87743e1 commit d9de32d

File tree

9 files changed

+274
-64
lines changed

9 files changed

+274
-64
lines changed

deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/helper/AsmMathHelper.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@ public final class AsmMathHelper {
2828
IFLT, value -> value < 0,
2929
IFGE, value -> value >= 0,
3030
IFGT, value -> value > 0,
31-
IFLE, value -> value <= 0
31+
IFLE, value -> value <= 0,
32+
IFNULL, Objects::isNull,
33+
IFNONNULL, Objects::nonNull
3234
);
3335

3436
private static final Map<Integer, BiPredicate<Integer, Integer>> TWO_VALUES_VALUE_CONDITION_PREDICATES = Map.of(
@@ -136,11 +138,11 @@ public static Number mathUnaryOperation(Number number, int opcode) {
136138
return MATH_UNARY_OPERATIONS.get(opcode).apply(number);
137139
}
138140

139-
public static boolean isOneValueCondition(int opcode) {
141+
public static boolean isOneValueCondition(Integer opcode) {
140142
return ONE_VALUE_CONDITION_PREDICATES.containsKey(opcode);
141143
}
142144

143-
public static boolean condition(int value, int opcode) {
145+
public static boolean condition(Integer value, int opcode) {
144146
return ONE_VALUE_CONDITION_PREDICATES.get(opcode).test(value);
145147
}
146148

@@ -171,6 +173,13 @@ public static Optional<Boolean> predictIf(JumpInsnNode jumpInsn, Frame<OriginalS
171173
jumpInsn.getOpcode() // Opcode
172174
);
173175

176+
return Optional.of(ifResult);
177+
} else if (constantValue.get() == null) {
178+
boolean ifResult = AsmMathHelper.condition(
179+
null, // Value
180+
jumpInsn.getOpcode() // Opcode
181+
);
182+
174183
return Optional.of(ifResult);
175184
}
176185
} else if (AsmMathHelper.isTwoValuesCondition(jumpInsn.getOpcode())) {

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

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

3-
import uwu.narumi.deobfuscator.api.transformer.ComposedTransformer;
3+
import uwu.narumi.deobfuscator.core.other.composed.ComposedGruntTransformer;
44
import uwu.narumi.deobfuscator.core.other.composed.ComposedHP888Transformer;
55
import uwu.narumi.deobfuscator.core.other.composed.ComposedSuperblaubeereTransformer;
66
import uwu.narumi.deobfuscator.core.other.composed.ComposedUnknownObf1Transformer;
@@ -12,7 +12,6 @@
1212
import uwu.narumi.deobfuscator.core.other.impl.clean.peephole.UselessPopCleanTransformer;
1313
import uwu.narumi.deobfuscator.core.other.impl.pool.InlineLocalVariablesTransformer;
1414
import uwu.narumi.deobfuscator.core.other.impl.pool.InlineStaticFieldTransformer;
15-
import uwu.narumi.deobfuscator.core.other.impl.universal.AccessRepairTransformer;
1615
import uwu.narumi.deobfuscator.core.other.impl.universal.RemapperTransformer;
1716
import uwu.narumi.deobfuscator.core.other.impl.universal.StringBuilderTransformer;
1817
import uwu.narumi.deobfuscator.core.other.impl.universal.UniversalNumberTransformer;
@@ -190,12 +189,7 @@ protected void registerAll() {
190189

191190
// Grunt
192191
test("Grunt Sample 1")
193-
.transformers(
194-
AccessRepairTransformer::new,
195-
() -> new ComposedTransformer(true,
196-
ComposedGeneralFlowTransformer::new,
197-
InlineStaticFieldTransformer::new
198-
))
192+
.transformers(ComposedGruntTransformer::new)
199193
.input(OutputType.MULTIPLE_CLASSES, InputType.CUSTOM_JAR, "grunt-obf.jar")
200194
.register();
201195

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
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.ComposedGeneralFlowTransformer;
5+
import uwu.narumi.deobfuscator.core.other.impl.pool.InlineStaticFieldTransformer;
6+
import uwu.narumi.deobfuscator.core.other.impl.universal.AccessRepairTransformer;
7+
import uwu.narumi.deobfuscator.core.other.impl.universal.InlinePureFunctionsTransformer;
8+
9+
/**
10+
* https://github.com/SpartanB312/Grunt
11+
*/
12+
// TODO: String encryption
13+
public class ComposedGruntTransformer extends ComposedTransformer {
14+
public ComposedGruntTransformer() {
15+
super(
16+
// Repair access
17+
AccessRepairTransformer::new,
18+
() -> new ComposedTransformer(true,
19+
// Fix flow
20+
ComposedGeneralFlowTransformer::new,
21+
InlineStaticFieldTransformer::new,
22+
// Inline pure functions
23+
InlinePureFunctionsTransformer::new
24+
)
25+
);
26+
}
27+
}

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import uwu.narumi.deobfuscator.api.transformer.ComposedTransformer;
44
import uwu.narumi.deobfuscator.core.other.composed.general.ComposedPeepholeCleanTransformer;
55
import uwu.narumi.deobfuscator.core.other.impl.clean.LocalVariableNamesCleanTransformer;
6+
import uwu.narumi.deobfuscator.core.other.impl.pool.InlineLocalVariablesTransformer;
7+
import uwu.narumi.deobfuscator.core.other.impl.universal.InlinePureFunctionsTransformer;
68
import uwu.narumi.deobfuscator.core.other.impl.universal.pool.UniversalNumberPoolTransformer;
79
import uwu.narumi.deobfuscator.core.other.impl.universal.pool.UniversalStringPoolTransformer;
810
import uwu.narumi.deobfuscator.core.other.impl.sb27.SuperblaubeereStringTransformer;
@@ -11,7 +13,6 @@
1113
/**
1214
* https://github.com/superblaubeere27/obfuscator
1315
*/
14-
// TODO: Transform comparison methods
1516
public class ComposedSuperblaubeereTransformer extends ComposedTransformer {
1617
public ComposedSuperblaubeereTransformer() {
1718
super(
@@ -27,6 +28,9 @@ public ComposedSuperblaubeereTransformer() {
2728
SuperblaubeereStringTransformer::new,
2829
UniversalStringPoolTransformer::new,
2930

31+
InlinePureFunctionsTransformer::new,
32+
InlineLocalVariablesTransformer::new,
33+
3034
// Cleanup
3135
ComposedPeepholeCleanTransformer::new
3236
);

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

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,30 @@
11
package uwu.narumi.deobfuscator.core.other.impl.pool;
22

33
import org.objectweb.asm.tree.AbstractInsnNode;
4+
import org.objectweb.asm.tree.IincInsnNode;
45
import org.objectweb.asm.tree.VarInsnNode;
56
import org.objectweb.asm.tree.analysis.OriginalSourceValue;
7+
import uwu.narumi.deobfuscator.api.asm.MethodContext;
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.VarLoadMatch;
611
import uwu.narumi.deobfuscator.api.helper.FramedInstructionsStream;
712
import uwu.narumi.deobfuscator.api.transformer.Transformer;
813

14+
import java.util.HashMap;
15+
import java.util.Map;
16+
917
/**
1018
* Inlines constant local variables
1119
*/
1220
public class InlineLocalVariablesTransformer extends Transformer {
21+
private static final Match VAR_STORE_LOAD_PAIR = SequenceMatch.of(
22+
// Store
23+
Match.of(ctx -> ctx.insn().isVarStore()).capture("store"),
24+
// Load
25+
Match.of(ctx -> ctx.insn().isVarLoad()).capture("load")
26+
);
27+
1328
@Override
1429
protected void transform() throws Exception {
1530
FramedInstructionsStream.of(this)
@@ -33,5 +48,39 @@ protected void transform() throws Exception {
3348
markChange();
3449
}
3550
});
51+
52+
// Find all variable store and load pairs
53+
scopedClasses().forEach(classWrapper -> classWrapper.methods().forEach(methodNode -> {
54+
// Count var usages
55+
Map<Integer, Integer> varUsages = new HashMap<>(); // var index -> usage count
56+
for (AbstractInsnNode insn : methodNode.instructions.toArray()) {
57+
if (insn instanceof VarInsnNode && insn.isVarLoad()) {
58+
varUsages.merge(((VarInsnNode) insn).var, 1, Integer::sum);
59+
} else if (insn instanceof IincInsnNode) {
60+
varUsages.merge(((IincInsnNode) insn).var, 1, Integer::sum);
61+
}
62+
}
63+
64+
// Now we can find pairs
65+
MethodContext methodContext = MethodContext.of(classWrapper, methodNode);
66+
VAR_STORE_LOAD_PAIR.findAllMatches(methodContext).forEach(match -> {
67+
VarInsnNode varLoadInsn = (VarInsnNode) match.captures().get("load").insn();
68+
VarInsnNode varStoreInsn = (VarInsnNode) match.captures().get("store").insn();
69+
if (varLoadInsn.var != varStoreInsn.var) {
70+
// If they are not the same variable, we cannot inline
71+
return;
72+
}
73+
int varIndex = varLoadInsn.var;
74+
if (varUsages.getOrDefault(varIndex, 0) > 1) {
75+
// If the variable is used more than once, we cannot inline
76+
return;
77+
}
78+
79+
// Remove the variable completely and use stack value instead
80+
match.removeAll();
81+
82+
markChange();
83+
});
84+
}));
3685
}
3786
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
package uwu.narumi.deobfuscator.core.other.impl.universal;
2+
3+
import org.objectweb.asm.Opcodes;
4+
import org.objectweb.asm.Type;
5+
import org.objectweb.asm.tree.AbstractInsnNode;
6+
import org.objectweb.asm.tree.IincInsnNode;
7+
import org.objectweb.asm.tree.JumpInsnNode;
8+
import org.objectweb.asm.tree.LabelNode;
9+
import org.objectweb.asm.tree.MethodInsnNode;
10+
import org.objectweb.asm.tree.MethodNode;
11+
import org.objectweb.asm.tree.VarInsnNode;
12+
import uwu.narumi.deobfuscator.api.asm.MethodRef;
13+
import uwu.narumi.deobfuscator.api.transformer.Transformer;
14+
15+
import java.util.Arrays;
16+
import java.util.HashSet;
17+
import java.util.List;
18+
import java.util.Map;
19+
import java.util.Set;
20+
import java.util.stream.Collectors;
21+
22+
/**
23+
* Transformer that inlines pure functions, which are functions that do not have side effects
24+
* and always return the same result for the same input.
25+
*/
26+
public class InlinePureFunctionsTransformer extends Transformer {
27+
// List of opcodes that may cause a side effect.
28+
private static final List<Integer> IMPURE_OPCODES = List.of(
29+
GETFIELD,
30+
GETSTATIC,
31+
INVOKEDYNAMIC,
32+
INVOKEINTERFACE,
33+
INVOKESPECIAL,
34+
INVOKESTATIC,
35+
INVOKEVIRTUAL,
36+
NEW,
37+
PUTFIELD,
38+
PUTSTATIC
39+
);
40+
41+
private final boolean removePureMethods;
42+
43+
public InlinePureFunctionsTransformer() {
44+
this(true);
45+
}
46+
47+
public InlinePureFunctionsTransformer(boolean removePureMethods) {
48+
this.removePureMethods = removePureMethods;
49+
}
50+
51+
@Override
52+
protected void transform() throws Exception {
53+
Set<MethodRef> pureMethods = new HashSet<>();
54+
scopedClasses().forEach(classWrapper -> classWrapper.methods().forEach(methodNode -> {
55+
if (methodNode.instructions.size() == 0) {
56+
return; // Skip empty methods
57+
}
58+
if (methodNode.name.equals("<init>") || methodNode.name.equals("<clinit>")) {
59+
return; // Skip constructors and class initializers
60+
}
61+
62+
boolean isPure = Arrays.stream(methodNode.instructions.toArray()).noneMatch(insn -> IMPURE_OPCODES.contains(insn.getOpcode()));
63+
64+
if (isPure) {
65+
LOGGER.info("Detected pure function: {}#{}{}", classWrapper.name(), methodNode.name, methodNode.desc);
66+
67+
pureMethods.add(MethodRef.of(classWrapper.classNode(), methodNode));
68+
}
69+
}));
70+
71+
// Inline pure methods
72+
scopedClasses().forEach(classWrapper -> classWrapper.methods().forEach(methodNode -> {
73+
for (AbstractInsnNode insn : methodNode.instructions.toArray()) {
74+
if (insn instanceof MethodInsnNode methodInsn) {
75+
MethodRef methodCallRef = MethodRef.of(methodInsn);
76+
if (pureMethods.contains(methodCallRef)) {
77+
LOGGER.info("Inlining pure method: {} in {}#{}{}", methodCallRef, classWrapper.name(), methodNode.name, methodNode.desc);
78+
79+
// Inline the pure method
80+
inlinePureMethod(methodNode, methodCallRef, methodInsn);
81+
82+
markChange();
83+
}
84+
}
85+
}
86+
}));
87+
88+
// Remove pure methods
89+
if (removePureMethods) pureMethods.forEach(pureMethodRef -> context().removeMethod(pureMethodRef));
90+
}
91+
92+
/**
93+
* Inline a pure method into the caller method.
94+
*/
95+
private void inlinePureMethod(MethodNode callerMethod, MethodRef pureMethodRef, MethodInsnNode methodCallerInsn) {
96+
MethodNode pureMethod = context().getMethodContext(pureMethodRef).orElseThrow().methodNode();
97+
98+
// Insert var stores
99+
int nextVarIndex = 0;
100+
AbstractInsnNode anchorInsn = methodCallerInsn;
101+
for (Type parameter : Type.getArgumentTypes(pureMethod.desc)) {
102+
// Insert variable instructions for each parameter
103+
int varIndex = callerMethod.maxLocals + nextVarIndex;
104+
VarInsnNode insn = new VarInsnNode(parameter.getOpcode(Opcodes.ISTORE), varIndex);
105+
106+
callerMethod.instructions.insertBefore(anchorInsn, insn);
107+
108+
// Move to the next variable index
109+
nextVarIndex += parameter.getSize();
110+
anchorInsn = insn; // Anchor is important to insert instructions in the correct order
111+
}
112+
113+
// Clone labels
114+
Map<LabelNode, LabelNode> clonedLabels = Arrays.stream(pureMethod.instructions.toArray())
115+
.filter(insn -> insn instanceof LabelNode)
116+
.map(labelNode -> (LabelNode) labelNode)
117+
.collect(Collectors.toMap(
118+
labelNode -> labelNode,
119+
labelNode -> new LabelNode()
120+
));
121+
122+
// Prepare end label
123+
LabelNode endLabel = new LabelNode();
124+
// Insert end label at the end of the caller method
125+
callerMethod.instructions.insert(methodCallerInsn, endLabel);
126+
127+
// Copy the pure method instructions into the caller method
128+
for (AbstractInsnNode pureInsn : pureMethod.instructions.toArray()) {
129+
AbstractInsnNode clonedInsn = pureInsn.clone(clonedLabels);
130+
131+
// Remap the variable index
132+
if (clonedInsn instanceof VarInsnNode varInsn) {
133+
varInsn.var += callerMethod.maxLocals;
134+
} else if (clonedInsn instanceof IincInsnNode iincInsn) {
135+
iincInsn.var += callerMethod.maxLocals;
136+
}
137+
138+
if (clonedInsn.getOpcode() >= IRETURN && clonedInsn.getOpcode() <= RETURN) {
139+
// Instead of the return, insert jump to the end label
140+
callerMethod.instructions.insertBefore(methodCallerInsn, new JumpInsnNode(GOTO, endLabel));
141+
} else {
142+
// Clone instruction as it is
143+
callerMethod.instructions.insertBefore(methodCallerInsn, clonedInsn);
144+
}
145+
}
146+
147+
// Remove the method call instruction
148+
callerMethod.instructions.remove(methodCallerInsn);
149+
150+
// Recompute max locals
151+
callerMethod.maxLocals = computeMaxLocals(callerMethod);
152+
}
153+
154+
private static int computeMaxLocals(final MethodNode method) {
155+
int maxLocals = Type.getArgumentsAndReturnSizes(method.desc) >> 2;
156+
if ((method.access & Opcodes.ACC_STATIC) != 0) {
157+
maxLocals -= 1;
158+
}
159+
for (AbstractInsnNode insnNode : method.instructions) {
160+
if (insnNode instanceof VarInsnNode) {
161+
int local = ((VarInsnNode) insnNode).var;
162+
int size =
163+
(insnNode.getOpcode() == Opcodes.LLOAD
164+
|| insnNode.getOpcode() == Opcodes.DLOAD
165+
|| insnNode.getOpcode() == Opcodes.LSTORE
166+
|| insnNode.getOpcode() == Opcodes.DSTORE)
167+
? 2
168+
: 1;
169+
maxLocals = Math.max(maxLocals, local + size);
170+
} else if (insnNode instanceof IincInsnNode) {
171+
int local = ((IincInsnNode) insnNode).var;
172+
maxLocals = Math.max(maxLocals, local + 1);
173+
}
174+
}
175+
return maxLocals;
176+
}
177+
}

testData/results/custom-classes/sb27/sample1/ConfigComplete.dec

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,10 @@ import org.bukkit.command.TabCompleter;
66
import org.bukkit.util.StringUtil;
77

88
public class ConfigComplete implements TabCompleter {
9-
private static boolean lIlllIlIIl(int var0, int var1) {
10-
return var0 < var1;
11-
}
12-
13-
private static boolean lIlllIlIII(int var0, int var1) {
14-
return var0 == var1;
15-
}
16-
179
public List<String> onTabComplete(CommandSender var1, Command var2, String var3, String[] var4) {
1810
ArrayList var5 = new ArrayList();
1911
ArrayList var6 = new ArrayList();
20-
if (lIlllIlIII(var4.length, 1)) {
12+
if (var4.length == 1) {
2113
var6.add("doBedExplode");
2214
var6.add("doBrutesSpawn");
2315
var6.add("oldPiglinDrop");

0 commit comments

Comments
 (0)