Titanium Browser 扩展注入完整指南:bundle.py 如何解析 CRX 头部与 bundled.json 索引机制
【免费下载链接】android-titanium-browserSecure open-source Android browser with support for extensions项目地址: https://gitcode.com/gh_mirrors/an/android-titanium-browser
Titanium Browser 是一款基于 Chromium 的安全开源 Android 浏览器,其最大亮点之一就是对浏览器扩展(Extensions)的原生支持。本文带你揭秘它内置扩展的注入原理:构建脚本 bundle.py 如何逐字节解析 CRX 文件头部、提取扩展 ID,并通过 bundled.json 索引文件,让扩展在浏览器启动时自动"落地"到扩展目录。
一、什么是"内置扩展注入"?
普通浏览器安装扩展需要用户在商店里逐个下载。而 Titanium Browser 支持把扩展直接打进 APK,浏览器首次启动时自动从安装包资产(assets)中把 CRX 文件释放到扩展安装目录,并写入一份"外部扩展偏好"(external prefs)索引。
整个机制由三个角色协作完成:
| 角色 | 文件 | 职责 |
|---|---|---|
| 构建期打包器 | extensions/bundle.py | 下载 CRX、解析头部、维护索引 |
| 构建配置 | extensions/BUILD.gn | 声明哪些资产随 APK 打包 |
| 运行时注入器 | extensions/stage_bundled_extensions.inc | 启动时释放 CRX 并合并偏好索引 |
二、CRX 文件结构速览:头部藏着扩展 ID
CRX 是 Chrome 扩展的安装包格式,本质是 ZIP,外面包了一层头部。它的结构大致是:
魔数 "Cr24"(4字节) | 版本号(4字节) | 头部长度(4字节,小端) | 头部数据(protobuf) | ZIP 数据bundle.py 只读取了文件的最开头,就精准定位到了头部数据:
data = open(path, "rb").read() header = data[12:12 + struct.unpack("<I", data[8:12])[0]]data[8:12]按小端序解出一个 32 位整数,就是头部的长度;从偏移 12 开始截取,就得到了纯头部。
变长整数(varint)遍历字段
头部内部是 protobuf 风格的键值结构,每个字段由"字段号 + 长度"两个 varint(变长整数)前缀引导。bundle.py 第 10-18 行 实现的varint()函数负责一次读出一个 varint:
def varint(buf, i): value = shift = 0 while True: byte = buf[i] i += 1 value |= (byte & 0x7F) << shift shift += 7 if not byte & 0x80: return value, i每字节的低 7 位是数据,最高位是"是否还有后续字节"的标志位。第 34-39 行的循环 不断消费"键 + 长度",用i += length跳过字段值,直到命中字段号 10000(key >> 3 == 10000)——这正是 CRX 头部中存放扩展 ID 的字段。
从 16 字节恢复 a-p 扩展 ID
找到目标字段后,第 41 行 用一段精巧的代码把 16 字节的公钥哈希还原为扩展 ID:
ext_id = "".join(chr(97 + n) for b in header[i + 2:i + 18] for n in (b >> 4, b & 15))每个字节被拆成高、低两个 4 位半字节,分别映射到a~p(97 是a的 ASCII 码),16 字节恰好生成 32 个字符——这就是你在 chrome://extensions 里看到的扩展 ID。
最后,脚本打开 CRX(即 ZIP)读取 manifest.json 中的version字段,打印出文件名: 扩展ID 版本号,供构建日志核对。
三、bundled.json:一份"扩展清单"索引
拿到扩展 ID 和版本后,bundle.py 第 45-49 行 把它写入 bundled.json:
index[ext_id] = {"external_crx": crx, "external_version": version}索引结构形如:
{ "ipalhnddbpatamejdfnbcphjbiaegm": { "external_crx": "uBlock.crx", "external_version": "1.71.0" } }这里有两个关键设计:
- 去重更新:写入前会先删掉指向同一 CRX 文件的旧条目,保证换包不产生脏数据;
- ID 冲突保护:如果新扩展的 ID 已存在于索引中,脚本会直接断言报错(
assert ext_id not in index),提示"需要指定 key"——因为同 ID 意味着两个扩展会互相顶替,必须显式处理。
四、BUILD.gn:把 CRX 装进 APK 资产
bundle.py 第 51-59 行 还会自动编辑 BUILD.gn,把新下载的 CRX 追加到renaming_sources/renaming_destinations中。这样 GN 构建时,CRX 文件会连同bundled.json一起被打进 APK 的assets/extensions/目录:
android_assets("extension_assets") { disable_compression = true renaming_sources = [ "bundled.json", ] renaming_destinations = [ "extensions/bundled.json", ] }其中disable_compression = true很讲究:未压缩的资产在 APK 内可以按偏移直接内存映射(mmap),运行时读取零拷贝、零解压。
五、运行时注入:启动即"释放"扩展
真正的注入发生在浏览器启动阶段,核心逻辑位于 stage_bundled_extensions.inc:
- 读取索引:StageBundledExtensions() 仅当目标路径是
DIR_EXTERNAL_EXTENSIONS(外部扩展偏好目录)时才工作,先通过 OpenApkAsset() 打开assets/extensions/bundled.json; - 逐个释放 CRX:遍历索引中的每个条目,取出
external_crx相对路径(并拒绝任何../越界或绝对路径),调用 StageBundledAsset() 把 CRX 从 APK 资产写到磁盘; - 增量写盘:如果磁盘上已有同名同大小的文件,直接跳过,避免每次启动都重复 IO;
- 合并偏好:最后
prefs.Merge()把整份索引并入外部扩展偏好——Chromium 的扩展偏好加载器读到这些条目后,就会像对待"官方预装扩展"一样自动加载它们。
这个 inc 文件并不是独立存在的:构建脚本 patch.sh 第 15-16 行 会把它注入 Chromium 源码的external_pref_loader.cc,在标准的外部偏好加载流程末尾追加一次StageBundledExtensions()调用;第 13 行 则把extension_assets目标挂到chrome/android/BUILD.gn的资产列表里。整套改动通过 sed 补丁完成,不侵入上游代码结构。
六、动手体验:给浏览器"装"一个内置扩展
如果你想自己往包里加扩展,整个流程就三条命令级别的事(以运行bundle.py为例):
python3 extensions/bundle.py <目标目录> <扩展名> <CRX下载地址>脚本会自动完成:下载 CRX → 解析头部取扩展 ID → 更新 bundled.json 索引 → 追加 BUILD.gn 条目,并在控制台打印xxx.crx: <扩展ID> <版本>供你核对。之后正常构建 APK,扩展即"内置"完成。
日常使用中,你也可以不依赖内置机制:打开chrome://extensions开启Developer mode,用Load unpacked通过存储访问框架(SAF)加载本地扩展文件夹,Manifest V2 扩展同样受支持,详见 README.md。
七、设计亮点小结
- 零依赖解析:用标准库
struct+ 手写 varint,几十行代码就逆向了 CRX 头部协议,无需任何 protobuf 库; - 索引与实体分离:
bundled.json只记录"ID → 文件 → 版本"三要素,体积小、易审计,运行时再按需释放实体; - 安全防线:扩展 ID 冲突断言、路径越界检查(
ReferencesParent/IsAbsolute)、同名同尺寸跳过写盘,层层把关; - 补丁化集成:通过 patch.sh 的 sed 钩子挂接 Chromium 标准偏好加载流程,上游升级后适配成本极低。
这套"构建期解析 + 运行时释放 + 偏好合并"的注入机制,正是 Titanium Browser 能在手机上带来桌面级扩展体验的关键一环。如果你想深挖更多实现细节,可以从 extensions/ 目录的完整源码读起,或参考 args.gn 中完整的 GN 构建配置。
【免费下载链接】android-titanium-browserSecure open-source Android browser with support for extensions项目地址: https://gitcode.com/gh_mirrors/an/android-titanium-browser
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考