点击数:702
本次破解的版本是: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 文件清单中查看。
// 该类在 t3.common.lic.c 包下
public class j extends f {
private final Preferences bJ;
// 构造器,传递是Edition版本信息
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
。
// 下面两个方法在 t3.utils.a 类中
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
节点。
打印结果如下:
installation-date-2020.7.1 ============== 1598427210
soduz3vqhnnja46uvu3szq-- ============== BoieKNcYUCAwxMTDz1Y8aOyeC1/9sopwcJLxCXtW2H4PpXpUzYSqdZ2bZOaHtvrK
--3mkysnku52awb7yxrapw-- ============== tX1qGc7cjivq06bi2FZ/f1Ck+5RjaB/JA6JnLGmsdbPmc2355jeNLdDQOrhtQJEHi6BKu1ZrhJy7TC8EMT0uy5gHKswjBW5JuWGPgBUR9kMObm/T6jN3F2wAjrHxfmOe998jYsU1+P0I/a/dyLuB/yTItMNCTcD3T/h8vgvneXCeGcZ5NLl4AwsPknWEIavYPyxVK8QBHxMsuZEuNXtUJfTvkLLvTB2krLlkrt4G+1Q=
bnrrjeye6fas6qaqgi-exa-- ============== xlqdI9ex9ft3/MStURk32bMu2FNViZhSP/YBAo17SzAKLA4PbjoCK+3iEZitICdoFSpnObVj0eRw/HsG1pNBBppp6vTcarVIwTIAaQdJ0//H49rzcrJNHXrSKUe8CSROa8XLNfSAD0SfkHif+MBVFTV9QNtwFmmVOtFx6Q6a26baWyVSeT8HFfhjr+euPudGPE2isas+3Rm71lqaHoNnlw==
我们跟踪入口类t3.dataman.mongodb.app.Studio3TApp
,我们就会看到下面的方法:
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方法:
// ME是静态方法
public class a {
public static boolean ME() {
return MC().getStatus().ai();
}
// 。。。其他方法
}
发现返回的是 ai
方法的值,继续跟踪该方法:
public boolean ai() {
return (this.J == e.ACTIVE);
}
// e 是枚举
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的代码贴出来。
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`类的代码:
package t3.common.lic;
import com.google.common.base.Preconditions;
public class d {
// 枚举类,存储当前状态
private final e J;
// 提示信息
private final String K;
// 构造函数
// parame 参数为枚举类型,软件状态
// paramString 参数为提示信息
private d(e parame, String paramString) {
Preconditions.checkNotNull(paramString);
this.J = parame;
this.K = paramString;
}
// 返回当前J中保存的状态,枚举类型,getter方法
public e Z() {
return this.J;
}
// 静态方法m,创建d对象,参数为提示信息,默认为激活状态
public static d m(String paramString) {
return new d(e.ACTIVE, paramString);
}
// 创建d对象,设置license 过期状态
public static d n(String paramString) {
return new d(e.LICENSE_EXPIRED, paramString);
}
// 用户token过期
public static d aa() {
return new d(e.USAGE_TOKEN_EXPIRED, "Please go online");
}
// license限制使用
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);
}
// token是否过期
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("已激活");
则激活软件。另外一种,就是修改上面函数的返回值。
package t3.common.lic;
// 删除google代码对空字符串的校验,没必要
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 (this.J == e.LICENSE_EXPIRED);
return false;
}
// 永不过期
public boolean af() {
// return (this.J == e.USAGE_TOKEN_EXPIRED);
return false;
}
public boolean ag() {
return (this.J == e.MACHINE_LIMIT_REACHED);
}
// 一直是指定位置的
public boolean ah() {
// return (this.J == e.NO_SEAT);
return false;
}
// 一直是激活的
public boolean ai() {
// return (this.J == e.ACTIVE);
return true;
}
}
将上面代码保存在目录为/Users/xxx/t3/common/lic/d.java
中,然后对其编译。
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。具体错误如下:
错误的类文件: /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窗口中设置环境变量:
# 注意替换为自己安装的路径
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中。
jar uf /Applications/Studio\ 3T.app/Contents/Resources/app/data-man-mongodb-ent-2020.7.1.jar t3/common/lic/d.class
至此,破解结束,启动Studio 3T发现已经没有限制了~