data keeping
This commit is contained in:
parent
d987c0b3af
commit
dca8bbfec8
6 changed files with 227 additions and 30 deletions
|
@ -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 {
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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) {}
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue