mirror of
https://github.com/funkemunky/AntiVPN.git
synced 2026-05-31 01:21:55 +00:00
Fixing MySQL Lib injection
This commit is contained in:
@@ -47,7 +47,7 @@
|
||||
<path>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.30</version>
|
||||
<version>1.18.44</version>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
</configuration>
|
||||
@@ -196,4 +196,4 @@
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
</project>
|
||||
|
||||
@@ -272,4 +272,4 @@ public class AntiVPN {
|
||||
private void registerCommands() {
|
||||
commands.add(new AntiVPNCommand());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,7 +255,6 @@ public class H2VPN implements VPNDatabase {
|
||||
try {
|
||||
String cidrString = set.getString("cidr_string");
|
||||
|
||||
AntiVPN.getInstance().getExecutor().log("CIDR String: %s", cidrString);
|
||||
ips.add(new CIDRUtils(cidrString));
|
||||
|
||||
} catch (UnknownHostException e) {
|
||||
|
||||
@@ -25,7 +25,6 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Properties;
|
||||
import java.util.logging.Level;
|
||||
@@ -36,12 +35,17 @@ public class MySQL {
|
||||
public static void init() {
|
||||
try {
|
||||
if (conn == null || conn.isClosed()) {
|
||||
DriverManager.registerDriver(new Driver());
|
||||
conn = DriverManager.getConnection("jdbc:mysql://" + AntiVPN.getInstance().getVpnConfig().getIp()
|
||||
+ ":" + AntiVPN.getInstance().getVpnConfig().getPort()
|
||||
+ "/?useSSL=true&autoReconnect=true",
|
||||
AntiVPN.getInstance().getVpnConfig().getUsername(),
|
||||
AntiVPN.getInstance().getVpnConfig().getPassword());
|
||||
String url = "jdbc:mysql://" + AntiVPN.getInstance().getVpnConfig().getIp()
|
||||
+ ":" + AntiVPN.getInstance().getVpnConfig().getPort()
|
||||
+ "/?useSSL=true&autoReconnect=true";
|
||||
Properties properties = new Properties();
|
||||
properties.setProperty("user", AntiVPN.getInstance().getVpnConfig().getUsername());
|
||||
properties.setProperty("password", AntiVPN.getInstance().getVpnConfig().getPassword());
|
||||
|
||||
conn = new Driver().connect(url, properties);
|
||||
if (conn == null) {
|
||||
throw new SQLException("MySQL driver did not accept URL: " + url);
|
||||
}
|
||||
conn.setAutoCommit(true);
|
||||
Query.use(conn);
|
||||
Query.prepare("CREATE DATABASE IF NOT EXISTS `"
|
||||
@@ -51,6 +55,7 @@ public class MySQL {
|
||||
}
|
||||
} catch (Exception e) {
|
||||
AntiVPN.getInstance().getExecutor().logException("Failed to load mysql: " + e.getMessage(), e);
|
||||
throw new RuntimeException("Could not initialize MySQL connection", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,9 +21,14 @@ import dev.brighten.antivpn.utils.NonnullByDefault;
|
||||
import dev.brighten.antivpn.utils.Supplier;
|
||||
import dev.brighten.antivpn.utils.Suppliers;
|
||||
import lombok.Getter;
|
||||
import org.objectweb.asm.AnnotationVisitor;
|
||||
import org.objectweb.asm.ClassReader;
|
||||
import org.objectweb.asm.ClassVisitor;
|
||||
import org.objectweb.asm.ClassWriter;
|
||||
import org.objectweb.asm.FieldVisitor;
|
||||
import org.objectweb.asm.MethodVisitor;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
import org.objectweb.asm.RecordComponentVisitor;
|
||||
import org.objectweb.asm.commons.ClassRemapper;
|
||||
import org.objectweb.asm.commons.Remapper;
|
||||
|
||||
@@ -31,6 +36,7 @@ import java.io.*;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.util.*;
|
||||
import java.util.jar.JarEntry;
|
||||
@@ -44,6 +50,8 @@ import java.util.jar.JarOutputStream;
|
||||
@SuppressWarnings("CallToPrintStackTrace")
|
||||
@NonnullByDefault
|
||||
public final class LibraryLoader {
|
||||
private static final int RELOCATION_FORMAT_VERSION = 5;
|
||||
private static final String RELOCATION_METADATA_PATH = "META-INF/antivpn-relocation.properties";
|
||||
|
||||
@SuppressWarnings("Guava")
|
||||
private static final Supplier<URLClassLoaderAccess> URL_INJECTOR = AntiVPN.getInstance().getClass().getClassLoader() instanceof URLClassLoader ?
|
||||
@@ -109,8 +117,8 @@ public final class LibraryLoader {
|
||||
}
|
||||
}
|
||||
|
||||
// If we have relocations, create a relocated jar
|
||||
if (!relocations.isEmpty() && !saveLocation.exists()) {
|
||||
// Rebuild relocated jars when the relocation format changes or the cached jar is stale.
|
||||
if (!relocations.isEmpty() && shouldRebuildRelocatedJar(saveLocation, relocations)) {
|
||||
try {
|
||||
System.out.println("Relocating packages for " + name + "...");
|
||||
relocateJar(originalJar, saveLocation, relocations);
|
||||
@@ -142,6 +150,8 @@ public final class LibraryLoader {
|
||||
// Track service files to avoid duplicates
|
||||
Map<String, StringBuilder> serviceFiles = new HashMap<>();
|
||||
|
||||
Files.deleteIfExists(targetJar.toPath());
|
||||
|
||||
try (JarFile jar = new JarFile(sourceJar);
|
||||
JarOutputStream jos = new JarOutputStream(Files.newOutputStream(targetJar.toPath()))) {
|
||||
|
||||
@@ -168,12 +178,14 @@ public final class LibraryLoader {
|
||||
jos.putNextEntry(newEntry);
|
||||
|
||||
byte[] classBytes = readAllBytes(is);
|
||||
byte[] relocatedBytes = relocateClass(classBytes, relocations);
|
||||
byte[] relocatedBytes = relocateClass(name, classBytes, relocations);
|
||||
jos.write(relocatedBytes);
|
||||
jos.closeEntry();
|
||||
} else {
|
||||
// Copy other files as-is
|
||||
JarEntry newEntry = new JarEntry(name);
|
||||
// Relocate package-scoped resources so ResourceBundle lookups follow relocated packages.
|
||||
String relocatedPath = relocateResourcePath(name, relocations);
|
||||
|
||||
JarEntry newEntry = new JarEntry(relocatedPath);
|
||||
jos.putNextEntry(newEntry);
|
||||
copyStream(is, jos);
|
||||
jos.closeEntry();
|
||||
@@ -186,7 +198,7 @@ public final class LibraryLoader {
|
||||
try {
|
||||
JarEntry serviceEntry = new JarEntry(entry.getKey());
|
||||
jos.putNextEntry(serviceEntry);
|
||||
jos.write(entry.getValue().toString().getBytes());
|
||||
jos.write(entry.getValue().toString().getBytes(StandardCharsets.UTF_8));
|
||||
jos.closeEntry();
|
||||
} catch (Exception e) {
|
||||
// Log but continue with other service files
|
||||
@@ -194,7 +206,64 @@ public final class LibraryLoader {
|
||||
entry.getKey() + ": " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
writeRelocationMetadata(jos, relocations);
|
||||
}
|
||||
|
||||
validateRelocatedJar(targetJar, relocations);
|
||||
}
|
||||
|
||||
private static boolean shouldRebuildRelocatedJar(File relocatedJar, Map<String, String> relocations) {
|
||||
if (!relocatedJar.exists()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try (JarFile jar = new JarFile(relocatedJar)) {
|
||||
JarEntry metadataEntry = jar.getJarEntry(RELOCATION_METADATA_PATH);
|
||||
if (metadataEntry == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Properties metadata = new Properties();
|
||||
try (InputStream is = jar.getInputStream(metadataEntry)) {
|
||||
metadata.load(is);
|
||||
}
|
||||
|
||||
if (!String.valueOf(RELOCATION_FORMAT_VERSION).equals(metadata.getProperty("formatVersion"))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (Map.Entry<String, String> relocation : relocations.entrySet()) {
|
||||
String key = "relocation." + relocation.getKey();
|
||||
if (!relocation.getValue().equals(metadata.getProperty(key))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return Integer.toString(relocations.size()).equals(metadata.getProperty("relocationCount"));
|
||||
} catch (IOException e) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static void writeRelocationMetadata(JarOutputStream jos, Map<String, String> relocations)
|
||||
throws IOException {
|
||||
Properties metadata = new Properties();
|
||||
metadata.setProperty("formatVersion", Integer.toString(RELOCATION_FORMAT_VERSION));
|
||||
metadata.setProperty("relocationCount", Integer.toString(relocations.size()));
|
||||
|
||||
Map<String, String> sortedRelocations = new TreeMap<>(relocations);
|
||||
for (Map.Entry<String, String> relocation : sortedRelocations.entrySet()) {
|
||||
metadata.setProperty("relocation." + relocation.getKey(), relocation.getValue());
|
||||
}
|
||||
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||
metadata.store(buffer, "AntiVPN relocation metadata");
|
||||
|
||||
JarEntry metadataEntry = new JarEntry(RELOCATION_METADATA_PATH);
|
||||
jos.putNextEntry(metadataEntry);
|
||||
jos.write(buffer.toByteArray());
|
||||
jos.closeEntry();
|
||||
}
|
||||
|
||||
private static void processServiceFile(String name, InputStream is,
|
||||
@@ -220,7 +289,7 @@ public final class LibraryLoader {
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] relocateClass(byte[] classBytes, Map<String, String> relocations) {
|
||||
private static byte[] relocateClass(String entryName, byte[] classBytes, Map<String, String> relocations) {
|
||||
try {
|
||||
// Convert to slash notation for ASM
|
||||
Remapper prefixRemapper = getPrefixRemapper(relocations);
|
||||
@@ -239,21 +308,87 @@ public final class LibraryLoader {
|
||||
}
|
||||
};
|
||||
|
||||
ClassVisitor visitor = new ClassRemapper(writer, prefixRemapper);
|
||||
ClassVisitor visitor = createStringRelocationVisitor(new ClassRemapper(writer, prefixRemapper), relocations);
|
||||
visitor = createMySqlUtilFallbackVisitor(entryName, visitor);
|
||||
|
||||
// Process class with remapper
|
||||
reader.accept(visitor, ClassReader.EXPAND_FRAMES);
|
||||
reader.accept(visitor, 0);
|
||||
|
||||
return writer.toByteArray();
|
||||
return relocateUtf8Constants(writer.toByteArray(), relocations);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return classBytes;
|
||||
throw new IllegalStateException("Failed to relocate class entry " + entryName, e);
|
||||
}
|
||||
}
|
||||
|
||||
public static String relocateReflectiveClassName(String className) {
|
||||
if (className == null || className.startsWith("dev.brighten.antivpn.shaded.")) {
|
||||
return className;
|
||||
}
|
||||
|
||||
if (className.startsWith("com.mysql.cj") || className.startsWith("com.mysql.jdbc")) {
|
||||
return "dev.brighten.antivpn.shaded." + className;
|
||||
}
|
||||
|
||||
return className;
|
||||
}
|
||||
|
||||
private static byte[] relocateUtf8Constants(byte[] classBytes, Map<String, String> relocations) throws IOException {
|
||||
Map<String, String> dotMappings = new HashMap<>();
|
||||
Map<String, String> slashMappings = new HashMap<>();
|
||||
for (Map.Entry<String, String> entry : relocations.entrySet()) {
|
||||
dotMappings.put(entry.getKey(), entry.getValue());
|
||||
slashMappings.put(entry.getKey().replace('.', '/'), entry.getValue().replace('.', '/'));
|
||||
}
|
||||
|
||||
DataInputStream in = new DataInputStream(new ByteArrayInputStream(classBytes));
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(classBytes.length + 256);
|
||||
DataOutputStream out = new DataOutputStream(baos);
|
||||
|
||||
out.writeInt(in.readInt());
|
||||
out.writeShort(in.readUnsignedShort());
|
||||
out.writeShort(in.readUnsignedShort());
|
||||
|
||||
int constantPoolCount = in.readUnsignedShort();
|
||||
out.writeShort(constantPoolCount);
|
||||
|
||||
for (int i = 1; i < constantPoolCount; i++) {
|
||||
int tag = in.readUnsignedByte();
|
||||
out.writeByte(tag);
|
||||
|
||||
switch (tag) {
|
||||
case 1 -> {
|
||||
String value = in.readUTF();
|
||||
String relocated = relocateStringValue(value, dotMappings, slashMappings);
|
||||
out.writeUTF(relocated);
|
||||
}
|
||||
case 3, 4 -> out.writeInt(in.readInt());
|
||||
case 5, 6 -> {
|
||||
out.writeLong(in.readLong());
|
||||
i++;
|
||||
}
|
||||
case 7, 8, 16, 19, 20 -> out.writeShort(in.readUnsignedShort());
|
||||
case 9, 10, 11, 12, 17, 18 -> {
|
||||
out.writeShort(in.readUnsignedShort());
|
||||
out.writeShort(in.readUnsignedShort());
|
||||
}
|
||||
case 15 -> {
|
||||
out.writeByte(in.readUnsignedByte());
|
||||
out.writeShort(in.readUnsignedShort());
|
||||
}
|
||||
default -> throw new IOException("Unknown constant pool tag " + tag);
|
||||
}
|
||||
}
|
||||
|
||||
copyStream(in, out);
|
||||
out.flush();
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
private static Remapper getPrefixRemapper(Map<String, String> relocations) {
|
||||
Map<String, String> slashMappings = new HashMap<>();
|
||||
Map<String, String> dotMappings = new HashMap<>();
|
||||
for (Map.Entry<String, String> entry : relocations.entrySet()) {
|
||||
dotMappings.put(entry.getKey(), entry.getValue());
|
||||
String fromSlash = entry.getKey().replace('.', '/');
|
||||
String toSlash = entry.getValue().replace('.', '/');
|
||||
slashMappings.put(fromSlash, toSlash);
|
||||
@@ -275,9 +410,359 @@ public final class LibraryLoader {
|
||||
}
|
||||
return typeName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object mapValue(Object value) {
|
||||
if (value instanceof String stringValue) {
|
||||
return relocateStringValue(stringValue, dotMappings, slashMappings);
|
||||
}
|
||||
return super.mapValue(value);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static ClassVisitor createMySqlUtilFallbackVisitor(String entryName, ClassVisitor delegate) {
|
||||
if (!"com/mysql/cj/util/Util.class".equals(entryName)) {
|
||||
return delegate;
|
||||
}
|
||||
|
||||
return new ClassVisitor(Opcodes.ASM9, delegate) {
|
||||
@Override
|
||||
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature,
|
||||
String[] exceptions) {
|
||||
MethodVisitor visitor = super.visitMethod(access, name, descriptor, signature, exceptions);
|
||||
if (visitor == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!"getInstance".equals(name)
|
||||
|| !"(Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/Class;[Ljava/lang/Object;Lcom/mysql/cj/exceptions/ExceptionInterceptor;)Ljava/lang/Object;".equals(descriptor)) {
|
||||
return visitor;
|
||||
}
|
||||
|
||||
return new MethodVisitor(Opcodes.ASM9, visitor) {
|
||||
@Override
|
||||
public void visitCode() {
|
||||
super.visitCode();
|
||||
super.visitVarInsn(Opcodes.ALOAD, 1);
|
||||
super.visitMethodInsn(Opcodes.INVOKESTATIC,
|
||||
"dev/brighten/antivpn/depends/LibraryLoader",
|
||||
"relocateReflectiveClassName",
|
||||
"(Ljava/lang/String;)Ljava/lang/String;",
|
||||
false);
|
||||
super.visitVarInsn(Opcodes.ASTORE, 1);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static ClassVisitor createStringRelocationVisitor(ClassVisitor delegate,
|
||||
Map<String, String> relocations) {
|
||||
Map<String, String> dotMappings = new HashMap<>();
|
||||
Map<String, String> slashMappings = new HashMap<>();
|
||||
for (Map.Entry<String, String> entry : relocations.entrySet()) {
|
||||
dotMappings.put(entry.getKey(), entry.getValue());
|
||||
slashMappings.put(entry.getKey().replace('.', '/'), entry.getValue().replace('.', '/'));
|
||||
}
|
||||
|
||||
return new ClassVisitor(Opcodes.ASM9, delegate) {
|
||||
@Override
|
||||
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
|
||||
return wrapAnnotationVisitor(super.visitAnnotation(descriptor, visible), dotMappings, slashMappings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnnotationVisitor visitTypeAnnotation(int typeRef, org.objectweb.asm.TypePath typePath,
|
||||
String descriptor, boolean visible) {
|
||||
return wrapAnnotationVisitor(super.visitTypeAnnotation(typeRef, typePath, descriptor, visible),
|
||||
dotMappings, slashMappings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecordComponentVisitor visitRecordComponent(String name, String descriptor, String signature) {
|
||||
RecordComponentVisitor visitor = super.visitRecordComponent(name, descriptor, signature);
|
||||
if (visitor == null) {
|
||||
return null;
|
||||
}
|
||||
return new RecordComponentVisitor(Opcodes.ASM9, visitor) {
|
||||
@Override
|
||||
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
|
||||
return wrapAnnotationVisitor(super.visitAnnotation(descriptor, visible),
|
||||
dotMappings, slashMappings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnnotationVisitor visitTypeAnnotation(int typeRef, org.objectweb.asm.TypePath typePath,
|
||||
String descriptor, boolean visible) {
|
||||
return wrapAnnotationVisitor(super.visitTypeAnnotation(typeRef, typePath, descriptor, visible),
|
||||
dotMappings, slashMappings);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
|
||||
FieldVisitor visitor = super.visitField(access, name, descriptor, signature,
|
||||
relocateAsmValue(value, dotMappings, slashMappings));
|
||||
if (visitor == null) {
|
||||
return null;
|
||||
}
|
||||
return new FieldVisitor(Opcodes.ASM9, visitor) {
|
||||
@Override
|
||||
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
|
||||
return wrapAnnotationVisitor(super.visitAnnotation(descriptor, visible),
|
||||
dotMappings, slashMappings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnnotationVisitor visitTypeAnnotation(int typeRef, org.objectweb.asm.TypePath typePath,
|
||||
String descriptor, boolean visible) {
|
||||
return wrapAnnotationVisitor(super.visitTypeAnnotation(typeRef, typePath, descriptor, visible),
|
||||
dotMappings, slashMappings);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature,
|
||||
String[] exceptions) {
|
||||
MethodVisitor visitor = super.visitMethod(access, name, descriptor, signature, exceptions);
|
||||
if (visitor == null) {
|
||||
return null;
|
||||
}
|
||||
return new MethodVisitor(Opcodes.ASM9, visitor) {
|
||||
@Override
|
||||
public AnnotationVisitor visitAnnotationDefault() {
|
||||
return wrapAnnotationVisitor(super.visitAnnotationDefault(), dotMappings, slashMappings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
|
||||
return wrapAnnotationVisitor(super.visitAnnotation(descriptor, visible),
|
||||
dotMappings, slashMappings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnnotationVisitor visitTypeAnnotation(int typeRef, org.objectweb.asm.TypePath typePath,
|
||||
String descriptor, boolean visible) {
|
||||
return wrapAnnotationVisitor(super.visitTypeAnnotation(typeRef, typePath, descriptor, visible),
|
||||
dotMappings, slashMappings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnnotationVisitor visitParameterAnnotation(int parameter, String descriptor,
|
||||
boolean visible) {
|
||||
return wrapAnnotationVisitor(super.visitParameterAnnotation(parameter, descriptor, visible),
|
||||
dotMappings, slashMappings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnnotationVisitor visitInsnAnnotation(int typeRef, org.objectweb.asm.TypePath typePath,
|
||||
String descriptor, boolean visible) {
|
||||
return wrapAnnotationVisitor(super.visitInsnAnnotation(typeRef, typePath, descriptor, visible),
|
||||
dotMappings, slashMappings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnnotationVisitor visitTryCatchAnnotation(int typeRef, org.objectweb.asm.TypePath typePath,
|
||||
String descriptor, boolean visible) {
|
||||
return wrapAnnotationVisitor(super.visitTryCatchAnnotation(typeRef, typePath, descriptor, visible),
|
||||
dotMappings, slashMappings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnnotationVisitor visitLocalVariableAnnotation(int typeRef,
|
||||
org.objectweb.asm.TypePath typePath,
|
||||
org.objectweb.asm.Label[] start,
|
||||
org.objectweb.asm.Label[] end,
|
||||
int[] index, String descriptor,
|
||||
boolean visible) {
|
||||
return wrapAnnotationVisitor(
|
||||
super.visitLocalVariableAnnotation(typeRef, typePath, start, end, index, descriptor, visible),
|
||||
dotMappings, slashMappings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitLdcInsn(Object value) {
|
||||
super.visitLdcInsn(relocateAsmValue(value, dotMappings, slashMappings));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitInvokeDynamicInsn(String name, String descriptor, org.objectweb.asm.Handle bootstrapMethodHandle,
|
||||
Object... bootstrapMethodArguments) {
|
||||
Object[] relocatedArgs = new Object[bootstrapMethodArguments.length];
|
||||
for (int i = 0; i < bootstrapMethodArguments.length; i++) {
|
||||
relocatedArgs[i] = relocateAsmValue(bootstrapMethodArguments[i], dotMappings, slashMappings);
|
||||
}
|
||||
super.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, relocatedArgs);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static AnnotationVisitor wrapAnnotationVisitor(AnnotationVisitor delegate,
|
||||
Map<String, String> dotMappings,
|
||||
Map<String, String> slashMappings) {
|
||||
if (delegate == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new AnnotationVisitor(Opcodes.ASM9, delegate) {
|
||||
@Override
|
||||
public void visit(String name, Object value) {
|
||||
super.visit(name, relocateAsmValue(value, dotMappings, slashMappings));
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnnotationVisitor visitAnnotation(String name, String descriptor) {
|
||||
return wrapAnnotationVisitor(super.visitAnnotation(name, descriptor), dotMappings, slashMappings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnnotationVisitor visitArray(String name) {
|
||||
return wrapAnnotationVisitor(super.visitArray(name), dotMappings, slashMappings);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static Object relocateAsmValue(Object value, Map<String, String> dotMappings,
|
||||
Map<String, String> slashMappings) {
|
||||
if (value instanceof String stringValue) {
|
||||
return relocateStringValue(stringValue, dotMappings, slashMappings);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private static String relocateStringValue(String value, Map<String, String> dotMappings,
|
||||
Map<String, String> slashMappings) {
|
||||
for (Map.Entry<String, String> entry : dotMappings.entrySet()) {
|
||||
String from = entry.getKey();
|
||||
String relocated = relocateByPrefixes(value, from, entry.getValue(), '.', '$');
|
||||
if (!relocated.equals(value)) {
|
||||
return relocated;
|
||||
}
|
||||
}
|
||||
|
||||
for (Map.Entry<String, String> entry : slashMappings.entrySet()) {
|
||||
String from = entry.getKey();
|
||||
String to = entry.getValue();
|
||||
|
||||
String relocated = relocateByPrefixes(value, from, to, '/', '$');
|
||||
if (!relocated.equals(value)) {
|
||||
return relocated;
|
||||
}
|
||||
|
||||
relocated = relocateByPrefixes(value, "/" + from, "/" + to, '/', '$');
|
||||
if (!relocated.equals(value)) {
|
||||
return relocated;
|
||||
}
|
||||
|
||||
relocated = relocateByPrefixes(value, "L" + from, "L" + to, '/', '$', ';');
|
||||
if (!relocated.equals(value)) {
|
||||
return relocated;
|
||||
}
|
||||
|
||||
relocated = relocateByPrefixes(value, "[L" + from, "[L" + to, '/', '$', ';');
|
||||
if (!relocated.equals(value)) {
|
||||
return relocated;
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private static String relocateByPrefixes(String value, String from, String to, char... delimiters) {
|
||||
if (value.equals(from)) {
|
||||
return to;
|
||||
}
|
||||
|
||||
for (char delimiter : delimiters) {
|
||||
if (value.startsWith(from + delimiter)) {
|
||||
return to + value.substring(from.length());
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private static void validateRelocatedJar(File targetJar, Map<String, String> relocations) throws IOException {
|
||||
Set<String> relocatedPrefixes = new HashSet<>();
|
||||
Map<String, String> dotMappings = new HashMap<>();
|
||||
Map<String, String> slashMappings = new HashMap<>();
|
||||
for (Map.Entry<String, String> relocation : relocations.entrySet()) {
|
||||
relocatedPrefixes.add(relocation.getValue().replace('.', '/') + "/");
|
||||
dotMappings.put(relocation.getKey(), relocation.getValue());
|
||||
slashMappings.put(relocation.getKey().replace('.', '/'), relocation.getValue().replace('.', '/'));
|
||||
}
|
||||
|
||||
try (JarFile jar = new JarFile(targetJar)) {
|
||||
Enumeration<JarEntry> entries = jar.entries();
|
||||
while (entries.hasMoreElements()) {
|
||||
JarEntry entry = entries.nextElement();
|
||||
if (entry.isDirectory() || !entry.getName().endsWith(".class")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
boolean shouldValidate = false;
|
||||
for (String relocatedPrefix : relocatedPrefixes) {
|
||||
if (entry.getName().startsWith(relocatedPrefix)) {
|
||||
shouldValidate = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!shouldValidate) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try (InputStream is = jar.getInputStream(entry)) {
|
||||
findUnrelocatedConstant(entry.getName(), readAllBytes(is), dotMappings, slashMappings);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void findUnrelocatedConstant(String entryName, byte[] classBytes, Map<String, String> dotMappings,
|
||||
Map<String, String> slashMappings) throws IOException {
|
||||
DataInputStream in = new DataInputStream(new ByteArrayInputStream(classBytes));
|
||||
in.readInt();
|
||||
in.readUnsignedShort();
|
||||
in.readUnsignedShort();
|
||||
int constantPoolCount = in.readUnsignedShort();
|
||||
|
||||
for (int i = 1; i < constantPoolCount; i++) {
|
||||
int tag = in.readUnsignedByte();
|
||||
switch (tag) {
|
||||
case 1 -> {
|
||||
String value = in.readUTF();
|
||||
String relocated = relocateStringValue(value, dotMappings, slashMappings);
|
||||
if (!value.equals(relocated)) {
|
||||
throw new IOException("Relocated jar still contains original reference '" + value
|
||||
+ "' in class entry " + entryName);
|
||||
}
|
||||
}
|
||||
case 3, 4 -> in.readInt();
|
||||
case 5, 6 -> {
|
||||
in.readLong();
|
||||
i++;
|
||||
}
|
||||
case 7, 8, 16, 19, 20 -> in.readUnsignedShort();
|
||||
case 9, 10, 11, 12, 17, 18 -> {
|
||||
in.readUnsignedShort();
|
||||
in.readUnsignedShort();
|
||||
}
|
||||
case 15 -> {
|
||||
in.readUnsignedByte();
|
||||
in.readUnsignedShort();
|
||||
}
|
||||
default -> throw new IOException("Unknown constant pool tag " + tag + " while validating " + entryName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String relocateClassPath(String path, Map<String, String> relocations) {
|
||||
// Convert path to package format (replacing / with .)
|
||||
String packagePath = path.substring(0, path.length() - 6).replace('/', '.');
|
||||
@@ -294,6 +779,23 @@ public final class LibraryLoader {
|
||||
return packagePath.replace('.', '/') + ".class";
|
||||
}
|
||||
|
||||
private static String relocateResourcePath(String path, Map<String, String> relocations) {
|
||||
if (path.startsWith("META-INF/")) {
|
||||
return path;
|
||||
}
|
||||
|
||||
for (Map.Entry<String, String> relocation : relocations.entrySet()) {
|
||||
String fromPath = relocation.getKey().replace('.', '/');
|
||||
String toPath = relocation.getValue().replace('.', '/');
|
||||
|
||||
if (path.startsWith(fromPath + "/")) {
|
||||
return toPath + path.substring(fromPath.length());
|
||||
}
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
private static byte[] readAllBytes(InputStream is) throws IOException {
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||
int bytesRead;
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
<path>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.30</version>
|
||||
<version>1.18.44</version>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
</configuration>
|
||||
|
||||
@@ -172,4 +172,4 @@
|
||||
</resources>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
</project>
|
||||
|
||||
+3
-5
@@ -31,7 +31,6 @@ import org.bstats.velocity.Metrics;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@@ -41,7 +40,7 @@ public class VelocityPlugin implements LoaderBootstrap {
|
||||
private final ProxyServer server;
|
||||
private final Logger logger;
|
||||
private final Metrics.Factory metricsFactory;
|
||||
private final Path configDir;
|
||||
private File dataFolder;
|
||||
|
||||
@Nullable
|
||||
private Metrics metrics;
|
||||
@@ -54,7 +53,6 @@ public class VelocityPlugin implements LoaderBootstrap {
|
||||
public VelocityPlugin(Map<Class<?>, Object> objectsMap) {
|
||||
this.server = (ProxyServer) objectsMap.get(ProxyServer.class);
|
||||
this.logger = (Logger) objectsMap.get(Logger.class);
|
||||
this.configDir = (Path) objectsMap.get(Path.class);
|
||||
this.metricsFactory = (Metrics.Factory) objectsMap.get(String.class);
|
||||
this.pluginInstance = objectsMap.get(LoaderBootstrap.class);
|
||||
}
|
||||
@@ -74,7 +72,7 @@ public class VelocityPlugin implements LoaderBootstrap {
|
||||
|
||||
@Override
|
||||
public void onLoad(File dataFolder) {
|
||||
|
||||
this.dataFolder = dataFolder;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -84,7 +82,7 @@ public class VelocityPlugin implements LoaderBootstrap {
|
||||
|
||||
//Loading plugin
|
||||
logger.info("Starting AntiVPN services...");
|
||||
AntiVPN.start(new VelocityListener(), new VelocityPlayerExecutor(), configDir.toFile());
|
||||
AntiVPN.start(new VelocityListener(), new VelocityPlayerExecutor(), dataFolder);
|
||||
|
||||
|
||||
if(AntiVPN.getInstance().getVpnConfig().metrics()) {
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
<path>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.30</version>
|
||||
<version>1.18.44</version>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
</configuration>
|
||||
@@ -83,7 +83,7 @@
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.30</version>
|
||||
<version>1.18.44</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
Reference in New Issue
Block a user