:3
This commit is contained in:
Jess 2023-09-27 21:58:19 +01:00
parent 227c76bfff
commit 2bb0d88e64
11 changed files with 414 additions and 125 deletions

View file

@ -6,9 +6,7 @@ import com.baseband.client.event.EventBus;
import com.baseband.client.event.FMLEventProcessor;
import com.baseband.client.module.Module;
import com.baseband.client.module.modules.*;
import de.tudbut.mcregistry.MCRegistry;
import de.tudbut.tools.Registry;
import de.tudbut.tools.Tools;
import net.minecraft.client.Minecraft;
import net.minecraft.launchwrapper.Launch;
import net.minecraftforge.common.MinecraftForge;
@ -31,11 +29,11 @@ import java.util.ArrayList;
@Mod(modid = "baseband")
public class BaseBand {
public static int majorVersion = 1;
public static int buildNumber = 189;
public static String hash = "19a91abc85a04461";
public static int buildNumber = 195;
public static String hash = "996032a4408f699a";
public static String name = "BaseBand";
public long timeOfCompile = 1695669761860L;
public long timeOfCompile = 1695766816016L;
public CommandManager commandRegistry;
public EventBus eventBus;
public ArrayList<Module> modules = new ArrayList<>();
@ -73,27 +71,7 @@ public class BaseBand {
downloadMCRegistry();
try {
Registry = MCRegistry.registerMod("baseband");
registryData = Registry.register("*");
} catch (Exception e) {
// tamper detected
Utils.crash();
}
// cant be a normal if statement because it might be null
if (registryData.get("LoaderPresent") == Boolean.TRUE) {
String key = registryData.getString("Key");
TCN data = TCN.readMap(Tools.stringToMap(new Key(key).decryptString(registryData.getString("Data"))));
registryData.set("Key", null);
registryData.set("Data", null);
this.level = data.getInteger("level");
} else {
// do other stuff here later?
log.info("No loader present, but able to start anyway ==> Debug environment detected.");
}
// unset so this won't be discovered and manipulated
registryData.set("LoaderPresent", null);
commandRegistry = new CommandManager();
eventBus = new EventBus();
@ -162,7 +140,7 @@ public class BaseBand {
}
public void addModule(Module m) {
Restrict annotation = m.getClass().getDeclaredAnnotation(Restrict.class)
Restrict annotation = m.getClass().getDeclaredAnnotation(Restrict.class);
if (annotation != null) {
if(level < annotation.value().level)
return;

View file

@ -71,8 +71,8 @@ dependencies {
}
// this is a tweaker, so it can go into the jar
jarLibs files('libs/mcregistry-1.0.jar')
//jarLibs files('libs/mcregistry-1.0.jar')
//No
annotationProcessor('org.spongepowered:mixin:0.8.5:processor') {
exclude module: 'gson'

View file

@ -1,3 +1,9 @@
/*
* Copyright (c) 2023 Jess H & Daniella H. All Rights Reserved.
*
* Unauthorized copying of this file via any medium is Strictly Prohibited.
*/
package org.baseband.launcher;
import net.minecraft.launchwrapper.ITweaker;

View file

@ -1,9 +1,18 @@
package org.baseband.launcher.util;
/*
* Copyright (c) 2023 Jess H & Daniella H. All Rights Reserved.
*
* Unauthorized copying of this file via any medium is Strictly Prohibited.
*/
package org.baseband.launcher.classloader;
import net.minecraft.launchwrapper.Launch;
import org.baseband.launcher.launch.Loader;
import org.baseband.launcher.util.security.SecureContainer;
import org.spongepowered.asm.service.MixinService;
import org.spongepowered.asm.service.mojang.MixinServiceLaunchWrapper;
import sun.misc.Unsafe;
import java.io.IOException;
import java.lang.reflect.Field;
@ -12,8 +21,7 @@ import java.util.HashMap;
public class CustomClassloader extends ClassLoader {
public CustomClassloader INSTANCE;
private static final DataKeeper<HashMap<String, byte[]>> encryptedClasses = new DataKeeper<>(new HashMap<>(), true);
private static SecureContainer<Object> encryptedClasses;
@ -21,9 +29,9 @@ public class CustomClassloader extends ClassLoader {
initClasses(obj);
}
public CustomClassloader() {}
public void initClasses(Object classes){
encryptedClasses = new SecureContainer<>(new HashMap<>(), true);
try {
CustomMixinServer customService = new CustomMixinServer();
Class<?> mixinServiceClass = Class.forName("org.spongepowered.asm.service.MixinService");
@ -41,17 +49,13 @@ public class CustomClassloader extends ClassLoader {
Loader.exit();
}
INSTANCE = new CustomClassloader();
//CustomClassloader.classes = classes;
encryptedClasses.access(accessor -> accessor.setValue((HashMap<String, byte[]>) classes));
encryptedClasses.access(accessor -> accessor.setValue(classes));
try {
Field parent = ClassLoader.class.getDeclaredField("parent");
parent.setAccessible(true);
parent.set(INSTANCE, parent.get(Launch.classLoader));
parent.set(Launch.classLoader, INSTANCE);
parent.set(this, parent.get(Launch.classLoader));
parent.set(Launch.classLoader, this);
} catch (IllegalAccessException | NoSuchFieldException var6) {
var6.printStackTrace();
}
@ -62,13 +66,20 @@ public class CustomClassloader extends ClassLoader {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
final byte[][] data = {null};
encryptedClasses.access(accessor -> data[0] = Loader.classKey.decryptByte(accessor.getValue().get(name)));
encryptedClasses.access(accessor -> data[0] = Loader.classKey.decryptByte(((HashMap<String, byte[]>)accessor.getValue()).get(name)));
if(data[0] != null) {
Class<?> clazz = defineClass(name, data[0], 0, data[0].length);
if (clazz == null) {
try {
Field b = Unsafe.class.getDeclaredField("theUnsafe");
Unsafe unsafe = (Unsafe)b.get(null);
Class<?> definedClass = unsafe.defineClass(name, data[0], 0, data[0].length, this, null);
if (definedClass == null) {
throw new ClassNotFoundException(name);
}
return clazz;
return definedClass;
}catch (Exception e){
return null;
}
} else {
try {
return Launch.classLoader.findClass(name);
@ -79,12 +90,12 @@ public class CustomClassloader extends ClassLoader {
}
static class CustomMixinServer extends MixinServiceLaunchWrapper {
public static class CustomMixinServer extends MixinServiceLaunchWrapper {
@Override
public byte[] getClassBytes(String name, String transformedName) throws IOException {
if(name.startsWith("com.baseband")) {
final byte[][] bytes = {null};
encryptedClasses.access(accessor -> bytes[0] = Loader.classKey.decryptByte(accessor.getValue().get(name)));
encryptedClasses.access(accessor -> bytes[0] = Loader.classKey.decryptByte(((HashMap<String, byte[]>)accessor.getValue()).get(name)));
if (bytes[0] != null) {
return bytes[0];
}
@ -96,7 +107,7 @@ public class CustomClassloader extends ClassLoader {
public byte[] getClassBytes(String name, boolean runTransformers) throws ClassNotFoundException, IOException {
if(name.startsWith("com.baseband")) {
final byte[][] bytes = {null};
encryptedClasses.access(accessor -> bytes[0] = Loader.classKey.decryptByte(accessor.getValue().get(name)));
encryptedClasses.access(accessor -> bytes[0] = Loader.classKey.decryptByte(((HashMap<String, byte[]>)accessor.getValue()).get(name)));
if (bytes[0] != null) {
return bytes[0];
}

View file

@ -1,26 +1,25 @@
/*
* Copyright (c) 2023 Jess H & Daniella H. All Rights Reserved.
*
* Unauthorized copying of this file via any medium is Strictly Prohibited.
*/
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;
import org.baseband.launcher.util.security.BaseBandSecurityManager;
import org.baseband.launcher.classloader.CustomClassloader;
import org.baseband.launcher.util.Key;
import sun.misc.Unsafe;
import tudbut.io.StreamRedirect;
import tudbut.parsing.TCN;
import javax.swing.*;
import java.io.*;
import java.lang.management.ManagementFactory;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.Socket;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.security.MessageDigest;
import java.util.*;
import java.util.jar.JarOutputStream;
@ -80,16 +79,7 @@ public class Loader {
objectKey = new Key();
if (System.getProperty("com.bb.debugKey") != null) {
if (System.getProperty("com.bb.debugKey").equalsIgnoreCase("true")) {
Tweaker.log("!!Warning!!\nEncryption Debug set to enabled.");
communicationKey.setDebug(true);
classKey.setDebug(true);
objectKey.setDebug(true);
}
}
outputF.writeUTF("loader")
outputF.writeUTF("loader");
outputF.writeUTF(communicationKey.encryptString(username));
outputF.writeUTF(communicationKey.encryptString(password));
outputF.writeUTF(communicationKey.encryptString(generate()));
@ -144,8 +134,7 @@ public class Loader {
}
String key = getRandomTicket();
/*
Registry baseBandRegistry = MCRegistry.registerMod("baseband");
TCN tcn = baseBandRegistry.register("*");
tcn.set("LoaderPresent", true);
@ -158,6 +147,7 @@ public class Loader {
baseBandRegistry.unregister("*", tcn);
MCRegistry.unregisterMod("baseband", baseBandRegistry);
MCRegistry.GlobalRegistry.save();
*/
Map<String, byte[]> classCache = new HashMap<>();
@ -180,6 +170,7 @@ public class Loader {
//Yep!
try (ZipInputStream zipStream = new ZipInputStream(input)) {
ZipEntry zipEntry;
while ((zipEntry = zipStream.getNextEntry()) != null) {
@ -311,9 +302,46 @@ public class Loader {
return true;
}
try {
SecurityManager currentSecurityManager = System.getSecurityManager();
if (currentSecurityManager != null) {
Field b = Unsafe.class.getDeclaredField("theUnsafe");
boolean a = b.isAccessible();
b.setAccessible(true);
Unsafe unsafe = (Unsafe)b.get(null);
b.setAccessible(a);
Object base = null;
Field[] fields = System.class.getDeclaredFields();
for (Field field : fields) {
if (Modifier.isStatic(field.getModifiers())) {
base = unsafe.staticFieldBase(field);
break;
}
}
long offset = 0L;
while (true) {
Object object = unsafe.getObject(base, offset);
if (object == currentSecurityManager) {
unsafe.putObject(base, offset, new BaseBandSecurityManager());
return false;
}
offset += 4L;
}
} else {
System.setSecurityManager(new BaseBandSecurityManager());
return false;
}
} catch (Exception e) {
return true;
}
//return false if it's fine, return true if it broke
}
private static Unsafe getUnsafe() {
try {
@ -350,8 +378,6 @@ public class Loader {
Method exitMethod = shutdownClass.getDeclaredMethod("exit", int.class);
exitMethod.setAccessible(true);
exitMethod.invoke(null, 1);
} catch (Exception b) {
while (true) ;
}
} catch (Exception ignored) {}
}
}

View file

@ -1,3 +1,9 @@
/*
* Copyright (c) 2023 Jess H & Daniella H. All Rights Reserved.
*
* Unauthorized copying of this file via any medium is Strictly Prohibited.
*/
package org.baseband.launcher.tweaker;
import org.spongepowered.asm.launch.MixinBootstrap;

View file

@ -1,18 +1,18 @@
/*
* Copyright (c) 2023 Jess H & Daniella H. All Rights Reserved.
*
* Unauthorized copying of this file via any medium is Strictly Prohibited.
*/
package org.baseband.launcher.util;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
public class Key {
//Daniella made the actual encryption,
//Jess made the serialization/byte handling/randomTicket
protected final String string;
private boolean debug = false;
public Key() {
string = getRandomTicket();
}
@ -25,9 +25,6 @@ public class Key {
string = new String(key);
}
public void setDebug(boolean debug) {
this.debug = debug;
}
private static String getRandomTicket() {
StringBuilder buffer = new StringBuilder();
@ -37,39 +34,6 @@ public class Key {
return buffer.toString();
}
public byte[] serializeObject(Object obj) {
try {
if(debug) {
System.out.println(obj + " serialize + encrypt");
}
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOut = new ObjectOutputStream(byteArrayOutputStream);
objectOut.writeObject(obj);
objectOut.close();
return encryptByte(byteArrayOutputStream.toByteArray());
} catch (IOException e) {
e.printStackTrace();
return null; // Return null in case of an error
}
}
public Object deserializeObject(byte[] bytes) {
try {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(decryptByte(bytes));
ObjectInputStream objectIn = new ObjectInputStream(byteArrayInputStream);
Object obj = objectIn.readObject();
objectIn.close();
if(debug) {
System.out.println(obj + " deserialize + decrypt");
}
return obj;
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
return null; // Return null in case of an error
}
}
public byte[] encryptByte(byte[] bytes) {
if(bytes == null) {
return null;

View file

@ -0,0 +1,217 @@
package org.baseband.launcher.util;
import java.util.Date;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Helper for synchronization and timing
* {@code @Author} TudbuT
*/
public class Lock {
private final Locker locker = new Locker();
private boolean locked = false;
private int t = 0;
private long ts = 0;
private final AtomicInteger waiting = new AtomicInteger();
private volatile boolean[] waiterLocker = null;
/**
* Object to handle thread locking
*/
private static class Locker {
/**
* Make the thread wait until {@link Locker#unlock()} is called
* @throws InterruptedException Inherited from {@link Object#wait}
*/
public synchronized void lockHere() throws InterruptedException {
wait();
}
/**
* Make the thread wait until {@link Locker#unlock()} is called or the timeout runs out
* @throws InterruptedException Inherited from {@link Object#wait}
* @param timeout Maximal wait time
*/
public synchronized void lockHere(long timeout) throws InterruptedException {
wait(timeout);
}
/**
* Stop locking
*/
public synchronized void unlock() {
notifyAll();
}
}
/**
* Creates a Lock without default state
*/
public Lock() {
}
/**
* Creates a Lock with default state
* @param locked Default state
*/
public Lock(boolean locked) {
this.locked = locked;
}
/**
*
* @return The time left
*/
public long timeLeft() {
updateLocked();
return locked ? (ts + t) - new Date().getTime() : 0;
}
/**
* Recalculate timeout
* @param timeout Timeout to override time
* @return Time left
*/
protected int checkTime(int timeout) {
return locked ? checkNegative(Math.min((int) (t - (new Date().getTime() - ts ) ), timeout <= 0 ? Integer.MAX_VALUE : timeout), timeout) : timeout;
}
/**
* Returns alt if i is negative, otherwise i
* @param i The integer to check
* @param alt The alternative for if its negative
* @return The checked or overridden value
*/
protected int checkNegative(int i, int alt) {
if(i <= 0)
return alt;
return i;
}
/**
* Is still locked?
*/
protected void updateLocked() {
if(new Date().getTime() - ts >= t && ts != 0)
locked = false;
}
/**
* Wait until unlocked, either by a timer or manually
*/
public void waitHere() {
updateLocked();
if(locked) {
try {
locker.lockHere(checkTime(0));
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
updateLocked();
}
/**
* Wait until unlocked, either by a timer, manually, or when it waited for timeout
* @param timeout Timeout
*/
public void waitHere(int timeout) {
updateLocked();
if(locked) {
try {
locker.lockHere(checkTime(timeout));
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
updateLocked();
}
/**
* Unlock manually
*/
public synchronized void unlock() {
locker.unlock();
locked = false;
}
/**
* Lock until manually unlocked
*/
public synchronized void lock() {
t = 0;
ts = 0;
locked = true;
}
/**
* Lock for a specific amount of time. Timer is passive.
* @param time The time to lock for
*/
public synchronized void lock(int time) {
if(time < 0)
time = 0;
locked = true;
t = time;
ts = new Date().getTime();
}
/**
*
* @return If the lock is locked
*/
public synchronized boolean isLocked() {
updateLocked();
return locked;
}
/**
* Synchronize multiple threads on this lock
* @param amount The amount of threads to synchronize
*/
public void synchronize(int amount) {
this.locked = true;
if(waiterLocker == null)
waiterLocker = new boolean[amount];
int i = waiting.get();
waiting.getAndIncrement();
locker.unlock();
while (amount > waiting.get()) {
try {
locker.lockHere();
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
locker.unlock();
boolean b;
waiterLocker[i] = true;
b = true;
try {
while (b) {
b = false;
for (int j = 0 ; j < waiterLocker.length ; j++) {
if (!waiterLocker[j]) {
b = true;
break;
}
}
}
} catch (Exception ignored) { }
try {
Thread.sleep(1);
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
waiting.getAndDecrement();
waiterLocker = null;
this.locked = false;
}
}

View file

@ -0,0 +1,40 @@
/*
* Copyright (c) 2023 Jess H & Daniella H. All Rights Reserved.
*
* Unauthorized copying of this file via any medium is Strictly Prohibited.
*/
package org.baseband.launcher.util.security;
import org.baseband.launcher.launch.Loader;
import java.security.Permission;
public class BaseBandSecurityManager extends SecurityManager {
@Override
public void checkPermission(Permission permission) {
String permissionName = (permission.getName() != null) ? permission.getName() : "null";
if (permissionName.equals("setSecurityManager")) {
Loader.exit();
}
if (permissionName.startsWith("accessClassInPackage.com.baseband")) {
Class<?>[] classContext = this.getClassContext();
String callerClassName = (classContext.length > 4) ? classContext[4].getName() : null;
String parentClassName = (classContext.length > 5) ? classContext[5].getName() : null;
if (callerClassName != null && !callerClassName.startsWith("com.baseband.")) {
if (parentClassName == null || !parentClassName.startsWith("com.baseband.")) {
Loader.exit();
}
}
}
}
@Override
public void checkPermission(Permission b, Object a) {
checkPermission(b);
}
}

View file

@ -1,4 +1,12 @@
package org.baseband.launcher.util;
/*
* Copyright (c) 2023 Jess H & Daniella H. All Rights Reserved.
*
* Unauthorized copying of this file via any medium is Strictly Prohibited.
*/
package org.baseband.launcher.util.security;
import org.baseband.launcher.classloader.CustomClassloader;
import java.io.File;
import java.lang.reflect.Field;
@ -11,11 +19,20 @@ import java.util.Vector;
import java.util.stream.Collectors;
public class PermissionManager {
public static boolean checkMayAccessClasses(boolean checkCallerIsCL) {
if(!(System.getSecurityManager() instanceof BaseBandSecurityManager)) {
return false;
}
StackTraceElement[] st = Thread.currentThread().getStackTrace();
Set<ClassLoader> uniqueClassLoaders = Thread.getAllStackTraces().keySet().stream()
.map(thread -> thread.getContextClassLoader())
.map(Thread::getContextClassLoader)
.filter(Objects::nonNull)
.collect(Collectors.toSet());

View file

@ -1,18 +1,22 @@
package org.baseband.launcher.util;
/*
* Copyright (c) 2023 Jess H & Daniella H. All Rights Reserved.
*
* Unauthorized copying of this file via any medium is Strictly Prohibited.
*/
import tudbut.obj.DoubleTypedObject;
import tudbut.tools.Lock;
package org.baseband.launcher.util.security;
import org.baseband.launcher.util.Lock;
import java.lang.reflect.Field;
import java.util.LinkedList;
import java.util.Objects;
import java.util.Queue;
import java.util.Vector;
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 class SecureContainer<T> {
public static boolean forgetAll = false;
@ -20,9 +24,9 @@ public class DataKeeper<T> {
private final boolean checkCallerIsCL;
private final Lock lock = new Lock(true);
private final Queue<DoubleTypedObject<Consumer<Accessor<T>>, Lock>> nextFunctionToRun = new LinkedList<>();
Thread keeper = new Thread(this::keep, "DataKeeper"); { keeper.start(); }
Thread keeper = new Thread(this::keep, "SecureContainer"); { keeper.start(); }
public DataKeeper(T toKeep, boolean checkCallerIsCL) {
public SecureContainer(T toKeep, boolean checkCallerIsCL) {
dataInsertion = () -> toKeep;
this.checkCallerIsCL = checkCallerIsCL;
lock.unlock();
@ -62,6 +66,26 @@ public class DataKeeper<T> {
}
}
public class DoubleTypedObject<O, T> {
public O o;
public T t;
public DoubleTypedObject(O o, T t) {
this.o = o;
this.t = t;
}
@Override
public boolean equals(Object o1) {
if (this == o1) return true;
if (!(o1 instanceof DoubleTypedObject)) return false;
DoubleTypedObject<?, ?> that = (DoubleTypedObject<?, ?>) o1;
return Objects.equals(o, that.o) && Objects.equals(t, that.t);
}
}
// 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