开场白就不说了,直接切入正题吧。

本破解的前提是安装了IDEA 5.0并获得了30天的评估序列号,即IDEA可以启动但有30天的时间限制。

首先根据bin目录下idea.bat的内容确定入口类:

SET CLASS_PATH=%IDEA_HOME%\lib\idea.jar
SET CLASS_PATH=%CLASS_PATH%;%IDEA_HOME%\lib\openapi.jar
SET CLASS_PATH=%CLASS_PATH%;%IDEA_HOME%\lib\jdom.jar
SET CLASS_PATH=%CLASS_PATH%;%IDEA_HOME%\lib\log4j.jar
SET CLASS_PATH=%CLASS_PATH%;%IDEA_HOME%\lib\extensions.jar
SET CLASS_PATH=%CLASS_PATH%;%IDEA_JDK%\lib\tools.jar

第一行中的idea.jar即是启动jar包。

用Win RAR打开idea.jar,依次进入com、intellij目录,可发现intellij下包甚多,应该就是IDEA的核心。接下来就是凭运气和“天赋”了,序列号的校验模块就在这些包中,找出来只是时间问题。

最终我把焦点定在com.intellij.licensecommon.license.LicenseDataImpl(LicenseData是个抽象类)。使用DJ Java Decompiler对LicenseDataImpl.class进行反编译,得到的源代码如下:

package com.intellij.licensecommon.license;

import com.intellij.licensecommon.util.*;
import java.util.*;

public class LicenseDataImpl extends AbstractLicenseData{
  static Date makeDate(int i,int j,int k){
    Calendar calendar = GregorianCalendar.getInstance(TimeZone.getTimeZone("Europe/Prague"));
    calendar.clear();
    calendar.set(i,j,k);
    return calendar.getTime();
  }

  public LicenseDataImpl(String s,String s1){
    super(s,s1);
    g = null;
  }

  public LicenseDataImpl(String s,String s1,long l){
    super(s,s1,l);
    g = null;
  }

  public String toString(){
    return getUserName() + ":" + getKey();
  }

  public boolean willNeedUpgrade(){
    a();
    if(g.majorVersion >= 5)
      return false;
    if(g.generationDate == null)
      return true;
    else
      return a(g.generationDate,FREE_UPGRADE_DATE);
  }

  private boolean a(Date date,Date date1){
    long l = date.getTime();
    long l1 = date1.getTime();
    return l < l1 - 0xa4cb80L;
  }

  public boolean needsUpgrade(Date date){
    if(date == null)
      return false;
    if(date.before(DEAD_LINE_DATE))
      return false;
    else
      return willNeedUpgrade();
  }

  public boolean isEvaluationExpired(Date date){
    if(date == null)
      return false;
    if(!willExpire())
      return false;
    else
      //return date.after(g.expirationDate);
      return false;
  }

  public boolean isValid(){
    a();
    //return b();

    return true;
  }

  private void a(){
    if(b())
      return;
    try{
      g = LicenseDecoder.decodeLicenseKey(getUserName(),getKey());
    }
    catch(InvalidLicenseKeyException invalidlicensekeyexception){}
  }

  private boolean b(){
    return g != null;
  }

  public boolean willExpire(){
    return getExpirationDate() != null;
  }

  public Date getExpirationDate(){
    a();
    return g.expirationDate;
  }

  public boolean isNonCommercial(){
    a();
    return g.licenseType == 1;
  }

  public boolean isCommercial(){
    a();
    return g.licenseType == 0;
  }

  public boolean isSite(){
    a();
    return g.licenseType == 2;
  }

  public boolean isOpenSource(){
    a();
    return g.licenseType == 3;
  }

  public boolean isYearAcademic(){
    a();
    return g.licenseType == 5;
  }

  public boolean shouldDetectDuplicates(){
    return!isSite() && !willExpire();
  }

  public Date getUpgradeDeadline(){
    return DEAD_LINE_DATE;
  }

  public boolean isPersonal(){
    a();
    return g.licenseType == 4;
  }

  public Date getGenerationDate(){
    a();
    return g.generationDate;
  }

  public int getMajorVersion(){
    a();
    return g.majorVersion;
  }

  public int getMinorVersion(){
    a();
    return g.minorVersion;
  }

  public static LicenseDataImpl create(String s,String s1){
    return new LicenseDataImpl(s,s1);
  }

  public static LicenseDataImpl createFromUser(String s,String s1){
    LicenseDataImpl licensedataimpl = new LicenseDataImpl(s,s1);
    licensedataimpl.setFromUser(true);
    licensedataimpl.setAccepted(false);
    return licensedataimpl;
  }

  public static LicenseDataImpl create(String s,String s1,long l){
    return new LicenseDataImpl(s,s1,l);
  }

  public static final int CURRENT_MAJOR_VERSION = 5;
  public static final int CURRENT_MINOR_VERSION = 0;
  public static final Date DEAD_LINE_DATE = makeDate(2005,8,1);
  public static final Date FREE_UPGRADE_DATE = makeDate(2005,4,1);
  private static final long f = 0xa4cb80L;
  public static final String IDEA_VERSION = "IntelliJ IDEA 5";
  private LicenseInfo g;
}

顺便说一句,对于Alloy来说,IDEA基本没有做混淆(可能是公共方法太多的原因),因此反编译后源代码的可读性很好,而且反编译后的源代码基本可以正确编译。

见第59行和65行,原先是调用了两个方法进行验证。根据方法名推测isEvaluationExpired()应该是校验评估序列号是否到期,isValid()则是验证授权的合法性,这样只要直接使用boolean常量绕开检查就行了(第60行和67行)。这样修改后重新编译生成新的LicenseDataImpl.class,放回jar包覆盖原先的class文件就行了。

最后是验证。我在我的机器上将时间调到2006年8月,启动IDEA,虽然界面仍显示“Expiration date: September, 1, 2005”,但是IDE依然正常工作,破解成功

补充一句,在验证时需要了解操作系统中的其它软件是否对系统时间更改有保护性措施。像在我的机器上将时间调后1年后,卡巴斯基就过期了,将时间调回来后卡巴斯基被锁死,保持过期状态,最后只能重装。所以大家在调系统时间的时候可要考虑周到啊。