diff --git a/deobfuscator-api/src/main/java/dev/xdark/ssvm/classloading/SupplyingClassLoader.java b/deobfuscator-api/src/main/java/dev/xdark/ssvm/classloading/SupplyingClassLoader.java new file mode 100644 index 0000000..d47c3cb --- /dev/null +++ b/deobfuscator-api/src/main/java/dev/xdark/ssvm/classloading/SupplyingClassLoader.java @@ -0,0 +1,71 @@ +package dev.xdark.ssvm.classloading; + +import dev.xdark.ssvm.VirtualMachine; +import dev.xdark.ssvm.api.MethodInvoker; +import dev.xdark.ssvm.api.VMInterface; +import dev.xdark.ssvm.mirror.member.JavaMethod; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLStreamHandler; + +/** + * A custom classloader to supply additional content to the {@link VirtualMachine}. + *
+ * You will want to install this into the VM and use {@link VMInterface#setInvoker(JavaMethod, MethodInvoker)} on the + * providing methods to supply classes and resources. + * + * @see SupplyingClassLoaderInstaller + * + * @author Matt Coley + */ +public class SupplyingClassLoader extends ClassLoader { + public native byte[] provideResource(String name); + + public native byte[] provideClass(String name); + + @Override + protected Class findClass(String name) throws ClassNotFoundException { + byte[] classBytes = provideClass(name); + if (classBytes != null) + return defineClass(name, classBytes, 0, classBytes.length); + return super.findClass(name); + } + + @Override + protected URL findResource(String name) { + byte[] resourceBytes = provideResource(name); + if (resourceBytes != null) { + try { + return new URL("memory", "", -1, "", new URLStreamHandler() { + @Override + protected URLConnection openConnection(URL u) { + return new URLConnection(u) { + private InputStream is; + + @Override + public void connect() { + // no-op + } + + @Override + public InputStream getInputStream() { + if (is == null) { + is = new ByteArrayInputStream(resourceBytes); + } + return is; + } + }; + } + }); + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + } + + return super.findResource(name); + } +} diff --git a/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/context/Context.java b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/context/Context.java index f47ba99..641999a 100644 --- a/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/context/Context.java +++ b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/context/Context.java @@ -5,8 +5,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import dev.xdark.ssvm.VirtualMachine; -import dev.xdark.ssvm.execution.VMException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import uwu.narumi.deobfuscator.api.asm.ClassWrapper; @@ -24,7 +22,7 @@ public class Context { private final DeobfuscatorOptions options; private final LibraryClassLoader libraryLoader; - private SandBox sandBox = null; + private SandBox globalSandBox = null; public Context(DeobfuscatorOptions options, LibraryClassLoader libraryLoader) { this.options = options; @@ -35,19 +33,11 @@ public Context(DeobfuscatorOptions options, LibraryClassLoader libraryLoader) { * Gets sandbox or creates if it does not exist. */ public SandBox getSandBox() { - if (this.sandBox == null) { + if (this.globalSandBox == null) { // Lazily load sandbox - VirtualMachine vm = options.virtualMachine() == null ? new VirtualMachine() : options.virtualMachine(); - try { - this.sandBox = new SandBox(this.libraryLoader, vm); - } catch (VMException ex) { - LOGGER.error("SSVM bootstrap failed. Make sure that you run this deobfuscator on java 17"); - SandBox.logVMException(ex, vm); - - throw new RuntimeException(ex); - } + this.globalSandBox = new SandBox(this); } - return this.sandBox; + return this.globalSandBox; } public DeobfuscatorOptions getOptions() { diff --git a/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/context/DeobfuscatorOptions.java b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/context/DeobfuscatorOptions.java index 39400be..2615cb9 100644 --- a/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/context/DeobfuscatorOptions.java +++ b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/context/DeobfuscatorOptions.java @@ -32,9 +32,7 @@ public record DeobfuscatorOptions( boolean printStacktraces, boolean continueOnError, - boolean verifyBytecode, - - VirtualMachine virtualMachine + boolean verifyBytecode ) { public static DeobfuscatorOptions.Builder builder() { return new DeobfuscatorOptions.Builder(); @@ -70,8 +68,6 @@ public static class Builder { private boolean continueOnError = false; private boolean verifyBytecode = false; - private VirtualMachine virtualMachine = null; - private Builder() { } @@ -203,12 +199,6 @@ public DeobfuscatorOptions.Builder verifyBytecode() { return this; } - @Contract("_ -> this") - public DeobfuscatorOptions.Builder virtualMachine(VirtualMachine virtualMachine) { - this.virtualMachine = virtualMachine; - return this; - } - /** * Build immutable {@link DeobfuscatorOptions} with options verification */ @@ -240,9 +230,7 @@ public DeobfuscatorOptions build() { // Other config printStacktraces, continueOnError, - verifyBytecode, - - virtualMachine + verifyBytecode ); } } diff --git a/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/execution/SandBox.java b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/execution/SandBox.java index 4faa07f..ce88e2f 100644 --- a/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/execution/SandBox.java +++ b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/execution/SandBox.java @@ -12,84 +12,88 @@ import dev.xdark.ssvm.invoke.InvocationUtil; import dev.xdark.ssvm.memory.management.MemoryManager; import dev.xdark.ssvm.mirror.type.InstanceClass; +import dev.xdark.ssvm.mirror.type.JavaClass; import dev.xdark.ssvm.operation.VMOperations; import dev.xdark.ssvm.symbol.Primitives; import dev.xdark.ssvm.symbol.Symbols; import dev.xdark.ssvm.thread.ThreadManager; import dev.xdark.ssvm.util.Reflection; + +import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; +import java.util.List; import dev.xdark.ssvm.value.InstanceValue; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import uwu.narumi.deobfuscator.api.library.LibraryClassLoader; +import uwu.narumi.deobfuscator.api.context.Context; /** - * A wrapper for {@link VirtualMachine} + * A wrapper for {@link VirtualMachine} with some additional features and patches */ public class SandBox { private static final Logger LOGGER = LogManager.getLogger(SandBox.class); - private final VirtualMachine virtualMachine; + private final VirtualMachine vm; + private final Context context; private final MemoryManager memoryManager; private final SupplyingClassLoaderInstaller.Helper helper; private final InvocationUtil invocationUtil; - public SandBox(LibraryClassLoader loader) { - this(loader, new VirtualMachine()); + public SandBox(Context context) { + this(context, new VirtualMachine()); } - public SandBox(LibraryClassLoader loader, VirtualMachine virtualMachine) { - this.virtualMachine = virtualMachine; + public SandBox(Context context, VirtualMachine vm) { + LOGGER.info("Initializing SSVM sandbox..."); + this.context = context; + this.vm = vm; try { - this.virtualMachine.initialize(); - this.virtualMachine.bootstrap(); - this.memoryManager = virtualMachine.getMemoryManager(); + this.vm.initialize(); + this.vm.bootstrap(); + this.memoryManager = vm.getMemoryManager(); // Install all classes from deobfuscator context - this.helper = SupplyingClassLoaderInstaller.install(virtualMachine, new ClassLoaderDataSupplier(loader)); - this.invocationUtil = InvocationUtil.create(virtualMachine); + this.helper = SupplyingClassLoaderInstaller.install(vm, new ClassLoaderDataSupplier(context.getLibraryLoader())); + this.invocationUtil = InvocationUtil.create(vm); patchVm(); - } catch (Exception e) { - throw new RuntimeException(e); + } catch (VMException ex) { + LOGGER.error("SSVM bootstrap failed. Make sure that you run this deobfuscator on java 17"); + SandBox.logVMException(ex, vm); + + throw new RuntimeException(ex); + } catch (IOException ex) { + throw new RuntimeException(ex); } + LOGGER.info("Initialized SSVM sandbox"); } private void patchVm() { // Some patches to circumvent bugs arising from VM implementation changes in later versions - if (virtualMachine.getJvmVersion() > 8) { + if (vm.getJvmVersion() > 8) { // Bug in SSVM makes it think there are overlapping sleeps, so until that gets fixed we stub // out sleeping. - InstanceClass thread = virtualMachine.getSymbols().java_lang_Thread(); - virtualMachine - .getInterface() + InstanceClass thread = vm.getSymbols().java_lang_Thread(); + vm.getInterface() .setInvoker(thread.getMethod("sleep", "(J)V"), MethodInvoker.noop()); // SSVM manages its own memory, and this conflicts with it. Stubbing it out keeps everyone // happy. - InstanceClass bits = (InstanceClass) virtualMachine.findBootstrapClass("java/nio/Bits"); + InstanceClass bits = (InstanceClass) vm.findBootstrapClass("java/nio/Bits"); if (bits != null) { - virtualMachine - .getInterface() + vm.getInterface() .setInvoker(bits.getMethod("reserveMemory", "(JJ)V"), MethodInvoker.noop()); } } } - public static String toString(Throwable t) { - StringWriter stringWriter = new StringWriter(); - PrintWriter printWriter = new PrintWriter(stringWriter); - t.printStackTrace(printWriter); - return stringWriter.toString(); - } - /** * @see SandBox#logVMException(VMException, VirtualMachine) */ public void logVMException(VMException ex) { - logVMException(ex, this.virtualMachine); + logVMException(ex, this.vm); } /** @@ -106,12 +110,12 @@ public static void logVMException(VMException ex, VirtualMachine vm) { LOGGER.error(vm.getOperations().toJavaException(oop)); } - public VirtualMachine getVirtualMachine() { - return virtualMachine; + public VirtualMachine vm() { + return vm; } public VMInterface getVMInterface() { - return virtualMachine.getInterface(); + return vm.getInterface(); } public MemoryManager getMemoryManager() { @@ -127,38 +131,47 @@ public InvocationUtil getInvocationUtil() { } public Symbols getSymbols() { - return virtualMachine.getSymbols(); + return vm.getSymbols(); } public Primitives getPrimitives() { - return virtualMachine.getPrimitives(); + return vm.getPrimitives(); } public VMOperations getOperations() { - return virtualMachine.getOperations(); + return vm.getOperations(); } public LinkResolver getLinkResolver() { - return virtualMachine.getLinkResolver(); + return vm.getLinkResolver(); } public RuntimeResolver getRuntimeResolver() { - return virtualMachine.getRuntimeResolver(); + return vm.getRuntimeResolver(); } public Reflection getReflection() { - return virtualMachine.getReflection(); + return vm.getReflection(); } public ThreadManager getThreadManager() { - return virtualMachine.getThreadManager(); + return vm.getThreadManager(); } public FileManager getFileManager() { - return virtualMachine.getFileManager(); + return vm.getFileManager(); } public ExecutionEngine getExecutionEngine() { - return virtualMachine.getExecutionEngine(); + return vm.getExecutionEngine(); + } + + /** + * Gets all classes from {@link Context} that were used by sandbox + */ + public List getUsedCustomClasses() { + return this.vm.getClassStorage().list().stream() + .filter(clazz -> this.context.getClasses().containsKey(clazz.getInternalName())) + .toList(); } } diff --git a/deobfuscator-impl/src/test/java/Bootstrap.java b/deobfuscator-impl/src/test/java/Bootstrap.java index 8ad2e9b..cd3f1aa 100644 --- a/deobfuscator-impl/src/test/java/Bootstrap.java +++ b/deobfuscator-impl/src/test/java/Bootstrap.java @@ -1,4 +1,3 @@ -import dev.xdark.ssvm.VirtualMachine; import java.nio.file.Path; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassWriter; @@ -12,10 +11,6 @@ public static void main(String[] args) { Deobfuscator.from( DeobfuscatorOptions.builder() .inputJar(Path.of("work", "obf-test.jar")) - .virtualMachine( - new VirtualMachine() { - // you can do shit - }) .transformers( // Pick your transformers here () -> new ComposedGeneralFlowTransformer() diff --git a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixLongEncryptionTransformer.java b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixLongEncryptionTransformer.java index ba43252..410981d 100644 --- a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixLongEncryptionTransformer.java +++ b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixLongEncryptionTransformer.java @@ -18,9 +18,6 @@ import uwu.narumi.deobfuscator.api.helper.AsmHelper; import uwu.narumi.deobfuscator.api.transformer.Transformer; -import java.util.ArrayList; -import java.util.List; - /** * Decrypts {@code long} numbers https://www.zelix.com/klassmaster/featuresLongEncryption.html * @@ -53,7 +50,7 @@ public class ZelixLongEncryptionTransformer extends Transformer { @Override protected void transform(ClassWrapper scope, Context context) throws Exception { - List classesToRemove = new ArrayList<>(); + SandBox sandBox = new SandBox(context); context.classes(scope).forEach(classWrapper -> classWrapper.findClInit().ifPresent(clinit -> { MethodContext methodContext = MethodContext.create(classWrapper, clinit); @@ -77,8 +74,6 @@ protected void transform(ClassWrapper scope, Context context) throws Exception { ClassWrapper longDecrypterCreatorClass = context.getClasses().get(createDecrypterInsn.owner); try { - SandBox sandBox = context.getSandBox(); - // Create decrypter InstanceClass clazz = sandBox.getHelper().loadClass(longDecrypterCreatorClass.canonicalName()); ObjectValue longDecrypterInstance = sandBox.getInvocationUtil().invokeReference( @@ -96,17 +91,6 @@ protected void transform(ClassWrapper scope, Context context) throws Exception { Argument.int64(decryptKey) ); - // Add classes to remove - if (!classesToRemove.contains(longDecrypterCreatorClass.name())) { - classesToRemove.add(longDecrypterCreatorClass.name()); - } - if (!classesToRemove.contains(longDecrypterClass.getInternalName())) { - classesToRemove.add(longDecrypterClass.getInternalName()); - } - if (!classesToRemove.contains(longDecrypterCreatorClass.getClassNode().interfaces.get(0))) { - classesToRemove.add(longDecrypterCreatorClass.getClassNode().interfaces.get(0)); - } - // Remove all instructions that creates decrypter decryptContext.removeAll(); @@ -120,6 +104,6 @@ protected void transform(ClassWrapper scope, Context context) throws Exception { } })); - classesToRemove.forEach(className -> context.getClasses().remove(className)); + sandBox.getUsedCustomClasses().forEach(clazz -> context.getClasses().remove(clazz.getInternalName())); } }