前言

之前去咸鱼租了一个专业版来用,感觉专业版和开源版差距不大,就多了几个插件,Java写的东西不是随便逆向吗?

先看效果,可以看见已经去除了付费检测直接用,功能全部正常的。

前提条件

  • 你首先得有这几个插件的jar包,当然你可以去咸鱼上租个一天,安装一下

  • Jadx

  • dex2jar

  • smali

  • d8工具

https://github.com/skylot/jadx https://github.com/pxb1988/dex2jar https://bitbucket.org/JesusFreke/smali/downloads/

工具介绍

  • 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 文件


步骤详解

  1. 起始:.class 文件 (Java 字节码)

    • 这是 Java 编译器 (javac) 将 Java 源代码 (.java) 编译后生成的标准 Java 字节码文件。
  2. 转换为:.dex 文件 (Dalvik/ART 字节码)

    • 操作:.class.dex
    • 工具: Android SDK 中的 dx 工具或新的 D8 编译器。
    • 说明: 此步骤将多个 .class 文件(以及引用的库)转换为 Android 运行时(Dalvik 或 ART)可执行的 .dex 文件。.dex 文件是针对移动设备优化的格式,通常包含在一个 APK 文件中。
  3. 转换为:.smali 文件 (Smali 汇编)

    • 操作:.dex.smali
    • 工具: baksmali 工具。
    • 说明: 此步骤将 .dex 文件反汇编成人类可读的 Smali 代码。Smali 是一种类似于 Jasmin 的汇编语言,用于 Dalvik/ART 字节码。这通常用于逆向工程、分析或修改 Android 应用。
  4. 转换回:.dex 文件

    • 操作:.smali.dex
    • 工具: smali 工具。
    • 说明: 此步骤将修改或未修改的 .smali 文件重新汇编成 .dex 文件。
  5. 转换为:.jar 文件 (Java Archive)

    • 操作:.dex.jar
    • 工具: dex2jar 或类似工具。
    • 说明: 此步骤将 .dex 文件转换回标准的 Java Archive (.jar) 文件,其中包含 .class 文件。这使得可以使用标准的 Java 工具来分析或反编译代码。
  6. 提取/最终形式:.class 文件

    • 操作:.jar.class
    • 说明: 一个 .jar 文件本身就是一个包含了多个 .class 文件(以及其他资源)的压缩包。可以使用任何标准的解压缩工具(如 jar 命令本身或 7-Zip, WinRAR 等)从 .jar 文件中提取出 .class 文件。提取出来的 .class 文件可以使用 Java 反编译器(如 JD-GUI, Fernflower, Procyon)进一步反编译成 Java 源代码(尽管可能不是原始的源代码)。

分析授权机制

我们使用Jadx打开jar

我们先看资源文件里面的plugin.yamlplugin.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啊,经常搞逆向的肯定能看懂,在此简单解释一下这段代码

方法结构概览

  • 作用 :插件启动时执行的初始化逻辑。

  • 流程

    1. 获取当前插件的上下文(context)和扩展客户端(client)。

    2. 通过插件上下文获取当前插件的名称(name)。

    3. 使用ExtensionClient从系统中获取当前插件的Plugin实例。

    4. Plugin实例的注解中提取app-id(用于许可证校验)。

    5. 校验app-id是否存在且许可证有效,若无效则抛出异常阻止插件启动。

    6. 无论是否通过校验,最终会注册一系列扩展实体到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.nullSafeAnnotationsPlugin实例中提取注解元数据。

  • 从元数据中获取键为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来查看

dxdvkajrou.png

上传安装

非常完美,去博客先将原本要授权的插件给卸载了,上传我们修改好的jar包安装,请注意你直接上传到plugin文件夹里面是没用的,开启插件,你就发现授权已经被去掉了,功能也是正常使用的,他所有插件其实都是大差不差的,按照我的方法来是一定可以去掉的

gaqcgigonz.png

工具下载

原谅我,我也不喜欢这种,口令是网站域名,你们看了教程回复一句呗