Skip to content

Commit

Permalink
refactor ZelixParametersTransformer a bit and also account empty obje…
Browse files Browse the repository at this point in the history
…ct arrays
  • Loading branch information
EpicPlayerA10 committed Sep 23, 2024
1 parent 1af5c96 commit 2012332
Show file tree
Hide file tree
Showing 9 changed files with 150 additions and 111 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,19 @@ public static AbstractInsnNode toConstantInsn(Object value) {
throw new IllegalArgumentException("Not a constant");
}

public static Type getTypeFromPrimitiveCast(MethodInsnNode insn) {
if (insn.getOpcode() != INVOKEVIRTUAL) throw new IllegalArgumentException("Instruction is not an INVOKEVIRTUAL");

if (insn.owner.equals("java/lang/Byte") && insn.name.equals("byteValue")) return Type.BYTE_TYPE;
if (insn.owner.equals("java/lang/Short") && insn.name.equals("shortValue")) return Type.SHORT_TYPE;
if (insn.owner.equals("java/lang/Integer") && insn.name.equals("intValue")) return Type.INT_TYPE;
if (insn.owner.equals("java/lang/Long") && insn.name.equals("longValue")) return Type.LONG_TYPE;
if (insn.owner.equals("java/lang/Double") && insn.name.equals("doubleValue")) return Type.DOUBLE_TYPE;
if (insn.owner.equals("java/lang/Float") && insn.name.equals("floatValue")) return Type.FLOAT_TYPE;

throw new IllegalStateException("Unexpected value: " + insn.owner+"."+insn.name+insn.desc);
}

public static InsnList from(AbstractInsnNode... nodes) {
InsnList insnList = new InsnList();
for (AbstractInsnNode node : nodes) {
Expand Down Expand Up @@ -189,7 +202,7 @@ public static MethodNode copyMethod(MethodNode methodNode) {
return copyMethod;
}

public void removeField(FieldInsnNode fieldInsnNode, Context context) {
public static void removeField(FieldInsnNode fieldInsnNode, Context context) {
if (!context.getClasses().containsKey(fieldInsnNode.owner)) return;

context
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,17 @@ public ComposedZelixTransformer() {

public ComposedZelixTransformer(Map<String, String> classInitializationOrder) {
super(
// Initial cleanup
JsrInlinerTransformer::new,
RecoverSyntheticsTransformer::new,

// Fixes flow a bit
ZelixUselessTryCatchRemoverTransformer::new,

// Decompose method parameters
ZelixParametersTransformer::new,

// Decrypt longs
() -> new ZelixLongEncryptionMPCTransformer(classInitializationOrder),
InlineStaticFieldTransformer::new,
UniversalNumberTransformer::new,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.IincInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.TypeInsnNode;
import org.objectweb.asm.tree.VarInsnNode;
import org.objectweb.asm.tree.analysis.OriginalSourceValue;
import uwu.narumi.deobfuscator.api.asm.ClassWrapper;
import uwu.narumi.deobfuscator.api.asm.InstructionContext;
import uwu.narumi.deobfuscator.api.asm.MethodContext;
Expand All @@ -17,7 +17,6 @@
import uwu.narumi.deobfuscator.api.asm.matcher.impl.OpcodeMatch;
import uwu.narumi.deobfuscator.api.asm.matcher.impl.StackMatch;
import uwu.narumi.deobfuscator.api.context.Context;
import uwu.narumi.deobfuscator.api.helper.AsmHelper;
import uwu.narumi.deobfuscator.api.helper.MethodHelper;
import uwu.narumi.deobfuscator.api.transformer.Transformer;

Expand All @@ -35,7 +34,7 @@
* </ul>
*
* <p>
* Example instruction set:
* Object array destructuring example:
* <pre>
* aload p0
* dup
Expand All @@ -57,26 +56,32 @@
// TODO: Remove object array creation and replace it with corresponding params
public class ZelixParametersTransformer extends Transformer {

private static final Match ARRAY_ACCESS = StackMatch.of(0, OpcodeMatch.of(CHECKCAST).save("cast")
private static final Match OBJECT_ARRAY_ALOAD = OpcodeMatch.of(ALOAD).and(
Match.predicate(context -> {
// The object array is always the first argument to method
return ((VarInsnNode) context.insn()).var == MethodHelper.getFirstParameterIdx(context.insnContext().methodNode());
}));

private static final Match OBJECT_ARRAY_ACCESS = StackMatch.of(0, OpcodeMatch.of(CHECKCAST).save("cast")
.and(StackMatch.of(0, OpcodeMatch.of(AALOAD)
.and(StackMatch.of(0, NumberMatch.numInteger().save("index")
.and(StackMatch.of(0, OpcodeMatch.of(DUP)
.and(StackMatch.ofOriginal(0, OpcodeMatch.of(ALOAD).and(
// The object array is always the first argument to method
Match.predicate(context -> {
return ((VarInsnNode) context.insn()).var == MethodHelper.getFirstParameterIdx(context.insnContext().methodNode());
})
).save("load-array")))
.and(StackMatch.ofOriginal(0, OBJECT_ARRAY_ALOAD.save("load-array")))
))
))
))
);

private static final Match ARRAY_VAR_USAGE = Match.predicate(ctx -> ctx.insn().isVarStore()).save("var-store")
private static final Match OBJECT_ARRAY_VAR_USAGE = Match.predicate(ctx -> ctx.insn().isVarStore()).save("var-store")
.and(
StackMatch.of(0, MethodMatch.invokeVirtual().save("to-primitive") // Converting to a primitive type
.and(OBJECT_ARRAY_ACCESS)
).or(OBJECT_ARRAY_ACCESS)
);

private static final Match OBJECT_ARRAY_POP = OpcodeMatch.of(POP)
.and(
StackMatch.of(0, MethodMatch.invokeVirtual().save("to-primitive") // Converting to primitive type
.and(ARRAY_ACCESS)
).or(ARRAY_ACCESS)
StackMatch.ofOriginal(0, OBJECT_ARRAY_ALOAD.save("load-array"))
);

@Override
Expand All @@ -86,103 +91,123 @@ protected void transform(ClassWrapper scope, Context context) throws Exception {
.forEach(methodNode -> {
MethodContext methodContext = MethodContext.framed(classWrapper, methodNode);

boolean decomposeArgs = false;
List<Type> newArgumentTypes = new ArrayList<>();

// ALOAD
VarInsnNode loadArrayInsn = null;

Map<Integer, Integer> newVarIndexes = new HashMap<>(); // old var index -> new var index
int nextVarIndex = MethodHelper.getFirstParameterIdx(methodNode);

// Find all casts from that Object array
for (AbstractInsnNode insn : methodNode.instructions.toArray()) {
InstructionContext insnContext = methodContext.newInsnContext(insn);

MatchContext matchContext = ARRAY_VAR_USAGE.matchResult(insnContext);
if (matchContext == null) continue;

// Found argument!
VarInsnNode varStore = (VarInsnNode) matchContext.storage().get("var-store").insn();
TypeInsnNode typeInsn = (TypeInsnNode) matchContext.storage().get("cast").insn();
int index = matchContext.storage().get("index").insn().asInteger();

Type type = Type.getObjectType(typeInsn.desc);
// If value is cast to primitive, then pass primitive
if (matchContext.storage().containsKey("to-primitive")) {
MethodInsnNode primitiveCastInsn = (MethodInsnNode) matchContext.storage().get("to-primitive").insn();
type = getTypeFromPrimitiveCast(primitiveCastInsn);
}

// Add new argument
newArgumentTypes.add(index, type);
//System.out.println(index + " -> " + type);

// Append new var index
newVarIndexes.put(varStore.var, nextVarIndex);
nextVarIndex = nextVarIndex + type.getSize();

// Clean up array access
loadArrayInsn = (VarInsnNode) matchContext.storage().get("load-array").insn();
for (AbstractInsnNode collectedInsn : matchContext.collectedInsns()) {
if (collectedInsn.equals(loadArrayInsn)) continue;
// Decompose object array to argument types
List<Type> newArgumentTypes = decomposeObjectArrayToTypes(methodContext, newVarIndexes);

methodNode.instructions.remove(collectedInsn);
}
markChange();
// This flag is used to determine if we need to decompose arguments
boolean shouldReplaceArgumentTypes = removeObjectArrayAccess(methodContext);

decomposeArgs = true;
}

if (decomposeArgs) {
if (shouldReplaceArgumentTypes) {
// Update method arguments
methodNode.desc = Type.getMethodDescriptor(Type.getReturnType(methodNode.desc), newArgumentTypes.toArray(new Type[0]));

// Remove array access and pop
for (AbstractInsnNode insn : methodNode.instructions.toArray()) {
if (insn.getOpcode() != POP) continue;

InstructionContext insnContext = methodContext.newInsnContext(insn);

OriginalSourceValue arrayAccess = insnContext.frame().getStack(insnContext.frame().getStackSize() - 1);
if (arrayAccess.originalSource.isOneWayProduced() && arrayAccess.originalSource.getProducer().equals(loadArrayInsn)) {
methodNode.instructions.remove(loadArrayInsn); // ALOAD
methodNode.instructions.remove(insn); // POP
markChange();
break;
}
}

// Replace local variables indexes with the corresponding ones
for (AbstractInsnNode insn : methodNode.instructions.toArray()) {
if (insn instanceof VarInsnNode varInsn) {
if (newVarIndexes.containsKey(varInsn.var)) {
// Replace it!
varInsn.var = newVarIndexes.get(varInsn.var);
markChange();
}
} else if (insn instanceof IincInsnNode iincInsn) {
if (newVarIndexes.containsKey(iincInsn.var)) {
// Replace it!
iincInsn.var = newVarIndexes.get(iincInsn.var);
markChange();
}
}
}
fixLocalVariableIndexes(methodNode, newVarIndexes);
}
}));
}

private static Type getTypeFromPrimitiveCast(MethodInsnNode insn) {
if (insn.getOpcode() != INVOKEVIRTUAL) throw new IllegalArgumentException("Instruction is not an INVOKEVIRTUAL");
/**
* Decomposes object array to argument types.
*
* @param methodContext Method context
* @param newVarIndexes New var indexes to fill
* @return Argument types
*/
private List<Type> decomposeObjectArrayToTypes(MethodContext methodContext, Map<Integer, Integer> newVarIndexes) {
List<Type> newArgumentTypes = new ArrayList<>();

int nextVarIndex = MethodHelper.getFirstParameterIdx(methodContext.methodNode());

// Find all casts from that Object array
for (AbstractInsnNode insn : methodContext.methodNode().instructions.toArray()) {
InstructionContext insnContext = methodContext.newInsnContext(insn);

MatchContext matchContext = OBJECT_ARRAY_VAR_USAGE.matchResult(insnContext);
if (matchContext == null) continue;

// Found argument!
VarInsnNode varStore = (VarInsnNode) matchContext.storage().get("var-store").insn();
TypeInsnNode typeInsn = (TypeInsnNode) matchContext.storage().get("cast").insn();
int index = matchContext.storage().get("index").insn().asInteger();

Type type = Type.getObjectType(typeInsn.desc);
// If value is cast to primitive, then pass primitive
if (matchContext.storage().containsKey("to-primitive")) {
MethodInsnNode primitiveCastInsn = (MethodInsnNode) matchContext.storage().get("to-primitive").insn();
type = getTypeFromPrimitiveCast(primitiveCastInsn);
}

// Add new argument
newArgumentTypes.add(index, type);
//System.out.println(index + " -> " + type);

// Append new var index
newVarIndexes.put(varStore.var, nextVarIndex);
nextVarIndex = nextVarIndex + type.getSize();

// Clean up array access
VarInsnNode loadArrayInsn = (VarInsnNode) matchContext.storage().get("load-array").insn();
for (AbstractInsnNode collectedInsn : matchContext.collectedInsns()) {
if (collectedInsn.equals(loadArrayInsn)) continue;

methodContext.methodNode().instructions.remove(collectedInsn);
}

markChange();
}

return newArgumentTypes;
}

/**
* Removes object array access. Removes its ALOAD and POP instructions.
*
* @return {@code true} if any object array access was removed
*/
private boolean removeObjectArrayAccess(MethodContext methodContext) {
// Remove all object array accesses
for (AbstractInsnNode insn : methodContext.methodNode().instructions.toArray()) {
InstructionContext insnContext = methodContext.newInsnContext(insn);

MatchContext matchContext = OBJECT_ARRAY_POP.matchResult(insnContext);
if (matchContext == null) continue;

AbstractInsnNode loadArrayInsn = matchContext.storage().get("load-array").insn();

if (insn.owner.equals("java/lang/Byte") && insn.name.equals("byteValue")) return Type.BYTE_TYPE;
if (insn.owner.equals("java/lang/Short") && insn.name.equals("shortValue")) return Type.SHORT_TYPE;
if (insn.owner.equals("java/lang/Integer") && insn.name.equals("intValue")) return Type.INT_TYPE;
if (insn.owner.equals("java/lang/Long") && insn.name.equals("longValue")) return Type.LONG_TYPE;
if (insn.owner.equals("java/lang/Double") && insn.name.equals("doubleValue")) return Type.DOUBLE_TYPE;
if (insn.owner.equals("java/lang/Float") && insn.name.equals("floatValue")) return Type.FLOAT_TYPE;
// Remove those instructions
methodContext.methodNode().instructions.remove(loadArrayInsn); // ALOAD
methodContext.methodNode().instructions.remove(insn); // POP

return true;
}

return false;
}

throw new IllegalStateException("Unexpected value: " + insn.owner+"."+insn.name+insn.desc);
/**
* Replace local variables indexes with the corresponding ones
*
* @param methodNode Method node
* @param newVarIndexes New var indexes to use to replace the old ones
*/
private void fixLocalVariableIndexes(MethodNode methodNode, Map<Integer, Integer> newVarIndexes) {
for (AbstractInsnNode insn : methodNode.instructions.toArray()) {
if (insn instanceof VarInsnNode varInsn) {
if (newVarIndexes.containsKey(varInsn.var)) {
// Replace it!
varInsn.var = newVarIndexes.get(varInsn.var);
markChange();
}
} else if (insn instanceof IincInsnNode iincInsn) {
if (newVarIndexes.containsKey(iincInsn.var)) {
// Replace it!
iincInsn.var = newVarIndexes.get(iincInsn.var);
markChange();
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ import net.minecraft.util.io.netty.buffer.ByteBuf;
import net.minecraft.util.io.netty.util.internal.EmptyArrays;

public final class ByteBufUtil_7 implements ByteBufUtil {
@Override
public Object Vulcan_y(Object[] var1) {
public Object Vulcan_y() {
return Unpooled.buffer();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ import me.frep.vulcan.spigot.check.AbstractCheck;
public final class ByteBufUtil_8 implements ByteBufUtil {
private static String[] Vulcan_n;

@Override
public Object Vulcan_y(Object[] var1) {
public Object Vulcan_y() {
return Unpooled.buffer();
}

Expand Down
4 changes: 2 additions & 2 deletions testData/results/custom-classes/zkm/sample2/a4.dec
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,15 @@ public class a4 {
private static final Integer[] k;
private static final Map l;

public static List a(Object[] var0) {
public static List a() {
return a5.a(new Object[0]);
}

private static List b(Predicate var0) {
return (List)a(new Object[0]).stream().filter(var0).collect(Collectors.toList());
}

public static Set a(Object[] var0) {
public static Set a() {
return a5.a(new Object[0]);
}

Expand Down
6 changes: 3 additions & 3 deletions testData/results/custom-classes/zkm/sample2/a_.dec
Original file line number Diff line number Diff line change
Expand Up @@ -178,15 +178,15 @@ public class a_<T> implements Serializable {
}
}

public Comparator a(Object[] var1) {
public Comparator a() {
return this.a;
}

public Object b(Object[] var1) {
public Object b() {
return this.c;
}

public Object c(Object[] var1) {
public Object c() {
return this.d;
}

Expand Down
4 changes: 2 additions & 2 deletions testData/results/custom-jars/SnakeGame-obf-zkm/c.dec
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ public class c {
this.b = var2;
}

public int a(Object[] var1) {
public int a() {
return this.a;
}

public int b(Object[] var1) {
public int b() {
return this.b;
}
}
Loading

0 comments on commit 2012332

Please sign in to comment.