diff --git a/README.md b/README.md index 6bc9baf1..a21eb45a 100644 --- a/README.md +++ b/README.md @@ -6,20 +6,39 @@ A deobfuscator for java ## ✅ How to run deobfuscator If you want to use this deobfuscator, you need to start it from your IDE manually. +### Prerequisites +**Important:** You need TWO different Java installations: +- **[Java 17](https://adoptium.net/temurin/releases/?version=17)** - Required for the project to compile and run +- **[Java 8](https://adoptium.net/temurin/releases/?version=8)** - Required for the sandbox (SSVM) to work properly + +### Instructions 1. Clone this repository and open it in IntelliJ -2. Make sure that you have selected [Java 17 (Temurin)](https://adoptium.net/temurin/releases/?version=17) in `Project Structure` -> `SDK` -3. Place your obfuscated jar inside the root project directory. For example in `work/obf-test.jar` -4. Navigate to class [`Bootstrap.java`](./deobfuscator-impl/src/test/java/Bootstrap.java) -5. In this class edit the deobfuscator configuration - - `inputJar` - Your obfuscated jar file that you placed in step 1 +2. Make sure that you have selected [Java 17](https://adoptium.net/temurin/releases/?version=17) in `Project Structure` -> `SDK` +3. Install [Java 8](https://adoptium.net/temurin/releases/?version=8) if you don't have it already +4. Place your obfuscated jar inside the root project directory. For example in `work/obf-test.jar` +5. Navigate to class [`Bootstrap.java`](./deobfuscator-impl/src/test/java/Bootstrap.java) +6. In this class edit the deobfuscator configuration + - `inputJar` - Your obfuscated jar file that you placed in step 4 - `transformers` - Pick transformers that you want to run. You can find them in [`deobfuscator-transformers`](./deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other) module. -6. Run this class manually from your IDE. You can use our pre-configured IntelliJ task named `Bootstrap`. +7. Run this class manually from your IDE. You can use our pre-configured IntelliJ task named `Bootstrap`. ![tak](./assets/run-deobfuscator.gif) ## 🔧 Contributing Contributions are welcome! See [CONTRIBUTING.md](./CONTRIBUTING.md) for a project introduction and some basics about java bytecode. +## ❓ FAQ + +**Q: Sandbox doesn't work / "rt.jar is required for sandbox to run" error** + +A: The sandbox requires rt.jar from **[Java 8](https://adoptium.net/temurin/releases/?version=8)** installation. The deobfuscator will try to auto-detect it, but if it fails: +- Make sure you have [Java 8](https://adoptium.net/temurin/releases/?version=8) installed +- You can manually set it via system property: `-DrtJarPath="path/to/rt.jar"` +- Or specify it in your Bootstrap configuration: `.rtJarPath(Path.of("path/to/rt.jar"))` +- Common rt.jar locations (may vary based on installation): + - Oracle JDK 8: `C:/Program Files/Java/jdk1.8.0_202/jre/lib/rt.jar` + - Eclipse Adoptium JDK 8: `C:/Program Files/Eclipse Adoptium/jdk-8.0.462.8-hotspot/jre/lib/rt.jar` + ## Links 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 8ac7e312..3a7ec3e7 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 @@ -1,9 +1,14 @@ package uwu.narumi.deobfuscator.api.context; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.intellij.lang.annotations.MagicConstant; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Nullable; import org.objectweb.asm.ClassWriter; +import uwu.narumi.deobfuscator.api.environment.JavaEnv; +import uwu.narumi.deobfuscator.api.environment.JavaInstall; +import uwu.narumi.deobfuscator.api.execution.SandBox; import uwu.narumi.deobfuscator.api.transformer.Transformer; import java.io.IOException; @@ -15,6 +20,7 @@ import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.util.Optional; import java.util.Set; import java.util.function.Supplier; @@ -25,6 +31,7 @@ public record DeobfuscatorOptions( @Nullable Path inputJar, List externalFiles, Set libraries, + @Nullable Path rtJarPath, @Nullable Path outputJar, @Nullable Path outputDir, @@ -53,11 +60,15 @@ public record ExternalFile(Path path, String pathInJar) { * Builder for {@link DeobfuscatorOptions} */ public static class Builder { + private static final Logger LOGGER = LogManager.getLogger(); + // Inputs @Nullable private Path inputJar = null; private final List externalFiles = new ArrayList<>(); private final Set libraries = new HashSet<>(); + @Nullable + private Path rtJarPath = null; // Outputs @Nullable @@ -161,6 +172,18 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO return this; } + /** + * Path to rt.jar from Java 8 binaries. Required for sandbox to work properly. + * Examples: + * - Oracle JDK 8: C:/Program Files/Java/jdk1.8.0_202/jre/lib/rt.jar + * - Eclipse Adoptium JDK 8: C:/Program Files/Eclipse Adoptium/jdk-8.0.462.8-hotspot/jre/lib/rt.jar + */ + @Contract("_ -> this") + public DeobfuscatorOptions.Builder rtJarPath(@Nullable Path rtJarPath) { + this.rtJarPath = rtJarPath; + return this; + } + /** * Output jar for deobfuscated classes. Automatically filled when input jar is set */ @@ -255,6 +278,30 @@ public DeobfuscatorOptions.Builder skipFiles() { return this; } + /** + * Try to find rt.jar from Java 8 installation + */ + @Nullable + private Path findRtJarPath() { + String userDefinedRtJarPath = System.getProperty("rtJarPath"); + if (userDefinedRtJarPath != null) { + return Path.of(userDefinedRtJarPath); + } + + Optional javaInstall = JavaEnv.getJavaInstalls().stream() + .filter(javaInstall1 -> javaInstall1.version() == 8) + .findFirst(); + + if (javaInstall.isPresent()) { + JavaInstall install = javaInstall.get(); + Path possibleRtJarPath = install.javaExecutable().getParent().getParent().resolve("jre").resolve("lib").resolve("rt.jar"); + if (Files.exists(possibleRtJarPath)) { + return possibleRtJarPath; + } + } + return null; + } + /** * Build immutable {@link DeobfuscatorOptions} with options verification */ @@ -269,12 +316,23 @@ public DeobfuscatorOptions build() { if (this.outputJar != null && this.outputDir != null) { throw new IllegalStateException("Output jar and output dir cannot be set at the same time"); } + // Try to auto-detect rt.jar path + if (this.rtJarPath == null) { + Path rtJar = findRtJarPath(); + if (rtJar != null) { + System.out.println("Auto-detected rt.jar path: " + rtJar); + this.rtJarPath = rtJar; + } else { + LOGGER.warn("Failed to auto-detect rt.jar path. Please provide path to rt.jar from Java 8 binaries, otherwise sandbox will not work."); + } + } return new DeobfuscatorOptions( // Input inputJar, externalFiles, libraries, + rtJarPath, // Output outputJar, outputDir, diff --git a/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/environment/JavaEnv.java b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/environment/JavaEnv.java new file mode 100644 index 00000000..c40760d6 --- /dev/null +++ b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/environment/JavaEnv.java @@ -0,0 +1,324 @@ +package uwu.narumi.deobfuscator.api.environment; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import uwu.narumi.deobfuscator.api.helper.PlatformType; +import uwu.narumi.deobfuscator.api.helper.SymLinks; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Stream; + +/** + * Tasks for Java environments. + * + * https://github.com/Col-E/Recaf-Launcher/blob/master/core/src/main/java/software/coley/recaf/launcher/task/JavaEnvTasks.java + */ +public class JavaEnv { + private static final Set javaInstalls = new HashSet<>(); + + /** + * Must call {@link #scanForJavaInstalls()} before this list will be populated. + * + * @return Set of discovered Java installations. + */ + @NotNull + public static Collection getJavaInstalls() { + return javaInstalls; + } + + static { + // On class-load, scan for java installs. + scanForJavaInstalls(); + } + + /** + * Detect common Java installations for the current platform. + */ + private static void scanForJavaInstalls() { + if (PlatformType.isWindows()) { + scanForWindowsJavaPaths(); + } else if (PlatformType.isLinux()) { + scanForLinuxJavaPaths(); + } else if (PlatformType.isMac()) { + scanforMacJavaPaths(); + } + } + + /** + * Detect common Java installations on Linux. + */ + private static void scanForLinuxJavaPaths() { + // Check java alternative link. + Path altJava = Paths.get("/etc/alternatives/java"); + if (Files.exists(altJava)) { + addJavaInstall(altJava); + } + + // Check home + String homeEnv = System.getenv("JAVA_HOME"); + if (homeEnv != null) { + Path homePath = Paths.get(homeEnv); + if (Files.isDirectory(homePath)) { + Path javaPath = homePath.resolve("bin/java"); + if (Files.exists(javaPath)) + addJavaInstall(javaPath); + } + } + + // Check common install locations. + String[] javaRoots = { + "/usr/lib/jvm/", + System.getenv("HOME") + "/.jdks/" + }; + for (String root : javaRoots) { + Path rootPath = Paths.get(root); + if (Files.isDirectory(rootPath)) { + try (Stream subDirStream = Files.list(rootPath)) { + subDirStream.filter(subDir -> Files.exists(subDir.resolve("bin/java"))) + .forEach(subDir -> { + Path javaPath = subDir.resolve("bin/java"); + if (Files.exists(javaPath)) + addJavaInstall(javaPath); + }); + } catch (IOException ignored) { + // Skip + } + } + } + } + + /** + * Detect common Java installations on Mac. + */ + private static void scanforMacJavaPaths() { + Path[] jvmsRoots = new Path[]{ + Paths.get("/Library/Java/JavaVirtualMachines/"), + Paths.get(System.getProperty("user.home")).resolve("Library/Java/JavaVirtualMachines/") + }; + for (Path jvmsRoot : jvmsRoots) { + if (Files.isDirectory(jvmsRoot)) { + try (Stream stream = Files.walk(jvmsRoot)) { + stream.forEach(path -> { + if (path.toString().endsWith("bin/java")) + addJavaMacInstall(path); + }); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + } + + /** + * Detect common Java installations on Windows. + */ + private static void scanForWindowsJavaPaths() { + String homeProp = System.getProperty("java.home"); + if (homeProp != null) + addJavaInstall(Paths.get(homeProp).resolve("bin/java.exe")); + + // Check java home + String homeEnv = System.getenv("JAVA_HOME"); + if (homeEnv != null) { + Path homePath = Paths.get(homeEnv); + if (Files.isDirectory(homePath)) + addJavaInstall(homePath.resolve("bin/java.exe")); + } + + // Check '%user%/.jdks' + String homePath = System.getProperty("user.home"); + if (homePath != null) { + Path jdksDir = Paths.get(homePath, ".jdks"); + if (Files.isDirectory(jdksDir)) + try (Stream subDirStream = Files.list(jdksDir)) { + subDirStream.filter(subDir -> Files.exists(subDir.resolve("bin/java.exe"))) + .forEach(subDir -> addJavaInstall(subDir.resolve("bin/java.exe"))); + } catch (IOException ignored) { + // Skip + } + } + + // Check system path for java entries. + String path = System.getenv("PATH"); + if (path != null) { + String[] entries = path.split(";"); + for (String entry : entries) + if (entry.endsWith("bin")) + addJavaInstall(Paths.get(entry).resolve("java.exe")); + } + + // Check common install locations. + String[] javaRoots = { + "C:/Program Files/Amazon Corretto/", + "C:/Program Files/Eclipse Adoptium/", + "C:/Program Files/Eclipse Foundation/", + "C:/Program Files/BellSoft/", + "C:/Program Files/Java/", + "C:/Program Files/Microsoft/", + "C:/Program Files/SapMachine/JDK/", + "C:/Program Files/Zulu/", + }; + for (String root : javaRoots) { + Path rootPath = Paths.get(root); + if (Files.isDirectory(rootPath)) { + try (Stream subDirStream = Files.list(rootPath)) { + subDirStream.filter(subDir -> Files.exists(subDir.resolve("bin/java.exe"))) + .forEach(subDir -> addJavaInstall(subDir.resolve("bin/java.exe"))); + } catch (IOException ignored) { + // Skip + } + } + } + } + + /** + * @param javaExecutable Path to executable to add. + * @return {@code true} when the path was recognized as a valid executable. + * {@code false} when discarded. + */ + @NotNull + public static AdditionResult addJavaInstall(@NotNull Path javaExecutable) { + return addJavaInstall(javaExecutable, executable -> { + // Most installs are structured like: /whatever/jvms/openjdk-21.0.3/bin/java.exe + // Thus, the parent of the bin directory has the name. + Path binDir = executable.getParent(); + if (binDir == null) + return null; + Path jdkDir = binDir.getParent(); + if (jdkDir == null) + return null; + return jdkDir.getFileName().toString(); + }); + } + + /** + * @param javaExecutable Path to executable to add. + * @return {@code true} when the path was recognized as a valid executable. + * {@code false} when discarded. + */ + @NotNull + public static AdditionResult addJavaMacInstall(@NotNull Path javaExecutable) { + return addJavaInstall(javaExecutable, executable -> { + // Mac structures things differently: /Library/Java/JavaVirtualMachines/openjdk-21.0.3.jdk/Contents/Home/bin/java.exe + // Thus, going up 4 directory levels will reveal the name. + Path binDir = executable.getParent(); + if (binDir == null) + return null; + Path jdkHomeDir = binDir.getParent(); + if (jdkHomeDir == null) + return null; + Path jdkContentsDir = jdkHomeDir.getParent(); + if (jdkContentsDir == null) + return null; + Path jdkDir = jdkContentsDir.getParent(); + if (jdkDir == null) + return null; + return jdkDir.getFileName().toString(); + }); + } + + /** + * @param javaExecutable Path to executable to add. + * @param executableToJvmName Lookup to find JDK name from the path of the executable. + * @return {@code true} when the path was recognized as a valid executable. + * {@code false} when discarded. + */ + @NotNull + public static AdditionResult addJavaInstall(@NotNull Path javaExecutable, @NotNull Function executableToJvmName) { + // Resolve sym-links + if (Files.isSymbolicLink(javaExecutable)) { + javaExecutable = SymLinks.resolveSymLink(javaExecutable); + if (javaExecutable == null) + return AdditionResult.ERR_RESOLVE_SYM_LINK; + } + + // Validate executable is 'java' or 'javaw' + String execName = javaExecutable.getFileName().toString(); + if (!execName.endsWith("java") && !javaExecutable.endsWith("java.exe") + && !execName.endsWith("javaw") && !javaExecutable.endsWith("javaw.exe")) + return AdditionResult.ERR_NOT_JAVA_EXEC; + + // Validate the given path points to a file that exists + if (!Files.exists(javaExecutable)) + return AdditionResult.ERR_NOT_JAVA_EXEC; + + // Validate bin structure + Path binDir = javaExecutable.getParent(); + if (binDir == null) + return AdditionResult.ERR_PARENT; + + // Validate it's a JDK and not a JRE + if (Files.notExists(binDir.resolve("javac")) && Files.notExists(binDir.resolve("javac.exe"))) + return AdditionResult.ERR_JRE_NOT_JDK; + + // Validate version + String jdkDirName = executableToJvmName.apply(javaExecutable); + if (jdkDirName == null) + return AdditionResult.ERR_PARENT; + int version = JavaVersion.fromVersionString(jdkDirName); + if (version == JavaVersion.UNKNOWN_VERSION) + return AdditionResult.ERR_UNRESOLVED_VERSION; + if (version >= 8) { + addJavaInstall(new JavaInstall(javaExecutable, version)); + return AdditionResult.SUCCESS; + } + return AdditionResult.ERR_TOO_OLD; + } + + /** + * @param path Path to executable to look up. + * @return Install entry for path, or {@code null} if not previously recorded as a valid installation. + */ + @Nullable + public static JavaInstall getByPath(@NotNull Path path) { + return javaInstalls.stream() + .filter(i -> i.javaExecutable().equals(path)) + .findFirst().orElse(null); + } + + private static void addJavaInstall(@NotNull JavaInstall install) { + javaInstalls.add(install); + } + + public enum AdditionResult { + SUCCESS, + ERR_NOT_JAVA_EXEC, + ERR_RESOLVE_SYM_LINK, + ERR_PARENT, + ERR_JRE_NOT_JDK, + ERR_UNRESOLVED_VERSION, + ERR_TOO_OLD; + + public boolean wasSuccess() { + return this == SUCCESS; + } + + @NotNull + public String message() { + switch (this) { + case SUCCESS: + return ""; + case ERR_RESOLVE_SYM_LINK: + return "The selected symbolic-link could not be resolved"; + case ERR_NOT_JAVA_EXEC: + return "The selected file was not 'java' or 'javaw'"; + case ERR_UNRESOLVED_VERSION: + return "The selected java executable could not have its version resolved"; + case ERR_PARENT: + return "The selected java executable could not have its parent directories"; + case ERR_JRE_NOT_JDK: + return "The selected java executable belongs to a JRE and not a JDK"; + case ERR_TOO_OLD: + return "The selected java executable is from a outdated/unsupported version of Java"; + } + return "The selected executable was not valid: " + name(); + } + } +} diff --git a/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/environment/JavaInstall.java b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/environment/JavaInstall.java new file mode 100644 index 00000000..b31fb617 --- /dev/null +++ b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/environment/JavaInstall.java @@ -0,0 +1,29 @@ +package uwu.narumi.deobfuscator.api.environment; + +import java.nio.file.Path; +import java.util.Comparator; + +/** + * Model of a Java installation. + * + * @param javaExecutable Path to the Java executable. + * @param version Major version of the installation. + * + * https://github.com/Col-E/Recaf-Launcher/blob/master/core/src/main/java/software/coley/recaf/launcher/info/JavaInstall.java + */ +public record JavaInstall(Path javaExecutable, int version) { + /** + * Compare installs by path. + */ + public static Comparator COMPARE_PATHS = Comparator.comparing(o -> o.javaExecutable); + /** + * Compare installs by version (newest first). + */ + public static Comparator COMPARE_VERSIONS = (o1, o2) -> { + // Negated so newer versions are sorted to be first + int cmp = -Integer.compare(o1.version, o2.version); + if (cmp == 0) + return COMPARE_PATHS.compare(o1, o2); + return cmp; + }; +} diff --git a/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/environment/JavaVersion.java b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/environment/JavaVersion.java new file mode 100644 index 00000000..4e92fbf2 --- /dev/null +++ b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/environment/JavaVersion.java @@ -0,0 +1,73 @@ +package uwu.narumi.deobfuscator.api.environment; + +import org.jetbrains.annotations.NotNull; + +import java.nio.file.Paths; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Supported Java version of the current JVM. + * + * https://github.com/Col-E/Recaf-Launcher/blob/master/core/src/main/java/software/coley/recaf/launcher/info/JavaVersion.java + */ +public class JavaVersion { + /** + * The offset from which a version and the version constant value is. For example, Java 8 is 52 (44 + 8). + */ + public static final int VERSION_OFFSET = 44; + /** + * Code indicator that we couldn't figure out the version. + */ + public static final int UNKNOWN_VERSION = -2; + /** + * Regex pattern which extracts the major release version from a string. + * Ignores most common suffix/prefix patterns. + */ + private static final Pattern JAVA_VERSION_EXTRACTOR = Pattern.compile("(?:(?:[^\\d\\W]|[- ])+)?(?:1\\D)?(\\d+)(?:_.+)?(?:\\..+)?"); + private static final String JAVA_CLASS_VERSION = "java.class.version"; + private static final String JAVA_VM_SPEC_VERSION = "java.vm.specification.version"; + private static int version = -1; + + /** + * @param version + * Version string. + * + * @return Version if parsable, otherwise {@link #UNKNOWN_VERSION}. + */ + public static int fromVersionString(@NotNull String version) { + try { + Matcher matcher = JAVA_VERSION_EXTRACTOR.matcher(version); + if (matcher.find()) + return Integer.parseInt(matcher.group(1)); + } catch (Exception ignored) { + // ignored + } + return UNKNOWN_VERSION; + } + + /** + * Get the supported Java version of the current JVM. + * + * @return Version. If normal detection means do not suffice, then {@link #UNKNOWN_VERSION}. + */ + public static int get() { + if (version == -1) { + // Check for class version + String property = System.getProperty(JAVA_CLASS_VERSION, ""); + if (!property.isEmpty()) + return version = (int) (Float.parseFloat(property) - VERSION_OFFSET); + + // Odd, not found. Try the spec version + property = System.getProperty(JAVA_VM_SPEC_VERSION, ""); + if (property.contains(".")) + return version = (int) Float.parseFloat(property.substring(property.indexOf('.') + 1)); + else if (!property.isEmpty()) + return version = Integer.parseInt(property); + + // Very odd + return version = UNKNOWN_VERSION; + } + return version; + } +} diff --git a/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/execution/JarBootClassFinder.java b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/execution/JarBootClassFinder.java new file mode 100644 index 00000000..6ce014d0 --- /dev/null +++ b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/execution/JarBootClassFinder.java @@ -0,0 +1,49 @@ +package uwu.narumi.deobfuscator.api.execution; + +import dev.xdark.ssvm.classloading.BootClassFinder; +import dev.xdark.ssvm.classloading.ParsedClassData; +import dev.xdark.ssvm.util.ClassUtil; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.tree.ClassNode; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Path; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +/** + * A {@link BootClassFinder} that finds classes in a given rt.jar file. + */ +public class JarBootClassFinder implements BootClassFinder { + private final JarFile rtJarFile; + + public JarBootClassFinder(Path rtJarPath) { + try { + this.rtJarFile = new JarFile(rtJarPath.toFile()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public ParsedClassData findBootClass(String name) { + // Find class in the rt.jar + JarEntry jarEntry = this.rtJarFile.getJarEntry(name + ".class"); + if (jarEntry == null) { + return null; + } + + ClassReader cr; + try (InputStream in = this.rtJarFile.getInputStream(jarEntry)) { + if (in == null) { + return null; + } + cr = new ClassReader(in); + } catch (IOException e) { + throw new RuntimeException(e); + } + ClassNode node = ClassUtil.readNode(cr); + return new ParsedClassData(cr, node); + } +} 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 79282c69..cdf397ae 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 @@ -4,6 +4,7 @@ import dev.xdark.ssvm.RuntimeResolver; import dev.xdark.ssvm.VirtualMachine; import dev.xdark.ssvm.api.VMInterface; +import dev.xdark.ssvm.classloading.BootClassFinder; import dev.xdark.ssvm.classloading.SupplyingClassLoaderInstaller; import dev.xdark.ssvm.execution.ExecutionEngine; import dev.xdark.ssvm.filesystem.FileManager; @@ -16,6 +17,7 @@ import dev.xdark.ssvm.thread.ThreadManager; import dev.xdark.ssvm.util.Reflection; +import java.nio.file.Path; import java.util.List; import org.apache.logging.log4j.LogManager; @@ -41,11 +43,24 @@ public SandBox(Context context) { this(new ClasspathDataSupplier( // We need to use compiled classes as they are already compiled new CombinedClassProvider(context.getCompiledClasses(), context.getLibraries()) - )); - } - - public SandBox(SupplyingClassLoaderInstaller.DataSupplier dataSupplier) { - this(dataSupplier, new VirtualMachine()); + ), context.getOptions().rtJarPath()); + } + + public SandBox(SupplyingClassLoaderInstaller.DataSupplier dataSupplier, Path rtJarPath) { + this(dataSupplier, new VirtualMachine() { + @Override + protected BootClassFinder createBootClassFinder() { + if (rtJarPath == null) { + throw new IllegalStateException("rt.jar is required for sandbox to run. Please see README.md for instructions"); + } + return new JarBootClassFinder(rtJarPath); + } + + @Override + public int getJvmVersion() { + return 8; // Java 8 + } + }); } public SandBox(SupplyingClassLoaderInstaller.DataSupplier dataSupplier, VirtualMachine vm) { diff --git a/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/helper/PlatformType.java b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/helper/PlatformType.java new file mode 100644 index 00000000..32eecc7a --- /dev/null +++ b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/helper/PlatformType.java @@ -0,0 +1,45 @@ +package uwu.narumi.deobfuscator.api.helper; + +/** + * Operating system enumeration. + * + * https://github.com/Col-E/Recaf-Launcher/blob/master/core/src/main/java/software/coley/recaf/launcher/info/PlatformType.java + */ +public enum PlatformType { + WINDOWS, + MAC, + LINUX; + + /** + * @return {@code true} when the current platform is windows. + */ + public static boolean isWindows() { + return get() == WINDOWS; + } + + /** + * @return {@code true} when the current platform is mac. + */ + public static boolean isMac() { + return get() == MAC; + } + + /** + * @return {@code true} when the current platform is linux. + */ + public static boolean isLinux() { + return get() == LINUX; + } + + /** + * @return Operating system type. + */ + public static PlatformType get() { + String osName = System.getProperty("os.name").toLowerCase(); + if (osName.contains("win")) + return WINDOWS; + if (osName.contains("mac") || osName.contains("osx")) + return MAC; + return LINUX; + } +} diff --git a/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/helper/SymLinks.java b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/helper/SymLinks.java new file mode 100644 index 00000000..7076355e --- /dev/null +++ b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/helper/SymLinks.java @@ -0,0 +1,39 @@ +package uwu.narumi.deobfuscator.api.helper; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +/** + * Symbolic link utils + * + * https://github.com/Col-E/Recaf-Launcher/blob/master/core/src/main/java/software/coley/recaf/launcher/util/SymLinks.java + */ +public class SymLinks { + private static final int MAX_LINK_DEPTH = 10; + + /** + * @param path + * Symbolic link to follow. + * + * @return Target path, or {@code null} if the path could not be resolved. + */ + @Nullable + public static Path resolveSymLink(@NotNull Path path) { + try { + int linkDepth = 0; + while (Files.isSymbolicLink(path)) { + if (linkDepth > MAX_LINK_DEPTH) + throw new IOException("Sym-link path too deep"); + path = Files.readSymbolicLink(path); + linkDepth++; + } + return path; + } catch (IOException ex) { + return null; + } + } +}