越狱 RabbitOS 揭示秘密日志与 GPL 违规行为
近日一位名叫 David Buchanan 的作者发布了一个博客文章,讲解他对前段时间火爆的 AI 硬件设备 Rabbit R1 进行的逆向工程
相信现现在大多数人已经听说过 Rabbit R1。
这款 AI 设备受到很多人的批评,甚至有人指责公司进行 故意误导。现在 ebay 上出售的 Rabbit R1 即便是未拆封的设备在二手市场上的售价也远低于官方建议售价($200)。
:
动 机
在看到相关的头条新闻后,我开始逆向工程并分析在网上找到的 APK 文件(是的,“RabbitOS”其实只是一个在 Android 13 AOSP 上以信息亭模式运行的应用)。没有所谓的“本地 AI 模型”这些东西,所以一旦你搞懂了它用来和云端™通信的 API,你就可以用一个小小的 Python 脚本替代 Rabbit R1 硬件。我已经逆向工程了它的 API,并分享了我的发现(并不复杂,只是通过 websocket 传输 JSON 数据)。
顺便提一下,你可能已经看到关于暴露的 API 密钥的头条新闻。据说这些 API 密钥是从服务器端代码泄露的,并未存储在设备上(这点我可以证明)。
大约一周前,我在 eBay 上花了 122 英镑买了一台 R1,虽然这价格远远超过了它实际的价值。那么,为什么我还要明知它是垃圾还买下它呢?
事情是这样的,后来他们在应用更新中开始混淆代码,这让我感到很不爽!我喜欢玩猫鼠游戏(或者说龟兔赛跑?),游戏开始了。他们到底在隐藏什么秘密呢?
他们使用了一个商用混淆器,说实话,这个混淆器真的很棒,使得我纯粹依靠静态分析变得很难。所以,我决定入手一台 R1,亲自研究一下。是的,虽然我已经搞清楚了 API,但我不希望在未来的更新中被踢出局。这不仅仅是因为我希望能够查询 Rabbit 那些平庸的 API,更是因为这关系到我的自尊心。
此外,我以前从未研究过现代 Android 设备的启动安全性,所以这也是一次有趣的学习机会。
在对他们的混淆代码进行静态分析时,我发现他们编写了一套逻辑能检测出市面上的分析工具,比如 Magisk 和 Frida。如果这些工具被发现,程序就会拒绝运行。因此,我可能需要开发自己的分析工具。这听起来挺有挑战性的!当然,我也可以尝试绕过他们的反分析机制,但这样做就没什么意思了。混淆代码还会检查程序是否运行在 R1 设备上,而不是其他 Android 设备。我可以通过伪造或修改这些检查来绕过它们,但这样会变得很枯燥(而且通常这种方法不可持续)。
总的来说,我更愿意做一名逆向工程师,而不是反反逆向工程师。
R1 硬件
该设备使用了 MediaTek MT6765 SoC,配备 4GB 的 DRAM 和 128GB 的 eMMC 存储。对于一个将在 2024 年发布的新产品来说,选择这个 SoC 很有趣,因为自 2019 年起它就被发现存在 bootrom 漏洞。另外,该设备配备 128GB 的存储空间也显得有些奇怪,因为它本地并不会存储太多内容。也许他们本打算开发本地的机器学习(ML)模型,然后放弃了这个想法。或者,这可能只是他们以折扣价出售的剩余库存。
R1 的用户很快发现,虽然引导加载程序默认是“锁定”的,但你可以使用 mtkclient 来解锁,然后通过 custom ROM 刷机和/或 root 设备。而且这甚至不需要用到前面提到的 bootrom 漏洞,因为设备配置相对宽松。不过,我对运行自定义的 Android 系统镜像其实不太感兴趣,我更想深入研究一下工厂预装的固件。
注意:许多人把重新刷机称为“越狱”,对此我并无异议。所以如果看到有人谈论 R1 的越狱,他们可能指的是这个过程。
虽然启动的第一阶段是完全开放的,但后续阶段实施了 Android Verified Boot 2.0。虽然我可以解锁引导加载程序并安装 Magisk(一种通过修补 boot
分区来进行 root 的工具),但这有几个问题:
- 可能会破坏 OTA 差分更新(值得一提的是,Rabbit 定期提供这些更新)。
- 可能会被当前的反分析代码检测到。
- 将来更新可能会检测到,例如检查
ro.boot.verifiedbootstate
(该状态由 AVB 设定 ,根据系统的完整性而定)。
这三个问题虽然可以用变通的方法解决,但如果一开始就避免这些问题就更好了。我希望尽量运行“原生”代码,并只进行最小的修改来获取本地 root 权限,这样我可以在运行时检查应用程序。改动得越少,反分析逻辑就越不容易检测到变化。
我的解决方案是编写一个类似 “bootkit” 的程序。在解释其工作原理之前,我先详细介绍一下默认的启动过程。请注意,这部分内容可能会比较复杂。
启动链
启动链的所有逻辑来自 SoC 供应商 MediaTek。
启动过程始于 bootrom (即 brom),它不可更改地嵌入在 CPU 的硅片中,位于物理地址 0。bootrom 完成基本的硬件初始化后,从 eMMC 的 boot0 分区加载下一阶段程序(称为“Preloader”)到 SRAM。Preloader 是经过签名的,bootrom 在执行它之前会 验证签名。(备注:实际上,在 R1 上可能根本不会验证,还需要进一步研究……)
Preloader 负责初始化 DRAM,然后从 eMMC 的 GPT 分区中加载三个镜像到 DRAM:
tee
Arm Trusted Firmware (EL3)gz
GenieZone Hypervisor (EL2)lk
Little Kernel (EL1)
签名验证通过后,系统跳转到 LK 。
通过一些我尚不完全理解的过程,LK 会跳转到 ATF,接着 ATF 完成初始化后跳转到 GZ,然后 GZ 也完成初始化,最后再返回 LK,继续引导过程。我还没有深入研究 ATF 和 GZ,所以这部分可能有些偏差。
LK 是关键部分。它实现了前面提到的 Android Verified Boot,并且作为其中的一部分,dm-verity 提供了块设备的透明完整性检查功能。dm-verity 可以防止驻留在设备中并持有 root 权限的持久性 rootkit,从而保护设备安全。这个特性帮助 Android 用户在启动设备时,确保设备状态与上次使用时一致。
LK 会加载并验证 GPT boot
分区 (来自 eMMC 用户数据,但不要与 eMMC boot0
混淆),这个分区包含了 Linux 内核和 initramfs。如果引导加载程序处于“锁定”状态,那么当验证失败时,它将拒绝启动设备。如果引导加载程序处于“解锁”状态,即使验证失败,设备仍然会启动,但屏幕上会显示一个巨大且吓人的警告,提示设备不可信,同时设置各种标志来通知即将启动的内核这种情况 (也称为“橙色状态”)。如果 dm-verity 检查失败,即使引导加载程序是解锁状态,设备也无法启动 (会显示一个警告,并提示“按电源按钮继续”,但实际上并不能继续。这可能是一个漏洞!)
假如所有必要的检查都通过了,LK 最后会解压并启动 Linux 内核,内核随后执行 initramfs 中的 /init
,该过程将挂载其他分区并进行其他引导步骤 (目前我对此了解不多 - 只需要理解到 /init
这一步即可)。
另外,它使用 A/B 分区方案 (所以当我之前提到 boot
时,这实际上是指 boot_a
或 boot_b
,具体取决于当前哪个插槽是激活的)。
另外需要提到的是,bootloader 的锁定或解锁状态是存储在 seccfg
GPT 分区中的。seccfg
数据包含一些标志和该数据的加密哈希。这个哈希使用 SoC 的硬件 AES 引擎加密,起到类似签名或消息认证码(MAC)的作用。此外,frp
分区的最后一个字节决定是否允许 bootloader 解锁(例如通过 fastboot flashing unlock
命令进行解锁,成功后会更新 seccfg
)。
打破信任链
所有的安全启动链都有一个信任根。在这种情况下,信任根是烧录在 CPU 的 efuses 中的证书哈希,以及用于验证它的 bootrom 代码。然而由于之前提到的“kamakiri” bootrom 漏洞,信任链的第一个环节已经被不可逆地破坏。如果我们能够破坏第一阶段,那么原则上可以破坏所有后续阶段,不论单独来看它们有多“安全”。这种硬件从根本上无法对用户隐藏任何秘密(坦率地说,我希望所有的硬件都是这样的)。
不过,在这里我们不需要使用任何漏洞。brom 和 Preloader 引导阶段都提供 USB bootloader 模式,在 r1 的情况下,它接收通过 USB 传输的未签名 DA(“Download Agent”)映像,并允许我们从内存中执行它们(brom 阶段在 SRAM 中执行,Preloader 阶段在 DRAM 中执行)。
因此,我编写了一个 DA payload。这个 payload 被 Preloader 加载到 DRAM 后,按以下步骤执行:
- 通过 USB 将自定义的 Android
boot
镜像(包含 kernel 和 initramfs)加载到 DRAM。 - 在 Preloader 跳转到 LK 前的最后部分安装一个钩子。
- 跳回到 Preloader 继续正常的启动过程。
- Preloader 从 eMMC 加载并验证
tee
,gz
和lk
镜像。 - 在 Preloader 即将跳转到 LK 时,我们的钩子启动,利用这个机会在 LK 中安装自定义钩子和补丁。
- LK 继续正常启动,从 eMMC 加载并验证原始的
boot
分区。 - 我们提到的 LK 钩子之一是在 memcpy 操作时,将
boot
镜像从 “AVB” 代码复制到 “boot linux” 代码部分(它们似乎是独立的模块),我们用初始通过 USB 加载的自定义 boot 镜像进行替换。 - 另一个 LK 钩子在屏幕上显示一条自定义消息,以做风采展示。
- 最终,我们的自定义 kernel/initramfs 启动,同时所有完整性验证检查都顺利通过了!
LK 使用 MMU 提供内存保护,尽管这些映射都是身份映射(虚拟地址 == 物理地址),但这给我带来了一些麻烦。为了简洁起见,我省略了具体细节。实际上,由于在 LK 启动过程的不同子阶段,不同的内存范围可能是可访问的或被覆盖的,我不得不多次复制引导镜像。虽然这一逻辑几乎可以简化,但它确实有效。
在每个阶段,我的一般做法是不改动引导过程,让系统验证需要验证的数据,然后在最后一刻——在验证和使用之间——替换上我修改过的数据。就像这样:
对于自定义引导镜像,我使用了 flashable-android-rootkit 项目,这基本上是一个精简版的 Magisk。它将 intramfs 中默认的 /init
二进制文件替换为一个能注入最高权限用户空间服务(“payload”)的文件,然后继续引导过程。
用于修补启动映像的工具 magiskboot
来自 Magisk 项目。它通常在设备上运行,但在我的案例中,这并不可行,因为在我们越狱之前无法在 R1 上运行自己的代码。幸运的是,有一个叫 magiskboot_build 的项目,它允许在常规的 Linux 系统上编译和执行 magiskboot。
为了实现我的目的,我写了一个简易的 TCP 绑定 shell,虽然不太“隐蔽”(可能会被 Rabbit 应用检测到),但以后我可以改进它。
由于我发送的是一个自定义 boot
映像,理论上我可以对内核进行修补,但目前还不需要。
我也可以从源代码构建一个完整的自定义内核,但 Rabbit Inc. 选择违反 GPL2 许可证,不提供源代码。特别要注意的是,他们用于霍尔效应滚轮传感和摄像头旋转步进电机控制的驱动程序是闭源的,但却静态链接到 GPL 授权的内核映像中。这种违规行为对自由软件生态系统的破坏是巨大的,而 Rabbit Inc. 又从中获益。
传播有效载荷
我开始用 Python 编写属于自己的 USB 客户端软件,并不是因为现有的 mtkclient 有什么问题(它已经实现了所有必需的功能),而是想通过自己动手来深入理解其中的原理。当软件成功运行后,我决定将其移植到 js/WebSerial,纯粹是为了好玩。
现在,我有一个网页可以对物理连接的 Rabbit R1 进行越狱:https://retr0.id/stuff/r1_jailbreak/
为了配合糟糕的兔子主题双关语,我将这个越狱程序命名为“carroot”。
启动时,它看起来是这样的:
启动后,我们可以登录并快速查看:
$ rlwrap nc 192.168.0.69 1337
# id
uid=0(root) gid=0(root) groups=0(root) context=u:r:rootkit:s0
# getprop ro.boot.verifiedbootstate
green
如你所见,我们获得了 root 权限,并且系统认为它已经安全启动,甚至不需要篡改系统属性值。
请注意,因为我的 TCP shell 非常基础,默认情况下没有“#”提示符,我在此处添加了它以便更清晰地展示。
特权 “rootkit” SELinux domain 是作为 flashable-android-rootkit 项目的一部分设置的。
研究过程
在研究 R1 的启动链过程中,我从许多前辈研究者和开发者的工作中获益良多,特别是以下几个项目:
- bkerler/mtkclient - 该项目提供操纵 MediaTek 设备 brom/preloader/DA 接口的代码,并包含进一步学习的资源链接。
- cyrozap/mediatek-lte-baseband-re - 主要关注基带,同时包含硬件和启动相关的笔记以及更多资源链接。
- 吴港南/preloader运行流程–基于MT6765 (“Preloader operation process - based on MT6765”) - 包含一些有用的图表和 MT6765 的专门笔记。
- ng-dst/flashable-android-rootkit, LuigiVampa92/unlocked-bootloader-backdoor-demo, topjohnwu/Magisk - 这些项目及其相关文档详细介绍了从
/init
开始的启动过程的后期阶段。 - RabbitHoleEscapeR1/r1_escape - 提供在 R1 上刷入“定制 ROM”工具和操作说明。
查看 iFixit 的 拆解照片时,你可以发现标有 TX 和 RX 的测试点。这些是 UART 测试点,在我的研究过程中非常有帮助。设备的逻辑电平是 1.8V,虽然好像能够耐受 3.3V(至少,我的设备在 3.3V 下没有损坏)。在引导链的每个阶段,设备都会通过 UART 记录调试信息(brom 阶段为 115200 波特率,其后为 921600 波特率)。
此外,我还通过修补 Linux 内核的命令行参数,使内核日志能够输出到 UART,具体如下:
earlycon console=ttyS1,921600
通过这些补丁,我能够收集整个引导过程的日志。
在开发越狱工具时,我利用自己的代码发出了 UART 日志,用于进行“printf 调试”。
P.S. 我好像发现了一个未焊接的 JTAG 接口,不过还没有进一步研究。
P.P.S. 在重置按钮旁边有一个测试点(可以通过 SIM 插槽接触,这个测试点最靠近电路板边缘),重置期间可以把这个点接地,以强制设备进入 brom 的 USB 模式。
发 现
他们到底在对我们隐藏什么?
老实说,我现在还没有发现特别有趣的东西。毕竟分析才刚刚开始!我之所以分享这个越狱工具,是希望有更多人能参与到我的分析中来。
我注意到的一件事是,他们把 所有 的日志都记录在了内部存储的文本文件中:
:/storage/emulated/0 # ls -al ./Android/data/tech.rabbit.r1launcher.r1/files/logs/
总计 7140
drwxrws--- 2 u0\_a66 ext\_data\_rw 4096 2024-07-07 00:52 .
drwxrws--- 3 u0\_a66 ext\_data\_rw 4096 2024-07-04 22:11 ..
-rw-rw---- 1 u0\_a66 ext\_data\_rw 671954 2024-07-05 01:37 2024-07-01.log
-rw-rw---- 1 u0\_a66 ext\_data\_rw 1472020 2024-07-04 23:40 2024-07-04.log
-rw-rw---- 1 u0\_a66 ext\_data\_rw 782800 2024-07-06 16:45 2024-07-05.log
-rw-rw---- 1 u0\_a66 ext\_data\_rw 1747449 2024-07-07 00:52 2024-07-06.log
-rw-rw---- 1 u0\_a66 ext\_data\_rw 2565224 2024-07-07 03:47 2024-07-07.log
在 7 月 7 日,我在 Rabbitude 社区的 Discord 频道中公开提到了这个情况。当时我只是觉得有趣,他们为什么用详细的日志记录填满了 128GB 的存储空间。
但我和其他人仔细研究后,发现事情其实很让人担忧。
这些日志包含了:
- 你的精确 GPS 位置(会被发送至他们的服务器)。
- 你的 WiFi 网络名称。
- 附近基站的 ID(即使没有插入 SIM 卡,这些 ID 还是会被发送至他们的服务器)。
- 你的公网 IP 地址。
- 设备用于与 Rabbit 后端 API 进行验证的用户 token。
- Rabbit 与你对话内容的 Base64 编码 MP3 文件(还有对应的文本记录)。
令人担忧的原因有:
-
记录这么详细的数据完全没有必要,尤其在这个设备并没有什么硬件安全防护的情况下。我真不敢想象他们在服务器端还记录了什么!
-
设备上没有任何可以让用户恢复出厂设置的途径,使得这些日志几乎是永久的。如果结合活跃的二手市场,这就是埋下了隐患。设备上甚至连个“退出”按钮都没有!
幸运的是,在我开始使用之前,我用 mtkclient
将我买到的这台 R1 恢复了出厂设置。
幸好,在我写这篇文章的过程中,RabbitOS 推出最新更新 (v0.8.112) 解决了这个问题。他们不仅减少了日志记录,还增加了出厂重置的选项。
这次的响应速度非常快,我第一次看到 Rabbit 在用户隐私和安全问题上有所主动,而不仅仅是应对负面新闻的报道。
其实,我自己并没有将这个问题报告给他们。首先,当时我还没完全考虑到它的影响;其次,是因为根据他们以往对待安全问题的态度,我并不指望他们会认真对待。此外,我那时也在忙于开发自己的越狱工具。不过,我想应该是其他人向他们报告了问题,他们的回应确实让我感到惊喜。我希望这次能够标志着他们在安全问题上的态度有所改进。他们没有试图淡化这个问题,反而是强调了它的严重性,超出了我的预期。
当然,如果他们一开始就不记录这些信息会更好,但我们也知道,这种“快速迭代,随意对待个人隐私”的理念,已经成为现代科技行业的常态了。
这进一步坚定了我的信念:消费者应该拥有完全检查和修改自己设备上代码的权利。当供应商不允许这种操作时,我会采取措施来纠正这种情况。
AOSP “定制”
当新闻爆出 RabbitOS 只是一个运行在 Android 13 上的应用时,Rabbit 的公关回应是将其称为“经过深度定制的 AOSP ,并进行了底层固件修改”。
那么,这些所谓的定制修改究竟是什么呢?
如我之前所说,我的分析才刚刚开始。但目前我发现,他们仅仅是禁用了所有可能影响他们单应用亭模式体验的 Android 功能。例如,没有导航栏、通知栏等。他们甚至还采取了一些(但无效的)措施来阻止用户启用 ADB(有一个名叫“Judy”的应用在后台运行,其唯一目的是发现 ADB 正在运行时将其禁用)。
在 7 月 4 日,@MarcelD505 分享了一个非常巧妙的 “kiosk 逃脱” 技巧(镜像)。这个技巧从 wifi 认证门户的登录网页浏览器开始,最终进入 Android 的系统设置应用。在系统设置中,你可以做各种有用的调整(虽然在演示视频中没有显示出来,但你也可以利用 Android 的无障碍设置来调整系统字体大小)。
在最新的更新中,Rabbit 通过完全移除设备中的 Android 系统设置应用程序来“修复”了这个问题。我肯定他们会说这样做是为了“提高安全性”或类似的理由。
如果你的产品安全性是依赖于不让用户访问自己的系统设置,那就说明你的设计在某些方面出了问题。
的确,这是一个相当定制化的 AOSP,但到目前为止,我所看到的只是试图去掉现有功能。
让我真正不满的是,他们其实有很多可以改进的地方。一个不只是“一个应用程序”的合理理由是,普通手机上的普通应用程序必须在 Google 或 Apple 的封闭生态系统内运行,从而限制了一些潜在有用的集成功能。例如,普通应用程序没有权限启动电话并将程序生成的音频传输到电话中。但目前,RabbitOS 也不能做到这一点。这甚至没有出现在他们已经非常宏大的 路线图上。
我还没有看到任何技术上的理由表明 RabbitOS 不能作为普通手机上的一个应用。
也许你喜欢专用设备完成单一任务的想法,比如成为 AI 助手领域的“iPod Classic”。那么你可以直接这么说!没有必要编造什么高深莫测的术语来搪塞。
给予普通 R1 用户的建议
如果你担心自己的设备被擅自“越狱”了,只需把它关机再重新开机。如果它能够正常启动,并且屏幕上没有任何警告信息,你 可能 是安全的。(编辑:除非 brom 已被设置成允许从 eMMC 启动未签名的 Preloader 镜像,在这种情况下,你还是不能完全确定 - 我很快会测试这个功能)
理想情况下,你可以使用供应商提供的固件映像刷新安全的官方固件,但供应商没有提供这种工具。你应该向他们询问如何获得这些工具!
永远不要将你的 R1 单独搁置,因为任何储存在上的数据都可能被懂行的人轻易提取。
如果你打算出售(或捐赠,或处理)你的 R1 设备,请先使用新增的设置选项将其恢复出厂设置。
结 论
那么总结一下:
- Rabbit R1 并没有什么特别的硬件:除了滚轮和旋转摄像头之外,它就是一个普通的 MediaTek Android 设备。
- 他们对 AOSP 的“定制”主要是移除一些功能以更好地执行单一应用的亭模式。
- Rabbit R1 的启动链安全性较差,这意味着你不能在无人看管的情况下安全地放置该设备。
- Rabbit Inc. 正在违反 Linux 内核的 GPL 许可协议。
- 以上几点并不是我的新发现,我只是更清楚地描述出来。
- 我发布了一个实验性的 Tethered Jailbreak 工具 tool,帮助研究人员访问他们自己的 R1,最终让高级用户能够扩展设备的功能。
- 发现 R1 会将大量用户信息记录到内部存储中,普通用户无法删除。Rabbit Inc. 在本文发表前迅速修正了这一问题,但他们无法修复自 2019 年以来已知的 bootrom 问题,因为 bootrom 是不可更改的。
结 尾
在 7 月 12 日,我询问了 Rabbit Inc. 是否对本文内容有任何评论,并明确问及他们是否有计划遵守 GPL 许可协议(我知道我不是第一个这样问的人)。
本文不构成安全问题披露(我并没有提及任何 新的 安全问题),但我认为给他们一个发表声明的机会是公平的,尤其是关于 GPL 合规性的问题。
截至 7 月 17 日,尚未收到他们的回应。