Use dynamic invocation to bypass launchwrapper/modlauncher

This commit is contained in:
yushijinhun 2019-01-22 20:50:00 +08:00
parent 925cc55266
commit 244f7b1796
No known key found for this signature in database
GPG key ID: 5BC167F73EA558E4
8 changed files with 137 additions and 85 deletions

View file

@ -57,7 +57,6 @@ import moe.yushi.authlibinjector.transform.MainArgumentsTransformer;
import moe.yushi.authlibinjector.transform.SkinWhitelistTransformUnit;
import moe.yushi.authlibinjector.transform.YggdrasilKeyTransformUnit;
import moe.yushi.authlibinjector.transform.support.CitizensTransformer;
import moe.yushi.authlibinjector.transform.support.LaunchWrapperTransformer;
import moe.yushi.authlibinjector.transform.support.MC52974Workaround;
import moe.yushi.authlibinjector.transform.support.MC52974_1710Workaround;
import moe.yushi.authlibinjector.util.Logging;
@ -356,7 +355,6 @@ public final class AuthlibInjector {
transformer.units.add(new MainArgumentsTransformer());
transformer.units.add(new ConstantURLTransformUnit(urlProcessor));
transformer.units.add(new CitizensTransformer());
transformer.units.add(new LaunchWrapperTransformer());
transformer.units.add(new SkinWhitelistTransformUnit(config.getSkinDomains().toArray(new String[0])));

View file

@ -17,7 +17,6 @@
package moe.yushi.authlibinjector.transform;
import static org.objectweb.asm.Opcodes.ASM7;
import static org.objectweb.asm.Opcodes.INVOKESTATIC;
import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
import java.lang.annotation.Annotation;
@ -44,6 +43,7 @@ public class AuthlibLogInterceptor implements TransformUnit {
private static Set<ClassLoader> interceptedClassloaders = Collections.newSetFromMap(new WeakHashMap<>());
@CallbackMethod
public static void onClassLoading(ClassLoader classLoader) {
Class<?> classLogManager;
try {
@ -223,14 +223,21 @@ public class AuthlibLogInterceptor implements TransformUnit {
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
if ("<clinit>".equals(name)) {
mv.visitLdcInsn(Type.getType("L" + className.replace('.', '/') + ";"));
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getClassLoader", "()Ljava/lang/ClassLoader;", false);
mv.visitMethodInsn(INVOKESTATIC, AuthlibLogInterceptor.class.getName().replace('.', '/'), "onClassLoading", "(Ljava/lang/ClassLoader;)V", false);
modifiedCallback.run();
return new MethodVisitor(ASM7, super.visitMethod(access, name, descriptor, signature, exceptions)) {
@Override
public void visitCode() {
super.visitCode();
CallbackInvocation callback = CallbackInvocation.push(mv, AuthlibLogInterceptor.class, "onClassLoading");
super.visitLdcInsn(Type.getType("L" + className.replace('.', '/') + ";"));
super.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "getClassLoader", "()Ljava/lang/ClassLoader;", false);
callback.invoke();
modifiedCallback.run();
}
};
} else {
return super.visitMethod(access, name, descriptor, signature, exceptions);
}
return mv;
}
});
}

View file

@ -0,0 +1,76 @@
/*
* Copyright (C) 2019 Haowei Wen <yushijinhun@gmail.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package moe.yushi.authlibinjector.transform;
import static org.objectweb.asm.Opcodes.ACONST_NULL;
import static org.objectweb.asm.Opcodes.INVOKESTATIC;
import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import org.objectweb.asm.MethodVisitor;
public class CallbackInvocation {
private static Method findCallbackMethod(Class<?> owner, String methodName) {
for (Method method : owner.getDeclaredMethods()) {
int modifiers = method.getModifiers();
if (Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers) &&
methodName.equals(method.getName()) &&
method.getAnnotation(CallbackMethod.class) != null) {
return method;
}
}
throw new IllegalArgumentException("No such method: " + methodName);
}
public static CallbackInvocation push(MethodVisitor mv, Class<?> owner, String methodName) {
String descriptor;
try {
descriptor = MethodHandles.publicLookup().unreflect(findCallbackMethod(owner, methodName)).type().toMethodDescriptorString();
} catch (IllegalAccessException e) {
throw new IllegalArgumentException(e);
}
mv.visitMethodInsn(INVOKESTATIC, "java/lang/invoke/MethodHandles", "publicLookup", "()Ljava/lang/invoke/MethodHandles$Lookup;", false);
mv.visitMethodInsn(INVOKESTATIC, "java/lang/ClassLoader", "getSystemClassLoader", "()Ljava/lang/ClassLoader;", false);
mv.visitLdcInsn(owner.getName());
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/ClassLoader", "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;", false);
mv.visitLdcInsn(methodName);
// we cannot use CONSTANT_MethodType_info here because the class file version may be lower than 51
mv.visitLdcInsn(descriptor);
mv.visitInsn(ACONST_NULL);
mv.visitMethodInsn(INVOKESTATIC, "java/lang/invoke/MethodType", "fromMethodDescriptorString", "(Ljava/lang/String;Ljava/lang/ClassLoader;)Ljava/lang/invoke/MethodType;", false);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/invoke/MethodHandles$Lookup", "findStatic", "(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/MethodHandle;", false);
return new CallbackInvocation(mv, descriptor);
}
private MethodVisitor mv;
private String descriptor;
private CallbackInvocation(MethodVisitor mv, String descriptor) {
this.mv = mv;
this.descriptor = descriptor;
}
public void invoke() {
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/invoke/MethodHandle", "invokeExact", descriptor, false);
}
}

View file

@ -0,0 +1,27 @@
/*
* Copyright (C) 2019 Haowei Wen <yushijinhun@gmail.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package moe.yushi.authlibinjector.transform;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface CallbackMethod {
}

View file

@ -20,7 +20,6 @@ import static java.util.stream.Collectors.joining;
import static org.objectweb.asm.Opcodes.ALOAD;
import static org.objectweb.asm.Opcodes.ASM7;
import static org.objectweb.asm.Opcodes.ASTORE;
import static org.objectweb.asm.Opcodes.INVOKESTATIC;
import java.util.List;
import java.util.Optional;
@ -31,7 +30,6 @@ import java.util.stream.Stream;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;
import moe.yushi.authlibinjector.util.Logging;
@ -50,8 +48,9 @@ public class MainArgumentsTransformer implements TransformUnit {
super.visitCode();
modifiedCallback.run();
CallbackInvocation callback = CallbackInvocation.push(mv, MainArgumentsTransformer.class, "processMainArguments");
super.visitVarInsn(ALOAD, 0);
super.visitMethodInsn(INVOKESTATIC, Type.getInternalName(MainArgumentsTransformer.class), "processMainArguments", "([Ljava/lang/String;)[Ljava/lang/String;", false);
callback.invoke();
super.visitVarInsn(ASTORE, 0);
}
};
@ -73,6 +72,7 @@ public class MainArgumentsTransformer implements TransformUnit {
// ==== Main arguments processing ====
private static final List<Function<String[], String[]>> ARGUMENTS_LISTENERS = new CopyOnWriteArrayList<>();
@CallbackMethod
public static String[] processMainArguments(String[] args) {
Logging.TRANSFORM.fine(() -> "Main arguments: " + Stream.of(args).collect(joining(" ")));

View file

@ -43,12 +43,12 @@ import java.util.concurrent.CopyOnWriteArrayList;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;
public class YggdrasilKeyTransformUnit implements TransformUnit {
private static final List<PublicKey> PUBLIC_KEYS = new CopyOnWriteArrayList<>();
@CallbackMethod
public static List<PublicKey> getPublicKeys() {
return PUBLIC_KEYS;
}
@ -76,7 +76,7 @@ public class YggdrasilKeyTransformUnit implements TransformUnit {
mv.visitInsn(IRETURN);
mv.visitLabel(l0);
mv.visitFrame(F_SAME, 0, null, 0, null);
mv.visitMethodInsn(INVOKESTATIC, Type.getInternalName(YggdrasilKeyTransformUnit.class), "getPublicKeys", "()Ljava/util/List;", false);
CallbackInvocation.push(mv, YggdrasilKeyTransformUnit.class, "getPublicKeys").invoke();
mv.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "iterator", "()Ljava/util/Iterator;", true);
mv.visitVarInsn(ASTORE, 2);
Label l1 = new Label();

View file

@ -1,66 +0,0 @@
/*
* Copyright (C) 2019 Haowei Wen <yushijinhun@gmail.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package moe.yushi.authlibinjector.transform.support;
import static org.objectweb.asm.Opcodes.ALOAD;
import static org.objectweb.asm.Opcodes.ASM7;
import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
import static org.objectweb.asm.Opcodes.RETURN;
import java.util.Optional;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import moe.yushi.authlibinjector.transform.TransformUnit;
public class LaunchWrapperTransformer implements TransformUnit {
@Override
public Optional<ClassVisitor> transform(ClassLoader classLoader, String className, ClassVisitor writer, Runnable modifiedCallback) {
if ("net.minecraft.launchwrapper.LaunchClassLoader".equals(className)) {
return Optional.of(new ClassVisitor(ASM7, writer) {
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
if ("<init>".equals(name)) {
return new MethodVisitor(ASM7, super.visitMethod(access, name, descriptor, signature, exceptions)) {
@Override
public void visitInsn(int opcode) {
if (opcode == RETURN) {
modifiedCallback.run();
super.visitVarInsn(ALOAD, 0);
super.visitLdcInsn("moe.yushi.authlibinjector.");
super.visitMethodInsn(INVOKEVIRTUAL, "net/minecraft/launchwrapper/LaunchClassLoader", "addClassLoaderExclusion", "(Ljava/lang/String;)V", false);
}
super.visitInsn(opcode);
}
};
} else {
return super.visitMethod(access, name, descriptor, signature, exceptions);
}
}
});
} else {
return Optional.empty();
}
}
@Override
public String toString() {
return "LaunchWrapper Support";
}
}

View file

@ -22,6 +22,7 @@ import static org.objectweb.asm.Opcodes.CHECKCAST;
import static org.objectweb.asm.Opcodes.DUP;
import static org.objectweb.asm.Opcodes.GETFIELD;
import static org.objectweb.asm.Opcodes.INVOKESTATIC;
import static org.objectweb.asm.Opcodes.SWAP;
import java.util.Map;
import java.util.Optional;
@ -31,9 +32,10 @@ import java.util.logging.Level;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;
import moe.yushi.authlibinjector.AuthlibInjector;
import moe.yushi.authlibinjector.transform.CallbackInvocation;
import moe.yushi.authlibinjector.transform.CallbackMethod;
import moe.yushi.authlibinjector.transform.MainArgumentsTransformer;
import moe.yushi.authlibinjector.transform.TransformUnit;
import moe.yushi.authlibinjector.util.Logging;
@ -57,12 +59,14 @@ public class MC52974_1710Workaround {
// Empty GameProfile -> Filled GameProfile?
private static final Map<Object, Optional<Object>> markedGameProfiles = new WeakIdentityHashMap<>();
@CallbackMethod
public static void markGameProfile(Object gp) {
synchronized (markedGameProfiles) {
markedGameProfiles.putIfAbsent(gp, Optional.empty());
}
}
@CallbackMethod
public static Object accessGameProfile(Object gp, Object minecraftServer, boolean isNotchName) {
synchronized (markedGameProfiles) {
Optional<Object> value = markedGameProfiles.get(gp);
@ -119,7 +123,10 @@ public class MC52974_1710Workaround {
if (opcode == ARETURN) {
modifiedCallback.run();
super.visitInsn(DUP);
super.visitMethodInsn(INVOKESTATIC, Type.getInternalName(MC52974_1710Workaround.class), "markGameProfile", "(Ljava/lang/Object;)V", false);
CallbackInvocation callback = CallbackInvocation.push(mv, MC52974_1710Workaround.class, "markGameProfile");
super.visitInsn(SWAP);
callback.invoke();
super.visitTypeInsn(CHECKCAST, "com/mojang/authlib/GameProfile");
}
super.visitInsn(opcode);
}
@ -150,20 +157,23 @@ public class MC52974_1710Workaround {
return new MethodVisitor(ASM7, super.visitMethod(access, name, descriptor, signature, exceptions)) {
@Override
public void visitFieldInsn(int opcode, String owner, String name, String descriptor) {
super.visitFieldInsn(opcode, owner, name, descriptor);
if (opcode == GETFIELD && (isNotchName
? "gb".equals(owner) && "b".equals(name) && "Lcom/mojang/authlib/GameProfile;".equals(descriptor)
: "net/minecraft/network/play/server/S0CPacketSpawnPlayer".equals(owner) && "field_148955_b".equals(name) && "Lcom/mojang/authlib/GameProfile;".equals(descriptor))) {
modifiedCallback.run();
CallbackInvocation callback = CallbackInvocation.push(mv, MC52974_1710Workaround.class, "accessGameProfile");
super.visitInsn(SWAP);
super.visitFieldInsn(opcode, owner, name, descriptor);
if (isNotchName) {
super.visitMethodInsn(INVOKESTATIC, "net/minecraft/server/MinecraftServer", "I", "()Lnet/minecraft/server/MinecraftServer;", false);
} else {
super.visitMethodInsn(INVOKESTATIC, "net/minecraft/server/MinecraftServer", "func_71276_C", "()Lnet/minecraft/server/MinecraftServer;", false);
}
super.visitLdcInsn(isNotchName ? 1 : 0);
super.visitMethodInsn(INVOKESTATIC, Type.getInternalName(MC52974_1710Workaround.class), "accessGameProfile", "(Ljava/lang/Object;Ljava/lang/Object;Z)Ljava/lang/Object;", false);
callback.invoke();
super.visitTypeInsn(CHECKCAST, "com/mojang/authlib/GameProfile");
} else {
super.visitFieldInsn(opcode, owner, name, descriptor);
}
}
};