
如何去掉某博客付费插件的检测
前言
之前去咸鱼租了一个专业版来用,感觉专业版和开源版差距不大,就多了几个插件,Java写的东西不是随便逆向吗?
先看效果,可以看见已经去除了付费检测直接用,功能全部正常的。
前提条件
-
你首先得有这几个插件的jar包,当然你可以去咸鱼上租个一天,安装一下
-
Jadx
-
dex2jar
-
smali
-
d8工具
工具介绍
-
Jadx
这个工具是逆向Java程序必备功能强大非常不错
-
dex2jar
这个也是逆向的老工具了
-
smali
Smali是Android虚拟机的反汇编语言。Android虚拟机的可执行文件并不是普通的class文件,而是再重新整合打包后生成的dex文件。dex文件反编译之后就是Smali代码,所以说,Smali语言是Android虚拟机的反汇编语言。
-
d8
d8 是一种命令行工具,Android Studio 和 Android Gradle 插件使用该工具来将项目的 Java 字节码编译为在 Android 设备上运行的 DEX 字节码
所需工具我已经全部打包好了,文末自取
思路解析
我们首先要去分析插件的授权机制,然后修改逻辑,直接修改.class文件不是很理想我们可以通过其他方式来修改
流程顺序
.class
文件 ➔ .dex
文件 ➔ .smali
文件 ➔ .dex
文件 ➔ .jar
文件 ➔ .class
文件
步骤详解
-
起始:
.class
文件 (Java 字节码)- 这是 Java 编译器 (javac) 将 Java 源代码 (
.java
) 编译后生成的标准 Java 字节码文件。
- 这是 Java 编译器 (javac) 将 Java 源代码 (
-
转换为:
.dex
文件 (Dalvik/ART 字节码)- 操作: 从
.class
到.dex
- 工具: Android SDK 中的
dx
工具或新的D8
编译器。 - 说明: 此步骤将多个
.class
文件(以及引用的库)转换为 Android 运行时(Dalvik 或 ART)可执行的.dex
文件。.dex
文件是针对移动设备优化的格式,通常包含在一个 APK 文件中。
- 操作: 从
-
转换为:
.smali
文件 (Smali 汇编)- 操作: 从
.dex
到.smali
- 工具:
baksmali
工具。 - 说明: 此步骤将
.dex
文件反汇编成人类可读的 Smali 代码。Smali 是一种类似于 Jasmin 的汇编语言,用于 Dalvik/ART 字节码。这通常用于逆向工程、分析或修改 Android 应用。
- 操作: 从
-
转换回:
.dex
文件- 操作: 从
.smali
到.dex
- 工具:
smali
工具。 - 说明: 此步骤将修改或未修改的
.smali
文件重新汇编成.dex
文件。
- 操作: 从
-
转换为:
.jar
文件 (Java Archive)- 操作: 从
.dex
到.jar
- 工具:
dex2jar
或类似工具。 - 说明: 此步骤将
.dex
文件转换回标准的 Java Archive (.jar
) 文件,其中包含.class
文件。这使得可以使用标准的 Java 工具来分析或反编译代码。
- 操作: 从
-
提取/最终形式:
.class
文件- 操作: 从
.jar
到.class
- 说明: 一个
.jar
文件本身就是一个包含了多个.class
文件(以及其他资源)的压缩包。可以使用任何标准的解压缩工具(如jar
命令本身或 7-Zip, WinRAR 等)从.jar
文件中提取出.class
文件。提取出来的.class
文件可以使用 Java 反编译器(如 JD-GUI, Fernflower, Procyon)进一步反编译成 Java 源代码(尽管可能不是原始的源代码)。
- 操作: 从
分析授权机制
我们使用Jadx打开jar
我们先看资源文件里面的plugin.yaml和plugin.yaml.tpl
可以发现均有
store.halo.run/payment-model: paid
明眼人一看就知道这个就是付费插件的标识,咱去掉我框中的区域就行,通过浏览插件的开发文档可以得知入口文件是继承了BasePlugin的一个类,那我们在源代码里面看是哪个类继承了BasePlugin找了找找到了是PostRestrictReadStarterPlugin
Java代码咱还是挺擅长的我们可以看出他授权检测的代码
public void start() {
String pluginId = this.context.getName();
Plugin plugin = (Plugin) this.client.fetch(Plugin.class, pluginId).orElse(null);
if (plugin != null) {
Map<String, String> annotations = MetadataUtil.nullSafeAnnotations(plugin);
String appId = annotations.get("store.halo.run/app-id");
if (StringUtils.hasText(appId) && !LicenseUtils.hasLicensed(appId)) {
throw new RuntimeException("Refuse to start plugin due to invalid license.");
}
}
this.schemeManager.register(LoginRecord.class);
this.schemeManager.register(ScanCodeRecord.class);
this.schemeManager.register(AnswerRecord.class);
this.schemeManager.register(CommentRecord.class);
this.schemeManager.register(PayOrderRecord.class);
this.schemeManager.register(PayProviderSetting.class);
}
我们只需要保留下面这段就行,就可以完美的去掉检测
public void start() {
this.schemeManager.register(LoginRecord.class);
this.schemeManager.register(ScanCodeRecord.class);
this.schemeManager.register(AnswerRecord.class);
this.schemeManager.register(CommentRecord.class);
this.schemeManager.register(PayOrderRecord.class);
this.schemeManager.register(PayProviderSetting.class);
}
修改逻辑
我们将PostRestrictReadStarterPlugin.class文件导出到工具目录,在工具目录打开命令行窗口
Class->Dex
在Powershell里面输入下面这个命令
.\bin\d8.bat PostRestrictReadStarterPlugin.class --output test.zip
使用D8工具将这个class文件转换成dex输出在test.zip文件夹
将这个test.zip文件解压出来我们就得到了classes.dex这个文件
Dex->Smali
我们使用baksmali.jar来实现键入以下命令
java -jar .\bin\smali\baksmali.jar disassemble classes.dex -o output
将smali文件放入output目录下
修改Smali代码
我们只需要修改start方法即可
.method public start()V
.registers 7
.line 35
iget-object v0, p0, Lrun/halo/post/resrict/read/starter/PostRestrictReadStarterPlugin;->context:Lrun/halo/app/plugin/PluginContext;
invoke-virtual {v0}, Lrun/halo/app/plugin/PluginContext;->getName()Ljava/lang/String;
move-result-object v0
.line 36
.local v0, "pluginId":Ljava/lang/String;
iget-object v1, p0, Lrun/halo/post/resrict/read/starter/PostRestrictReadStarterPlugin;->client:Lrun/halo/app/extension/ExtensionClient;
const-class v2, Lrun/halo/app/core/extension/Plugin;
invoke-interface {v1, v2, v0}, Lrun/halo/app/extension/ExtensionClient;->fetch(Ljava/lang/Class;Ljava/lang/String;)Ljava/util/Optional;
move-result-object v1
const/4 v2, 0x0
invoke-virtual {v1, v2}, Ljava/util/Optional;->orElse(Ljava/lang/Object;)Ljava/lang/Object;
move-result-object v1
check-cast v1, Lrun/halo/app/core/extension/Plugin;
.line 37
.local v1, "plugin":Lrun/halo/app/core/extension/Plugin;
if-eqz v1, :cond_38
.line 38
invoke-static {v1}, Lrun/halo/app/extension/MetadataUtil;->nullSafeAnnotations(Lrun/halo/app/extension/AbstractExtension;)Ljava/util/Map;
move-result-object v2
.line 39
.local v2, "annotations":Ljava/util/Map;, "Ljava/util/Map<Ljava/lang/String;Ljava/lang/String;>;"
const-string v3, "store.halo.run/app-id"
invoke-interface {v2, v3}, Ljava/util/Map;->get(Ljava/lang/Object;)Ljava/lang/Object;
move-result-object v3
check-cast v3, Ljava/lang/String;
.line 40
.local v3, "appId":Ljava/lang/String;
invoke-static {v3}, Lorg/springframework/util/StringUtils;->hasText(Ljava/lang/String;)Z
move-result v4
if-eqz v4, :cond_38
invoke-static {v3}, Lrun/halo/post/resrict/read/starter/utils/LicenseUtils;->hasLicensed(Ljava/lang/String;)Z
move-result v4
if-eqz v4, :cond_30
goto :goto_38
.line 41
:cond_30
new-instance v4, Ljava/lang/RuntimeException;
const-string v5, "Refuse to start plugin due to invalid license."
invoke-direct {v4, v5}, Ljava/lang/RuntimeException;-><init>(Ljava/lang/String;)V
throw v4
.line 45
.end local v2 # "annotations":Ljava/util/Map;, "Ljava/util/Map<Ljava/lang/String;Ljava/lang/String;>;"
.end local v3 # "appId":Ljava/lang/String;
:cond_38
:goto_38
iget-object v2, p0, Lrun/halo/post/resrict/read/starter/PostRestrictReadStarterPlugin;->schemeManager:Lrun/halo/app/extension/SchemeManager;
const-class v3, Lrun/halo/post/resrict/read/starter/extension/LoginRecord;
invoke-interface {v2, v3}, Lrun/halo/app/extension/SchemeManager;->register(Ljava/lang/Class;)V
.line 46
iget-object v2, p0, Lrun/halo/post/resrict/read/starter/PostRestrictReadStarterPlugin;->schemeManager:Lrun/halo/app/extension/SchemeManager;
const-class v3, Lrun/halo/post/resrict/read/starter/extension/ScanCodeRecord;
invoke-interface {v2, v3}, Lrun/halo/app/extension/SchemeManager;->register(Ljava/lang/Class;)V
.line 47
iget-object v2, p0, Lrun/halo/post/resrict/read/starter/PostRestrictReadStarterPlugin;->schemeManager:Lrun/halo/app/extension/SchemeManager;
const-class v3, Lrun/halo/post/resrict/read/starter/extension/AnswerRecord;
invoke-interface {v2, v3}, Lrun/halo/app/extension/SchemeManager;->register(Ljava/lang/Class;)V
.line 48
iget-object v2, p0, Lrun/halo/post/resrict/read/starter/PostRestrictReadStarterPlugin;->schemeManager:Lrun/halo/app/extension/SchemeManager;
const-class v3, Lrun/halo/post/resrict/read/starter/extension/CommentRecord;
invoke-interface {v2, v3}, Lrun/halo/app/extension/SchemeManager;->register(Ljava/lang/Class;)V
.line 49
iget-object v2, p0, Lrun/halo/post/resrict/read/starter/PostRestrictReadStarterPlugin;->schemeManager:Lrun/halo/app/extension/SchemeManager;
const-class v3, Lrun/halo/post/resrict/read/starter/extension/PayOrderRecord;
invoke-interface {v2, v3}, Lrun/halo/app/extension/SchemeManager;->register(Ljava/lang/Class;)V
.line 50
iget-object v2, p0, Lrun/halo/post/resrict/read/starter/PostRestrictReadStarterPlugin;->schemeManager:Lrun/halo/app/extension/SchemeManager;
const-class v3, Lrun/halo/post/resrict/read/starter/extension/PayProviderSetting;
invoke-interface {v2, v3}, Lrun/halo/app/extension/SchemeManager;->register(Ljava/lang/Class;)V
.line 51
return-void
.end method
这是Smali代码,你看不懂很正常,AI时代,你不会的就去问AI啊,经常搞逆向的肯定能看懂,在此简单解释一下这段代码
方法结构概览
-
作用 :插件启动时执行的初始化逻辑。
-
流程 :
-
获取当前插件的上下文(
context
)和扩展客户端(client
)。 -
通过插件上下文获取当前插件的名称(
name
)。 -
使用
ExtensionClient
从系统中获取当前插件的Plugin
实例。 -
从
Plugin
实例的注解中提取app-id
(用于许可证校验)。 -
校验
app-id
是否存在且许可证有效,若无效则抛出异常阻止插件启动。 -
无论是否通过校验,最终会注册一系列扩展实体到
SchemeManager
中。
-
许可证验证逻辑
关键步骤
获取插件名称 :
iget-object v0, p0, Lrun/halo/post/resrict/read/starter/PostRestrictReadStarterPlugin;->context:Lrun/halo/app/plugin/PluginContext;
invoke-virtual {v0}, Lrun/halo/app/plugin/PluginContext;->getName()Ljava/lang/String;
-
从插件上下文获取当前插件的名称。
获取当前插件的Plugin
实例 :
iget-object v1, p0, Lrun/halo/post/resrict/read/starter/PostRestrictReadStarterPlugin;->client:Lrun/halo/app/extension/ExtensionClient;
const-class v2, Lrun/halo/app/core/extension/Plugin;
invoke-interface {v1, v2, v0}, Lrun/halo/app/extension/ExtensionClient;->fetch(Ljava/lang/Class;Ljava/lang/String;)Ljava/util/Optional;
-
使用
ExtensionClient
根据插件名称和Plugin
类类型获取当前插件的实例。
从Plugin
实例的注解中提取app-id
:
invoke-static {v1}, Lrun/halo/app/extension/MetadataUtil;->nullSafeAnnotations(Lrun/halo/app/extension/AbstractExtension;)Ljava/util/Map;
const-string v3, "store.halo.run/app-id";
-
MetadataUtil.nullSafeAnnotations
从Plugin
实例中提取注解元数据。 -
从元数据中获取键为
store.halo.run/app-id
的值(即应用ID)。
许可证校验 :
invoke-interface {v2, v3}, Ljava/util/Map;->get(Ljava/lang/Object;)Ljava/lang/Object;
invoke-static {v3}, Lorg/springframework/util/StringUtils;->hasText(Ljava/lang/String;)Z
invoke-static {v3}, Lrun/halo/post/resrict/read/starter/utils/LicenseUtils;->hasLicensed(Ljava/lang/String;)Z
-
如果
app-id
为空,跳过校验(:cond_37
)。 -
否则调用
LicenseUtils.hasLicensed(app-id)
校验许可证。 -
如果许可证无效(返回
false
),抛出RuntimeException
阻止插件启动。
逻辑分支
-
通过校验 :继续执行扩展注册。
-
未通过校验 :抛出异常,插件无法启动。
-
未找到
Plugin
实例 :直接跳过校验,进入扩展注册。
扩展注册逻辑
关键步骤
注册以下扩展实体到SchemeManager
:
LoginRecord
ScanCodeRecord
AnswerRecord
CommentRecord
PayOrderRecord
PayProviderSetting
代码示例 :
iget-object v2, p0, Lrun/halo/post/resrict/read/starter/PostRestrictReadStarterPlugin;->schemeManager:Lrun/halo/app/extension/SchemeManager;
const-class v3, Lrun/halo/post/resrict/read/starter/extension/LoginRecord;
invoke-interface {v2, v3}, Lrun/halo/app/extension/SchemeManager;->register(Ljava/lang/Class;)V
-
每个扩展实体对应一种业务数据
-
SchemeManager.register(Class)
用于注册数据库表或数据模型。
修改代码
知道了这段代码的基本原理就好办了,我们直接进行修改就行,你不会修改就去问AI
.method public start()V
.registers 4
.line 45
:cond_37
iget-object v0, p0, Lrun/halo/post/resrict/read/starter/PostRestrictReadStarterPlugin;->schemeManager:Lrun/halo/app/extension/SchemeManager;
const-class v1, Lrun/halo/post/resrict/read/starter/extension/LoginRecord;
invoke-interface {v0, v1}, Lrun/halo/app/extension/SchemeManager;->register(Ljava/lang/Class;)V
.line 46
iget-object v0, p0, Lrun/halo/post/resrict/read/starter/PostRestrictReadStarterPlugin;->schemeManager:Lrun/halo/app/extension/SchemeManager;
const-class v1, Lrun/halo/post/resrict/read/starter/extension/ScanCodeRecord;
invoke-interface {v0, v1}, Lrun/halo/app/extension/SchemeManager;->register(Ljava/lang/Class;)V
.line 47
iget-object v0, p0, Lrun/halo/post/resrict/read/starter/PostRestrictReadStarterPlugin;->schemeManager:Lrun/halo/app/extension/SchemeManager;
const-class v1, Lrun/halo/post/resrict/read/starter/extension/AnswerRecord;
invoke-interface {v0, v1}, Lrun/halo/app/extension/SchemeManager;->register(Ljava/lang/Class;)V
.line 48
iget-object v0, p0, Lrun/halo/post/resrict/read/starter/PostRestrictReadStarterPlugin;->schemeManager:Lrun/halo/app/extension/SchemeManager;
const-class v1, Lrun/halo/post/resrict/read/starter/extension/CommentRecord;
invoke-interface {v0, v1}, Lrun/halo/app/extension/SchemeManager;->register(Ljava/lang/Class;)V
.line 49
iget-object v0, p0, Lrun/halo/post/resrict/read/starter/PostRestrictReadStarterPlugin;->schemeManager:Lrun/halo/app/extension/SchemeManager;
const-class v1, Lrun/halo/post/resrict/read/starter/extension/PayOrderRecord;
invoke-interface {v0, v1}, Lrun/halo/app/extension/SchemeManager;->register(Ljava/lang/Class;)V
.line 50
iget-object v0, p0, Lrun/halo/post/resrict/read/starter/PostRestrictReadStarterPlugin;->schemeManager:Lrun/halo/app/extension/SchemeManager;
const-class v1, Lrun/halo/post/resrict/read/starter/extension/PayProviderSetting;
invoke-interface {v0, v1}, Lrun/halo/app/extension/SchemeManager;->register(Ljava/lang/Class;)V
.line 51
return-void
.end method
到此我们修改完了,现在就要去编译回去
Smali->Dex
我们使用smali.jar工具,键入以下命令
java -jar .\bin\smali\smali.jar assemble output -o new_class.dex
将我们修改好的Smali代码重新打包为dex
Dex->Jar
将dex文件搞成jar包方便我们提取class文件键入以下命令
.\bin\dex2jar\d2j-dex2jar.bat new_class.dex -o new_class.jar
这样我们也就得到了jar包
Jar->class
直接当压缩包打开提取就完事了
替换文件
到此我们也就得到了我们修改后的class文件,就是这么简单,我们将原jar包里面的PostRestrictReadStarterPlugin进行替换,替换完了我们再用Jadx来查看
上传安装
非常完美,去博客先将原本要授权的插件给卸载了,上传我们修改好的jar包安装,请注意你直接上传到plugin文件夹里面是没用的,开启插件,你就发现授权已经被去掉了,功能也是正常使用的,他所有插件其实都是大差不差的,按照我的方法来是一定可以去掉的
工具下载
原谅我,我也不喜欢这种,口令是网站域名,你们看了教程回复一句呗