最近几天开发公司的项目,发现重启成了一个很大的问题,服务器端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试试吧,哈哈哈