最近破解上瘾了,昨天干到3点多,今天又花了一中午把deskzilla,一个bugzilla的桌面客户端)破解了。

这次破解花了很长时间,一是因为代码编译后被混淆了,找关键代码的时候着实花了不少时间。另一方面这个软件的license验证不是使用常见的布尔判断,而是用异常。这意味着必须仔细研究方法的调用栈,于是这次用了一个比较偷巧的方法:在代码中抛出并捕捉异常。在Java中,无论在代码何处抛出异常,JVM都会生成一个从程序入口到抛出异常方法的调用栈,这个机制在需要调用关系的场合非常有用,比如log4j,就是使用这种机制记录log的方法、行数、类名等等信息的。但是,这种机制是非常消耗资源的,因为调用栈里的每一个element都记录着方法、类、甚至行数(如果编译时打开“debug”开关)等信息,而这些信息都是通过反射机制从class文件里直接获得的。

通过对一堆类似huv、dww、dtg、gjt的混淆后的类的分析,终于找出了几个关键类:

package z;

public class dza extends hwn {

    // ...

    static  {
        l = epv.a("Application.License.FULL", "Single-user license");
        m = epv.a("Application.License.EVAL", "Evaluation license");
        n = epv.a("Application.License.EAP", "EAP license");
        o = epv.a("Application.License.OS", "License for open-source projects");
        p = epv.a("Application.License.INVALID", "License is INVALID");
        r = epv.a("Application.License.FLOATING", "Floating license");
        s = epv.a("Application.License.PERSONAL", "Personal license");
        t = epv.a("Application.License.ACADEMIC", "Academic license");
        u = epv.a("Application.License.SITE", "Site license");
        a = new dza("FULL", l);
        b = new dza("PERSONAL", s);
        c = new dza("FLOATING", r);
        d = new dza("ACADEMIC", t);
        e = new dza("SITE", u);
        f = new dza("OS", o);
        g = new dza("EAP", n);
        h = new dza("KLEVAL", m);
        i = new dza("EVAL", m);
        j = new dza("INVALID", p);
    }

    // ...
}

可以看出这是定义license类型的类

package z;

public class aef extends hwn {
    // ...

    public static final aef a = new gdo("UserName");
    public static final aef b = new gdo("UserCompany");
    public static final aef c = new fsf("CustomerID");
    public static final aef d = new fir("ExpirationDate");
    public static final aef e = new aef("LicenseType", dza.class);
    public static final aef f = new gdo("AdditionalInfo");
    public static final aef g = new fsf("MinBuild");
    public static final aef h = new fsf("MaxBuild");
    public static final aef i = new fsf("MaxLeaseCount");
    public static final aef m = new fsf("R");
    public static final Date j = new Date(0L);
    public static final Date k = new Date(1L);

    // ...
}

这个类定义了需要验证哪些内容,最后关键类的关键代码:

package z;

public class huv implements cud {
    // ...

    public String a(aef aef1) {
        String s;

        if (aef1 == aef.d) {
            Date time = new Date(System.currentTimeMillis() + 24 * 60 * 60 * 1000);
            Calendar cal = Calendar.getInstance();
            cal.setTime(time);
            int year = cal.get(Calendar.YEAR);
            int month = cal.get(Calendar.MONTH) + 1;
            int day = cal.get(Calendar.DAY_OF_MONTH);

            s = "" + year;
            if (month < 10) {
                s += ("0" + month);
            }
            if (day < 10) {
                s += ("0" + day);
            }
        } else if (aef1 == aef.b) {
            s = "Nazca";
        } else if (aef1 == aef.f) {
            s = "to memorize my Macbook...";
        } else if (aef1 == aef.e) {
            return dza.a.h();
        } else if (aef1 == aef.a) {
            return "Cracked by Jay";
        } else if (aef1 == aef.i) {
            return "20";
        } else if (aef1 == aef.c) {
            return "1";
        } else {
            s = "";
        }

        return s;
    }

    // ...
}

可以看出过期时间定为当前时间的后一天,这样就永远不会过期了。最后编译,打包,替换原先的jar包,就行了。

最后提一句,破解时选择切入点非常重要。比如这次,我选择的切入点是相当靠后的,仅仅在字符串被送入验证方法之前。如果切入点再往前的话会很麻烦,因为看了一下代码,deskzilla的license是一个经3DES加密的XML文件,XML还需要用MD5校验,如果切入点是生成XML文件,那我还得搞到3DES的密钥,以及保证MD5验证通过。所谓打蛇打七寸,选对了切入点,往往有事半功倍的效果。</pre>