JRebel 2.x 破解

最近几天开发公司的项目,发现重启成了一个很大的问题,服务器端JS、Freemarker脚本、Java类,随便修改点东西就需要重启服务,在公司一般2分钟能起一次,在家里起一次得20分钟(连得公司数据库)
突然想起很久以前看的Java Rebel这个获了Jolt大奖的东东,遂去官网下载了最新版,果然不错
可惜只有30天的Trial,毛爷爷说的自己动手、丰衣足食,那么就破吧。。。

虽然标题写的是2.x,但不表示真的是所有的2.x版本都能破解,至少到我这里2.1.1-nightly还是可以的
因为我这个人总是喜欢用最新版的,而网上大家提供的都是之前的老版本
加之觉得授人以鱼不如授人以渔,干脆把破解过程写下来,这样大家以后总是可以用到破解的最新版,不是很爽?
废话少说,下面就开始吧

首先,自然是去官网上下载最新的JRebel的代码

然后自然是在本机的服务上试试看效果,比如用的是否舒服啦之类的,使用方法这里就不介绍了,网上大把的文章

第三步是找个反编译工具把整个jar包都反编译过来,我用的是http://java.decompiler.free.fr/ ,图形化界面,还算方便。
不过这里需要注意一点,反编译的产物由于被混淆过,所以会有类似a.java和A.java的产物,如果在Windows下面,由于不区分大小写,这两个文件会互相覆盖,因此最好是找台Linux的机器来做这个事情会比较方便点

第四步就要用到第二步的东东了,如果我们平时使用下载的evaluation版本启动项目时,会出现一行字“You are running JRebel evaluation license”,拿着这段字在所有的源码包里查找,虽然是混淆,但是字符串是不会变的
我找到了一个叫jZ.java的类,你找到的可能与我的名字不同,这是混淆器弄的

第五步自然就是看懂这个类及其周边类了,如果你比较懒不想看,可以跳过这步,直接按照我下面的办法来做就行
这个类的代码我就不贴了,最终结果就是,根据这个类及其周边类的代码,我总结出了第六步代码

第六步,将下面这段充满try/catch的恶心的代码复制到本地执行,执行时保证jrebel.jar在CLASSPATH中

package com.baidu.jrebel.crack;

import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Map;

import com.zeroturnaround.licensing.UserLicense;

/**
 *
 * LicenseParser
 *
 * @author GuoLin
 *
 */
public class LicenseParser {

    private UserLicense userLicense;

    private Map<String, Object> licenseMap;

    /**
     * 从一个classpath中的license文件中加载数据.
     * @param filename 在classpath中的文件名
     */
    public LicenseParser(String filename) {
        userLicense = getLicense(filename);
        licenseMap = buildMap(userLicense.getLicense());
    }
   
    /**
     * 修改licenseMap的值.
     * @param key 键
     * @param value 值
     */
    public void set(String key, Object value) {
        licenseMap.put(key, value);
    }
   
    /**
     * 移除licenseMap的键值对.
     * @param key 键
     */
    public void remove(String key) {
        licenseMap.remove(key);
    }

    /**
     * 将License存储到新的文件中.
     * @param newFilename 新文件名
     */
    public void store(String newFilename) {
        // 将Map中的值写入UserLicense对象
        ByteArrayOutputStream byteArrayOutputStream = null;
        try {
            byteArrayOutputStream = new ByteArrayOutputStream();
            ObjectOutputStream objectOutputStream = null;
            try {
                objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
                objectOutputStream.writeObject(licenseMap);
                userLicense.setLicense(byteArrayOutputStream.toByteArray());
            } catch (Exception ex) {
                throw new RuntimeException(ex);
            } finally {
                try {
                    objectOutputStream.close();
                } catch (IOException ex) {
                    throw new RuntimeException(ex);
                }
            }
        } finally {
            if (byteArrayOutputStream != null) {
                try {
                    byteArrayOutputStream.close();
                } catch (IOException ex) {
                    throw new RuntimeException(ex);
                }
            }
        }

        // 保存UserLicense的值到文件
        ObjectOutputStream objectOutputStream = null;
        try {
            objectOutputStream = new ObjectOutputStream(new BufferedOutputStream(
                    new FileOutputStream(newFilename)));
            objectOutputStream.writeObject(userLicense);
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        } finally {
            if (objectOutputStream != null) {
                try {
                    objectOutputStream.close();
                } catch (Exception ex) {
                    throw new RuntimeException(ex);
                }
            }
        }
    }

    public String toString() {
        return licenseMap.toString();
    }

    /**
     * 从文件中进行反序列化到UserLicense类.
     * @param filename 文件名,CLASSPATH下
     * @return 反序列化完成的UserLicense对象
     * @throws Exception
     */
    private static UserLicense getLicense(String filename) {
        ObjectInputStream licenseFileObjectInputStream = null;
        try {
            licenseFileObjectInputStream = new ObjectInputStream(ClassLoader
                    .getSystemClassLoader().getResourceAsStream(filename));
            return (UserLicense) licenseFileObjectInputStream.readObject();
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        } finally {
            if (licenseFileObjectInputStream != null) {
                try {
                    licenseFileObjectInputStream.close();
                } catch (IOException ex) {
                    throw new RuntimeException(ex);
                }
            }
        }
    }

    /**
     * 根据license的字节码获取最终的Map.
     * @param licenseBytes 字节码流
     * @return 由字节码流反序列化后的Map
     */
    @SuppressWarnings("unchecked")
    private static Map<String, Object> buildMap(byte[] licenseBytes) {
        ObjectInputStream localObjectInputStream = null;
        try {
            localObjectInputStream = new ObjectInputStream(
                    new ByteArrayInputStream(licenseBytes));
            return (Map<String, Object>) localObjectInputStream.readObject();
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        } finally {
            if (localObjectInputStream != null) {
                try {
                    localObjectInputStream.close();
                } catch (IOException ex) {
                    throw new RuntimeException(ex);
                }
            }
        }
    }

    public static void main(String[] args) throws Exception {
        LicenseParser parser = new LicenseParser("jrebel.lic");
       
        // 显示内容
        System.out.println(parser.toString());
       
        // 修改各类属性,参考反编译后的代码中搜索"Seats"结果所在文件的逻辑
        Calendar cal = new GregorianCalendar(2020, 01, 01);
        parser.set("validUntil", cal.getTime());  // 过期时间
        parser.set("Seats", "Unlimited");
        parser.set("Organization", "IIT Xiaoheiwu");
        parser.set("Comment", "Congratulations! I have bean cracked!");
        parser.set("commercial", "true");
        parser.remove("limited");
       
        // 显示内容
        System.out.println(parser.toString());
       
        parser.store("D:/jrebel.lic");
    }

}
 

以上代码主要是有了一个将license文件加载->修改->保存的过程,如果想知道为什么要修改那些参数,请回到第五步,这里就不多说了
如果执行正常,那么最终会在D:/上生成一个jrebel.lic文件,这个就是我们新的License文件 

到这里还没完,因为虽然我们生成了一个新的License文件,但是代码中会对这个文件做签名验证,那么我们下一步需要去掉这个签名

在第四步所指的类中,找到类似这样的代码(可以根据"Product"字符串进行查找):

if (AClass.aMethod(localUserLicense)) {
  x = 1;
}
...
if ((!("JavaRebel".equals(localMap.get("Product")))) && (!("JRebel".equals(localMap.get("Product"))))) {
  x = 0;
}
 

注意这段中的几个有黄底的地方,x表示一个变量,这个变量用于标识License的种类,AClass.aMethod是调用AClass这个类上 的一个静态方法(类名和方法名由于被混淆会不同,例如我的是eY.a(localUserLicense)),这个if语句实际是表示签名是否通过,如果 通过就x=1否则x默认为0,0表示非法License

明白了以上这些,现在很简单了,在同包下找到AClass类(对于我来说是eY,对于你来说可能是任何值,这也是上面一段之所以要说明一下的原因),找到aMethod方法(可能有很多同名方法,找到参数、返回值都匹配的方法),方法描述大致如下

public static boolean aMethod(UserLicense paramUserLicense) throws Exception

本来是只需要简单的将这个方法中的所有代码删除并大方的写上return true;就万事大吉的,但我反编译出来的类中有两个同名的aMethod方法(参数相同),编译不过去了,只好再深一步

在这个方法中主要是调用了另一个类(暂时称为BClass),遂找到BClass类,发现这个类太适合伪造了——没有同名public方法,所有public方法只有一个需要返回boolean值,其他均无需返回值
大方的将其所有private方法删除,将其所有private成员变量删除,将所有的public方法的内容全部删除,需要返回boolean的方法直接return true;

于是代码改造完成,接下来编译BClass类:

javac com/zeroturnaround/javarebel/BClass.java -cp ../jrebel.jar

 再用WinRAR之类的东东将我们本次产物BClass.class文件和第六步生成的jrebel.lic文件复制到jar包中对应的位置中覆盖原有的文件

大功告成!重启Tomcat试试吧,哈哈哈

 

你可能感兴趣的:(jvm,tomcat,linux,freemarker,bean)