diff --git a/deobfuscator-impl/src/main/java/uwu/narumi/deobfuscator/Deobfuscator.java b/deobfuscator-impl/src/main/java/uwu/narumi/deobfuscator/Deobfuscator.java index 59db8b8f..314e0674 100644 --- a/deobfuscator-impl/src/main/java/uwu/narumi/deobfuscator/Deobfuscator.java +++ b/deobfuscator-impl/src/main/java/uwu/narumi/deobfuscator/Deobfuscator.java @@ -37,6 +37,10 @@ public static Deobfuscator from(DeobfuscatorOptions options) { private final Context context; private Deobfuscator(DeobfuscatorOptions options) { + if (options.inputJar() != null && Files.notExists(options.inputJar())) { + throw new IllegalArgumentException("Input jar does not exist"); + } + if (options.outputJar() != null && Files.exists(options.outputJar())) { LOGGER.warn("Output file already exist, data will be overwritten"); } diff --git a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/universal/UniversalFlowTransformer.java b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/universal/UniversalFlowTransformer.java index 96c33849..6e8e6d8c 100644 --- a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/universal/UniversalFlowTransformer.java +++ b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/universal/UniversalFlowTransformer.java @@ -1,110 +1,14 @@ package uwu.narumi.deobfuscator.core.other.impl.universal; -import org.objectweb.asm.tree.AbstractInsnNode; -import org.objectweb.asm.tree.JumpInsnNode; -import org.objectweb.asm.tree.MethodNode; -import org.objectweb.asm.tree.analysis.Frame; -import org.objectweb.asm.tree.analysis.OriginalSourceValue; -import uwu.narumi.deobfuscator.api.asm.ClassWrapper; -import uwu.narumi.deobfuscator.api.context.Context; -import uwu.narumi.deobfuscator.api.helper.AsmMathHelper; -import uwu.narumi.deobfuscator.api.transformer.Transformer; - -import java.util.Map; - -public class UniversalFlowTransformer extends Transformer { - - private boolean changed = false; - - @Override - protected boolean transform(ClassWrapper scope, Context context) throws Exception { - context.classes(scope).forEach(classWrapper -> classWrapper.methods().forEach(methodNode -> { - simplifyJumpInstructions(classWrapper, methodNode); - })); - - return changed; - } - - // TODO: Add LOOKUPSWITCH and TABLESWITCH - private void simplifyJumpInstructions(ClassWrapper classWrapper, MethodNode methodNode) { - Map> frames = analyzeOriginalSource(classWrapper.getClassNode(), methodNode); - if (frames == null) return; - - // Simplify 'jump' instructions - for (AbstractInsnNode insn : methodNode.instructions.toArray()) { - if (AsmMathHelper.isOneValueCondition(insn.getOpcode())) { - // One-value if statement - - Frame frame = frames.get(insn); - if (frame == null) continue; - - JumpInsnNode jumpInsn = (JumpInsnNode) insn; - - // Get instruction from stack that is passed to if statement - OriginalSourceValue sourceValue = frame.getStack(frame.getStackSize() - 1); - if (!sourceValue.originalSource.isOneWayProduced()) continue; - - AbstractInsnNode valueInsn = sourceValue.originalSource.getProducer(); - - // Process if statement - if (valueInsn.isInteger()) { - boolean ifResult = AsmMathHelper.condition( - valueInsn.asInteger(), // Value - jumpInsn.getOpcode() // Opcode - ); - - // Correctly transform redundant if statement - processRedundantIfStatement(methodNode, jumpInsn, ifResult); - - // Cleanup value - methodNode.instructions.remove(sourceValue.getProducer()); - - changed = true; - } - } else if (AsmMathHelper.isTwoValuesCondition(insn.getOpcode())) { - // Two-value if statements - - Frame frame = frames.get(insn); - if (frame == null) continue; - - JumpInsnNode jumpInsn = (JumpInsnNode) insn; - - // Get instructions from stack that are passed to if statement - OriginalSourceValue sourceValue1 = frame.getStack(frame.getStackSize() - 2); - OriginalSourceValue sourceValue2 = frame.getStack(frame.getStackSize() - 1); - if (!sourceValue1.originalSource.isOneWayProduced() || !sourceValue2.originalSource.isOneWayProduced()) continue; - - AbstractInsnNode valueInsn1 = sourceValue1.originalSource.getProducer(); - AbstractInsnNode valueInsn2 = sourceValue2.originalSource.getProducer(); - - // Process if statement - if (valueInsn1.isInteger() && valueInsn2.isInteger()) { - boolean ifResult = AsmMathHelper.condition( - valueInsn1.asInteger(), // First value - valueInsn2.asInteger(), // Second value - jumpInsn.getOpcode() // Opcode - ); - - // Correctly transform redundant if statement - processRedundantIfStatement(methodNode, jumpInsn, ifResult); - - // Cleanup values - methodNode.instructions.remove(sourceValue1.getProducer()); - methodNode.instructions.remove(sourceValue2.getProducer()); - - changed = true; - } - } - } - } - - private void processRedundantIfStatement(MethodNode methodNode, JumpInsnNode ifStatement, boolean ifResult) { - if (!ifResult) { - // Remove unreachable if statement - methodNode.instructions.remove(ifStatement); - } else { - // Replace always reachable if statement with GOTO - ifStatement.setOpcode(GOTO); - } +import uwu.narumi.deobfuscator.api.transformer.ComposedTransformer; +import uwu.narumi.deobfuscator.core.other.impl.universal.flow.JumpFlowTransformer; +import uwu.narumi.deobfuscator.core.other.impl.universal.flow.SwitchFlowTransformer; + +public class UniversalFlowTransformer extends ComposedTransformer { + public UniversalFlowTransformer() { + super( + JumpFlowTransformer::new, + SwitchFlowTransformer::new + ); } } diff --git a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/universal/flow/JumpFlowTransformer.java b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/universal/flow/JumpFlowTransformer.java new file mode 100644 index 00000000..2f1c413e --- /dev/null +++ b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/universal/flow/JumpFlowTransformer.java @@ -0,0 +1,85 @@ +package uwu.narumi.deobfuscator.core.other.impl.universal.flow; + +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.JumpInsnNode; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.analysis.Frame; +import org.objectweb.asm.tree.analysis.OriginalSourceValue; +import uwu.narumi.deobfuscator.api.asm.ClassWrapper; +import uwu.narumi.deobfuscator.api.helper.AsmMathHelper; +import uwu.narumi.deobfuscator.api.transformer.FramedInstructionsTransformer; + +public class JumpFlowTransformer extends FramedInstructionsTransformer { + @Override + protected boolean transformInstruction(ClassWrapper classWrapper, MethodNode methodNode, AbstractInsnNode insn, Frame frame) { + if (AsmMathHelper.isOneValueCondition(insn.getOpcode())) { + // One-value if statement + + JumpInsnNode jumpInsn = (JumpInsnNode) insn; + + // Get instruction from stack that is passed to if statement + OriginalSourceValue sourceValue = frame.getStack(frame.getStackSize() - 1); + if (!sourceValue.originalSource.isOneWayProduced()) return false; + + AbstractInsnNode valueInsn = sourceValue.originalSource.getProducer(); + + // Process if statement + if (valueInsn.isInteger()) { + boolean ifResult = AsmMathHelper.condition( + valueInsn.asInteger(), // Value + jumpInsn.getOpcode() // Opcode + ); + + // Correctly transform redundant if statement + processRedundantIfStatement(methodNode, jumpInsn, ifResult); + + // Cleanup value + methodNode.instructions.remove(sourceValue.getProducer()); + + return true; + } + } else if (AsmMathHelper.isTwoValuesCondition(insn.getOpcode())) { + // Two-value if statements + + JumpInsnNode jumpInsn = (JumpInsnNode) insn; + + // Get instructions from stack that are passed to if statement + OriginalSourceValue sourceValue1 = frame.getStack(frame.getStackSize() - 2); + OriginalSourceValue sourceValue2 = frame.getStack(frame.getStackSize() - 1); + if (!sourceValue1.originalSource.isOneWayProduced() || !sourceValue2.originalSource.isOneWayProduced()) return false; + + AbstractInsnNode valueInsn1 = sourceValue1.originalSource.getProducer(); + AbstractInsnNode valueInsn2 = sourceValue2.originalSource.getProducer(); + + // Process if statement + if (valueInsn1.isInteger() && valueInsn2.isInteger()) { + boolean ifResult = AsmMathHelper.condition( + valueInsn1.asInteger(), // First value + valueInsn2.asInteger(), // Second value + jumpInsn.getOpcode() // Opcode + ); + + // Correctly transform redundant if statement + processRedundantIfStatement(methodNode, jumpInsn, ifResult); + + // Cleanup values + methodNode.instructions.remove(sourceValue1.getProducer()); + methodNode.instructions.remove(sourceValue2.getProducer()); + + return true; + } + } + + return false; + } + + private void processRedundantIfStatement(MethodNode methodNode, JumpInsnNode ifStatement, boolean ifResult) { + if (!ifResult) { + // Remove unreachable if statement + methodNode.instructions.remove(ifStatement); + } else { + // Replace always reachable if statement with GOTO + ifStatement.setOpcode(GOTO); + } + } +} diff --git a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/universal/flow/SwitchFlowTransformer.java b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/universal/flow/SwitchFlowTransformer.java new file mode 100644 index 00000000..1bf5e233 --- /dev/null +++ b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/universal/flow/SwitchFlowTransformer.java @@ -0,0 +1,72 @@ +package uwu.narumi.deobfuscator.core.other.impl.universal.flow; + +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.JumpInsnNode; +import org.objectweb.asm.tree.LabelNode; +import org.objectweb.asm.tree.LookupSwitchInsnNode; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.TableSwitchInsnNode; +import org.objectweb.asm.tree.analysis.Frame; +import org.objectweb.asm.tree.analysis.OriginalSourceValue; +import uwu.narumi.deobfuscator.api.asm.ClassWrapper; +import uwu.narumi.deobfuscator.api.transformer.FramedInstructionsTransformer; + +/** + * Simplify LOOKUPSWITCH and TABLESWITCH instructions + */ +public class SwitchFlowTransformer extends FramedInstructionsTransformer { + + @Override + protected boolean transformInstruction(ClassWrapper classWrapper, MethodNode methodNode, AbstractInsnNode insn, Frame frame) { + if (insn.getOpcode() == LOOKUPSWITCH) { + LookupSwitchInsnNode lookupSwitchInsn = (LookupSwitchInsnNode) insn; + + OriginalSourceValue sourceValue = frame.getStack(frame.getStackSize() - 1); + if (!sourceValue.originalSource.isOneWayProduced()) return false; + + AbstractInsnNode valueInsn = sourceValue.originalSource.getProducer(); + if (valueInsn.isInteger()) { + int value = valueInsn.asInteger(); + int index = lookupSwitchInsn.keys.indexOf(value); + + if (index == -1) { + // Jump to default + methodNode.instructions.set(lookupSwitchInsn, new JumpInsnNode(GOTO, lookupSwitchInsn.dflt)); + } else { + // Match found! Jump to target + LabelNode targetLabel = lookupSwitchInsn.labels.get(index); + methodNode.instructions.set(lookupSwitchInsn, new JumpInsnNode(GOTO, targetLabel)); + } + // Remove value from stack + methodNode.instructions.remove(sourceValue.getProducer()); + + return true; + } + } else if (insn.getOpcode() == TABLESWITCH) { + TableSwitchInsnNode tableSwitchInsn = (TableSwitchInsnNode) insn; + + OriginalSourceValue sourceValue = frame.getStack(frame.getStackSize() - 1); + if (!sourceValue.originalSource.isOneWayProduced()) return false; + + AbstractInsnNode valueInsn = sourceValue.originalSource.getProducer(); + if (valueInsn.isInteger()) { + int value = valueInsn.asInteger(); + int index = value - tableSwitchInsn.min; + + if (index < 0 || index >= tableSwitchInsn.labels.size()) { + // Jump to default + methodNode.instructions.set(tableSwitchInsn, new JumpInsnNode(GOTO, tableSwitchInsn.dflt)); + } else { + // Match found! Jump to target + LabelNode targetLabel = tableSwitchInsn.labels.get(index); + methodNode.instructions.set(tableSwitchInsn, new JumpInsnNode(GOTO, targetLabel)); + } + // Remove value from stack + methodNode.instructions.remove(sourceValue.getProducer()); + + return true; + } + } + return false; + } +} diff --git a/testData/results/java/TestSimpleFlowObfuscation.dec b/testData/results/java/TestSimpleFlowObfuscation.dec index d14a946c..224c0e98 100644 --- a/testData/results/java/TestSimpleFlowObfuscation.dec +++ b/testData/results/java/TestSimpleFlowObfuscation.dec @@ -13,4 +13,33 @@ public class TestSimpleFlowObfuscation { a++; } } + + public void switches() { + System.out.println("REACHABLE 100"); + System.out.println("REACHABLE 3"); + byte var5 = -1; + if ("test5".equals("test5")) { + var5 = 4; + } + + switch (var5) { + case 0: + System.out.println("unreachable test"); + break; + case 1: + System.out.println("unreachable test2"); + break; + case 2: + System.out.println("unreachable test3"); + break; + case 3: + System.out.println("unreachable test4"); + break; + case 4: + System.out.println("REACHABLE test5"); + break; + default: + System.out.println("unreachable default"); + } + } } diff --git a/testData/src/java/src/main/java/TestSimpleFlowObfuscation.java b/testData/src/java/src/main/java/TestSimpleFlowObfuscation.java index 074f2a4c..140c46f1 100644 --- a/testData/src/java/src/main/java/TestSimpleFlowObfuscation.java +++ b/testData/src/java/src/main/java/TestSimpleFlowObfuscation.java @@ -45,4 +45,66 @@ public void compareTest() { a += 1; } } + + public void switches() { + // LOOKUPSWITCH + int b = 100; + switch (b) { + case 100: + System.out.println("REACHABLE 100"); + break; + case 200: + System.out.println("unreachable 200"); + break; + default: + System.out.println("unreachable default"); + break; + } + + // TABLESWITCH + int c = 3; + switch (c) { + case 1: + System.out.println("unreachable 1"); + break; + case 2: + System.out.println("unreachable 2"); + break; + case 3: + System.out.println("REACHABLE 3"); + break; + case 4: + System.out.println("unreachable 4"); + break; + case 5: + System.out.println("unreachable 5"); + break; + default: + System.out.println("unreachable default"); + break; + } + + // TODO: Simplify "equals" calls (in UniversalNumberTransformer) + String d = "test5"; + switch (d) { + case "test": + System.out.println("unreachable test"); + break; + case "test2": + System.out.println("unreachable test2"); + break; + case "test3": + System.out.println("unreachable test3"); + break; + case "test4": + System.out.println("unreachable test4"); + break; + case "test5": + System.out.println("REACHABLE test5"); + break; + default: + System.out.println("unreachable default"); + break; + } + } }