本次破解的版本是:Studio 3T for MongoDB 2020.7.1
Studio 3T 是 MongoDB 非官方开源客户端 Robo 3T的高级版。Studio 3T 集成了 MongoDB的开发环境,拥有全面的功能界面和客户端功能,以及便捷、快速的特点,适用于Windows, Mac, 和Linux操作系统。
Studio 3T 新安装则会免费试用30天,之后就需要收费才能试用。网上给出的大多数教程是的windows系统下的删除注册表延长试用。
经过常看反编译的代码发现,Studio 3T 会在安装的过程中使用用户树根节点,将配置信息以k-v键值对的形式写在/3t/mongochef节点下面。
小提示:反编译查看入口类,也就是main方法,需要在 META-INF/MANIFEST.MF 文件清单中查看。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class j extends f { private final Preferences bJ; public j (af paramaf) { super ((paramaf == af.ENTERPRISE)); String str; switch (k.aa[paramaf.ordinal()]) { case 1 : str = "core" ; break ; case 2 : str = "pro" ; break ; case 3 : str = "enterprise" ; break ; default : throw new IllegalArgumentException ("Edition is unknown." ); } this .bJ = Preferences.userRoot().node("/3t/mongochef/" + str); } }
这里不得不说说java.util.prefs.Preferences
类,该类是在JDK1.4中首次提供的,可以用其保存应用程序的配置数据。JDK中的 Preferences 类也提供了一个让 Java 应用程序存放配置信息的统一方法,即每一个基于Java的应用程序都可以使用Preferences类存放自己的配置信息。
Preferences 类是平台无关的,也就是在 Windows 平台上运行的Java应用程序可以用Preferences类存放配置信息,在Linux平台上运行的Java应用程序也可以使用Preferences类来存放自己的配置信息,对应用程序来说,它只管用Preferences类就好了, 不用管最终的配置信息在程序运行平台上的具体存放位置。
Preferences 类将应用程序的配置信息存放在具体的操作系统平台上,具体来说,在Windows操作系统下存放在注册表中,在*nux
平台(Linux、Mac等)下是放在使用应用程序的用户的home目录下面的一个隐藏文件中。
Preferences 类用树状结构来存放应用程序的配置信息,树中的每个节点的路径名是/com/xxx/yyy
这种形式的,每个节点上都存放了一个键值对组成的表。每个应用程序可以在属于自己的节点上存放自己的配置信息,这些配置信息就构成了一个表,这个表就是节点的内容。
Preferences类有两个树,分别是系统树(它用来存放全部用户的共有信息)和用户树(它用来存放用户自己的配置信息)。在Windows操作系统下,系统树的根节点是HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Prefs,而用户树的根节点是HKEY_CURRENT_USER\software\JavaSoft\Prefs。
说完 Preferences 类,看看上面代码 this.bJ = Preferences.userRoot().node("/3t/mongochef/" + str);
,这段代码是说,获取用户树的根节点,并且为应用程序在用户树中建立一个/3t/mongochef/
+版本的节点或者获取已有的节点(如果已经存在),并且该节点的引用赋值到 this.bJ
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public static Instant MK () { try { Long long_ = Long.valueOf(Long.parseLong((new j (af.ENTERPRISE)) .v("installation-date-" + aD().Xw()))); return Instant.ofEpochSecond(long_.longValue()); } catch (Exception exception) { return Instant.now(); } } public static void ML () { try { (new j (af.ENTERPRISE)).c("installation-date-" + aD().Xw(), Long.toString(MK().toEpochMilli() / 1000L )); } catch (IOException iOException) { Logger.error(iOException, "Could not set installation date." ); } }
仔细看,这两个方法中用到了 j
这个类, 该类就是上面分析的类 t3.common.lic.c.j
, 那么根据上面函数我们可以看出,在程序首次启动的时候会将一个 key 以 installation-date-
+ 日期开头,value为秒的时间戳写入到用户树根目录/3t/mongochef/
+版本节点中,如果是企业版则为/3t/mongochef/enterprise
节点。我们通过写一段代码读取保存在/3t/mongochef/enterprise节点树中的key-value,已验证我们的分析。
我们跟踪入口类t3.dataman.mongodb.app.Studio3TApp
,我们就会看到下面的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 private static void zQ () { Display display = new Display (); try { if (!(new u ()).bM()) System.exit(-1 ); a.MJ(); if (a.an() && !a.aq()) { a.D(); } else if (a.ao() && !a.aq()) { c c1 = a.MD(); c c2 = a.MF(); d d = new d (a.aD(), c1.W(), (c1.R() || c1.isNone()), c2 instanceof t3.common.lic.a.a); if (d.dd()) a.E(); } else { a.aHG().j(a.aD()); a.aHG().Sq(); } if (!a.ME()) { b.aL().a(10000 ); } else { b.aL().a(4000 ); } a.MJ(); if (!a.ME()) { r.bjx.i(display); (new l (display.getActiveShell())).open(); l.a(display.getActiveShell(), true ); } a.MJ(); if (!a.ME()) { Logger.error("No active license, exiting." ); System.exit(-1 ); } } catch (Exception exception) { Logger.error(exception, "Unexpected exception while running setup wizard." ); System.exit(-1 ); } finally { display.dispose(); } }
如果没有这行日志No active license, exiting.
,恐怕不会那么容易找到。既然日志给出了提示,那么校验是否激活的函数入口便由此可找到。继续跟踪a.ME方法:
1 2 3 4 5 6 7 public class a { public static boolean ME () { return MC().getStatus().ai(); } }
发现返回的是 ai
方法的值,继续跟踪该方法:
1 2 3 4 5 6 7 8 9 public boolean ai () { return (this .J == e.ACTIVE); } public enum e { ACTIVE, LICENSE_EXPIRED, USAGE_TOKEN_EXPIRED, MACHINE_LIMIT_REACHED, NO_SEAT, USAGE_DENIED_UNKNOWN_REASON; }
e 是枚举类,由此可知,J 是枚举,MC() 是 t3.common.lic.p
,p也是枚举,不过p 实现了接口 c,从代码中发现p 为枚举的目的是实现单例模式。getStatus
返回的是t3.common.lic.d
的实例。为了看着方便,也罢p的代码贴出来。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 package t3.common.lic;import java.util.EnumSet;import t3.utils.af;public enum p implements c { INSTANCE; private boolean isTampered; public String getName () { return this .isTampered ? "NONE-TAMPERED" : "NONE" ; } public Object accept (n paramn) { return paramn.b(this ); } public EnumSet getEditions () { return EnumSet.noneOf(af.class); } public boolean isNone () { return true ; } public d getStatus () { return d.n("You have no license installed yet." ); } public void setTampered (boolean paramBoolean) { this .isTampered = paramBoolean; } public boolean isTampered () { return this .isTampered; } }
回头我们看看``t3.common.lic.d`类的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 package t3.common.lic;import com.google.common.base.Preconditions;public class d { private final e J; private final String K; private d (e parame, String paramString) { Preconditions.checkNotNull(paramString); this .J = parame; this .K = paramString; } public e Z () { return this .J; } public static d m (String paramString) { return new d (e.ACTIVE, paramString); } public static d n (String paramString) { return new d (e.LICENSE_EXPIRED, paramString); } public static d aa () { return new d (e.USAGE_TOKEN_EXPIRED, "Please go online" ); } public static d ab () { return new d (e.MACHINE_LIMIT_REACHED, "License is being used in too many computers" ); } public static d ac () { return new d (e.NO_SEAT, "No seat assigned" ); } public static d ad () { return new d (e.USAGE_DENIED_UNKNOWN_REASON, "Update required" ); } public String getTitle () { return this .K; } public boolean ae () { return (this .J == e.LICENSE_EXPIRED); } public boolean af () { return (this .J == e.USAGE_TOKEN_EXPIRED); } public boolean ag () { return (this .J == e.MACHINE_LIMIT_REACHED); } public boolean ah () { return (this .J == e.NO_SEAT); } public boolean ai () { return (this .J == e.ACTIVE); } }
看到这个类想必大家知道怎么破解了吧,把不该返回的信息按照你的想法设置即可。两种思路,第一种,在入口函数中,增加对d的调用,例如 d.m("已激活");
则激活软件。另外一种,就是修改上面函数的返回值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 package t3.common.lic;public class d { private final e J; private final String K; private d (e parame, String paramString) { this .J = parame; this .K = paramString; } public e Z () { return this .J; } public static d m (String paramString) { return new d (e.ACTIVE, paramString); } public static d n (String paramString) { return new d (e.LICENSE_EXPIRED, paramString); } public static d aa () { return new d (e.USAGE_TOKEN_EXPIRED, "Please go online" ); } public static d ab () { return new d (e.MACHINE_LIMIT_REACHED, "License is being used in too many computers" ); } public static d ac () { return new d (e.NO_SEAT, "No seat assigned" ); } public static d ad () { return new d (e.USAGE_DENIED_UNKNOWN_REASON, "Update required" ); } public String getTitle () { return this .K; } public boolean ae () { return false ; } public boolean af () { return false ; } public boolean ag () { return (this .J == e.MACHINE_LIMIT_REACHED); } public boolean ah () { return false ; } public boolean ai () { return true ; } }
将上面代码保存在目录为/Users/xxx/t3/common/lic/d.java
中,然后对其编译。
1 javac -classpath /Applications/Studio\ 3T.app/Contents/Resources/app/data-man-mongodb-ent-2020.7.1.jar /Users/xxx/t3/common/lic/d.java -d .
编译时发现需要jdk11。具体错误如下:
1 2 3 错误的类文件: /Applications/Studio 3T.app/Contents/Resources/app/data-man-mongodb-ent-2020.7.1.jar (t3/common/lic/e.class) 类文件具有错误的版本 55.0, 应为 52.0 请删除该文件或确保该文件位于正确的类路径子目录中。
下载并安装jdk11, 下载页面 https://www.oracle.com/java/technologies/javase-jdk11-downloads.html,选择 jdk-11.0.8_osx-x64_bin.tar.gz ,用 tar.gz 解压即可使用,因为我们开发的jdk版本是8,没必要替换原来的。
下载jdk解压后放在合适的目录,直接在当前shell窗口中设置环境变量:
1 2 export JAVA_HOME=/Users/xxx/Documents/software/jdk-11.0.8.jdk/Contents/Home
再次编译,发现已经成功,在/Users/xxx下生成了d.class文件,执行打包命令,将生成的 d.class 文件重新打包到data-man-mongodb-ent-2020.7.1.jar中。
1 jar uf /Applications/Studio\ 3T.app/Contents/Resources/app/data-man-mongodb-ent-2020.7 .1 .jar t3/common/lic/d.class
至此,破解结束,启动Studio 3T发现已经没有限制了~