[JAVA] Eine Geschichte über das Bemühen, JAR-Dateien zu dekompilieren

Dies ist der Artikel am 17. Tag von "Java Adventskalender 2016".

Einführung

Der Anfang der Dinge

Manche sagen

Ich dachte so

Sie können es von einem Loader aus starten, der die Java-Klassendatei verschlüsselt, entschlüsselt und dynamisch lädt. Da die Klassendatei selbst verschlüsselt ist, sollten Sie den Code nicht nur durch Einfügen in den Entschlüsseler anzeigen können.

Ich habe so etwas versucht

Generieren einer JAR-Datei mit einer verschlüsselten Klassendatei

Verschlüsselung von Klassendateien

Passend dazu

private Key key;// KeyGenerator#generateKey()Passend

void encrypt(File file) {
    byte[] inByte = null;
    try {
        inByte = FileUtils.readFileToByteArray(file);

        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.ENCRYPT_MODE, key);
        byte[] encrypted = cipher.doFinal(inByte);

        FileUtils.writeByteArrayToFile(file, encrypted);
        decryptTest(file, key, inByte);
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    } catch (NoSuchPaddingException e) {
        e.printStackTrace();
    } catch (InvalidKeyException e) {
        e.printStackTrace();
    } catch (BadPaddingException e) {
        e.printStackTrace();
    } catch (IllegalBlockSizeException e) {
        e.printStackTrace();
    }
}

MainClass umschreiben

Spigot liest die Hauptklasse des Plug-Ins aus der Konfigurationsdatei (plugin.yml) und schreibt sie in die der Loader-Klasse um

Yaml yaml = new Yaml();
try {
        String str = FileUtils.readFileToString(new File(appTmpDir, "plugin.yml"), "utf-8");
        Map map = yaml.loadAs(str, Map.class);
        map.put("main", getPackageName(getMainClass()) + ".PluginLoader");
        str = yaml.dumpAsMap(map);
        FileUtils.writeStringToFile(new File(appTmpDir, "plugin.yml"), str, "utf-8");
    } catch (IOException e) {
        e.printStackTrace();
}

Entschlüsselung und dynamisches Laden von Klassendateien

Entschlüsselung

Von Datei zu Byte-String

private byte[] read(InputStream inputStream) {
    byte[] buf = new byte[1024];
    int len;
    BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

    try {
        while ((len = bufferedInputStream.read(buf)) > 0) {
            byteArrayOutputStream.write(buf, 0, len);
        }
        inputStream.close();
    } catch (IOException e) {
        e.printStackTrace();
    }

    return byteArrayOutputStream.toByteArray();
}

Entschlüsselung (Schlüssel wird von ObjectInputStream gelesen)

private byte[] decrypt(byte[] bytes, Key key) {
    byte[] inByte = null;
    try {

        if (key == null) throw new IllegalArgumentException("Schlüssel ist null.");
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.DECRYPT_MODE, key);
        inByte = cipher.doFinal(bytes);

    } catch (NoSuchAlgorithmException e) {
        e.printStackTrace();
    } catch (NoSuchPaddingException e) {
        e.printStackTrace();
    } catch (InvalidKeyException e) {
        e.printStackTrace();
    } catch (BadPaddingException e) {
        e.printStackTrace();
    } catch (IllegalBlockSizeException e) {
        e.printStackTrace();
    }
    return inByte;
}

Dynamisches Laden

Lesen Sie die Byte-Zeichenfolge mithilfe von Reflektion gut durch Die defineClass0-Methode von ClassLoader ist wichtig

private void loadClass(byte[] bytes, String name) throws ClassFormatError {
    try {
        String packageName = name.replaceAll("/", ".").substring(0, name.length() - 6);
        Method define0Method = ClassLoader.class.getDeclaredMethod("defineClass0", new Class[]{String.class, byte[].class, int.class, int.class, ProtectionDomain.class});
        define0Method.setAccessible(true);
        Class loadedClass = (Class) define0Method.invoke(getClassLoader(), packageName, bytes, 0, bytes.length, null);
        if (packageName.equals(mainClassName)) {
            this.mainClass = loadedClass;
        }
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        Throwable cause = e.getCause();
        if (cause instanceof ClassFormatError) {
            throw (ClassFormatError) cause;
        }
    }
    stub.add(name);
}

Das passiert

Da jedes Plug-In vom Paketnamen + Klassennamen auf dem Server verwaltet wird, werden das zweite und die nachfolgenden Plug-Ins geladen, wenn zwei oder mehr Plug-Ins aufgrund eines Konflikts zwischen dem Namen des Loader-Pakets und dem Klassennamen schwer zu dekompilieren sind. Scheitern. Es gibt kein Problem bei der Verteilung als eigenständiges Java-Programm.

Lösung

Das schwierige JAR-Dekompilierungsprogramm (vorläufig) enthält den Quellcode des Loaders im Hauptteil, und der Paketname wird dynamisch geändert, sodass er jedes Mal kompiliert wird. Das Argument von getTask wurde durch Ausprobieren entschieden und sollte normal funktionieren.

private void addLoader(String packageName, File target, File bukkitJar) {
    try {
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        String pluginLoaderJava = FileUtils.readFileToString(new File(ClassLoader.getSystemResource("PluginLoader.java").getFile()), "utf-8");
        JavaFileObject file = new JavaSourceFromString("PluginLoader", pluginLoaderJava.replace("{{package}}", "package " + packageName + ";"));

        String[] compileOptions = new String[]{"-d", target.getAbsolutePath(), "-classpath", bukkitJar.getAbsolutePath()};
        Iterable<String> compilationOption = Arrays.asList(compileOptions);
        Iterable<? extends JavaFileObject> compilationUnits = Arrays.asList(file);

        JavaCompiler.CompilationTask task = compiler.getTask(
                null,
                null,
                null,
                compilationOption,
                null,
                compilationUnits);

        task.call();
    } catch (IOException e) {
        e.printStackTrace();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

Was ich nicht mag

Der Punkt, an dem ein JDK erforderlich ist, um den Loader zu kompilieren, wenn eine schwierige Dekompilierung ausgeführt wird. (Kann auf JDK verzichtet werden, indem die Klassendatei direkt geändert wird?) Auch wenn große Unternehmen zu Open Source werden, wie wäre es, den Quellcode überhaupt zu verstecken?

Recommended Posts

Eine Geschichte über das Bemühen, JAR-Dateien zu dekompilieren
Die Geschichte des Versuchs, JAVA File zu bedienen
Eine Geschichte über den Versuch, mit Mockito auszukommen
Datei zur JAR-Datei hinzufügen
Einführung in JAR-Dateien
Eine Geschichte über die Reduzierung des Speicherverbrauchs auf 1/100 mit find_in_batches
Eine Geschichte über die Erstellung von PKIX-Pfaden schlug fehl, als versucht wurde, mit Jenkins eine Tomcat-Bereitstellung durchzuführen
Eine Geschichte über die Schwierigkeiten beim Versuch, PHP 7.4 auf CentOS 8 von GCE zu erstellen
Eine Geschichte über Missverständnisse im Umgang mit Java-Scannern (Memo)
Eine lustige Geschichte, die beim Import von fx-clj in einem Chaos steckt
So dekompilieren Sie eine Java-Klassendatei
Eine Geschichte über die Herstellung eines Taschenrechners zur Berechnung der Muschelhügelrate
[Rails] Ich habe etwas über Migrationsdateien gelernt! (Hinzufügen einer Spalte zur Tabelle)
Die Geschichte des Jobwechsels von einem christlichen Pastor (Lehrling) zu einem Webingenieur
Eine Geschichte über das Konvertieren von Zeichencodes von UTF-8 in Shift-jis in Ruby
Eine Geschichte über das Senden einer Pull-Anfrage an MinGW, um die libgr-Version zu aktualisieren
Eine Geschichte, die süchtig nach Platzhaltern für JDBC-Vorlagen ist
Beachten Sie, dass Junit 4 zu Android Studio hinzugefügt wurde
Eine Geschichte, die süchtig nach EntityNotFoundException von getOne of JpaRepository ist
Eine Geschichte über die Java 11-Unterstützung für Webdienste
Eine Geschichte über das JDK in der Java 11-Ära
Eine Geschichte, die Zeit brauchte, um eine Verbindung herzustellen
Eine sehr nützliche Geschichte über Rubys Struct-Klasse
Eine Geschichte über das Erstellen eines Builders, der den Builder erbt
Die Geschichte eines neuen Ingenieurs, der einen leidenschaftlichen Programmierer liest
Eine Geschichte über das Erstellen eines Dienstes, der mithilfe einer API für maschinelles Lernen Verbesserungen an einer Website vorschlägt