Crack PDF Studio

Table of Contents

1 背景

PDF Studio是非常好用的跨平台PDF编辑软件,但它不是开源的,且要注册。不注册软件,保存时会有水印:"PDF Studio - PDF Editor for Mac, Windows, Linux. For Evaluation. http://www.qoppa.com/pdfstudio"

本文提供破解PDF Studio 7.4.0的思路。

特别说明:本文仅供技术交流,本人反对盗版软件。请支持正版软件。

2 破解思路和步骤

初步想法:修改软件注册的相关代码,以绕过注册。

2.1 用到的工具

工具1.
JD-GUI
官方网站:http://java.decompiler.free.fr/
用来反编译Java代码,直接得到源码(得到的源码无法直接编译为jar包,因为工程大,有很多信赖问题,这里仅仅使用jd-gui来查看源码,分析软件内部逻辑)。
JD-GUI是目前为止,我见过最好的Java反编译工具,C++实现,小巧精致,非常实用!但有两个缺点:一是不开源,二是没有提供命令行接口,这样无法胜任批量工作。
除JD-GUI外,网站还提供了另一产品:JD-Eclipse,它是Eclipse插件,功能类似,由于JD-GUI很好满足我的要求,这个就没有尝试了。
(更新: 好消息!JD-GUI已经开源 https://github.com/java-decompiler/jd-gui ,且改为Java实现。)

工具2.
JBE - Java Bytecode Editor
下载网站:http://set.ee/jbe/
用JBE来直接修改class文件。

2.2 破解7.4.0

下面以PDF Studio 7.4.0为样本进行尝试破解。

第1步,安装软件,找到入手的jar包。
安装后,在安装目录的lib子目录中发现有很多第三方jar包,但pdfstudio.jar引起了我的注意,通过文件名猜测它是核心模块。

第2步,尝试寻找关键类(也就是最可能和破解相关的类)。
随便输入1个license key,发现提示错误"The key you have entered is invalid, please try again."
解压jar文件,unzip pdfstudio.jar,会得到很多class文件。
用不同的关键字进行尝试搜索:

find . |xargs grep "invalid"
find . |xargs grep "PDF Studio - PDF Editor for Mac, Windows, Linux. For Evaluation. http://www.qoppa.com/pdfstudio"

第一个搜索结果太多,第二个搜索匹配到两个文件:
Binary file ./com/qoppa/p/i/m.class matches
Binary file ./com/qoppa/b/h.class matches
重点关注这两个文件。

第3步,用jd-gui查看源码,定位相关Java类。
用jd-gui打开pdfstudio.jar后,可以发现源码的变量名,类名等基本不可读,因为PDF Studio采用yGuard Bytecode Obfuscator混淆器处理过。
定位到类./com/qoppa/p/i/m.class
下面方法中出现了"PDF Studio - PDF Editor for Mac, Windows, Linux. For Evaluation. http://www.qoppa.com/pdfstudio"

protected void b(Vector paramVector)
  {
    double d1 = this.b.getWidth() / 612.0D;
    double d2 = this.b.getHeight() / 792.0D;
    double d3 = 72.0D * Math.max(d1, d2);
    com.qoppa.p.c.d locald = e.c.b((float)d3);
    Rectangle2D localRectangle2D = locald.getStringBounds("Qoppa Software", null);
    double d4 = Math.sqrt(localRectangle2D.getWidth() * localRectangle2D.getWidth() / 2.0D) + Math.sqrt(localRectangle2D.getHeight() * localRectangle2D.getHeight() / 2.0D);
    double d5 = (this.b.getWidth() - d4) / 2.0D + Math.sqrt(localRectangle2D.getHeight() * localRectangle2D.getHeight() / 2.0D) - 10.0D * d1 + this.b.getX();
    double d6 = (this.b.getHeight() - d4) / 2.0D + 10.0D * d2 + this.b.getY();
    Color localColor = new Color(0.85F, 0.85F, 0.85F);
    com.qoppa.pdf.l.c.z localz = new com.qoppa.pdf.l.c.z("Qoppa Software", d5, d6, l.b((float)d3), localColor);
    localz.c(-45);
    paramVector.add(localz);
    d3 = 12.0D * Math.max(d1, d2);
    locald = e.c.b((float)d3);
    localRectangle2D = locald.getStringBounds("PDF Studio - PDF Editor for Mac, Windows, Linux. For Evaluation. http://www.qoppa.com/pdfstudio", null);
    d5 = (this.b.getWidth() - localRectangle2D.getWidth()) / 2.0D + this.b.getX();
    d6 = this.b.getHeight() - 20.0D * d2 + this.b.getY();
    localz = new com.qoppa.pdf.l.c.z("PDF Studio - PDF Editor for Mac, Windows, Linux. For Evaluation. http://www.qoppa.com/pdfstudio", d5, d6, l.b((float)d3), new Color(0.0F, 0.17F, 0.65F));
    paramVector.add(localz);
  }

这个类看上去应该是加水印相关的类,在当前文件(./com/qoppa/p/i/m.class)中有个t()方法对b(Vector paramVector)进行了调用,相关代码如下:

if (com.qoppa.w.b.e())
    {
      localVector = locald.h();
      localVector.insertElementAt(new wb(), 0);
      localVector.add(new kb());
      b(localVector);//调用了b()
    }

if的条件非常关键(大胆猜测含义“只有当com.qoppa.w.b.e()返回true时才加水印”),用jd-gui定位查看com.qoppa.w.b.e():

public static boolean e()
  {
    if (m != 0)
      return (e != 0) && (e < Runtime.getRuntime().availableProcessors());
    if (!b)
      return !c();
    return true;
  }

这个方法比较简单。 大胆猜测:如果让com.qoppa.w.b.e()返回false,前面的水印可能就不加。

第4步,解压pdfstudio.jar,用JBE打开相应的class文件,修改com.qoppa.w.b.e()的bytecode。
删除e()对应的bytecode,写入下面两行(return false的bytecode)

iconst_0
ireturn

第5步,重新打包测试。

zip -r pdfstudio.jar *

用这个破解的pdfstudio.jar替换原版(一定要删除原版pdfstudio.jar,不能重名为pdfstudio1.jar等,因为加载java类并不是通过文件名),启动PDF Studio,提示注册框消失,编辑文件,不再有水印!已经成功破解!

2.3 破解8/9/10

在尝试破解PDF Studio 8.0.3时,本想按照7.4.0的破解经验一步一步破解,尝试时发现无法进行下去!只能取巧了——查找破解7.4.0时的关键字。

第1步:用jd-gui-0.3.5反编译得到pdfstudio.jar的源代码。
用jd-gui-0.3.5打开pdfstudio.jar后,File->Sava All Sources得到源码文件pdfstudio.src.zip。
说明:得到源代码的目的不是为了修改源码,而是为了在源码中查找关键字(在源码中查找关键字比在class文件中查找肯定信息量更大)。

第2步:解压得到的源码,并查找关键字availableProcessors(为什么要查找availableProcessors这个关键字,请参考7.4.0的破解过程)。

unzip pdfstudio.src.zip
find . -type f|xargs grep -A3 -B3 -n "availableProcessors"

可以找到好几个结果,但有一个结果和破解7.4.0时的“特征代码”一致!

./com/qoppa/v/f.java-57-  public static boolean e()
./com/qoppa/v/f.java-58-  {
./com/qoppa/v/f.java-59-    if (m != 0)
./com/qoppa/v/f.java:60:      return (e != 0) && (e < Runtime.getRuntime().availableProcessors());
./com/qoppa/v/f.java-61-    if (!b)
./com/qoppa/v/f.java-62-      return !c();
./com/qoppa/v/f.java-63-    return true;

第3步(该步骤和后面的步骤和7.4.0类似了):用JBE修改上一步中找到函数(com.qoppa.v.f.e())的bytecode。

第4步:重新打包,替换原版pdfstudio.jar

说明1:PDF Studio 8.0.3破解成功的关键完全来自破解7.4.0的经验!更高版本PDF Studio的破解过程和破解8.0.3类似。
说明2:使用类似的思路可以破解PDF Studio 9和PDF Studio 10(PDF Studio 11未测试)。

2.4 破解12.0.6+

尝试破解PDF Studio 12.0.6时,发现前面的思路行不通了。

下面介绍另外一种思路:去除打开文件时显示的水印以及保存文件时的水印。

第1步,用jd-gui反编译得到pdfstudio.jar的源代码。在源码中查找水印相关关键字,如:

$ find com -type f | xargs  grep "PDF Studio - PDF Editor for Mac, Windows, Linux."
com/qoppa/gb/k/o.java:      o = "PDF Studio - PDF Editor for Mac, Windows, Linux. For Evaluation. https://www.qoppa.com/pdfstudio";
com/qoppa/z/d/l.java:      d = "PDF Studio - PDF Editor for Mac, Windows, Linux. For Evaluation. https://www.qoppa.com/pdfstudio";

找到了两个结果(字段o和字段d)。通过查看源码可知,一共有下面三个方法引用了字段o和字段d(注:第3个引用处比较难发现,仔细分析可发现类com.qoppa.z.h继承于第2处com.qoppa.gb.k.o,而类com.qoppa.z.h中的方法dg直接使用了字段o):

com/qoppa/z/d/l.java
public void b(com.qoppa.z.h paramh)

com/qoppa/gb/k/o.java
private final void eb(Vector<com.qoppa.pdf.o.d.n> paramVector)

com/qoppa/z/h.java
final void dg(com.qoppa.pdf.s.r paramr)

第2步,使用JBE删除上面三个函数(它们的返回值都是void)的所有字节码,写入下面这行(也就是return的bytecode):

return

到现在为止,PDF Studio已经不会显示和添加水印了。但有下面两点不足:
1、打开pdf文件时,会弹出一个提示注册的对话框,如图 1 所示。
2、修改文件后,点击“Save”按钮时,会弹出对话框,提示“In evaluation mode, a watermark is added to each page\nwhen saving a document. Are you sure you want to continue?”。
第1点比较恼火,因为每次打开文件都要多点击一下来关闭对话框。下面将介绍去除打开pdf文件时的弹出对话框。

crack_pdfstudio.png

Figure 1: 打开文件时显示的对话框

第3步(去除打开pdf文件时的弹出对话框),当对话框出现时,不关闭它,找到PDF Studio进程号,使用 jstack 进行分析,看是否能找到相关线索,如:

$ jstack 1530 | grep -A5 -B5 com.qoppa           # 注:你的PDF Studio进程号很可能不是1530
	at java.awt.Dialog.show(Dialog.java:1084)
	at java.awt.Component.show(Component.java:1671)
	at java.awt.Component.setVisible(Component.java:1623)
	at java.awt.Window.setVisible(Window.java:1014)
	at java.awt.Dialog.setVisible(Dialog.java:1005)
	at com.qoppa.pdfStudio.l.nd.lee(Unknown Source)
	at com.qoppa.pdfStudio.l.th$7.windowOpened(Unknown Source)
	at java.awt.AWTEventMulticaster.windowOpened(AWTEventMulticaster.java:340)
	at java.awt.AWTEventMulticaster.windowOpened(AWTEventMulticaster.java:339)
	at java.awt.Window.processWindowEvent(Window.java:2051)
	at javax.swing.JFrame.processWindowEvent(JFrame.java:305)
	at java.awt.Window.processEvent(Window.java:2013)

通过这个stack信息,大胆猜测打开pdf文件时的弹出对话框由com.qoppa.pdfStudio.l.nd.lee/com.qoppa.pdfStudio.l.th启动。

分析“com/qoppa/pdfStudio/l/th.java”,发现有下面代码:

if (th.hrb())
{
  if ((th.dsb(th.this).ri() != null) && (th.dsb(th.this).mj() != null))
  {
    localObject = new yc(th.this, localObject.myd(), th.dsb(th.this).mj());
    ((yc)localObject).zde();
    if (th.hrb())
    {
      nd localnd = new nd(th.this, localObject);
      int j = localnd.lee();
      if (j != nd.qed) {
        System.exit(0);
      }
    }
  }
  else
  {
    localObject = new nd(th.this, localObject);
    int i = ((nd)localObject).lee();
    if (i != nd.qed) {
      System.exit(0);
    }
  }
  localObject.yyd();
  th.this.hob();
}
else
{
  new k(th.dsb(th.this), th.oqb(th.this), localObject.bvc);
}

可得到结论:只有当方法hrb()返回true时才会执行com.qoppa.pdfStudio.l.nd.lee()。

为了不执行com.qoppa.pdfStudio.l.nd.lee(),我们用工具JBE修改类com.qoppa.pdfStudio.l.th中函数hrb()的bytecode为(即return false;对应的bytecode):

iconst_0
ireturn

第4步:重新打包class文件为pdfstudio.jar,替换原版pdfstudio.jar。

测试发现图 1 所示的对话框不再出现。不过,默认使用的是“PDF Studio Professional”版本,去除了对话框,也没有机会点击“Try PDF Studio Professional”了,如果你只是使用一些基本功能,可以忽略这个限制。如果你想用“PDF Studio Professional”版本,请参考下节内容。

现在剩下的不足是修改文件后,点击“Save”按钮后会弹出对话框提示增加水印(不影响使用,因为它并不会增加水印,本文不介绍如何去除这个对话框)。

2.4.1 升级为Pro版

经过前面步骤破解后,你得到的是“PDF Studio Standard”版本,如果你想要“PDF Studio Professional”版本中的功能(比如编辑pdf中的文件和图片),请看接下来的步骤。

我们知道未破解时,打开pdf文件时会弹出一个提示注册的对话框,如图 1 所示。我们从字符串“Try PDF Studio Professional”入手。由于PDF Studio支持多种语言,它并没有直接在java中使用,而是保存在一个properties文件中:

$ grep -r 'Try PDF Studio Professional' .
./java/app/.install4j/i4j_extf_1_1qhiycz.properties:TryPDFStudioPro=Try PDF Studio Professional
./java/app/labels/StudioLabels.properties:TryPDFStudioPro=Try PDF Studio Professional
./Resources/app/.install4j/i4j_extf_1_1qhiycz.properties:TryPDFStudioPro=Try PDF Studio Professional
./Resources/app/labels/StudioLabels.properties:TryPDFStudioPro=Try PDF Studio Professional

下一步,搜索关键字“TryPDFStudioPro”:

$ grep -rwn 'TryPDFStudioPro' .
./com/qoppa/pdfStudio/l/nd.java:17:  private static String zed = "TryPDFStudioPro";
./com/qoppa/pdfStudio/l/yf.java:59:      this.g = new JButton(nb.b.b("TryPDFStudioPro"));

我们把重点关注类“com.qoppa.pdfStudio.l.nd”的字段zed,它在下面方法中被引用:

  public void actionPerformed(ActionEvent paramActionEvent)
  {
    if (paramActionEvent.getActionCommand() == zed) {       // 这里使用了字段zed
      iee(true);
    } else if (paramActionEvent.getActionCommand() == sed) {
      iee(false);
    } else if (paramActionEvent.getActionCommand() == ted) {
      jee();
    } else if (paramActionEvent.getActionCommand() == ved) {
      try
      {
        fc.b(wed);
      }
      catch (Exception localException)
      {
        oc.en(this.xed, "Opening URL", localException.getMessage(), localException);
      }
    } else if (paramActionEvent.getActionCommand() == red) {
      kee();
    } else if (paramActionEvent.getActionCommand() == ued) {
      mee();
    }
  }

从上面代码中,可以推测:用户点击了按钮“Try PDF Studio Professional”后,会执行 iee(true); (而不是 iee(false); ),这个方法的实现如下:

  public void iee(boolean paramBoolean)
  {
    this.ped.dnb().xm(paramBoolean);
    afd = qed;
    this.xed.dispose();
  }

iee方法的参数传给了一个名为xm的方法。我们搜索参数类型为boolean且名为xm的方法:

$ fgrep -rwn 'xm(boolean' .
./com/qoppa/pdfStudio/p/f.java:509:  public void xm(boolean paramBoolean)

类“com.qoppa.pdfStudio.p.f”中方法xm(boolean paramBoolean)的定义如下:

509:   public void xm(boolean paramBoolean)
510:   {
511:     if (th.hrb())
512:     {
513:       this.bl = paramBoolean;
514:       if (paramBoolean) {
515:         tb.q("Professional Version");
516:       }
517:     }
518:   }

方法xm的参数paramBoolean设置给了 this.bl ,接下来搜索字段bl的引用处:

$ grep -wn bl ./com/qoppa/pdfStudio/p/f.java
55:  private boolean bl = false;
491:    return this.bl;
496:    this.bl = ((com.qoppa.p.c.u() & 0x4) == 4);
513:      this.bl = paramBoolean;
865:      bl((com.qoppa.g.d)localObject1);
945:  public void bl(com.qoppa.g.d paramd)

重点关注 return this.bl; ,这是上面结果中唯一读取字段bl值的地方。491行被下面方法使用:

489:   public boolean xi()
490:   {
491:     return this.bl;
492:   }

我们大胆猜测,如果修改上面方法内容为 return true; 则可以设置为“PDF Studio Professional”版本。

用工具JBE修改类com.qoppa.pdfStudio.p.f中函数xi()的bytecode为(即return true;对应的bytecode):

iconst_1
ireturn

重新打包class文件为pdfstudio.jar,替换原版pdfstudio.jar。测试发现现在已经显示为“PDF Studio Professional”版本,破解成功。


Author: cig01

Created: <2012-07-08 Sun 00:00>

Last updated: <2018-03-07 Wed 22:22>

Creator: Emacs 25.3.1 (Org mode 9.1.4)