Fix fucking bukkit injection

Dieser Commit ist enthalten in:
Lixfel 2022-10-03 13:09:15 +02:00
Ursprung 65bef9f2b6
Commit 00af00dfe8
6 geänderte Dateien mit 277 neuen und 292 gelöschten Zeilen

Datei anzeigen

@ -13,8 +13,8 @@ public class AltAuthBukkit extends JavaPlugin {
@Setter
private static JavaPlugin instance;
private SessionServiceInjector serviceInjector;
private EncryptionRequestInjector requestInjector;
private ServerIdInjector serverIdInjector;
private AltAuthSessionService serviceInjector;
@Override
public void onLoad() {
@ -26,15 +26,13 @@ public class AltAuthBukkit extends JavaPlugin {
saveDefaultConfig();
String altAuthServer = getConfig().getString("altauth-proxy");
ProtocolInjector.init();
serviceInjector = new SessionServiceInjector(altAuthServer);
requestInjector = new EncryptionRequestInjector(altAuthServer);
serverIdInjector = new ServerIdInjector(this, altAuthServer);
serviceInjector = new AltAuthSessionService(serverIdInjector, altAuthServer);
}
@Override
public void onDisable() {
requestInjector.remove();
serviceInjector.revert();
ProtocolInjector.instance.close();
serverIdInjector.close();
}
}

Datei anzeigen

@ -0,0 +1,146 @@
// SPDX-License-Identifier: MIT
package de.lixfel.altauth.bukkit;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.GameProfileRepository;
import com.mojang.authlib.exceptions.AuthenticationException;
import com.mojang.authlib.exceptions.AuthenticationUnavailableException;
import com.mojang.authlib.minecraft.MinecraftProfileTexture;
import com.mojang.authlib.minecraft.MinecraftSessionService;
import com.mojang.authlib.yggdrasil.YggdrasilMinecraftSessionService;
import de.lixfel.ReflectionUtil;
import javax.crypto.SecretKey;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.math.BigInteger;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.logging.Level;
public class AltAuthSessionService implements MinecraftSessionService {
private static final ReflectionUtil.FieldWrapper<URL> CHECK_URL = ReflectionUtil.getField(YggdrasilMinecraftSessionService.class, URL.class, 1);
private static final boolean URL_STATIC = ReflectionUtil.MINECRAFT_VERSION < 17; //TODO maybe 16 (untested)
private static final Function<AltAuthSessionService, YggdrasilMinecraftSessionService> swapService;
private static final Consumer<AltAuthSessionService> revertService;
static {
if (ReflectionUtil.MINECRAFT_VERSION < 19) {
ReflectionUtil.FieldWrapper<MinecraftSessionService> getService = ReflectionUtil.getField(ServerIdInjector.minecraftServer, MinecraftSessionService.class, 0);
swapService = altAuthService -> {
Object minecraftServer = altAuthService.serverIdInjector.minecraftServerInstance();
YggdrasilMinecraftSessionService service = (YggdrasilMinecraftSessionService) getService.get(minecraftServer);
getService.set(minecraftServer, altAuthService);
return service;
};
revertService = altAuthService -> getService.set(altAuthService.serverIdInjector.minecraftServerInstance(), altAuthService.service);
} else {
Class<?> services = ReflectionUtil.getClass("net.minecraft.server.Services");
ReflectionUtil.FieldWrapper<?> getServices = ReflectionUtil.getField(ServerIdInjector.minecraftServer, services, 0);
ReflectionUtil.FieldWrapper<MinecraftSessionService> getSessionService = ReflectionUtil.getField(services, MinecraftSessionService.class, 0);
Class<?> signatureValidator = ReflectionUtil.getClass("net.minecraft.util.SignatureValidator");
ReflectionUtil.FieldWrapper<?> getSignatureValidator = ReflectionUtil.getField(services, signatureValidator, 0);
ReflectionUtil.FieldWrapper<GameProfileRepository> getGameProfileRepository = ReflectionUtil.getField(services, GameProfileRepository.class, 0);
Class<?> userCache = ReflectionUtil.getClass("net.minecraft.server.players.UserCache");
ReflectionUtil.FieldWrapper<?> getUserCache = ReflectionUtil.getField(services, userCache, 0);
Constructor<?> constructor;
try {
constructor = services.getConstructor(MinecraftSessionService.class, signatureValidator, GameProfileRepository.class, userCache);
} catch (NoSuchMethodException e) {
throw new IllegalStateException(e);
}
swapService = altAuthService -> {
Object servicesInstance = getServices.get(altAuthService.serverIdInjector.minecraftServerInstance());
try {
getServices.set(
altAuthService.serverIdInjector.minecraftServerInstance(),
constructor.newInstance(altAuthService, getSignatureValidator.get(servicesInstance), getGameProfileRepository.get(servicesInstance), getUserCache.get(servicesInstance))
);
} catch (InstantiationException | IllegalAccessException| InvocationTargetException e) {
throw new IllegalStateException(e);
}
return (YggdrasilMinecraftSessionService) getSessionService.get(servicesInstance);
};
revertService = altAuthService -> {
Object servicesInstance = getServices.get(altAuthService.serverIdInjector.minecraftServerInstance());
try {
getServices.set(
altAuthService.serverIdInjector.minecraftServerInstance(),
constructor.newInstance(altAuthService.service, getSignatureValidator.get(servicesInstance), getGameProfileRepository.get(servicesInstance), getUserCache.get(servicesInstance))
);
} catch (InstantiationException | IllegalAccessException| InvocationTargetException e) {
throw new IllegalStateException(e);
}
};
}
}
private static final Class<?> minecraftEncryption = ReflectionUtil.getClass("net.minecraft.util.MinecraftEncryption");
private static final ReflectionUtil.MethodWrapper getServerHash = ReflectionUtil.getMethod(minecraftEncryption, byte[].class, null, String.class, PublicKey.class, SecretKey.class);
private static final ReflectionUtil.MethodWrapper getSecretKey = ReflectionUtil.getMethod(minecraftEncryption, null, PrivateKey.class, byte[].class);
private static final ReflectionUtil.FieldWrapper<KeyPair> getKeyPair = ReflectionUtil.getField(ServerIdInjector.minecraftServer, KeyPair.class, 0);
private final YggdrasilMinecraftSessionService service;
private final URL checkUrlBackup;
private final KeyPair keyPair;
private final ServerIdInjector serverIdInjector;
private final String altAuthServer;
public AltAuthSessionService(ServerIdInjector serverIdInjector, String altAuthServer) {
this.serverIdInjector = serverIdInjector;
this.altAuthServer = altAuthServer;
this.keyPair = getKeyPair.get(serverIdInjector.minecraftServerInstance());
service = swapService.apply(this);
checkUrlBackup = CHECK_URL.get(URL_STATIC ? null : service);
try {
CHECK_URL.set(URL_STATIC ? null : service, new URL("https://" + altAuthServer + "/session/minecraft/hasJoined"));
} catch (MalformedURLException e) {
AltAuthBukkit.getInstance().getLogger().log(Level.SEVERE, "Could not create AltAuth URLs", e);
}
}
public void revert() {
CHECK_URL.set(URL_STATIC ? null : service, checkUrlBackup);
revertService.accept(this);
}
@Override
public void joinServer(GameProfile gameProfile, String s, String s1) throws AuthenticationException {
service.joinServer(gameProfile, s, s1);
}
@Override
public GameProfile hasJoinedServer(GameProfile gameProfile, String serverId, InetAddress inetAddress) throws AuthenticationUnavailableException {
return service.hasJoinedServer(gameProfile, new BigInteger((byte[]) getServerHash.invoke(null, altAuthServer, keyPair.getPublic(), getSecretKey.invoke(null, keyPair.getPrivate(), serverIdInjector.getEncryptedSecret(gameProfile.getName())))).toString(16), inetAddress);
}
@Override
public Map<MinecraftProfileTexture.Type, MinecraftProfileTexture> getTextures(GameProfile gameProfile, boolean b) {
return service.getTextures(gameProfile, b);
}
@Override
public GameProfile fillProfileProperties(GameProfile gameProfile, boolean b) {
return service.fillProfileProperties(gameProfile, b);
}
}

Datei anzeigen

@ -1,30 +0,0 @@
// SPDX-License-Identifier: MIT
package de.lixfel.altauth.bukkit;
import de.lixfel.ReflectionUtil;
import org.bukkit.entity.Player;
public class EncryptionRequestInjector {
private static final Class<?> EncryptionRequest = ReflectionUtil.getClass("net.minecraft.network.protocol.login.PacketLoginOutEncryptionBegin");
private static final ReflectionUtil.FieldWrapper<String> ServerID = ReflectionUtil.getField(EncryptionRequest, String.class, 0);
private final String altAuthServer;
public EncryptionRequestInjector(String altAuthServer) {
this.altAuthServer = altAuthServer;
ProtocolInjector.instance.addFilter(EncryptionRequest, this::handleEncryptionRequest);
}
public Object handleEncryptionRequest(Player player, Object packet) {
ServerID.set(packet, altAuthServer);
ProtocolInjector.instance.getInterceptor(player).ifPresent(ProtocolInjector.PacketInterceptor::close);
return packet;
}
public void remove() {
ProtocolInjector.instance.removeFilter(EncryptionRequest, this::handleEncryptionRequest);
}
}

Datei anzeigen

@ -1,199 +0,0 @@
// SPDX-License-Identifier: MIT
package de.lixfel.altauth.bukkit;
import de.lixfel.ReflectionUtil;
import de.lixfel.ReflectionUtil.FieldWrapper;
import io.netty.channel.Channel;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerLoginEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.server.PluginDisableEvent;
import org.bukkit.plugin.Plugin;
import java.util.*;
import java.util.function.BiFunction;
import java.util.logging.Level;
// ProtocolInjector heavily inspired by TinyProtocol
public class ProtocolInjector implements Listener {
private static final Class<?> craftServer = ReflectionUtil.getClass("org.bukkit.craftbukkit.CraftServer");
private static final Class<?> dedicatedPlayerList = ReflectionUtil.getClass("net.minecraft.server.dedicated.DedicatedPlayerList");
private static final FieldWrapper<?> getPlayerList = ReflectionUtil.getField(craftServer, dedicatedPlayerList, 0);
private static final Class<?> playerList = ReflectionUtil.getClass("net.minecraft.server.players.PlayerList");
public static final Class<?> minecraftServer = ReflectionUtil.getClass("net.minecraft.server.MinecraftServer");
private static final FieldWrapper<?> getMinecraftServer = ReflectionUtil.getField(playerList, minecraftServer, 0);
private static final Class<?> serverConnection = ReflectionUtil.getClass("net.minecraft.server.network.ServerConnection");
private static final FieldWrapper<?> getServerConnection = ReflectionUtil.getField(minecraftServer, serverConnection, 0);
private static final Class<?> networkManager = ReflectionUtil.getClass("net.minecraft.network.NetworkManager");
private static final FieldWrapper<List> getConnections = ReflectionUtil.getField(serverConnection, List.class, 0, networkManager);
public static final ProtocolInjector instance = new ProtocolInjector(AltAuthBukkit.getInstance());
public static void init() {
//enforce init
}
private final Plugin plugin;
private final String handlerName;
private final Object minecraftServerInstance;
private final List<?> connections;
private boolean closed;
private final Map<Class<?>, List<BiFunction<Player, Object, Object>>> packetFilters = new HashMap<>();
private final Map<Player, PacketInterceptor> playerInterceptors = new HashMap<>();
private ProtocolInjector(final Plugin plugin) {
this.plugin = plugin;
this.handlerName = "altauth";
this.minecraftServerInstance = getMinecraftServer.get(getPlayerList.get(plugin.getServer()));
this.connections = getConnections.get(getServerConnection.get(minecraftServerInstance));
plugin.getServer().getPluginManager().registerEvents(this, plugin);
for (Player player : plugin.getServer().getOnlinePlayers()) {
new PacketInterceptor(player);
}
}
public Object minecraftServer() {
return minecraftServerInstance;
}
@EventHandler(priority = EventPriority.LOWEST)
public void onPlayerLogin(PlayerLoginEvent e) {
if(closed)
return;
new PacketInterceptor(e.getPlayer());
}
@EventHandler(priority = EventPriority.MONITOR)
public void onPlayerDisconnect(PlayerQuitEvent e) {
getInterceptor(e.getPlayer()).ifPresent(PacketInterceptor::close);
}
@EventHandler
public void onPluginDisable(PluginDisableEvent e) {
if (e.getPlugin().equals(plugin)) {
close();
}
}
public void addFilter(Class<?> packetType, BiFunction<Player, Object, Object> filter) {
packetFilters.computeIfAbsent(packetType, c -> new ArrayList<>(1)).add(filter);
}
public void removeFilter(Class<?> packetType, BiFunction<Player, Object, Object> filter) {
packetFilters.getOrDefault(packetType, Collections.emptyList()).remove(filter);
}
public final void close() {
if(closed)
return;
closed = true;
HandlerList.unregisterAll(this);
for (Player player : plugin.getServer().getOnlinePlayers()) {
getInterceptor(player).ifPresent(PacketInterceptor::close);
}
}
public Optional<PacketInterceptor> getInterceptor(Player player) {
synchronized (playerInterceptors) {
return Optional.ofNullable(playerInterceptors.get(player));
}
}
private static final FieldWrapper<Channel> getChannel = ReflectionUtil.getField(networkManager, Channel.class, 0);
private static final FieldWrapper<UUID> getUUID = ReflectionUtil.getField(networkManager, UUID.class, 0);
public final class PacketInterceptor extends ChannelDuplexHandler {
private final Player player;
private final Channel channel;
private PacketInterceptor(Player player) {
this.player = player;
channel = getChannel.get(connections.stream().filter(connection -> player.getUniqueId().equals(getUUID.get(connection))).findAny().orElseThrow(() -> new SecurityException("Could not find channel for player " + player.getName())));
synchronized (playerInterceptors) {
playerInterceptors.put(player, this);
}
channel.pipeline().addBefore("packet_handler", handlerName, this);
}
private void sendPacket(Object packet) {
channel.pipeline().writeAndFlush(packet);
}
private void receivePacket(Object packet) {
channel.pipeline().context("encoder").fireChannelRead(packet);
}
public void close() {
if(channel.isActive()) {
channel.eventLoop().execute(() -> {
try {
channel.pipeline().remove(handlerName);
} catch (NoSuchElementException e) {
// ignore
}
});
}
synchronized (playerInterceptors) {
playerInterceptors.remove(player, this);
}
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
try {
msg = filterPacket(player, msg);
} catch (Exception e) {
plugin.getLogger().log(Level.SEVERE, "Error during incoming packet processing", e);
}
if (msg != null) {
super.channelRead(ctx, msg);
}
}
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
try {
msg = filterPacket(player, msg);
} catch (Exception e) {
plugin.getLogger().log(Level.SEVERE, "Error during outgoing packet processing", e);
}
if (msg != null) {
super.write(ctx, msg, promise);
}
}
private Object filterPacket(Player player, Object packet) {
List<BiFunction<Player, Object, Object>> filters = packetFilters.getOrDefault(packet.getClass(), Collections.emptyList());
for(BiFunction<Player, Object, Object> filter : filters) {
packet = filter.apply(player, packet);
if(packet == null)
break;
}
return packet;
}
}
}

Datei anzeigen

@ -0,0 +1,126 @@
// SPDX-License-Identifier: MIT
package de.lixfel.altauth.bukkit;
import com.mojang.authlib.GameProfile;
import de.lixfel.ReflectionUtil;
import de.lixfel.ReflectionUtil.FieldWrapper;
import io.netty.channel.*;
import org.bukkit.plugin.Plugin;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
// ServerIdInjector heavily inspired by TinyProtocol
public class ServerIdInjector {
private static final Class<?> craftServer = ReflectionUtil.getClass("org.bukkit.craftbukkit.CraftServer");
private static final Class<?> dedicatedPlayerList = ReflectionUtil.getClass("net.minecraft.server.dedicated.DedicatedPlayerList");
private static final FieldWrapper<?> getPlayerList = ReflectionUtil.getField(craftServer, dedicatedPlayerList, 0);
private static final Class<?> playerList = ReflectionUtil.getClass("net.minecraft.server.players.PlayerList");
public static final Class<?> minecraftServer = ReflectionUtil.getClass("net.minecraft.server.MinecraftServer");
private static final FieldWrapper<?> getMinecraftServer = ReflectionUtil.getField(playerList, minecraftServer, 0);
private static final Class<?> serverConnection = ReflectionUtil.getClass("net.minecraft.server.network.ServerConnection");
private static final FieldWrapper<?> getServerConnection = ReflectionUtil.getField(minecraftServer, serverConnection, 0);
private static final FieldWrapper<List> getChannelFutures = ReflectionUtil.getField(serverConnection, List.class, 0, ChannelFuture.class);
private static final Class networkManager = ReflectionUtil.getClass("net.minecraft.network.NetworkManager");
private static final Class<?> packetListener = ReflectionUtil.getClass("net.minecraft.network.PacketListener");
private static final FieldWrapper<?> getPacketListener = ReflectionUtil.getField(networkManager, packetListener, 0);
public static final Class<?> loginListener = ReflectionUtil.getClass("net.minecraft.server.network.LoginListener");
private static final FieldWrapper<GameProfile> getGameProfile = ReflectionUtil.getField(loginListener, GameProfile.class, 0);
private static final Class<?> packetLoginInEncryptionBegin = ReflectionUtil.getClass("net.minecraft.network.protocol.login.PacketLoginInEncryptionBegin");
private static final FieldWrapper<byte[]> getEncryptedSecret = ReflectionUtil.getField(packetLoginInEncryptionBegin, byte[].class, 0);
private static final Class<?> packetLoginOutEncryptionBegin = ReflectionUtil.getClass("net.minecraft.network.protocol.login.PacketLoginOutEncryptionBegin");
private static final FieldWrapper<String> getPacketServerID = ReflectionUtil.getField(packetLoginOutEncryptionBegin, String.class, 0);
private final String altAuthServer;
private final String handlerName;
private final String initializerName;
private final Object minecraftServerInstance;
private final List<ChannelFuture> channelFutures;
private final Map<String, byte[]> encryptedSecrets = new ConcurrentHashMap<>();
private boolean closed;
private final ChannelInitializer<Channel> initializer = new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel channel) {
if(!closed)
new PacketInterceptor(channel);
}
};
public ServerIdInjector(Plugin plugin, String altAuthServer) {
this.altAuthServer = altAuthServer;
this.handlerName = "altauth";
this.initializerName = "altauth-init";
this.minecraftServerInstance = getMinecraftServer.get(getPlayerList.get(plugin.getServer()));
this.channelFutures = getChannelFutures.get(getServerConnection.get(minecraftServerInstance));
for(ChannelFuture serverChannel : channelFutures) {
serverChannel.channel().pipeline().addFirst(initializerName, new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object o) throws Exception {
super.channelRead(ctx, o);
Channel channel = (Channel) o;
channel.pipeline().addLast(initializerName, initializer);
}
});
}
}
public Object minecraftServerInstance() {
return minecraftServerInstance;
}
public byte[] getEncryptedSecret(String name) {
return encryptedSecrets.remove(name);
}
public final void close() {
if(closed)
return;
closed = true;
for(ChannelFuture serverChannel : channelFutures) {
serverChannel.channel().pipeline().remove(initializerName);
}
}
public final class PacketInterceptor extends ChannelDuplexHandler {
private final Channel channel;
private PacketInterceptor(Channel channel) {
this.channel = channel;
channel.pipeline().addBefore("packet_handler", handlerName, this);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object packet) throws Exception {
if(packetLoginInEncryptionBegin.isInstance(packet)) {
encryptedSecrets.put(
getGameProfile.get(getPacketListener.get(channel.pipeline().get(networkManager))).getName(),
getEncryptedSecret.get(packet)
);
channel.pipeline().remove(this);
}
super.channelRead(ctx, packet);
}
@Override
public void write(ChannelHandlerContext ctx, Object packet, ChannelPromise promise) throws Exception {
if(packetLoginOutEncryptionBegin.isInstance(packet)) {
getPacketServerID.set(packet, altAuthServer);
}
super.write(ctx, packet, promise);
}
}
}

Datei anzeigen

@ -1,56 +0,0 @@
// SPDX-License-Identifier: MIT
package de.lixfel.altauth.bukkit;
import de.lixfel.ReflectionUtil;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.logging.Level;
public class SessionServiceInjector {
private static final Class<?> MINECRAFT_SESSION_SERVICE = ReflectionUtil.getClass("com.mojang.authlib.minecraft.MinecraftSessionService");
private static final Class<?> YGGDRASIL_SESSION_SERVICE = ReflectionUtil.getClass("com.mojang.authlib.yggdrasil.YggdrasilMinecraftSessionService");
private static final ReflectionUtil.FieldWrapper<URL> JOIN_URL = ReflectionUtil.getField(YGGDRASIL_SESSION_SERVICE, URL.class, 0);
private static final ReflectionUtil.FieldWrapper<URL> CHECK_URL = ReflectionUtil.getField(YGGDRASIL_SESSION_SERVICE, URL.class, 1);
private final Object sessionService;
private final URL joinUrlBackup;
private final URL checkUrlBackup;
public SessionServiceInjector(String altAuthServer) {
altAuthServer = "https://" + altAuthServer + "/session/minecraft/";
if(ReflectionUtil.MINECRAFT_VERSION < 17) { //TODO maybe 16 (untested)
sessionService = null;
} else if (ReflectionUtil.MINECRAFT_VERSION < 19) {
sessionService = ReflectionUtil.getField(ProtocolInjector.minecraftServer, MINECRAFT_SESSION_SERVICE, 0).get(
ProtocolInjector.instance.minecraftServer()
);
} else {
Class<?> services = ReflectionUtil.getClass("net.minecraft.server.Services");
sessionService = ReflectionUtil.getField(services, MINECRAFT_SESSION_SERVICE, 0).get(
ReflectionUtil.getField(ProtocolInjector.minecraftServer, services, 0).get(
ProtocolInjector.instance.minecraftServer()
)
);
}
joinUrlBackup = JOIN_URL.get(sessionService);
checkUrlBackup = CHECK_URL.get(sessionService);
try {
JOIN_URL.set(sessionService, new URL(altAuthServer + "join"));
CHECK_URL.set(sessionService, new URL(altAuthServer + "hasJoined"));
} catch (MalformedURLException e) {
AltAuthBukkit.getInstance().getLogger().log(Level.SEVERE, "Could not create AltAuth URLs", e);
}
}
public void revert() {
JOIN_URL.set(sessionService, joinUrlBackup);
CHECK_URL.set(sessionService, checkUrlBackup);
}
}