AI摘要:本文介绍了如何为Wearable Debug模块适配最新版本的小米运动健康App(V3.55.0)。由于插件未更新,作者通过逆向工程修复了插件的界面、固件安装和表盘安装功能。具体修复包括更新方法参数签名、调整类路径以及在表盘安装中添加预安装步骤。最终,作者成功恢复了插件的功能,使其能够在新版本中正常使用。

前言

最近折腾了一下小米手环,一直想找一个方便简单的安装表盘和更新固件的软件,常见的那两个一个全是广告,一个要登陆小米账号,都不太喜欢,后找到了这个Wearable Debug插件,直接通过hook小米运动健康来实现相关功能。但是已经很久没有更新,不能作用在最新版本小米运动健康(V3.55.0)上。由于找不到作者,所以没有源码,只能自己尝试逆向修改适配了。

修复插件界面

插件hook小米运动健康以后通过劫持点击关于界面的用户协议来打开模块界面,在新版本上已失效。主要问题出在 startWebView 方法的参数签名更新了。在新版本中,这个方法末尾增加了一个新的 boolean 类型参数 isAddUrlToSet
具体修复方案如下:


修改MainHook.smali里的handleLoadPackage方法,将其中

const/4 v8, 0x7
.line 212
new-array v8, v8, [Ljava/lang/Class;

修改为

const/16 v8, 0x8
.line 212
new-array v8, v8, [Ljava/lang/Class;

将其中

const/4 v14, 0x6

.line 235
const-class v15, Ljava/lang/Boolean;

.line 237
aput-object v15, v8, v14

修改为

const/4 v14, 0x6

const-class v15, Ljava/lang/Boolean;

aput-object v15, v8, v14

const/4 v14, 0x7

aput-object v10, v8, v14

修复安装固件功能

旧版插件寻找的类是 com.mi.fitness.checkupdate.util.CheckUpdateExtKt。但在新版小米运动健康代码中,这个类被移动到了 export 包下,变成了 com.mi.fitness.checkupdate.export.CheckUpdateExtKt。因为包名变了,插件反射找不到这个类,所以固件升级毫无反应。
修复操作:
修改插件中的a.smali,将

const-string vX, "com.mi.fitness.checkupdate.util.CheckUpdateExtKt"

修改为

const-string vX, "com.mi.fitness.checkupdate.export.CheckUpdateExtKt"

修复安装表盘功能

旧版插件试图实例化 com.xiaomi.fitness.watch.face.viewmodel.FaceDetailViewModel。这个类在新版中已经被彻底删除了,取而代之的是 FaceBleInfoViewModel。但是 FaceInstallPushCallback 接口依然存在!这说明新版的底层 Controller(也就是 FaceInstallBleImpl)大概率还保留着兼容这个接口的 doInstall 方法。所以我们需要把实例化的目标 ViewModel 换成新的。同时新版的表盘安装存在两个步骤 preInstall (App 会先向手表发送一个预安装指令(抓包看的话是 e=4, f=4 数据包)。这个指令的作用是告诉手表:“我要给你发一个表盘了,它的 ID 是 XX,文件大小是 XX,请准备好内存空间”。)、doInstall (预装指令被手表确认后,App 才会调用 doInstall,通过底层的文件传输通道把 .bin 表盘文件推送到手表。),现在的插件代码只调用了 doInstall,所以必须在调用 doInstall 之前,强行用反射调用一次 preInstall。
修复方案:


修改插件中a.smali,将其中

const-string vX, "com.xiaomi.fitness.watch.face.viewmodel.FaceDetailViewModel"

修改为

const-string vX, "com.xiaomi.fitness.watch.face.viewmodel.FaceBleInfoViewModel"

将其中

.line 268
move-result-object v0

.line 269
const-string v5, "doInstall"

修改为

    .line 268
    move-result-object v0

    # ================= 缝合开始:预安装 preInstall =================
    # 1. 动态代理伪造 Kotlin 的 Function3 接口
    const-string v14, "kotlin.jvm.functions.Function3"
    invoke-static {v14, v11}, Lde/robv/android/xposed/XposedHelpers;->findClass(Ljava/lang/String;Ljava/lang/ClassLoader;)Ljava/lang/Class;
    move-result-object v14

    const/4 v13, 0x1
    new-array v13, v13, [Ljava/lang/Class;
    const/4 v5, 0x0
    aput-object v14, v13, v5

    new-instance v14, Ltest/hook/debug/xp/DummyHandler;
    invoke-direct {v14}, Ltest/hook/debug/xp/DummyHandler;-><init>()V

    invoke-static {v11, v13, v14}, Ljava/lang/reflect/Proxy;->newProxyInstance(Ljava/lang/ClassLoader;[Ljava/lang/Class;Ljava/lang/reflect/InvocationHandler;)Ljava/lang/Object;
    move-result-object v10

    # 2. 构造长度为 9 的 Object[] 参数数组存入 v5
    const/16 v14, 0x9
    new-array v5, v14, [Ljava/lang/Object;

    # [0] id
    const/4 v14, 0x0
    aput-object v12, v5, v14

    # [1] path (获取文件的绝对路径)
    invoke-virtual {v7}, Ljava/io/File;->getAbsolutePath()Ljava/lang/String;
    move-result-object v14
    const/4 v13, 0x1
    aput-object v14, v5, v13

    # [2] version = 0L
    const-wide/16 v13, 0x0
    invoke-static {v13, v14}, Ljava/lang/Long;->valueOf(J)Ljava/lang/Long;
    move-result-object v14
    const/4 v13, 0x2
    aput-object v14, v5, v13

    # [3] size = 文件实际大小 (原插件传0,这里修复为真实大小)
    invoke-virtual {v7}, Ljava/io/File;->length()J
    move-result-wide v13
    invoke-static {v13, v14}, Ljava/lang/Long;->valueOf(J)Ljava/lang/Long;
    move-result-object v14
    const/4 v13, 0x3
    aput-object v14, v5, v13

    # [4] needCheckStorage = true
    sget-object v14, Ljava/lang/Boolean;->TRUE:Ljava/lang/Boolean;
    const/4 v13, 0x4
    aput-object v14, v5, v13

    # [5] license = null
    const/4 v14, 0x0
    const/4 v13, 0x5
    aput-object v14, v5, v13

    # [6] sign = null
    const/4 v13, 0x6
    aput-object v14, v5, v13

    # [7] trialDuration = 0
    const/4 v13, 0x0
    invoke-static {v13}, Ljava/lang/Integer;->valueOf(I)Ljava/lang/Integer;
    move-result-object v14
    const/4 v13, 0x7
    aput-object v14, v5, v13

    # [8] action = 伪造的 Function3 (目前存在 v10)
    const/16 v13, 0x8
    aput-object v10, v5, v13

    # 3. 反射调用 preInstall
    const-string v14, "preInstall"
    invoke-static {v9, v14, v5}, Lde/robv/android/xposed/XposedHelpers;->callMethod(Ljava/lang/Object;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/Object;

    # 4. 恢复被占用的 v10 寄存器内容(关键,防止后续代码崩溃)
    const-class v10, Ljava/lang/String;
    # ================= 缝合结束 =================

    .line 269
    const-string v5, "doInstall"

同时在test/hook/debug/xp/目录下新建DummyHandler.smali,填入以下内容:

.class public Ltest/hook/debug/xp/DummyHandler;
.super Ljava/lang/Object;
.source "DummyHandler.smali"
.implements Ljava/lang/reflect/InvocationHandler;

.method public constructor <init>()V
    .registers 1
    invoke-direct {p0}, Ljava/lang/Object;-><init>()V
    return-void
.end method

.method public invoke(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;
    .registers 4
    const/4 v0, 0x0
    return-object v0
.end method

后记

这样就简单修复了插件功能。至于软件安装功能测试没有问题,不需要修复。

END
本文作者:
文章标题:为Wearable Debug模块适配最新版本小米运动健康app
本文地址:https://233.517128.xyz/archives/51.html
版权说明:若无注明,本文皆学习笔记原创,转载请保留文章出处。
最后修改:2026 年 04 月 26 日
如果觉得我的文章对你有用,请随意赞赏