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
后记
这样就简单修复了插件功能。至于软件安装功能测试没有问题,不需要修复。