Record the process of cracking and decompiling an encrypted jar package once.

Recently, I built a private server of Adventure Island privately for entertainment, and obtained the source code of Adventure Island from the Internet. Then I built the service and found a lot of bugs in the process of the game, so I wanted to see the source code decompile and fix the bugs.
After decompressing the jar, I found that it could not be decompiled with the decompilation tool, and then I tried various decompilation tools and failed. Then open the bat script that starts the service and find that there is such a command in the command.

Generally, adding such a command is equivalent to going back to execute the dll file for a series of operations before loading the class, and the file name is also very straightforward - decryption. That must be the class in the jar package is encrypted. Then I opened the hexadecimal file of the class and found that the magic number at the beginning of the class hexadecimal file should be ca fe ba be. At this time, it is found that it does not correspond at all. The decryption starts below.
First of all, we can't get the encryption method. It is naturally unrealistic to write and decrypt through the encrypted area. There is no way to decrypt the file by writing the decryption method, then we should first think of the process of jvm loading and executing the class. Since the virtual machine can parse the class file normally, the class loaded by the virtual machine must be decrypted. So, the decrypted file will definitely be loaded in the jvm. Then after my Google and some programming for Google and Baidu, I discovered the java probe technology. In fact, this probe technology is somewhat similar to spring's aop. It is to dynamically proxy for a class when it is executed. Insert some method operations you want before and after execution.
under the specific code

import java.io.*;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

public class ClazzDumpCustomAgent implements ClassFileTransformer {
    /**
     * Export filter expression, here is the class name prefix, specified with the -f parameter
     */
    private String filterStr;

    /**
     * The root directory of the export file directory, specified with the -d parameter
     */
    private String exportBaseDir = "/tmp/";

    /**
     * Whether to create a multi-level directory, specified with the -r parameter
     */
    private boolean packageRecursive;

    public ClazzDumpCustomAgent(String exportBaseDir, String filterStr) {
        this(exportBaseDir, filterStr, false);
    }

    public ClazzDumpCustomAgent(String exportBaseDir, String filterStr, boolean packageRecursive) {
        if(exportBaseDir != null) {
            this.exportBaseDir = exportBaseDir;
        }
        this.packageRecursive = packageRecursive;
        this.filterStr = filterStr;
    }

    /**
     * entry address
     *
     * @param agentArgs agent parameter
     * @param inst
     */
    public static void premain(String agentArgs, Instrumentation inst) {
        System.out.println("agentArgs: " + agentArgs);
        String exportDir = null;
        String filterStr = null;
        boolean recursiveDir = false;
        if(agentArgs != null) {
            if(agentArgs.contains(";")) {
                String[] args = agentArgs.split(";");
                for (String param1 : args) {
                    String[] kv = param1.split("=");
                    if("-d".equalsIgnoreCase(kv[0])) {
                        exportDir = kv[1];
                    }
                    else if("-f".equalsIgnoreCase(kv[0])) {
                        filterStr = kv[1];
                    }
                    else if("-r".equalsIgnoreCase(kv[0])) {
                        recursiveDir = true;
                    }
                }
            }
            else {
                filterStr = agentArgs;
            }
        }
        inst.addTransformer(new ClazzDumpCustomAgent(exportDir, filterStr, recursiveDir));
    }

    @Override
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
                            ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
        if (needExportClass(className)) {
            int lastSeparatorIndex = className.lastIndexOf("/") + 1;
            String fileName = className.substring(lastSeparatorIndex) + ".class";
            String exportDir = exportBaseDir;
            if(packageRecursive) {
                exportDir += className.substring(0, lastSeparatorIndex);
            }
            exportClazzToFile(exportDir, fileName, classfileBuffer);         //"D:/server-tool/tmp/bytecode/exported/"
            System.out.println(className + " --> EXPORTED");
        }
        return classfileBuffer;
    }

    /**
     * Check if file export is required
     *
     * @param className class name, such as com.xx.abc.AooMock
     * @return y/n
     */
    private boolean needExportClass(String className) {
        String str[] = className.split("/");
        String packageName = str[0];
        List<String>  list = new ArrayList<>();
        String a = "a";
        String b = "b";
        String c = "c";
        String client = "client";
        String configs = "configs";
        String constants ="constants";
        String d = "d";
        String scripts = "scripts";
        String server = "server";
        String tools = "tools";
        String Test = "Test";
        list.add(a);
        list.add(b);
        list.add(c);
        list.add(client);
        list.add(configs);
        list.add(constants);
        list.add(d);
        list.add(scripts);
        list.add(server);
        list.add(tools);
        list.add(Test);
        String callback = "callback";
        String com = "com";
        String config = "config";
        String mode = "mode";
        String okhttp3 = "okhttp3";
        String okio = "okio";
        String org = "org";
        String util = "util";
        list.add(callback);
        list.add(com);
        list.add(config);
        list.add(okhttp3);
        list.add(okio);
        list.add(org);
        list.add(util);
        list.add(mode);

        //write file
        SimpleDateFormat sdf2=new SimpleDateFormat("yyyy-MM-dd");
        String dateString = sdf2.format(new Date());

        File file = new File("D:/file/"+dateString+".txt");
        BufferedWriter out = null;
        String conent1 = "className is: "+className;
        String conent2 = "packageName is: "+packageName;
        try {
            if(!file.exists()) {
                //first write
                file.createNewFile();
                out = new BufferedWriter( new OutputStreamWriter(
                        new FileOutputStream(file)));
            } else {
                //append
                out = new BufferedWriter( new OutputStreamWriter(
                        new FileOutputStream(file, true)));
            }

            out.write(conent1+"       "+conent2+";");
            out.write("\r\n");;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        if(list != null) {
            if(list.contains(packageName)) {
                System.out.println("packageName is: " + packageName);
                return true;
            }
            else {
                return false;
            }
        }
        if (!className.startsWith("java") && !className.startsWith("sun")) {
            return true;
        }
        return false;
    }

    /**
     * Execute file export and write
     *
     * @param dirPath export directory
     * @param fileName export filename
     * @param data byte stream
     */
    private void exportClazzToFile(String dirPath, String fileName, byte[] data) {
        try {
            File dir = new File(dirPath);
            if(!dir.isDirectory()) {
                dir.mkdirs();
            }
            File file = new File(dirPath + fileName);
            if (!file.exists()) {
                System.out.println(dirPath + fileName + " is not exist, creating...");
                file.createNewFile();
            }
            else {

//                String os = System.getProperty("os.name"); // Mainly for the case insensitivity of windows files
//                if(os.toLowerCase().startsWith("win")){
//                    // it's win
//                }
                try {
                    int maxLoop = 9999;
                    int renameSuffixId = 2;
                    String[] cc = fileName.split("\\.");
                    do {
                        Long fileLen = file.length();
                        byte[] fileContent = new byte[fileLen.intValue()];
                        FileInputStream in = new FileInputStream(file);
                        in.read(fileContent);
                        in.close();
                        if(!Arrays.equals(fileContent, data)) {
                            fileName = cc[0] + "_" + renameSuffixId + "." + cc[1];
                            file = new File(dirPath + fileName);
                            if (!file.exists()) {
                                System.out.println("new create file: " + dirPath + fileName);
                                file.createNewFile();
                                break;
                            }
                        }
                        else {
                            break;
                        }
                        renameSuffixId++;
                        maxLoop--;
                    } while (maxLoop > 0);
                }
                catch (Exception e) {
                    System.err.println("exception in read class file..., path: " + dirPath + fileName);
                    e.printStackTrace();
                }
            }
            FileOutputStream fos = new FileOutputStream(file);
            fos.write(data);
            fos.close();
        }
        catch (Exception e) {
            System.err.println("exception occur while export class.");
            e.printStackTrace();
        }
    }
}

After writing the above code, compile the code into a class file and put it in the specified location and then run the jar package through the following command.
jre\bin\java.exe -server -Xms2000m -Xmx2000m -Xss256k -XX:ReservedCodeCacheSize=256m -jar -agentlib:jre/bin/decrypt -javaagent:config/jvmti1.jar=-d=config/Servers/classes;-r config/Server.jar
Note the bold font, this paragraph is the key.
Then as long as the class file is run, it will be loaded in the directory you specify.
So far this crack has been completed, of course, if you want to get all the files, you still need to slowly execute each class file. After all, only the class loaded by the jvm will be intercepted by the proxy to generate the decrypted class file.

Tags: Java

Posted by RON_ron on Thu, 05 May 2022 01:20:40 +0300