Skip to content

Commit

Permalink
Merge pull request #82 from EpicPlayerA10/feat/simplify-switches
Browse files Browse the repository at this point in the history
Simplify switches flow
  • Loading branch information
narumii authored Aug 29, 2024
2 parents 86606c0 + 9877993 commit e8951c3
Show file tree
Hide file tree
Showing 6 changed files with 262 additions and 106 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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<AbstractInsnNode, Frame<OriginalSourceValue>> 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<OriginalSourceValue> 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<OriginalSourceValue> 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
);
}
}
Original file line number Diff line number Diff line change
@@ -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<OriginalSourceValue> 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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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<OriginalSourceValue> 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;
}
}
29 changes: 29 additions & 0 deletions testData/results/java/TestSimpleFlowObfuscation.dec
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
}
}
62 changes: 62 additions & 0 deletions testData/src/java/src/main/java/TestSimpleFlowObfuscation.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
}

0 comments on commit e8951c3

Please sign in to comment.