LOADING

加载过慢请开启缓存 浏览器默认开启

逆向小米搜索框:给 com.android.quicksearchbox 换必应 + 改造跳转 Via

2026/8/31 0 技术 8.1k 字 · 约 30 分钟 Android 逆向 HyperOS

设备:小米 13(HyperOS),KernelSU root
目标:把搜索框的百度换成必应;把网页搜索从写死的 Mi 浏览器改跳到 Via
结果:引擎切换 + 跳转改造均生效,附带一键切换脚本

一、搜索引擎配置藏在哪

小米搜索(com.android.quicksearchbox)的引擎逻辑集中在 com.android.quicksearchbox.xiaomi.searchengine 包,配置有三级数据源:

  1. APK 内置默认assets/websearch-default/searchengineNew.json
  2. 本地缓存(云端下发落盘):/data/user/0/<pkg>/files/data/websearch/websearch-<区域>-<hash>/searchengineNew.json
  3. 云端接口POST https://api.browser.miui.com/global/search/api/searchEngine/forGlobalSearch(默认 30 分钟~1 小时刷新一次)

核心 JSON 结构:

{
  "data": {
    "updateIntervalMinutes": 30,
    "defaultSearchEngineMap": {
      "globalSearchSearchBox": "baidu",
      "globalSearchHotList": "baidu"
    },
    "searchEngineSceneMap": {
      "globalSearchSearchBox": {
        "searchEngines": [
          {
            "searchEngineName": "baidu",
            "searchUrl": "https://m.baidu.com/s?...&word={searchTerms}...",
            "title_zh_CN": "百度"
          }
        ],
        "homepageSearchEngineCount": 3,
        "resetSearchEngineData": false
      }
    }
  }
}

两个场景:搜索框(globalSearchSearchBox)和热搜榜(globalSearchHotList)。{searchTerms} 是关键词占位符。

用户的选择保存在 SharedPreferences searchEngine

Key 含义
current_engine 当前引擎名(默认 baidu
engine_click_count 是否手动切换过
client_scene_info 场景 → {sceneId, currentEngine, hasChanged}

二、把百度换成必应

思路:不动引擎名current_engine 保持 baidu,避免服务端逻辑漂移),只把本地缓存 JSON 中 baidu 条目的 searchUrl / 标题换成必应:

  • searchUrl: https://cn.bing.com/search?q={searchTerms}
  • 标题:必应 / 必應 / Bing
  • 图标:本地 PNG(Glide 不支持 ICO/SVG,注意选 PNG 源)

防覆盖是关键:配置目录和文件都加 chattr +i(immutable)。云端刷新写文件失败时不会清本地文件,也不会切换 hash 文件名。

chattr +i searchengineNew.json
chattr +i websearch-zh-CN-<hash>/

三、点击建议”没反应”的真相

改完必应后测试:点击搜索建议词毫无反应。排查发现网页搜索跳转在 V2/n1.java硬编码了 Mi 浏览器:

intent.setClassName("com.android.browser",
    "com.android.browser.fullsearch.FullSearchActivity");

而这台手机上 com.android.browser 根本没安装——startActivity 抛的异常被 catch 静默吞掉。也就是说:任何引擎都会无反应,跟换必应无关

四、改 smali:跳转 Via

不装 Mi 浏览器、也不做桥接 APK,直接改 QSB 的 dex:把 V2/n1.c() 方法整体替换为”对 Via 发标准 VIEW”:

.method public static c(Landroid/content/Context;Landroid/content/Intent;Ljava/lang/String;Ljava/lang/String;Ljava/util/HashMap;)V
    .locals 3

    if-eqz p2, :cond_0

    :try_start_0
    invoke-static {p2}, Landroid/net/Uri;->parse(Ljava/lang/String;)Landroid/net/Uri;
    move-result-object v0

    new-instance v1, Landroid/content/Intent;
    const-string v2, "android.intent.action.VIEW"
    invoke-direct {v1, v2, v0}, Landroid/content/Intent;-><init>(Ljava/lang/String;Landroid/net/Uri;)V

    const-string v0, "mark.via"
    invoke-virtual {v1, v0}, Landroid/content/Intent;->setPackage(Ljava/lang/String;)Landroid/content/Intent;

    const/high16 v0, 0x10000000
    invoke-virtual {v1, v0}, Landroid/content/Intent;->addFlags(I)Landroid/content/Intent;

    invoke-virtual {p0, v1}, Landroid/content/Context;->startActivity(Landroid/content/Intent;)V
    :try_end_0
    .catch Ljava/lang/Exception; {:try_start_0 .. :try_end_0} :catch_0

    :cond_0
    return-void

    :catch_0
    move-exception v0
    return-void
.end method

c() 的第二个参数恒为 {searchTerms} 替换后的完整 URL(仅被 K() 内部两处调用),替换安全。

工具链与部署(全程 root):

# Termux
pkg install apktool python
apktool d -f --no-res base.apk -o qsb_d   # 只解 smali
# Python 正则整块替换 V2/n1.smali 的 c() 方法
apktool b qsb_d -o qsb_new.apk           # 只重编 smali,资源原样

# 部署:覆盖 base.apk + 删 oat 缓存
cp qsb_new.apk /data/app/<pkg路径>/base.apk
chown system:system base.apk && chmod 644 && restorecon
rm -rf /data/app/<pkg路径>/oat
am force-stop com.android.quicksearchbox

几个关键点:

  • base.apk 无 fs-verity(lsattrV 标志),可直接替换文件
  • 签名校验只在安装时发生;重启后 PMS 读 packages.xml 缓存,不重校验
  • 替换 dex 必须删 oat/(art/odex/vdex),否则 checksum 不匹配;首次启动 JIT 稍慢
  • 备份原 APK 和 websearch 目录到 /data/local/tmp/,出问题覆盖回去 + 删 oat 即回滚

五、一键脚本

把整个流程沉淀成脚本:engine(换引擎)、browser(换跳转目标)、restore(回滚)、status(体检)。换引擎时自动 chattr 解锁/加锁、修 SELinux context;换浏览器时基于原版备份幂等重建 APK。支持 via/chrome/edge,自定义引擎 URL 也行。

qsb-tool.sh status                 # 当前引擎/跳转目标/保护状态
qsb-tool.sh engine bing            # 换成必应
qsb-tool.sh browser via            # 跳 Via
qsb-tool.sh restore all            # 一键回滚
qsb-tool.sh refresh-backup         # OTA 升级后刷新 APK 备份

一键脚本下载qsb-tool.sh

OTA 后一键恢复ota-recover.sh——应用商店/系统更新后跑一次即可(自动检测:资源完整+已patch→只刷引擎;原版→重打补丁;资源缺陷(小米更新包 bug)→自动降级分区版再打补丁)。

依赖:root(KernelSU/Magisk)+ Termux 的 python3apktool(仅 browser 命令需要)。放到任意目录,bash qsb-tool.sh help 查看用法。

五·五、LSPosed 模块方案(进阶,推荐长期使用)

改 dex 的痛点:OTA 升级后失效、云端换 hash 绕过锁。如果你装了 LSPosed(Zygisk 版),可以直接 hook,不改任何文件、永久免疫 OTA

  • Hook 点 1com.android.quicksearchbox.xiaomi.searchengine.f.h(Context)(返回 searchUrl)→ 强制替换为 Bing URL
  • Hook 点 2V2.n1.c(Context, Intent, String, String, HashMap) 后置 → 拦截 Mi 浏览器跳转,重定向到已安装的浏览器(默认 mark.via

用法:下载 qsb-hook.apk 安装 → LSPosed Manager 里启用 → 作用域勾选 com.android.quicksearchbox → 重启 zygote(软重启或重启手机)生效。

⚠️ 注意:混淆类名 V2/n1 随 QSB 版本变化,本模块仅验证于 QSB 13.1.0.08242(HyperOS 4 / Android 17)。其它版本若 hook 失败,LSPosed 日志会提示 hook ... FAIL,不影响原功能。

LSPosed 模块下载qsb-hook.apk

六、经验总结

  1. 改系统应用优先改数据,其次改 dex,最后才重打包:配置 JSON + chattr 保护就解决了 90% 的问题。
  2. 无反应 ≠ 没调用:先看代码里异常是不是被吞了,再看目标组件是否存在。
  3. sed 处理 XML 转义串是坑& 是特殊符号),字符串替换一律用 Python。
  4. OTA 是最大敌人:升级会覆盖 /data/app 的修改,脚本里 refresh-backup + 重放流程即可恢复。