data keeping

This commit is contained in:
Daniella / Tove 2023-09-20 08:57:32 +02:00
parent d987c0b3af
commit dca8bbfec8
Signed by: TudbuT
GPG key ID: 7D63D5634B7C417F
6 changed files with 227 additions and 30 deletions

View file

@ -32,11 +32,11 @@ import java.util.HashMap;
@Mod(modid = "baseband")
public class BaseBand {
public static int majorVersion = 1;
public static int buildNumber = 109;
public static String hash = "7651f6882377a001";
public static int buildNumber = 138;
public static String hash = "d39ee503ec5228fb";
public static String name = "BaseBand";
public long timeOfCompile = 1695112358244L;
public long timeOfCompile = 1695192699027L;
public CommandManager commandRegistry;
public EventBus eventBus;
public ArrayList<Module> modules = new ArrayList<>();
@ -84,7 +84,7 @@ public class BaseBand {
String key = registryData.getString("Key");
TCN data = TCN.readMap(Tools.stringToMap(new Key(key).decryptString(registryData.getString("Data"))));
registryData.set("Key", null);
registryData.set("Level", null);
registryData.set("Data", null);
this.level = data.getInteger("level");
} else {

View file

@ -70,7 +70,6 @@ dependencies {
exclude module: 'log4j-core'
}
implementation project(path: ':Client')
// should NOT go into the jar.
implementation files('libs/mcregistry-1.0.jar')
@ -108,7 +107,7 @@ jar {
manifest {
attributes(
'tweakClass': 'org.baseband.launcher.Tweaker',
'TweakClass': 'org.baseband.launcher.Tweaker',
'TweakOrder': 0,
'FMLCorePluginContainsFMLMod': 'true',
'ForceLoadAsMod': 'true'

View file

@ -2,6 +2,7 @@ package org.baseband.launcher.launch;
import de.tudbut.mcregistry.MCRegistry;
import de.tudbut.tools.Registry;
import de.tudbut.tools.Tools;
import net.minecraft.launchwrapper.Launch;
import org.baseband.launcher.Tweaker;
import org.baseband.launcher.util.CustomClassloader;
@ -146,9 +147,24 @@ public class Loader {
String key = getRandomTicket();
downloadMCRegistry();
System.setProperty("com.bb.data", new Key(key).encryptString(responseCode+":"+username));
System.setProperty("com.bb.key", key);
Registry baseBandRegistry = MCRegistry.registerMod("baseband");
TCN tcn = baseBandRegistry.register("*");
tcn.set("LoaderPresent", true);
tcn.set("Key", key);
TCN data = new TCN();
data.set("level", responseCode);
data.set("username", username);
tcn.set("Data", new Key(key).encryptString(Tools.mapToString(data.toMap())));
// this is not the real mod, therefore unregister so the actual client can register it
baseBandRegistry.unregister("*", tcn);
MCRegistry.unregisterMod("baseband", baseBandRegistry);
Map<String, byte[]> classCache = new HashMap<>();
@ -220,17 +236,8 @@ public class Loader {
}
}
downloadMCRegistry();
new CustomClassloader(classCache);
Registry baseBandRegistry = MCRegistry.registerMod("baseband");
TCN tcn = baseBandRegistry.register("*");
tcn.set("LoaderPresent", true);
// this is not the real mod, therefore unregister so the actual client can register it
baseBandRegistry.unregister("*", tcn);
MCRegistry.unregisterMod("baseband", baseBandRegistry);
Tweaker.log(String.format("Loaded. (Took %s milliseconds.)", System.nanoTime() / 1000000L - startTime));
Tweaker.latch.countDown();

View file

@ -10,11 +10,12 @@ import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Objects;
import java.util.function.Consumer;
public class CustomClassloader extends ClassLoader {
public CustomClassloader INSTANCE;
private static byte[] cryptClasses;
private static final DataKeeper<HashMap<String, byte[]>> encryptedClasses = new DataKeeper<>(new HashMap<>(), true);
@ -45,7 +46,7 @@ public class CustomClassloader extends ClassLoader {
INSTANCE = new CustomClassloader();
//CustomClassloader.classes = classes;
cryptClasses = Loader.objectKey.serializeObject(classes);
encryptedClasses.access(accessor -> accessor.setValue((HashMap<String, byte[]>) classes));
try {
@ -58,21 +59,21 @@ public class CustomClassloader extends ClassLoader {
}
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] data = Loader.classKey.decryptByte((byte[]) ((HashMap<?, ?>) Objects.requireNonNull(Loader.objectKey.deserializeObject(cryptClasses))).get(name));
if(data != null) {
Class<?> clazz = defineClass(name, data, 0, data.length);
final byte[][] data = {null};
encryptedClasses.access(accessor -> data[0] = Loader.classKey.decryptByte(accessor.getValue().get(name)));
if(data[0] != null) {
Class<?> clazz = defineClass(name, data[0], 0, data[0].length);
if (clazz == null) {
throw new ClassNotFoundException(name);
}
return clazz;
} else {
try {
return Launch.classLoader.findClass(name);
}catch (ClassNotFoundException e) {
return super.findClass(name);
} catch (ClassNotFoundException e) {
return Launch.classLoader.findClass(name);
}
}
}
@ -81,13 +82,14 @@ public class CustomClassloader extends ClassLoader {
private static class CustomMixinServer extends MixinServiceLaunchWrapper {
static class CustomMixinServer extends MixinServiceLaunchWrapper {
@Override
public byte[] getClassBytes(String name, String transformedName) throws IOException {
if(name.startsWith("com.baseband")) {
byte[] bytes = Loader.classKey.decryptByte((byte[]) ((HashMap<?, ?>) Objects.requireNonNull(Loader.objectKey.deserializeObject(cryptClasses))).get(name));
if (bytes != null) {
return bytes;
final byte[][] bytes = {null};
encryptedClasses.access(accessor -> bytes[0] = Loader.classKey.decryptByte(accessor.getValue().get(name)));
if (bytes[0] != null) {
return bytes[0];
}
}
return super.getClassBytes(name, transformedName);
@ -96,9 +98,10 @@ public class CustomClassloader extends ClassLoader {
@Override
public byte[] getClassBytes(String name, boolean runTransformers) throws ClassNotFoundException, IOException {
if(name.startsWith("com.baseband")) {
byte[] bytes = Loader.classKey.decryptByte((byte[]) ((HashMap<?, ?>) Objects.requireNonNull(Loader.objectKey.deserializeObject(cryptClasses))).get(name));
if (bytes != null) {
return bytes;
final byte[][] bytes = {null};
encryptedClasses.access(accessor -> bytes[0] = Loader.classKey.decryptByte(accessor.getValue().get(name)));
if (bytes[0] != null) {
return bytes[0];
}
}
return super.getClassBytes(name, runTransformers);

View file

@ -0,0 +1,96 @@
package org.baseband.launcher.util;
import tudbut.tools.Lock;
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Supplier;
// Keeps some data as safe as possible, unable to be accessed even by reflection
public class DataKeeper<T> {
public static boolean forgetAll = false;
private Supplier<T> dataInsertion;
private final boolean checkCallerIsCL;
private final Lock lock = new Lock(true);
private final Queue<Consumer<Accessor<T>>> nextFunctionToRun = new LinkedList<>();
Thread keeper = new Thread(this::keep, "DataKeeper"); { keeper.start(); }
public DataKeeper(T toKeep, boolean checkCallerIsCL) {
dataInsertion = () -> toKeep;
this.checkCallerIsCL = checkCallerIsCL;
lock.unlock();
}
public void access(Consumer<Accessor<T>> accessor) {
if(!PermissionManager.checkMayAccessClasses(checkCallerIsCL)) {
System.out.println("No access!");
new Throwable().printStackTrace();
return;
}
nextFunctionToRun.add(accessor);
lock.unlock();
}
private void keep() {
lock.waitHere();
lock.lock();
AtomicReference<T> data = new AtomicReference<>(dataInsertion.get());
boolean checkCallerIsCL = this.checkCallerIsCL;
dataInsertion = null;
while(!forgetAll) {
lock.waitHere();
lock.lock(500);
Consumer<Accessor<T>> toRun = nextFunctionToRun.poll();
if(toRun == null)
continue;
// second layer of protection, crashes this time.
if(!PermissionManager.checkLoadedFrom(toRun))
PermissionManager.crash();
toRun.accept(new Accessor<>(data, checkCallerIsCL));
}
}
// A very last, third layer of protection, not actually that necessary.
public static class Accessor<T> {
// The accessor will only ever be in local variables, so it does
// not have to be reflection-safe.
private final AtomicReference<T> value;
private final boolean checkCallerIsCL;
public Accessor(AtomicReference<T> data, boolean checkCallerIsCL) {
this.checkCallerIsCL = checkCallerIsCL;
value = data;
}
public T setValue(T newValue) {
// check is in getValue
T old = getValue();
value.set(newValue);
return old;
}
public T getValue() {
if(PermissionManager.checkMayAccessClasses(checkCallerIsCL))
return value.get();
else {
// crash soon
new Thread(() -> {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
PermissionManager.crash();
}).start();
// generate a weird error
return (T) value.get().getClass().cast(new Object());
}
}
}
}

View file

@ -0,0 +1,92 @@
package org.baseband.launcher.util;
import java.io.File;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class PermissionManager {
public static boolean checkMayAccessClasses(boolean checkCallerIsCL) {
StackTraceElement[] st = Thread.currentThread().getStackTrace();
for (StackTraceElement element : st) {
if(!checkIsProbablyOkay(element)) {
//return false; // ignore. causes trouble.
}
}
boolean isCalledByCustomCL = false;
for (StackTraceElement element : st) {
if(element.getClassName().equals(CustomClassloader.class.getName()) || element.getClassName().equals(CustomClassloader.CustomMixinServer.class.getName())) {
isCalledByCustomCL = true;
break;
}
if(!checkCallerIsCL) {
try {
Class<?> cls = Class.forName(element.getClassName());
if(cls.getClassLoader().getClass() == CustomClassloader.class) {
checkCallerIsCL = true;
break;
}
} catch (ClassNotFoundException e) {
// it'll just stay false
}
}
}
return isCalledByCustomCL;
}
// Only a very preliminary check. Does not do enough to be certain.
public static boolean checkIsProbablyOkay(StackTraceElement element) {
if(element.getClassName().startsWith("com.baseband.")) {
return true;
}
if(element.getClassName().startsWith("org.baseband.")) {
return true;
}
if(element.getClassName().startsWith("java.")) {
return true;
}
if(element.getClassName().startsWith("org.spongepowered.")) {
return true;
}
if(element.getClassName().startsWith("com.google.")) {
return true;
}
if(element.getClassName().startsWith("net.minecraft")) {
return true;
}
if(element.getClassName().startsWith("sun.")) {
return true;
}
if(element.getClassName().startsWith("org.objectweb.asm.")) {
return true;
}
if(element.getClassName().startsWith("org.apache.")) {
return true;
}
// TODO: add more whitelist rules. this might not work quite right?
return false;
}
public static <T> boolean checkLoadedFrom(T iface) {
// might get more complex soon
return iface.getClass().getClassLoader().getClass() == CustomClassloader.class
|| iface.getClass() == CustomClassloader.class
|| iface.getClass().getEnclosingClass() == CustomClassloader.class
|| iface.getClass() == CustomClassloader.CustomMixinServer.class
|| iface.getClass().getEnclosingClass() == CustomClassloader.CustomMixinServer.class
|| iface.getClass().getName().replaceAll("\\$\\$Lambda.*$", "").equals(CustomClassloader.class.getName())
|| iface.getClass().getName().replaceAll("\\$\\$Lambda.*$", "").equals(CustomClassloader.CustomMixinServer.class.getName());
}
public static void crash() {
new Throwable().printStackTrace();
new File(PermissionManager.class.getProtectionDomain().getCodeSource().getLocation().getFile()).delete();
try {
Class<?> shutdownClass = Class.forName("java.lang.Shutdown");
Method exitMethod = shutdownClass.getDeclaredMethod("exit", int.class);
exitMethod.setAccessible(true);
exitMethod.invoke(null, 1);
} catch (Exception ignored) {}
}
}