本帖最后由 万里无云 于 2026-8-13 19:34 编辑
南风天气 Skypulse 高级功能解锁指北
适用于 v3.2.2X ~ 当前 v3.6.51(1162) 版本
应用介绍
SkyPulse 是一款基于第三方彩云天气 Api 及小米天气 Api 的安卓天气查询类应用程序,轻量体积,无广告,界面简洁易用。当前软件基础功能免费,付费可解锁更多高级功能。
应用信息
本应用为 GitHub 开源应用(会员核心逻辑代码为私有),项目地址为:qnmlgbd250/weather-none
| 包名 | com.skypulse.weather | | 大小 | 约 6 MB | | 适配系统 | Android 8 ~ 15 |
第一阶段:早期无限制免费版
该应用早期是完全开源免费的,功能上没有任何任何限制。无意间存档了该阶段相对较新的版本 (v3.2.17r791),有需要需要的可以自取:蓝奏云(密码:1lls,PC模式打开)
第二阶段:本地激活码验证
后续不知道从哪个版本开始,作者将原有免费功能添加限制作为付费高级功能。直至 v3.5.43(1054) 版本,都使用同同一套机器码+本地比对激活码的方式进行激活。当输入的激活码与本地计算的激活码匹配后,应用将写入激活信息。
该阶段可以通过设备码生成合法激活码的方式进行激活。我们可以通过寻找机器码生成逻辑的方法快速定位到目标类名:
现在已经找到关键类,接下来就是找到关键方法,进入后右上角菜单 "转为Java",便于查看。整理一下就可以得到类似下面的核心代码:
- package l2;
- public final class l {
- private final Context a;
-
- private final SharedPreferences l() {
- return (SharedPreferences) this.b.getValue();
- }
- private final boolean n() {
- return l().getBoolean("membership_premium", false);
- }
- public final a h(String str) {
- o.g(str, "code");
- if (((Boolean) this.c.getValue()).booleanValue()) {
- return a.d;
- }
- String upperCase = m.N0(m.z(str, "-", "", false, 4, (Object) null))
- .toString().toUpperCase(Locale.ROOT);
- o.f(upperCase, "toUpperCase(...)");
- if (upperCase.length() != 8) {
- return a.b;
- }
- if (!m.s(upperCase, e.e(k()), true)) {
- return a.b;
- }
- l().edit().putBoolean("membership_premium", true)
- .putLong("membership_activated_at", System.currentTimeMillis()).commit();
- this.c.setValue(Boolean.TRUE);
- return a.a;
- }
- public final long i() {
- return l().getLong("membership_activated_at", 0L);
- }
- }
复制代码
接下来的思路就非常简单了,最简单的将 h(String str) 的几个 if 判断删除,就可以完全绕过验证,直接加密写入激活配置信息。
当然也可以进行进阶的修改。深入分析最后一个 if 判断,可以得知以下信息:
- !m.s(upperCase, e.e(k()), true) // 用于比对用户输入是否与激活码一致
- upperCase // 根据前面几个 if 判断,可以得知其为用户输入的激活码
- e.e(k()) // 与用户输入的相对,那么这个的值就是真正的激活码
- k() // 这个就是应用生成的机器码
复制代码
观察到 a: Context,我们可以通过在最后一个判断 return 之前插入构造出的下列方法将其变成一个注册机:
- Toast.makeText(this.a, e.e(upperCase), Toast.LENGTH_SHORT).show();
复制代码
具体的 Smali 代码可以通过 NP 管理器从 Java 自动生成。这里就不做赘述。当然也可以参考完整修改 v3.4.19r995 后的测试样本。你也可以边对比边自行修改支持该方案的最后一个官方 v3.5.43r1054 版本。可从网盘自行获取:
下面简单介绍一下它的设备码及本阶段激活码生成算法:
第三阶段:云端激活验证
从 v3.5.44(1055) 开始,原有本地配置信息彻底失效,激活码生成代码从本地移除,算法变为未知,验证流程转为云端完成,原激活码全面失效。通过分析发现具体表现为:
- 客户端发送明文数据给云端
- 云端返回错误信息,或通过 RS256 私钥签发 JWT 返回 激活信息 和 机器码
- 客户端验证本地签名是否未修改
- 确认签名无误后,通过硬编码公钥解密,比对与云端返回的机器码是否一致
此外,从第二阶段旧版升级后,应用会自动将机器码发送给云端服务器进行验证,成功匹配设备码后则自动更新为新版激活配置。机器码生成逻辑与第二阶段保持一致未更改。
此阶段应用会不定期从云端获取信息并刷新本地激活配置,该方案应用直至当前 v3.6.51(1162) 版本。论坛内有一篇针对本阶段某个版本的修改教程:
原教程比较简洁,同样的只需要修改两个地方。这里提供一个详细的通用版本:
第一处:强制为本地会员
第二处:跳过云端验证
仔细观察附近的代码,也就是通过修改目标方法让其直接返回已经激活状态,从而跳过云端验证流程。
- public final class a {
- public static final a a = new a("SUCCESS", 0); // 激活码正确
- public static final a b = new a("INVALID_CODE", 1);
- public static final a c = new a("WRONG_DEVICE", 2);
- public static final a d = new a("ALREADY_ACTIVATED", 3); // 已经激活
- public static final a e = new a("TOO_MANY_ATTEMPTS", 4); // 尝试激活次数过多
- public static final a f = new a("NETWORK_ERROR", 5);
- }
复制代码
当前阶段的就不提供样本附件了,有兴趣可自行前往项目仓库下载最新版本尝试。
更新:附带坛友的方法帖子,全版本通用,可以前往学习一下:
免责声明
本文内容及相关附件资源仅用于个人学习使用,禁止用于商业用途和倒卖,请于下载后 24 小时内删除。所有权益归原作者所有,如真正需要,请通过付费、捐赠等方式支持官方正版。
|