diff --git a/deobfuscator-api/src/main/java/dev/xdark/ssvm/operation/DefaultClassOperations.java b/deobfuscator-api/src/main/java/dev/xdark/ssvm/operation/DefaultClassOperations.java new file mode 100644 index 00000000..4a7ae0b9 --- /dev/null +++ b/deobfuscator-api/src/main/java/dev/xdark/ssvm/operation/DefaultClassOperations.java @@ -0,0 +1,538 @@ +package dev.xdark.ssvm.operation; + +import dev.xdark.ssvm.LanguageSpecification; +import dev.xdark.ssvm.RuntimeResolver; +import dev.xdark.ssvm.classloading.BootClassFinder; +import dev.xdark.ssvm.classloading.ClassDefiner; +import dev.xdark.ssvm.classloading.ClassDefinitionOption; +import dev.xdark.ssvm.classloading.ClassLoaderData; +import dev.xdark.ssvm.classloading.ClassLoaders; +import dev.xdark.ssvm.classloading.ClassStorage; +import dev.xdark.ssvm.classloading.ParsedClassData; +import dev.xdark.ssvm.execution.Locals; +import dev.xdark.ssvm.execution.PanicException; +import dev.xdark.ssvm.execution.VMException; +import dev.xdark.ssvm.inject.InjectedClassLayout; +import dev.xdark.ssvm.jvmti.VMEventCollection; +import dev.xdark.ssvm.memory.allocation.MemoryData; +import dev.xdark.ssvm.memory.management.MemoryManager; +import dev.xdark.ssvm.mirror.MirrorFactory; +import dev.xdark.ssvm.mirror.member.JavaField; +import dev.xdark.ssvm.mirror.member.JavaMethod; +import dev.xdark.ssvm.mirror.member.MemberIdentifier; +import dev.xdark.ssvm.mirror.member.area.ClassArea; +import dev.xdark.ssvm.mirror.member.area.EmptyClassArea; +import dev.xdark.ssvm.mirror.member.area.SimpleClassArea; +import dev.xdark.ssvm.mirror.type.ClassLinkage; +import dev.xdark.ssvm.mirror.type.InitializationState; +import dev.xdark.ssvm.mirror.type.InstanceClass; +import dev.xdark.ssvm.mirror.type.JavaClass; +import dev.xdark.ssvm.symbol.Primitives; +import dev.xdark.ssvm.symbol.Symbols; +import dev.xdark.ssvm.thread.ThreadManager; +import dev.xdark.ssvm.util.AsmUtil; +import dev.xdark.ssvm.util.Assertions; +import dev.xdark.ssvm.util.CloseableLock; +import dev.xdark.ssvm.value.InstanceValue; +import dev.xdark.ssvm.value.ObjectValue; +import org.jetbrains.annotations.NotNull; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.FieldNode; +import org.objectweb.asm.tree.MethodNode; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +/** + * Default implementation. + * + * @author xDark + */ +public final class DefaultClassOperations implements ClassOperations { + + private final MirrorFactory mirrorFactory; + private final MemoryManager memoryManager; + private final ThreadManager threadManager; + private final BootClassFinder bootClassFinder; + private final RuntimeResolver runtimeResolver; + private final Symbols symbols; + private final Primitives primitives; + private final ClassLoaders classLoaders; + private final ClassDefiner classDefiner; + private final ClassStorage classStorage; + private final VMEventCollection eventCollection; + private final VMOperations ops; + + public DefaultClassOperations(MirrorFactory mirrorFactory, MemoryManager memoryManager, ThreadManager threadManager, BootClassFinder bootClassFinder, RuntimeResolver runtimeResolver, Symbols symbols, Primitives primitives, ClassLoaders classLoaders, ClassDefiner classDefiner, ClassStorage classStorage, VMEventCollection eventCollection, VMOperations ops) { + this.mirrorFactory = mirrorFactory; + this.memoryManager = memoryManager; + this.threadManager = threadManager; + this.bootClassFinder = bootClassFinder; + this.runtimeResolver = runtimeResolver; + this.symbols = symbols; + this.primitives = primitives; + this.classLoaders = classLoaders; + this.classDefiner = classDefiner; + this.classStorage = classStorage; + this.eventCollection = eventCollection; + this.ops = ops; + } + + @Override + public void link(@NotNull InstanceClass instanceClass) { + InitializationState state = instanceClass.state(); + state.lock(); + state.set(InstanceClass.State.IN_PROGRESS); + try { + eventCollection.getClassPrepare().invoke(instanceClass); + ClassLinkage linkage = instanceClass.linkage(); + ClassNode node = instanceClass.getNode(); + String superName = node.superName; + List interfaces = node.interfaces; + if (superName != null) { + linkage.setSuperClass((InstanceClass) findClass(instanceClass, superName, false)); + } + // Create method and field area + MirrorFactory mf = this.mirrorFactory; + // Set methods + List methods = node.methods; + List allMethods = new ArrayList<>(methods.size()); + for (int i = 0, j = methods.size(); i < j; i++) { + allMethods.add(mf.newMethod(instanceClass, methods.get(i), i)); + } + linkage.setMethodArea(new SimpleClassArea<>(allMethods)); + List virtualFields = new ArrayList<>(); + InstanceClass jc = instanceClass.getSuperClass(); + JavaField lastField = null; + while (jc != null) { + ClassArea area = jc.virtualFieldArea(); + // May be java/lang/Class calling to java/lang/Object + if (area == null) { + Assertions.check(jc == symbols.java_lang_Object(), "null area is only allowed for java/lang/Object"); + } else { + JavaField field = area.stream() + .filter(x -> (x.getModifiers() & Opcodes.ACC_STATIC) == 0) + .max(Comparator.comparingLong(JavaField::getOffset)) + .orElse(null); + if (field != null && (lastField == null || field.getOffset() > lastField.getOffset())) { + lastField = field; + } + } + jc = jc.getSuperClass(); + } + long offset; + MemoryManager memoryManager = this.memoryManager; + if (lastField != null) { + offset = lastField.getOffset(); + offset += safeSizeOf(lastField.getDesc()); + } else { + offset = memoryManager.valueBaseOffset(instanceClass); + } + + List fields = node.fields; + int slot = 0; + for (int i = 0, j = fields.size(); i < j; i++) { + FieldNode fieldNode = fields.get(i); + if ((fieldNode.access & Opcodes.ACC_STATIC) == 0) { + JavaField field = mf.newField(instanceClass, fieldNode, slot++, offset); + offset += safeSizeOf(field.getDesc()); + virtualFields.add(field); + } + } + linkage.setVirtualFieldArea(new SimpleClassArea<>(virtualFields)); + linkage.setOccupiedInstanceSpace(offset - memoryManager.valueBaseOffset(instanceClass)); + int slotOffset = slot; + // Static fields are stored right after java/lang/Class virtual fields + // At this point of linkage java/lang/Class must already set its virtual + // fields as we are doing it before (see above) + InstanceClass jlc = symbols.java_lang_Class(); + if (jlc == null) { + // Linking it now? + Assertions.check("java/lang/Class".equals(node.name), "bad first class for linking"); + jlc = instanceClass; + } + Assertions.notNull(jlc, "null java/lang/Class"); + ClassArea jlcFieldArea = jlc.virtualFieldArea(); + if (jlcFieldArea == null) { + Assertions.check("java/lang/Object".equals(node.name), "virtual field area"); + // No static fields allowed here. + linkage.setStaticFieldArea(EmptyClassArea.create()); + linkage.setOccupiedStaticSpace(0L); + } else { + JavaField maxVirtualField = jlcFieldArea.stream() + .max(Comparator.comparingLong(JavaField::getOffset)) + .orElseThrow(() -> new PanicException("No fields in java/lang/Class")); + offset = maxVirtualField.getOffset() + memoryManager.sizeOfType(maxVirtualField.getType()); // TODO calling getType may lead to exception + long baseStaticOffset = offset; + List staticFields = new ArrayList<>(fields.size() - slot); + for (int i = 0, j = fields.size(); i < j; i++) { + FieldNode fieldNode = fields.get(i); + if ((fieldNode.access & Opcodes.ACC_STATIC) != 0) { + JavaField field = mf.newField(instanceClass, fieldNode, slot++, offset); + offset += safeSizeOf(field.getDesc()); + staticFields.add(field); + } + } + linkage.setStaticFieldArea(new SimpleClassArea<>(staticFields, slotOffset)); + linkage.setOccupiedStaticSpace(offset - baseStaticOffset); + } + // Load interfaces now + if (!interfaces.isEmpty()) { + InstanceClass[] classes = new InstanceClass[interfaces.size()]; + for (int i1 = 0; i1 < interfaces.size(); i1++) { + classes[i1] = (InstanceClass) findClass(instanceClass, interfaces.get(i1), false); + } + linkage.setInterfaces(Arrays.asList(classes)); + } else { + linkage.setInterfaces(Collections.emptyList()); + } + if (jlc.getOop() != null) { + // VM might be still starting up + // All classes without mirrors will be fixed later + instanceClass.setOop(memoryManager.newClassOop(instanceClass)); + } + eventCollection.getClassLink().invoke(instanceClass); + // After we're done, set the state back to PENDING, + // so that the class can be initialized + state.set(InstanceClass.State.PENDING); + } catch (VMException ex) { + state.set(InstanceClass.State.FAILED); + throwClassException(ex); + } finally { + state.condition().signalAll(); + state.unlock(); + } + } + + @Override + public void initialize(@NotNull InstanceClass instanceClass) { + InitializationState state = instanceClass.state(); + state.lock(); + if (state.is(InstanceClass.State.COMPLETE) || state.is(InstanceClass.State.IN_PROGRESS)) { + state.unlock(); + return; + } + if (state.is(InstanceClass.State.FAILED)) { + state.unlock(); + ops.throwException(symbols.java_lang_NoClassDefFoundError(), instanceClass.getInternalName()); + } + state.set(InstanceClass.State.IN_PROGRESS); + try { + // Initialize hierarchy + InstanceClass superClass = instanceClass.getSuperClass(); + if (superClass != null) { + initialize(superClass); + } + // note: interfaces are *not* initialized here + initializeStaticFields(instanceClass); + JavaMethod clinit = instanceClass.getMethod("", "()V"); + if (clinit != null) { + Locals locals = threadManager.currentThreadStorage().newLocals(clinit); + ops.invokeVoid(clinit, locals); + } + } catch (VMException ex) { + state.set(InstanceClass.State.FAILED); + throwClassException(ex); + } finally { + state.condition().signalAll(); + state.unlock(); + } + } + + @Override + public boolean isInstanceOf(@NotNull ObjectValue value, @NotNull JavaClass type) { + if (value.isNull()) { + return false; + } + return type.isAssignableFrom(value.getJavaClass()); + } + + @Override + public @NotNull JavaClass findClass(JavaClass klass, String internalName, boolean initialize) { + return findClass0(classLoaders.getClassLoaderData(klass), klass.getClassLoader(), internalName, initialize, true); + } + + @Override + public @NotNull JavaClass findClass(ObjectValue classLoader, String internalName, boolean initialize) { + return findClass0(classLoaders.getClassLoaderData(classLoader), classLoader, internalName, initialize, true); + } + + @Override + public JavaClass findBootstrapClassOrNull(String internalName, boolean initialize) { + ObjectValue cl = memoryManager.nullValue(); + return findClass0(classLoaders.getClassLoaderData(cl), cl, internalName, initialize, false); + } + + @Override + public @NotNull InstanceClass defineClass(ObjectValue classLoader, ParsedClassData data, ObjectValue protectionDomain, String source, int options) { + ClassReader reader = data.getClassReader(); + InstanceClass jc = mirrorFactory.newInstanceClass(classLoader, reader, data.getNode()); + InitializationState state = jc.state(); + state.lock(); + try { + if ((options & ClassDefinitionOption.ANONYMOUS) == 0) { + ClassLoaderData classLoaderData = classLoaders.getClassLoaderData(classLoader); + if (!classLoaderData.linkClass(jc)) { + ops.throwException(symbols.java_lang_NoClassDefFoundError(), "Duplicate class: " + reader.getClassName()); + } + } + link(jc); + if ((options & ClassDefinitionOption.ANONYMOUS) != 0) { + if (!classLoaders.createAnonymousClassLoaderData(jc).linkClass(jc)) { + ops.throwException(symbols.java_lang_NoClassDefFoundError(), "Failed to link to anonymous data: " + reader.getClassName()); + } + } + if (!classLoader.isNull()) { + ops.putReference(jc.getOop(), "classLoader", "Ljava/lang/ClassLoader;", classLoader); + // Narumii start - Fix loading classes in root directory with no package + ObjectValue unnamedModule = ops.getReference(classLoader, "unnamedModule", "Ljava/lang/Module;"); + ops.putReference(jc.getOop(), "module", "Ljava/lang/Module;", unnamedModule); + // Narumii end + } + if (!protectionDomain.isNull()) { + ops.putReference(jc.getOop(), InjectedClassLayout.java_lang_Class_protectionDomain.name(), InjectedClassLayout.java_lang_Class_protectionDomain.descriptor(), protectionDomain); + } + classStorage.register(jc); + } finally { + state.unlock(); + } + return jc; + } + + @Override + public @NotNull InstanceClass defineClass(ObjectValue classLoader, String name, byte[] b, int off, int len, ObjectValue protectionDomain, String source, int options) { + VMOperations ops = this.ops; + if ((off | len | (off + len) | (b.length - (off + len))) < 0) { + ops.throwException(symbols.java_lang_ArrayIndexOutOfBoundsException()); + } + ParsedClassData data = classDefiner.parseClass(name, b, off, len, source); + if (data == null) { + ops.throwException(symbols.java_lang_NoClassDefFoundError(), name); + } + String classReaderName = data.getClassReader().getClassName(); + if (name == null) { + name = classReaderName; + } else if (!classReaderName.equals(name.replace('.', '/'))) { + ops.throwException(symbols.java_lang_ClassNotFoundException(), "Expected class name " + classReaderName.replace('/', '.') + " but received: " + name); + } + if (name.contains("[") || name.contains("(") || name.contains(")") || name.contains(";")) { + ops.throwException(symbols.java_lang_NoClassDefFoundError(), "Bad class name: " + classReaderName); + } + return defineClass(classLoader, data, protectionDomain, source, options); + } + + @Override + public @NotNull JavaClass findClass(JavaClass klass, Type type, boolean initialize) { + return findClass(klass.getClassLoader(), type, initialize); + } + + @Override + public @NotNull JavaClass findClass(ObjectValue classLoader, Type type, boolean initialize) { + int sort = type.getSort(); + if (sort == Type.ARRAY) { + Type primitive = type.getElementType(); + int psort = primitive.getSort(); + if (psort < Type.ARRAY) { + JavaClass cls = lookupPrimitive(psort); + for (int i = 0, j = type.getDimensions(); i < j;i++) { + cls = cls.newArrayClass(); + } + return cls; + } + } + if (sort < Type.ARRAY) { + return lookupPrimitive(sort); + } + return findClass(classLoader, type.getInternalName(), initialize); + } + + private JavaClass lookupPrimitive(int sort) { + Primitives primitives = this.primitives; + switch (sort) { + case Type.VOID: + return primitives.voidPrimitive(); + case Type.BOOLEAN: + return primitives.booleanPrimitive(); + case Type.CHAR: + return primitives.charPrimitive(); + case Type.BYTE: + return primitives.bytePrimitive(); + case Type.SHORT: + return primitives.shortPrimitive(); + case Type.INT: + return primitives.intPrimitive(); + case Type.FLOAT: + return primitives.floatPrimitive(); + case Type.LONG: + return primitives.longPrimitive(); + case Type.DOUBLE: + return primitives.doublePrimitive(); + } + throw new PanicException("unreachable code"); + } + + private JavaClass lookupPrimitiveOrNull(char desc) { + Primitives primitives = this.primitives; + switch (desc) { + case 'Z': + return primitives.booleanPrimitive(); + case 'C': + return primitives.charPrimitive(); + case 'B': + return primitives.bytePrimitive(); + case 'S': + return primitives.shortPrimitive(); + case 'I': + return primitives.intPrimitive(); + case 'F': + return primitives.floatPrimitive(); + case 'J': + return primitives.longPrimitive(); + case 'D': + return primitives.doublePrimitive(); + } + return null; + } + + private void initializeStaticFields(InstanceClass instanceClass) { + InstanceValue oop = instanceClass.getOop(); + Assertions.notNull(oop, "oop not created"); + MemoryManager memoryManager = this.memoryManager; + MemoryData data = oop.getData(); + for (JavaField field : instanceClass.staticFieldArea().list()) { + MemberIdentifier identifier = field.getIdentifier(); + String desc = identifier.getDesc(); + FieldNode fn = field.getNode(); + Object cst = fn.value; + if (cst == null) { + cst = AsmUtil.getDefaultValue(desc); + } + long offset = field.getOffset(); + switch (desc.charAt(0)) { + case 'J': + data.writeLong(offset, (Long) cst); + break; + case 'D': + data.writeLong(offset, Double.doubleToRawLongBits((Double) cst)); + break; + case 'I': + data.writeInt(offset, (Integer) cst); + break; + case 'F': + data.writeInt(offset, Float.floatToRawIntBits((Float) cst)); + break; + case 'C': + data.writeChar(offset, (char) ((Integer) cst).intValue()); + break; + case 'S': + data.writeShort(offset, ((Integer) cst).shortValue()); + break; + case 'B': + case 'Z': + data.writeByte(offset, ((Integer) cst).byteValue()); + break; + default: + memoryManager.writeValue(oop, offset, cst == null ? memoryManager.nullValue() : ops.referenceValue(cst)); + } + } + } + + private long safeSizeOf(String desc) { + Type type = Type.getType(desc); + int sort = type.getSort(); + if (sort < Type.ARRAY) { + return LanguageSpecification.primitiveSize(sort); + } + // Anything else is a reference. + return memoryManager.objectSize(); + } + + private void throwClassException(VMException ex) { + InstanceValue oop = ex.getOop(); + Symbols symbols = this.symbols; + if (!symbols.java_lang_Error().isAssignableFrom(oop.getJavaClass())) { + InstanceClass jc = symbols.java_lang_ExceptionInInitializerError(); + initialize(jc); + InstanceValue cause = oop; + oop = memoryManager.newInstance(jc); + // Can't use newException here + JavaMethod init = jc.getMethod("", "(Ljava/lang/Throwable;)V"); + Locals locals = threadManager.currentThreadStorage().newLocals(init); + locals.setReference(0, oop); + locals.setReference(1, cause); + ops.invokeVoid(init, locals); + throw new VMException(oop); + } + throw ex; + } + + private JavaClass findClass0(ClassLoaderData data, ObjectValue classLoader, String internalName, boolean initialize, boolean _throw) { + int dimensions = 0; + while (internalName.charAt(dimensions) == '[') { + dimensions++; + } + VMOperations ops = this.ops; + if (dimensions >= LanguageSpecification.ARRAY_DIMENSION_LIMIT) { + ops.throwException(symbols.java_lang_ClassNotFoundException(), internalName); + } + JavaClass klass; + if(dimensions > 0) { + if (internalName.charAt(dimensions) != 'L') { + // Primitive array + klass = lookupPrimitiveOrNull(internalName.charAt(dimensions)); + if (klass == null) { + ops.throwException(symbols.java_lang_ClassNotFoundException(), internalName); + return null; + } + + while (dimensions-- != 0) { + klass = klass.newArrayClass(); + } + return klass; + } + } + String trueName = dimensions == 0 ? internalName : internalName.substring(dimensions + 1, internalName.length() - 1); + try (CloseableLock lock = data.lock()) { + klass = data.getClass(trueName); + if (klass == null) { + if (classLoader.isNull()) { + ParsedClassData cdata = bootClassFinder.findBootClass(trueName); + if (cdata != null) { + klass = defineClass(classLoader, cdata, memoryManager.nullValue(), "JVM_DefineClass"); + } + } else { + // Ask Java world + JavaMethod method = runtimeResolver.resolveVirtualMethod(classLoader, "loadClass", "(Ljava/lang/String;Z)Ljava/lang/Class;"); + Locals locals = threadManager.currentThreadStorage().newLocals(method); + locals.setReference(0, classLoader); + locals.setReference(1, ops.newUtf8(trueName.replace('/', '.'))); + locals.setInt(2, initialize ? 1 : 0); + InstanceValue result = ops.checkNotNull(ops.invokeReference(method, locals)); + klass = classStorage.lookup(result); + } + if (klass == null) { + if (_throw) { + ops.throwException(symbols.java_lang_ClassNotFoundException(), internalName.replace('/', '.')); + } + dimensions = 0; + initialize = false; + } + } + } + if (initialize) { + if (klass instanceof InstanceClass) { + initialize((InstanceClass) klass); + } + } + while (dimensions-- != 0) { + klass = klass.newArrayClass(); + } + return klass; + } +} \ No newline at end of file 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 ce88e2f0..0845747d 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 @@ -19,9 +19,6 @@ 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; @@ -59,12 +56,14 @@ public SandBox(Context context, VirtualMachine vm) { this.helper = SupplyingClassLoaderInstaller.install(vm, new ClassLoaderDataSupplier(context.getLibraryLoader())); this.invocationUtil = InvocationUtil.create(vm); patchVm(); - } catch (VMException ex) { + } catch (Exception ex) { LOGGER.error("SSVM bootstrap failed. Make sure that you run this deobfuscator on java 17"); - SandBox.logVMException(ex, vm); + if (ex instanceof VMException vmException) { + SandBox.logVMException(vmException, vm); + } else if (ex.getCause() instanceof VMException vmException) { + SandBox.logVMException(vmException, vm); + } - throw new RuntimeException(ex); - } catch (IOException ex) { throw new RuntimeException(ex); } LOGGER.info("Initialized SSVM sandbox"); @@ -100,14 +99,10 @@ public void logVMException(VMException ex) { * Converts {@link VMException} into readable java exception */ public static void logVMException(VMException ex, VirtualMachine vm) { - InstanceValue oop = ex.getOop(); - if (oop.getJavaClass() == vm.getSymbols().java_lang_ExceptionInInitializerError()) { - oop = (InstanceValue) vm.getOperations().getReference(oop, "exception", "Ljava/lang/Throwable;"); - } + InstanceValue exceptionInstance = ex.getOop(); // Print pretty exception - LOGGER.error(oop); - LOGGER.error(vm.getOperations().toJavaException(oop)); + LOGGER.error("VM thrown an exception", WrappedVMException.wrap(exceptionInstance, vm)); } public VirtualMachine vm() { diff --git a/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/execution/WrappedVMException.java b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/execution/WrappedVMException.java new file mode 100644 index 00000000..4f7290cf --- /dev/null +++ b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/execution/WrappedVMException.java @@ -0,0 +1,81 @@ +package uwu.narumi.deobfuscator.api.execution; + +import dev.xdark.ssvm.VirtualMachine; +import dev.xdark.ssvm.mirror.type.InstanceClass; +import dev.xdark.ssvm.operation.VMOperations; +import dev.xdark.ssvm.value.ArrayValue; +import dev.xdark.ssvm.value.InstanceValue; +import dev.xdark.ssvm.value.ObjectValue; + +import java.util.Arrays; +import java.util.Collections; +import java.util.stream.IntStream; + +/** + * A wrapped {@link dev.xdark.ssvm.execution.VMException} that will print much prettier and readable exception + */ +public class WrappedVMException extends Throwable { + private final String className; + + public WrappedVMException(String className, String message) { + super(message); + this.className = className; + } + + @Override + public String toString() { + String s = this.className; // Set correct class name + String message = getLocalizedMessage(); + return (message != null) ? (s + ": " + message) : s; + } + + public static WrappedVMException wrap(InstanceValue exceptionInstance, VirtualMachine vm) { + // Copied and modified from DefaultExceptionOperations#toJavaException + VMOperations ops = vm.getOperations(); + // Exception message + String msg = ops.readUtf8(ops.getReference(exceptionInstance, "detailMessage", "Ljava/lang/String;")); + + WrappedVMException wrappedVMException = new WrappedVMException(exceptionInstance.toString(), msg); + + // Get stacktrace + ObjectValue backtrace = ops.getReference(exceptionInstance, "backtrace", "Ljava/lang/Object;"); + if (!backtrace.isNull()) { + ArrayValue arrayValue = (ArrayValue) backtrace; + StackTraceElement[] stackTrace = IntStream.range(0, arrayValue.getLength()) + .mapToObj(i -> { + InstanceValue value = (InstanceValue) arrayValue.getReference(i); + String declaringClass = ops.readUtf8(ops.getReference(value, "declaringClass", "Ljava/lang/String;")); + String methodName = ops.readUtf8(ops.getReference(value, "methodName", "Ljava/lang/String;")); + String fileName = ops.readUtf8(ops.getReference(value, "fileName", "Ljava/lang/String;")); + int line = ops.getInt(value, "lineNumber"); + return new StackTraceElement(declaringClass, methodName, fileName, line); + }) + .toArray(StackTraceElement[]::new); + Collections.reverse(Arrays.asList(stackTrace)); + // Set stacktrace + wrappedVMException.setStackTrace(stackTrace); + } + ObjectValue cause = ops.getReference(exceptionInstance, "cause", "Ljava/lang/Throwable;"); + if (!cause.isNull() && cause != exceptionInstance) { + // Set cause + wrappedVMException.initCause(wrap((InstanceValue) cause, vm)); + } + + // Init suppressed exceptions + ObjectValue suppressedExceptions = ops.getReference(exceptionInstance, "suppressedExceptions", "Ljava/util/List;"); + if (!suppressedExceptions.isNull()) { + InstanceClass cl = (InstanceClass) ops.findClass(vm.getMemoryManager().nullValue(), "java/util/ArrayList", false); + if (cl == suppressedExceptions.getJavaClass()) { + InstanceValue value = (InstanceValue) suppressedExceptions; + int size = ops.getInt(value, "size"); + ArrayValue array = (ArrayValue) ops.getReference(value, "elementData", "[Ljava/lang/Object;"); + for (int i = 0; i < size; i++) { + InstanceValue ref = (InstanceValue) array.getReference(i); + wrappedVMException.addSuppressed(ref == exceptionInstance ? wrappedVMException : wrap(ref, vm)); + } + } + } + + return wrappedVMException; + } +} diff --git a/deobfuscator-impl/src/test/java/reverseengineering/zelix/longdecrypter/LongDecrypter1.java b/deobfuscator-impl/src/test/java/reverseengineering/zelix/longdecrypter/LongDecrypter1.java index 1b8dca7b..bc85bad7 100644 --- a/deobfuscator-impl/src/test/java/reverseengineering/zelix/longdecrypter/LongDecrypter1.java +++ b/deobfuscator-impl/src/test/java/reverseengineering/zelix/longdecrypter/LongDecrypter1.java @@ -224,18 +224,18 @@ private static long decryptNumber(long key, int const1, int const2, int[] encryp } byte var13 = 64; - long var14 = var6; + long result = var6; int var11 = var13 - 1 - const2; if (var11 > 0) { - var14 = var6 << var11; + result = var6 << var11; } long var15 = const1 + var13 - 1 - const2; if (var15 > 0) { - var14 >>>= var15; + result >>>= var15; } - return var14; + return result; } private static void fillRestLongDecrypters() { @@ -243,12 +243,12 @@ private static void fillRestLongDecrypters() { a(0, cachedLongDecrypters.size() - 1, cachedLongDecrypters, new ArrayList<>(cachedLongDecrypters), var0); } - private static void a(int startIdx, int endIdx, ArrayList mutableLongDecrypters, ArrayList longDecryptersClone, int var4) { + private static void a(int startIdx, int endIdx, ArrayList mutableLongDecrypters, ArrayList longDecryptersClone, int iteration) { if (startIdx < endIdx) { int half = startIdx + (endIdx - startIdx) / 2; - if (++var4 < CONST_17) { - a(startIdx, half, mutableLongDecrypters, longDecryptersClone, var4); - a(half + 1, endIdx, mutableLongDecrypters, longDecryptersClone, var4); + if (++iteration < CONST_17) { + a(startIdx, half, mutableLongDecrypters, longDecryptersClone, iteration); + a(half + 1, endIdx, mutableLongDecrypters, longDecryptersClone, iteration); } a(startIdx, half, endIdx, mutableLongDecrypters, longDecryptersClone); diff --git a/deobfuscator-impl/src/test/java/reverseengineering/zelix/longdecrypter/LongDecrypter2.java b/deobfuscator-impl/src/test/java/reverseengineering/zelix/longdecrypter/LongDecrypter2.java index 2391e079..ba3cadb3 100644 --- a/deobfuscator-impl/src/test/java/reverseengineering/zelix/longdecrypter/LongDecrypter2.java +++ b/deobfuscator-impl/src/test/java/reverseengineering/zelix/longdecrypter/LongDecrypter2.java @@ -84,17 +84,17 @@ static void a(boolean var0) { e = var0; } - static ILongDecrypter getPairStatic(ILongDecrypter first, ILongDecrypter second) { - return INSTANCE.getPair(first, second); + static ILongDecrypter getPairStatic(ILongDecrypter key, ILongDecrypter value) { + return INSTANCE.getPair(key, value); } - private ILongDecrypter getPair(ILongDecrypter firstDecryptor, ILongDecrypter secondDecryptor) { - Object result = this.decrypterToDecrypterMap.get(firstDecryptor); + private ILongDecrypter getPair(ILongDecrypter key, ILongDecrypter value) { + Object result = this.decrypterToDecrypterMap.get(key); if (result == null) { result = this; } - Object var4 = this.decrypterToDecrypterMap.put(secondDecryptor, firstDecryptor); + Object var4 = this.decrypterToDecrypterMap.put(value, key); return (ILongDecrypter)result; } diff --git a/deobfuscator-impl/src/test/java/reverseengineering/zelix/longdecrypter/Main.java b/deobfuscator-impl/src/test/java/reverseengineering/zelix/longdecrypter/Main.java index d66474eb..25d036fd 100644 --- a/deobfuscator-impl/src/test/java/reverseengineering/zelix/longdecrypter/Main.java +++ b/deobfuscator-impl/src/test/java/reverseengineering/zelix/longdecrypter/Main.java @@ -4,9 +4,30 @@ public class Main { // Usage - private static final long result = LongDecrypter1.buildNumberDecryptor(5832394289974403481L, -8943439614781261032L, MethodHandles.lookup().lookupClass()).decrypt(19597665297729L); + // LongDecrypter2 instances + private static final long a1 = LongDecrypter1.buildNumberDecryptor(900058104405336414L, 6106449219005125011L, MethodHandles.lookup().lookupClass()).decrypt(99062861074978L); + private static final long a2 = LongDecrypter1.buildNumberDecryptor(5832394289974403481L, -8943439614781261032L, MethodHandles.lookup().lookupClass()).decrypt(19597665297729L); + private static final long a3 = LongDecrypter1.buildNumberDecryptor(-1563944528177415659L, 8240211990857304620L, MethodHandles.lookup().lookupClass()).decrypt(224919788586450L); + + // LongDecrypter1 instances +// private static final long b1 = LongDecrypter1.buildNumberDecryptor(-2891110000934166428L, 4534565905501632758L, MethodHandles.lookup().lookupClass()).decrypt(77751647876842L); +// private static final long b2 = LongDecrypter1.buildNumberDecryptor(3109756102241299096L, 3300563161622516573L, MethodHandles.lookup().lookupClass()).decrypt(3703773754795L); public static void main(String[] args) { - System.out.println(result); + System.out.println(a1); // 110160429747013 + System.out.println(a2); // 119662894797887 + System.out.println(a3); // 62547565276859 + + //System.out.println(b1); // 97375689351077 + //System.out.println(b2); // 92083991818967 + + ILongDecrypter b1Instance = LongDecrypter1.buildNumberDecryptor(-2891110000934166428L, 4534565905501632758L, MethodHandles.lookup().lookupClass()); + ILongDecrypter b2Instance = LongDecrypter1.buildNumberDecryptor(3109756102241299096L, 3300563161622516573L, MethodHandles.lookup().lookupClass()); + System.out.println(b1Instance); + System.out.println(b2Instance); + long b1 = b1Instance.decrypt(77751647876842L); + long b2 = b2Instance.decrypt(3703773754795L); + System.out.println(b1); + System.out.println(b2); } }