记录一次加密jar包破解反编译的过程。

最近私下搭建了一下冒险岛的私服来自娱自乐下,从网上获取了冒险岛的源码。然后搭建了服务在游戏的过程中发现了不少BUG,于是就想看下源码反编译后修改下bug。
解压jar后,用反编译工具发现竟然不能反编译,然后有尝试了各种各样的反编译工具结果都失败了。然后打开了启动服务的bat脚本发现命令中有着这样的命令。
在这里插入图片描述
一般加了这样的命令等于在加载class之前回去执行这个dll文件进行一系列操作,这个文件名也很直白-解密。那肯定这个jar包里的class就是被加密了。然后打开了class的16进制文件发现,class16进制文件开头的魔改数字应该是ca fe ba be。此时打开发现完全对应不上。下面就开始解密了。
首先我们获取不到加密的方式,想通过加密区写解密的方法自然不现实。没法通过写解密方法去解密文件,那我们应该首先想到jvm加载执行class的过程。既然虚拟机可以正常解析class文件,那么经过虚拟机加载的class一定是解密之后。所以,解密后的文件肯定会在jvm中加载。然后经过我谷歌,一番面向谷歌百度的编程之后,发现了java探针技术,其实这个探针技术和spring的aop有点类似。就是在执行某个class的时候对他进行动态代理。在执行前后插入一些自己想要的方法操作。
具体代码下

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 {
    /**
     * 导出过滤表达式,此处为类名前缀, 以 -f 参数指定
     */
    private String filterStr;

    /**
     * 导出文件目录根目录, 以 -d 参数指定
     */
    private String exportBaseDir = "/tmp/";

    /**
     * 是否创建多级目录, 以 -r 参数指定
     */
    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;
    }

    /**
     * 入口地址
     *
     * @param agentArgs agent参数
     * @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;
    }

    /**
     * 检测是否需要进行文件导出
     *
     * @param className class名,如 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);

        //写文件
        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()) {
                //初次写入
                file.createNewFile();
                out = new BufferedWriter( new OutputStreamWriter(
                        new FileOutputStream(file)));
            } else {
                //追加
                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;
    }

    /**
     * 执行文件导出写入
     *
     * @param dirPath 导出目录
     * @param fileName 导出文件名
     * @param data 字节流
     */
    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");        // 主要针对windows文件不区分大小写问题
//                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();
        }
    }
}

编写以上代码后将代码编译成class文件放在指定的位置然后通过以下指令运行jar包。
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
注意加粗的字体,这一段是关键。
config/jvmti1.jar 代表上面代码打成的jar包。
-d=config/Servers/classes 代表解析后文件地址。
-r 代表循环解析,会按照对应目录生成文件。
然后只要运行过得class文件都会在你指定的目录下被加载出来。
至此这次破解就已经全部完成,当然想要获取所有的文件还是需要慢慢的去执行各个class文件才行。毕竟只有jvm加载过得class才会去被这个代理拦截生成解密后的class文件。


版权声明:本文为javayimeng原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。