概述:

去年,流行的 E-Ink 平板供应商 Ratta Software 发布了 SuperNote A6 X2 Nomad - 一款运行 Android 11 的 7.8 英寸平板电脑。 作为效率爱好者,我们在 2024 年 7 月购买了一台,目的是用于其既定用途:记笔记和阅读学术论文。然而,作为骨子里的黑客,不到 24 小时我们就彻底放弃了这个想法,并决定研究它。

接下来是一篇博客文章,详细介绍了我们如何将一个漏洞和一些错误配置串联成一个可远程安装的、0-click 的 rootkit。与受害者在同一网络上的恶意攻击者可以在没有任何用户交互的情况下完全入侵目标设备。 编辑:发布后,此问题被分配给 CVE-2025-32409

信息收集:

这项研究始于一次无辜的 Nmap 扫描,只是为了看看在默认配置下设备上是否有任何有趣的东西在监听。果然,有一个结果非常突出:

如上所示,我们发现端口 60002 处于打开和监听状态。Nmap 无法直接识别该服务,因此我们决定通过从 Ratta Software 的 "Updates" page 获取设备的固件镜像来进一步调查这个神秘的端口。

固件未加密,我们能够挂载各种文件系统镜像,并在其中搜索与该端口号相关的任何内容。

这使我们找到了 SuperNoteLauncher.apk ,我们将其放入 jadx 并开始逆向工程。

逆向 SuperNoteLauncher

定位端口:

在 jadx 中打开 apk 后,我们首先搜索了该端口号被明确引用的位置。如下所示,这使我们找到了一个名为 COMMAND_RECEIVE_FILE_PORTstatic final int

通过查找交叉引用,我们最终找到了它的使用位置:com.ratta.supernote.wifip2p.receive

识别服务:

此时,我们的目标是更好地了解在 60002 上运行的服务。我们可以在下面的屏幕截图中看到,当端口打开时,在通过 ServerSocket 收到某些内容后,会触发一些函数。具体来说,感兴趣的代码位于 DeviceThread 类中。

DeviceThread 实现了 Runnable,其 r un() 函数非常庞大。也就是说,快速浏览一下它表明我们可能正在处理自定义 HTTP 服务器,因为错误消息会传回给客户端。

run() 的第一行将 socket 传递给 getDeviceName()

那些眼光敏锐的人可能可以分辨出这段代码正在做什么:解析我们的 HTTP 服务器期望的自定义标头。这使我们对 run() 中观察到的大量奇怪行为有了一定的认识 - 基于客户端传递的自定义标头,触发了不同的操作。

例如,我们可以通过提供大于 1 的 version 标头来触发 501 错误代码

并且,当我们发送请求时,我们实际上会收到一个弹出错误,提示 SuperNote 设备不是最新版本

文件上传

所有这些都很有趣,但最让我们感兴趣的是类名:RecieverManagerWiFiP2PService。未经身份验证的设备到设备文件共享听起来像是一个潜在的巨大攻击面。

鉴于我们至少知道如何与服务器交互,此时我们继续模拟了一个 python 客户端,该客户端仅发送一个带有附加文件和所有必要标头的 POST 请求......文件出现在设备上的 INBOX 目录中!

调查路径遍历:

在弄清楚如何将文件放入 INBOX 目录后,我们做了所有优秀的的安全研究人员都会做的事情:在我们的 payload 中添加了一些 "dot-dot-slashes",看看会发生什么。具体来说,Nomad 有一个名为 EXPORT 的目录,该目录与 INBOX 处于同一级别。可以在 UI 中访问此 EXPORT 目录,从而使我们能够快速验证 payload 是否有效。 我们的新 headers dict 包括路径遍历 payload,如下所示:

headers = {"version": "1", "content-length": "1234", "name": "../../../../../../../../sdcard/EXPORT/testfile.txt", "devicename": "testdevice"}

你猜怎么着?它奏效了!

但是,看到附加在我们文件名末尾的那个讨厌的 "(1)" 了吗?是的,这将被证明是此处利用过程中的一个大问题。

漏洞利用计划:

到目前为止,我们还没有完全诚实。在开始之前,我们对如果能够找到任意文件写入,漏洞利用 可能 会是什么样子有一些了解。这个想法源于我们在信息收集中遇到的 3 个关键信息。

  1. SuperNote 在固件更新页面上的说明明确要求您将下载的 zip 文件放入 EXPORT 目录中
  2. 如果在 EXPORT 目录中找到有效的更新镜像,则手动固件更新将在热插拔事件或重新启动后自动开始
  3. 一位研究人员 研究了上一代 SuperNote 设备 并发现固件镜像是使用公开可用的调试密钥签名的,并且引导加载程序默认情况下是解锁的……很好。我们确认,虽然密钥在新设备上已重命名,但它们仍然相同。

因此,我们认为我们可以执行以下操作:

  1. 创建一个后门固件镜像,并使用公开可用的调试密钥对其进行签名
  2. 使用任意写入将下载直接放入“EXPORT”目录
  3. 在正常的设备使用过程中会自动启动更新,从而安装我们的恶意 rootkit。

从理论上讲,这都是好的,但有一个主要问题

问题

还记得附加在我们文件名上的 "(1) " 吗?是的,这就是它会让我们失望的地方。您看,在 EXPORT 目录中扫描更新的服务具有以下代码:

文件名必须完全是 update.zip...而不是 update(1).zip。因此,我们需要弄清楚 为什么 额外的数字会附加到我们的路径遍历 payload 中,以及我们如何摆脱它。

为什么文件名会更改?

在对 launcher apk 进行更多的逆向工程之后,我们最终确定了问题的根本原因。虽然通过屏幕截图包含太多代码,但高级文件接收逻辑如下:

  1. 服务器首先在其应用程序目录中的 "/ receiver _file_cache/file_name " 路径下创建一个文件
  2. 然后,它将传入的数据流复制到该新创建的 "receiver _file_cache/file_name " 路径中的文件中
  3. 当它到达传入数据流的末尾时,它会创建一个新文件,名为 INBOX/file_name
  4. 它将内容从 "receiver _file_cache/file_name" 复制到 "INBOX/file_name"
  5. 最后,它删除缓存的文件。

问题在于继续对 所有 操作使用攻击者提供的文件名。因此,对于我们最初的攻击计划,设备上实际发生的事情是:

  1. 服务器接收并创建 "/ receiver _file_cache/../../../../sdcard/EXPORT/update.zip " 文件,认为这应该是缓存文件
  2. 然后,在完成接收数据后,它会创建 INBOX 文件,但实际上会再次尝试创建 "/INBOX/../../../../sdcard/EXPORT/update.zip "

在每次文件创建事件之前,都会运行以下代码:

如图所示,如果名称已经存在于要写入新文件的位置,则会在文件名中添加 "1"(或下一个后续数字)。因此,第二个文件创建事件不是创建 "/EXPORT/update.zip ",而是创建 "EXPORT/update(1).zip "。然后,该过程的其余步骤发生:文件内容被复制,并且原始文件("/EXPORT/update.zip ")被删除,仅留下重命名的文件。

通过 "竞争条件" 查找命名问题绕过

因此,鉴于我们当前的策略,我们陷入了困境。我们需要弄清楚如何以正确的名称将我们实际的更新文件放入 EXPORT 中。此时,我们花费了大量时间尝试找到实际的命名绕过,这自然没有奏效。

确实 奏效的是利用逻辑“竞争条件”......从技术上讲,它_实际上_不是漏洞或传统的竞争条件,但我们可以利用它来发挥我们的优势。

您看,合法的更新文件为 1.1GB 大。这意味着,传输需要花费大量时间。其他三个因素也对我们有利:

  1. 首先创建文件,然后将数据流逐字节写入其中。
  2. 传输完成后,将创建第二个文件,然后开始复制。
  3. 服务器是多线程的,这意味着它可以同时接收多个文件

我们可以使用这三个事实来稍微修改我们的漏洞利用。

修订后的漏洞利用策略:

使用我们对底层服务器逻辑的理解,我们可以使用以下技巧来满足我们的命名要求。

  1. 创建一个非常小的虚拟文件,名为 update.zip
  2. 创建一个合法的 update.zip 文件,其中包含我们的恶意后门,并使用公开可用的开发密钥进行签名
  3. 首先发送非常小的虚拟文件
  4. 紧随其后发送合法的 update.zip 文件。

如果这样做,设备上会发生以下情况(注意 - 线程一是_斜体_,线程二是粗体):

  1. 在缓存/初始接收步骤中,虚拟文件会创建 EXPORT/update.zip
  2. 在缓存/初始接收步骤中,真实更新会创建 EXPORT/update(1).zip
  3. 在复制步骤中,虚拟文件会创建 EXPORT/update(2).zip
  4. 完成复制步骤后,虚拟文件会删除 EXPORT/update.zip
  5. 在复制步骤中,真实更新会创建 EXPORT/update.zip
  6. 完成复制步骤后,真实更新会删除 EXPORT/update(1).zip

因为我们的虚拟文件比真正的更新文件小得多,所以在接收到有效更新完成之前,它会完成缓存、复制和删除步骤。这在有效更新的复制步骤开始时会使 EXPORT/update.zip 名称可用。

因此,在我们的漏洞利用运行后,我们留下了一个正确命名的恶意更新镜像。我们还有虚拟文件复制操作遗留下来的 "EXPORT/update(2).zip" 构件,但我们并不关心它。如果我们想要清理,可以在安装 rootkit 后执行。

后门固件镜像

我们不会深入探讨此步骤,但经过一番 Google 搜索后,我们找到了所需的开发密钥。从那里,我们使用 flashable-android-rootkit 创建了一个后门,并编写了一个简单的 C 反向 shell payload。要重新打包固件,我们遵循了 先前研究 中推荐的方法,并使用了 Multi Image Kitchen(注意:找到正确的 JDK 版本来使 Multi Image Kitchen 正常工作是此漏洞利用的较难部分之一)。

将它们组合在一起

一旦 payload 处于正确位置,它将在设备的正常操作期间自动安装。安装程序会在热插拔事件(usb-c 插入或拔出设备)或重新启动期间检查 EXPORT 目录。因此,您所要做的就是坐下来,等待,并希望用户不会在 EXPORT 中找到可能可疑的 update.zip 文件。

请注意,在热插拔事件之后,用户_确实_会收到有关更新的提示。但是,这是一个选择退出提示,这意味着除非单击“中止”,否则更新将在 30 秒内安装。如果一切按计划进行,您将获得以下结果:

披露时间表:

关于 PRIZM Labs:

PRIZM Labs 是一家主要专注于国防和航空航天领域的安全公司。该团队目前与 Catalyst Campus 合作,参与由太空部队赞助的加速器,以开发用于关键任务软件的二进制分析工具。 PRIZM 提供一系列服务,从产品安全评估到定制工具开发。无论您是制造卫星还是电子墨水平板电脑,我们都能满足您的需求。 有兴趣参加免费的探索电话会议吗? Contact Us

© 2025 PRIZM Labs. 版权所有