diff --git a/obfuscator/build.gradle b/obfuscator/build.gradle index dda9ecb..0e41e3f 100644 --- a/obfuscator/build.gradle +++ b/obfuscator/build.gradle @@ -26,9 +26,9 @@ repositories { dependencies { implementation project(':annotations') - implementation 'org.ow2.asm:asm:9.5' - implementation 'org.ow2.asm:asm-tree:9.5' - implementation 'org.ow2.asm:asm-commons:9.5' + implementation 'org.ow2.asm:asm:9.6' + implementation 'org.ow2.asm:asm-tree:9.6' + implementation 'org.ow2.asm:asm-commons:9.6' implementation 'info.picocli:picocli:4.6.3' diff --git a/obfuscator/src/main/java/by/radioegor146/bytecode/IndyPreprocessor.java b/obfuscator/src/main/java/by/radioegor146/bytecode/IndyPreprocessor.java index 7db6be4..4c493a9 100644 --- a/obfuscator/src/main/java/by/radioegor146/bytecode/IndyPreprocessor.java +++ b/obfuscator/src/main/java/by/radioegor146/bytecode/IndyPreprocessor.java @@ -22,9 +22,43 @@ private static void processIndy(ClassNode classNode, MethodNode methodNode, InsnList bootstrapInstructions = new InsnList(); bootstrapInstructions.add(bootstrapStart); // 0 + + switch (platform) { case STD_JAVA: { Type[] bsmArguments = Type.getArgumentTypes(invokeDynamicInsnNode.bsm.getDesc()); + int targetArgLength = bsmArguments.length - 3; + int originArgLength = invokeDynamicInsnNode.bsmArgs.length; + + // process variable arguments for bsm like StringConcatFactory.makeConcatWithConstants(Lookup, String, MethodType, String, Object...) + // jvm will process variable argument automatically when using linkCallSite + // but if we want to use invokeWithArguments, we need to process variable argument manually + if (originArgLength < targetArgLength) { + Object[] newArgs = new Object[targetArgLength]; + System.arraycopy(invokeDynamicInsnNode.bsmArgs, 0, newArgs, 0, originArgLength); + + if (targetArgLength - originArgLength != 1) + throw new RuntimeException("Impossible BootstrapMethod Arguments Length"); + + if (bsmArguments[originArgLength + 3].getSort() == Type.ARRAY) { + newArgs[originArgLength] = new Object[0]; + } else { + throw new RuntimeException("Last Argument of BootstrapMethod is NOT a Variable Argument"); + } + + invokeDynamicInsnNode.bsmArgs = newArgs; + } else if (originArgLength > targetArgLength || (bsmArguments[bsmArguments.length - 1].getSort() == Type.ARRAY && Type.getType(invokeDynamicInsnNode.bsmArgs[invokeDynamicInsnNode.bsmArgs.length - 1].getClass()).getSort() != Type.ARRAY)) { + Object[] newArgs = new Object[targetArgLength]; + System.arraycopy(invokeDynamicInsnNode.bsmArgs, 0, newArgs, 0, targetArgLength - 1); + + Object[] varArgs = new Object[originArgLength - targetArgLength + 1]; + System.arraycopy(invokeDynamicInsnNode.bsmArgs, targetArgLength - 1, varArgs, 0, originArgLength - targetArgLength + 1); + + newArgs[targetArgLength - 1] = varArgs; + invokeDynamicInsnNode.bsmArgs = newArgs; + } + + if (bsmArguments.length < 3 || !bsmArguments[0].getDescriptor().equals("Ljava/lang/invoke/MethodHandles$Lookup;") || !bsmArguments[1].getDescriptor().equals("Ljava/lang/String;") || !bsmArguments[2].getDescriptor().equals("Ljava/lang/invoke/MethodType;")) { @@ -43,32 +77,6 @@ private static void processIndy(ClassNode classNode, MethodNode methodNode, Type[] arguments = Type.getArgumentTypes(invokeDynamicInsnNode.desc); - // compatibility with java 9+ - // "aaa" + 1 + "bbb" - // --> - // iconst_1 - // - // bsmArgs: - // "aaa\u0001bbb" - // - // invokedynamic StringConcatFactory.makeConcatWithConstants(Lookup, String, MethodType, String, Object...)CallSite - - int targetArgLength = bsmArguments.length - 3; - int originArgLength = invokeDynamicInsnNode.bsmArgs.length; - if (originArgLength != targetArgLength) { - Object[] newArgs = new Object[targetArgLength]; - System.arraycopy(invokeDynamicInsnNode.bsmArgs, 0, newArgs, 0, originArgLength); - - for (int index = originArgLength; index < targetArgLength; index++) { - if (bsmArguments[index + 3].getSort() == Type.ARRAY) { - newArgs[index] = new Object[0]; - } else { - throw new RuntimeException("Wrong argument type: " + bsmArguments[index + 3].getClass()); - } - } - - invokeDynamicInsnNode.bsmArgs = newArgs; - } bootstrapInstructions.add(new LdcInsnNode(arguments.length)); // 1 bootstrapInstructions.add(new TypeInsnNode(Opcodes.ANEWARRAY, "java/lang/Object")); // 1 diff --git a/obfuscator/src/main/java/by/radioegor146/special/DefaultSpecialMethodProcessor.java b/obfuscator/src/main/java/by/radioegor146/special/DefaultSpecialMethodProcessor.java index f4da947..e3f2bed 100644 --- a/obfuscator/src/main/java/by/radioegor146/special/DefaultSpecialMethodProcessor.java +++ b/obfuscator/src/main/java/by/radioegor146/special/DefaultSpecialMethodProcessor.java @@ -40,7 +40,13 @@ public void postProcess(MethodContext context) { context.method.instructions.clear(); if (Util.getFlag(context.clazz.access, Opcodes.ACC_INTERFACE)) { InsnList list = new InsnList(); + + if (Util.getFlag(context.method.access, Opcodes.ACC_STATIC)) { + list.add(new LdcInsnNode(Type.getObjectType(context.clazz.name))); + } + int localVarsPosition = 0; + for (Type arg : context.argTypes) { list.add(new VarInsnNode(arg.getOpcode(Opcodes.ILOAD), localVarsPosition)); localVarsPosition += arg.getSize(); diff --git a/obfuscator/src/test/java/by/radioegor146/StringConcatFactoryTest.java b/obfuscator/src/test/java/by/radioegor146/StringConcatFactoryTest.java new file mode 100644 index 0000000..d14a45c --- /dev/null +++ b/obfuscator/src/test/java/by/radioegor146/StringConcatFactoryTest.java @@ -0,0 +1,51 @@ +package by.radioegor146; + +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Handle; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.*; + +import java.io.FileOutputStream; +import java.io.IOException; + +public class StringConcatFactoryTest implements Opcodes { + public static void main(String[] args) throws IOException { + ClassNode classNode = new ClassNode(); + classNode.version = V17; + classNode.access = ACC_PUBLIC | ACC_SUPER; + classNode.superName = "java/lang/Object"; + classNode.name = "TestStringConcatFactory"; + MethodNode method = new MethodNode(ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null); + + + method.instructions.add(new FieldInsnNode(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;")); + + method.instructions.add(new InsnNode(ICONST_1)); + method.instructions.add(new InsnNode(ICONST_2)); + method.instructions.add(new InsnNode(ICONST_3)); + + Handle handle = new Handle(H_INVOKESTATIC, + "java/lang/invoke/StringConcatFactory", + "makeConcatWithConstants", + "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;", + false); + method.instructions.add(new InvokeDynamicInsnNode("makeConcatWithConstants", + "(III)Ljava/lang/String;", + handle, + "\u0001-\u0001-\u0001-\u0002-\u0002-\u0002", + 3.14, 123, true)); + method.instructions.add(new MethodInsnNode(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false)); + method.instructions.add(new InsnNode(RETURN)); + // System.out.println(String); + classNode.methods.add(method); + + // read + ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES); + classNode.accept(writer); + + // save + FileOutputStream stream = new FileOutputStream("TestStringConcatFactory.class"); + stream.write(writer.toByteArray()); + stream.close(); + } +}