mac App 破解之路六 studio 3t
不想无限使用,直接破解到正版:
输入邮箱 名字之后 还有licence信息之后,处理函数是:
this.text.getText() 很明显是你输入的licence. 然后交给父类okPress处理了. licence字符串存放的变量是 this.r. 获取这个变量的方法是 am()
查看调用licence信息的地方:
t3.common.lic.a.d.bb(). : 把用户输入用this.aT.a()进行处理. aT对象的类名是:t3.utils.a
展示licence信息的地方: t3.common.lic.j.a():
licence信息存储组织方法 t3.common.lic.a.j.a():。 猜测xVar应该是服务器传过来的.
核心类: 对用户输入的licence进行处理
分析:
String x = RegisteredLicenseDocumentFormatter.m886x(str);
License f = this.f711aX.mo903f(x);
if (!f.getStatus().isActive()) {
this.f710aW.mo720O(f.getStatus().getDescription());
return;
}
随便输入licence信息得到如下信息. .f710aW.mo720O 是显示错误的对话框.
Your license key seems to be corrupted. Make sure you have copied all text between and including the --- markers at the start and end of the license key.
这个信息的显示意味着走了异常的代码, 这个异常处有一个打印具体的信息. 等会看这个异常原因. 因为不知道是哪一步异常的.
先找到输出日志的文件:
错误详细:
从错误信息中可以看到 是 执行License f = this.f711aX.mo903f(x); 这个语句就异常了. [ at t3.common.lic.a.i.a(ManagerController.java:61)]
.method private z(Ljava/lang/String;)Lt3/common/lic/h; .registers 9 .prologue const/4 v1, 0x0 const/4 v3, 0x1 const/4 v4, 0x0 .line 154 .line 156 :try_start_3 const-string v0, "X.509" invoke-static {v0}, Ljava/security/cert/CertificateFactory;->getInstance(Ljava/lang/String;)Ljava/security/cert/CertificateFactory; move-result-object v0 .line 157 const-class v2, Lt3/common/lic/ae; const-string v5, "/t3/common/lic/licensing_public.cer" invoke-virtual {v2, v5}, Ljava/lang/Class;->getResourceAsStream(Ljava/lang/String;)Ljava/io/InputStream; :try_end_10 .catch Ljava/security/cert/CertificateException; {:try_start_3 .. :try_end_10} :catch_49 move-result-object v2 .line 160 if-eqz v2, :cond_aa .line 163 :try_start_13 invoke-virtual {v0, v2}, Ljava/security/cert/CertificateFactory;->generateCertificate(Ljava/io/InputStream;)Ljava/security/cert/Certificate; move-result-object v0 check-cast v0, Ljava/security/cert/X509Certificate; :try_end_19 .catchall {:try_start_13 .. :try_end_19} :catchall_63 .line 167 :try_start_19 invoke-virtual {v2}, Ljava/io/InputStream;->close()V :try_end_1c .catch Ljava/io/IOException; {:try_start_19 .. :try_end_1c} :catch_59 .catch Ljava/security/cert/CertificateException; {:try_start_19 .. :try_end_1c} :catch_49 .line 177 :goto_1c :try_start_1c const-string v2, "SHA-1" invoke-static {v2}, Ljava/security/MessageDigest;->getInstance(Ljava/lang/String;)Ljava/security/MessageDigest; move-result-object v2 .line 178 invoke-virtual {v0}, Ljava/security/cert/X509Certificate;->getEncoded()[B move-result-object v5 invoke-virtual {v2, v5}, Ljava/security/MessageDigest;->update([B)V .line 179 invoke-virtual {v2}, Ljava/security/MessageDigest;->digest()[B :try_end_2c .catch Ljava/security/cert/CertificateEncodingException; {:try_start_1c .. :try_end_2c} :catch_72 .catch Ljava/security/NoSuchAlgorithmException; {:try_start_1c .. :try_end_2c} :catch_a8 .catch Ljava/security/cert/CertificateException; {:try_start_1c .. :try_end_2c} :catch_49 move-result-object v1 .line 185 :goto_2d if-nez v1, :cond_7c move v2, v3 :goto_30 :try_start_30 sget-object v5, Lt3/common/lic/ae;->am:[B invoke-static {v1, v5}, Ljava/util/Arrays;->equals([B[B)Z move-result v1 if-nez v1, :cond_7e move v1, v3 :goto_39 or-int/2addr v1, v2 if-eqz v1, :cond_80 .line 186 const-string v0, "Failed to verify the integrity of the certificate." invoke-static {v0}, Lorg/pmw/tinylog/Logger;->error(Ljava/lang/String;)V .line 187 new-instance v0, Ljava/lang/SecurityException; const-string v1, "License verification has failed." invoke-direct {v0, v1}, Ljava/lang/SecurityException;-><init>(Ljava/lang/String;)V throw v0 :try_end_49 .catch Ljava/security/cert/CertificateException; {:try_start_30 .. :try_end_49} :catch_49 .line 193 :catch_49 move-exception v0 .line 194 const-string v1, "Failed to access the 3t certificate." new-array v2, v4, [Ljava/lang/Object; invoke-static {v0, v1, v2}, Lorg/pmw/tinylog/Logger;->error(Ljava/lang/Throwable;Ljava/lang/String;[Ljava/lang/Object;)V .line 195 new-instance v0, Ljava/lang/SecurityException; const-string v1, "License verification has failed." invoke-direct {v0, v1}, Ljava/lang/SecurityException;-><init>(Ljava/lang/String;)V throw v0 .line 169 :catch_59 move-exception v2 .line 170 :try_start_5a const-string v5, "Failed to find or load 3t certificate." const/4 v6, 0x0 new-array v6, v6, [Ljava/lang/Object; invoke-static {v2, v5, v6}, Lorg/pmw/tinylog/Logger;->error(Ljava/lang/Throwable;Ljava/lang/String;[Ljava/lang/Object;)V :try_end_62 .catch Ljava/security/cert/CertificateException; {:try_start_5a .. :try_end_62} :catch_49 goto :goto_1c .line 166 :catchall_63 move-exception v0 .line 167 :try_start_64 invoke-virtual {v2}, Ljava/io/InputStream;->close()V :try_end_67 .catch Ljava/io/IOException; {:try_start_64 .. :try_end_67} :catch_68 .catch Ljava/security/cert/CertificateException; {:try_start_64 .. :try_end_67} :catch_49 .line 172 :goto_67 :try_start_67 throw v0 .line 169 :catch_68 move-exception v1 .line 170 const-string v2, "Failed to find or load 3t certificate." const/4 v3, 0x0 new-array v3, v3, [Ljava/lang/Object; invoke-static {v1, v2, v3}, Lorg/pmw/tinylog/Logger;->error(Ljava/lang/Throwable;Ljava/lang/String;[Ljava/lang/Object;)V goto :goto_67 .line 181 :catch_72 move-exception v2 .line 182 :goto_73 const-string v5, "Failed to hash the certificate." const/4 v6, 0x0 new-array v6, v6, [Ljava/lang/Object; invoke-static {v2, v5, v6}, Lorg/pmw/tinylog/Logger;->error(Ljava/lang/Throwable;Ljava/lang/String;[Ljava/lang/Object;)V goto :goto_2d :cond_7c move v2, v4 .line 185 goto :goto_30 :cond_7e move v1, v4 goto :goto_39 .line 190 :cond_80 invoke-virtual {v0}, Ljava/security/cert/X509Certificate;->getPublicKey()Ljava/security/PublicKey; :try_end_83 .catch Ljava/security/cert/CertificateException; {:try_start_67 .. :try_end_83} :catch_49 move-result-object v0 .line 198 :goto_84 if-nez v0, :cond_8e .line 199 new-instance v0, Ljava/lang/SecurityException; const-string v1, "Failed to load the 3T certificate." invoke-direct {v0, v1}, Ljava/lang/SecurityException;-><init>(Ljava/lang/String;)V throw v0 .line 203 :cond_8e new-instance v1, Lt3/common/lic/h; invoke-direct {v1}, Lt3/common/lic/h;-><init>()V .line 204 invoke-virtual {v1, p1}, Lt3/common/lic/h;->d(Ljava/lang/String;)V .line 205 invoke-virtual {v1, v0}, Lt3/common/lic/h;->verify(Ljava/security/PublicKey;)Z .line 208 invoke-virtual {v1}, Lt3/common/lic/h;->E()Z move-result v0 if-nez v0, :cond_a7 .line 209 new-instance v0, Ljava/lang/SecurityException; const-string v1, "License verification failed." invoke-direct {v0, v1}, Ljava/lang/SecurityException;-><init>(Ljava/lang/String;)V throw v0 .line 211 :cond_a7 return-object v1 .line 181 :catch_a8 move-exception v2 goto :goto_73 :cond_aa move-object v0, v1 goto :goto_84 .end method
贴出来的代码就是校验用户输入的licence关键代码, 可以这个方法反编译失败了,有时间再大力研究这段代码吧.
把这个jar包放在android studio中 居然完全可以看到源码, 不可思议
看了一下这个方法, 他的思路是:
获取licensing_public.cer文件, 然后用sha-1算法验证这个文件对不对, 如果不对就跑异常. 这样可以防止有人修改cer文件.
之后创建 t3.common.lic.h 对象, 然后把这个返回. var1 是用户输入. 他是用#在分割. 用户完全完全可以输入 xxx#xxxx 这样的格式数据.
破解思路方法就可多了:
方案一:
自己生成X.509 公私密钥key. 计算公有key的sha-1
用自己生成的公钥替换licensing_public.cer。 然后替换代码中 sha-1的值. 就OK了.
然后用注册机:
调用 public static af a(String var0, PrivateKey var1)这个方法生成lincese就可以.
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package t3.common.lic; import com.google.common.io.BaseEncoding; import java.io.UnsupportedEncodingException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.Signature; import java.security.SignatureException; import org.pmw.tinylog.Logger; public class af { private String aq = null; private String content = null; private boolean ar = false; public String aL() { return this.aq; } public String getContent() { return this.content; } public boolean E() { return this.ar; } private af() { } public static af a(String var0, PublicKey var1) throws IllegalArgumentException { af var2 = new af(); var2.aq = var0; var2.ar = false; if (null != var0 && null != var1) { String[] var3 = w(var0).split("\\\\"); if (var3.length != 3) { throw new IllegalArgumentException("signedString is not readable."); } else { byte[] var4; byte[] var5; byte[] var6; BaseEncoding var7; IllegalArgumentException var8; try { var7 = BaseEncoding.base64(); var4 = var7.decode(var3[0]); if (var4.length == 0) { return var2; } var5 = var7.decode(var3[1]); var6 = var7.decode(var3[2]); } catch (IllegalArgumentException var10) { var8 = new IllegalArgumentException("signedString is not readable."); var8.initCause(var10); throw var8; } if (var4[0] != 3) { throw new IllegalArgumentException("signedString is not readable (version mismatch)."); } else { try { var7 = null; Signature var11 = Signature.getInstance("SHA256withRSA"); var11.initVerify(var1); var11.update(var5); if (var11.verify(var6)) { var2.content = new String(var5, "UTF-8"); var2.ar = true; return var2; } else { return var2; } } catch (InvalidKeyException | SignatureException | UnsupportedEncodingException | NoSuchAlgorithmException var9) { var8 = new IllegalArgumentException("signedString is not readable."); var8.initCause(var9); throw var8; } } } } else { throw new IllegalArgumentException("One of the arguments is null."); } } public static af a(String var0, PrivateKey var1) { af var2 = new af(); var2.content = var0; var2.ar = false; byte[] var3 = new byte[]{3}; byte[] var4; try { var4 = var0.getBytes("UTF-8"); } catch (UnsupportedEncodingException var12) { var12.printStackTrace(); Logger.error(var12, "Encoding failure", new Object[0]); return null; } Object var5 = null; byte[] var13; try { Signature var6 = Signature.getInstance("SHA256withRSA"); var6.initSign(var1); var6.update(var4); var13 = var6.sign(); } catch (InvalidKeyException | SignatureException | NoSuchAlgorithmException var11) { Logger.error(var11, "Encryption failure", new Object[0]); return var2; } BaseEncoding var14 = BaseEncoding.base64(); try { var14.encode(var3); var14.encode(var4); var14.encode(var13); } catch (IllegalArgumentException var10) { Logger.error(var10, "Encryption failure", new Object[0]); return var2; } String var7 = String.format("%s\\%s\\%s", var14.encode(var3), var14.encode(var4), var14.encode(var13)); var2.aq = w(var7); var2.ar = true; return var2; } private static String B(String var0) { String[] var1 = var0.split("#"); String var2 = var0; if (var1.length > 2) { var2 = var1[var1.length - 2]; } else if (var1.length == 2) { var2 = var1[1]; } return var2; } private static String w(String var0) { return var0.replaceAll("[^0-9a-zA-Z/=+\\\\]", ""); } }
方案二:
直接修改代码此处代码, 然后随便输入:
=========
开始动态调试之旅:
查看安装时间:
var1 是记录了安装时间. 看是什么赋值的?
var1和var2记录是在本地: ~/.3T/studio-3t/soduz3vqhnnja46uvu3szq--
调试得知: ~/.3T/studio-3t/soduz3vqhnnja46uvu3szq--/settings.dat 文件里的内容就是记录安装时间的. 只不过加密了.
理论上删除这个文件,就可以无限试用了, 但是实际情况不是:
本人删除这个文件之后, 又重新生成了一个一摸一样的数据, 这个setting.data不是最终源头. 继续调试之旅,
开始从加密的地方断点调试.
t3.common.lic.b.j 类中是数据源头, 因为是这个类的 this.bj.get("soduz3vqhnnja46uvu3szq--") 之后得到了数据.
继续追踪,发现安装时间的数据存放在5个类中:
是一个List 这个 list类的信息分别是:
如果把这5个地方的安装数据都删除, 那就达到无限试用了. 接下来找到5个地方.
第一个地方: t3.common.lic.b.j
this.bj = Preferences.userRoot().node("/3t/mongochef/" + var2); var2 = "enterprise"
// Preferences.userRoot() 目录在: ~/Library/Preferences/com.apple.java.util.prefs.plist
第二到五地方分别是:
/Users/dengzhongqiang/.3T/studio-3t/Lwm3TdTxgYJkXBgVk4s3/settings.dat
/Users/dengzhongqiang/.cache/ftuwWNWoJl-STeZhVGHKkQ--/5rpyYIZGkVBXle1pseFY2g
/var/folders/x4/xwpchqcd1m9539py__22l0ww0000gn/T/t3/dataman/mongodb/app/AppRunner/soduz3vqhnnja46uvu3szq--
/var/folders/x4/xwpchqcd1m9539py__22l0ww0000gn/T/ftuwWNWoJl-STeZhVGHKkQ--/5rpyYIZGkVBXle1pseFY2g--.log
说明:
第一个地方比较特别 com.apple.java.util.prefs.plist。 这个是所有Java软件共享的, 直接删除可能会影响其他应用.
其他第二到五地方随意删.
写了一个脚本:
rm -f ~/Library/Preferences/3t.* rm -rf ~/.3T rm -rf ~/.cache/ftuwWNWoJl-STeZhVGHKkQ-- rm -rf /var/folders/x4/xwpchqcd1m9539py__22l0ww0000gn/T/t3 rm -rf /var/folders/x4/xwpchqcd1m9539py__22l0ww0000gn/T/ftuwWNWoJl-STeZhVGHKkQ--
发现了一个奇怪现象,执行了之后并没有效果, 时间没有重置. 不得不佩服这个软件安全措施做的很到位.
没办法,继续挖掘.
查找到原因了: t3.common.lic.b.j 没有存放在com.apple.java.util.prefs.plist文件中. 在跟踪就到JVM虚拟机了. osx库在处理这个数据, 具体怎么处理的就不清楚了.
只能写代码删除了.
package com.company.dzqCrack; import java.util.prefs.Preferences; public class Main { public static void main(String[] args) { // write your code here Preferences bt = Preferences.userRoot().node("/3t/mongochef/enterprise"); bt.remove("soduz3vqhnnja46uvu3szq--"); } }
到此之际,破解完成. 又有30天了
关于动态调试别人的jar包总结:
修改info.plist文件: 圈出来的就是新增的
然后在 idea编辑器中创建一个remote debug
=======================
虽然可以无限试用了, 但是心里有一个结,就是
为什么删了com.apple.java.util.prefs.plist 3t.mongochef.enterprise.plist 等plist文件,为什么还有存有安装信息. 跟踪的时候 发现它是加载了 osx库.
一定打破砂锅探到底
进入jre.bundle目录。
将osx库拖入hopper
可以看到Java层面的东西,最终调用是这些方法.
发现 _getStringsForNode调用了两次 _toCF:
查了一下苹果开发文档:
也就是这个_toCF核心是把 c语言的字符串 转换为 CFString .
转换之后,有一个方法被调用了:
(*(*r15 + 0x530))(r15, r14, rax); // r15 = arg0. 其实这个就是JNIEnv *env 查看jni.h 中的定义 是struct JNINativeInterface类型
0x530是这个结构体的偏移, 暂且放一放.
通过跟踪发现: 一切数据来源都是调用 CFPreferencesCopyValue,
上面英文的大概意思。
CFPreferences 不能直接转 NSUserDefault. 是线程安全的. 和应用程序还有用户ID,host有关. 没有发现特别之处. 之所以删除plist文件不生效是因为数据已经加载到内存中了.
至此大完结, 删除~/Library/Preferences 目录下 t3* 3t*文件是有用的, 删除plist文件之后重启电脑就好了, 也就是上面的shell脚本是OK的.
如果不想重启电脑, 就执行一段Java代码, 用Java代码操作删除,立马生效.
======
截止到2020年04月30日,我发现之前的shell脚本对所有studio3T的版本都是有效的,studio3t可以随意升级版本。
但是之前的shell脚本不具有普适性,只对本人的电脑有效。 鉴于此,为了让所有人都可以无限试用,我改造了一下之前的脚本。 如果发现以下脚本无法使用,请留言。
#!/bin/sh rm -f ~/Library/Preferences/3t.* rm -rf ~/.3T rm -rf ~/.cache/ftuwWNWoJl-STeZhVGHKkQ-- ftPath=`find /var/folders -name "ftuwWNWoJl-STeZhVGHKkQ--" -print 2>&1 | fgrep -v "Permission denied" | fgrep -v "Operation not permitted"` t3Path=`dirname ${ftPath}`/t3 if [ -e ${ftPath} ];then rm -rf ${ftPath} fi if [ -e ${t3Path} ];then rm -rf ${t3Path} fi echo "删除文件成功,请立即重启电脑生效" echo "如果不想立刻重启,那么请在重启电脑前,都不要重新运行studio3T, 否则执行脚本将不起作用"