目标软件:justinmind官方网站下载30天试用版Justinmind Prototyper
保护方式:使用jProductivity的license文件方式保护
调试环境:win7 + MyEclipse8.6
代码阅读:SourceInsight 3.5
文件编辑:UltraEdit
java反编译:XJad 2.2、jd-gui 0.3.3

破解过程:
1.  经过初步分析,定位到jar包com.justinmind.evc_4.0.1.jar
2.  用XJad、jd-gui反编译此jar包。
3.  在SourceInsight中阅读代码,初步确定保护代码位置在com\jp目录,因为其中有com\jp\protection\security目录,com\jp\protection\pub目录下存在License*.java文件,经过网上搜索com.jp.protection,得知是jProductivity的产品。基本确认保护代码位于这里。
4.  暂时放弃反编译得到的除com\jp目录之外的其余部分,结合两个反编译情况,在MyEclipse中修正将com\jp目录的反编译结果,消除编译错误,以便能进行跟踪调试。
此步骤对于反编译软件不能正确反编译而直接显示为java byte code的部分,参考了网上搜索到的相关文章,以及java规范中对于java byte code的描述,在这些知识基础上,此步骤完全是体力活儿,这里略过。
这里稍微值得一提的:
a) 对于不能反编译的情况,XJad反编译结果中,给出了原始的java byte code,相当于c/c++程序的汇编码,可以自己分析处理,而jd-gui中给出的则非常不完整,没法使用。
b) 对于一些try/catch异常保护,以及其他循环、跳转等,jd-gui的反编译结果相对来说,比Xjad要好一些。
c) 代码中做的java代码混淆,主要是:把所有代码中的明文字符串,做了简单异或,在运行使用时才异或回来;除个别类外,大部分的包、类、方法、变量等命名,都采用单字符以降低可读性。

5.  跟踪调试,暴破。
a) 随便造一个文本文件,在软件的license文件选择对话框中选中,提示license文件破损。
b) 经过跟踪调试,发现了com\jp\protection\pub\LicenseReader.java中以下两个方法:
  // 验证license文件的入口函数
  public synchronized void a(InputStream inputstream, String s1)
    throws IOException
  {
    int i1;
    i1 = com.jp.protection.pub.m.q;
    g = true;
    b = null;
    Object obj = null;
    byte abyte0[];
    try{
      abyte0 = b(inputstream);//取文件头明文字符串,验证后续数据的CRC32
    }finally{
    inputstream.close();
    }
label0:
    {
      if (abyte0 == null)
      {
        b(this, s1);
        if (i1 == 0)
          break label0;
      }
      // 省掉这一步,自己构造license文件,不加密 ????
      //abyte0 = c(abyte0);  // 解密数据,需要公钥
      if (abyte0 == null)
      {
        b(this, s1);
        if (i1 == 0)
          break label0;
      }
      e(abyte0);
      if (b != null)
      {
        ((LicenseImpl)b).d(s1);
        c(this, s1);
        if (i1 == 0)
          break label0;
      }
      b(this, s1);
    }
    return;
  }

  protected byte[] c(InputStream inputstream)
  {
    byte abyte0[] = null;
    DataInputStream datainputstream = new DataInputStream(inputstream);
    try
    {
      String s1 = datainputstream.readUTF();
      d(this, s1);
      long l1 = datainputstream.readLong();
      int i1 = datainputstream.available();
      abyte0 = new byte[i1];
      int j1 = 0;
      int k1;
      do
      {
        k1 = datainputstream.read(abyte0, j1, i1);
        j1 += k1;
        i1 -= k1;
      } while (k1 != -1 && i1 > 0);
      t t1 = new t();
      t1.a(abyte0);
      // 去掉校验CRC的部分
      /*if (l1 != t1.a() && (inputstream instanceof FileInputStream))
        abyte0 = null;
        */
    }
    catch (IOException ioexception)
    {
      a(ioexception);
      abyte0 = null;
    }
    return abyte0;
  }

其中“//abyte0 = c(abyte0);  // 解密数据,需要公钥”,这里的方法c,内部是调用了RSA算法使用公钥解密,这说明正常license应该是需要私钥加密的,私钥我们没法拿到,所以这里只能把解密跳过。
至于“// 去掉校验CRC的部分”下边的代码,当然也可以保留,不过那需要构造license文件时计算一个正确的CRC32值,不方便手工构造license文件,因此注释掉。
事实上,代码中还有其他位置用到了RSA公钥,用于验证部分class文件的签名,以防止这些文件被篡改,幸运的是,LicenseReader.class没在这个验证文件列表中,因此我这里没有列出那部分代码,也没有考虑对其进行额外处理。

c) 以上步骤,基本上已经完成了暴力破解过程。后续就是构造合法的license文件,通过进一步跟踪上述licence文件解析代码,得到license文件格式如下:
[2字节UTF字符个数] + [UTF字符串] + [8字节后续数据的CRC32值,实际CRC32只用4字节,但是java的long型是8字节,所以这里用的8字节] + [文本内容]
其中[文本内容]都是类似如下的字符串:
 # type 1:Evaluation , 2:Extended Evaluation ,  3:Commercial 
 typ=3
 # Options: 1,2,8(fUserLicensingModel 0,2),64(fUserLicensingModel 3)
 opt=0
 # issue date: Mon May 31 00:00:00 CST 2010
 isd=1283824000000
 # expire date: Thu Mar 15 00:00:00 CST 2255
 exd=9000000000000
 
d) 根据c)中分析得到license文件格式,在UltraEdit中手工编辑构造license文件,进一步调试跟踪代码的其他部分,以便确认在程序运行中都需要用到哪些属性,以及这些属性的合法取值范围都应该是多少,并将其以[属性]=[属性值]的形式补充到license文件中,最终形成合法license文件。

6. 以上都是在调试环境运行。
将编译得到的LicenseReader.class,直接覆盖安装目录Justinmind\Justinmind Prototyper 4.0.1\plugins\com.justinmind.evc_4.0.1.jar中的同名文件,程序可以正常运行,在程序About中可以看到预期的license信息描述。

暴破初步成功。

7. 由于此程序是30天试用版,关闭程序,将系统时间增加2个月,重新启动程序,仍能正常运行,且license信息显示正确。
关闭程序,把系统时间还原,再次启动程序,提示许可证已过期,无法正常运行,说明内部还存在其他验证。
继续进调试环境跟踪,在license文件读取解析后信息的访问位置设置断点,发现程序在C盘的两个目录,以及注册表中,都写入了信息,根据跟踪到的信息,将相关位置数据清空后,再次启动程序,正常启动,license信息显示正常,功能正常使用。
这里对c盘文件、注册表项,只作了简单异或处理,而这里的破解过程,也只是基本的跟踪调试,不再详细说明。
事实上这里也可以修改代码,把这个判断屏蔽掉,由于正常使用时,一般不会减小系统时间,这个用处不大,没有做这个修改。

至此,破解完全成功。

另:由于30天试用版在功能上是没有任何限制的(30试用版没有license文件,是代码内嵌的,只要从未提供过license文件,就当作试用版处理),所以根据上述步骤7,该程序可以完全不做暴破,只是在提示license到期时把C盘2个目录,以及注册表项清空,就可以再有30天的试用期。