越狱我车上的导航系统,只为能修改时间

一辆 2014 款雷诺 Latitude 在未插入导航 SD 卡时拒绝让你设置时钟。车主逆向分析了固件,串联了一个路径遍历漏洞和 core_pattern 技巧,最终拿到了 root 权限。

中文
复制
汽车中控屏的蓝色界面,中间一行爱沙尼亚语 Kaarte ei leitud!(未找到地图),底部是蓝牙音频条,曲目名 Romeo,右上角显示时间 18:13

我开的是一辆 2014 款雷诺 Latitude。这车谈不上高级,也开始显老了,但能把我从 A 点送到 B 点,其他方面也都还凑合,所以我一直没什么可抱怨的。

买车的时候,前任车主问我要不要随车带一张“导航地图 SD 卡”。

他说这车好像必须插上这张 SD 卡才能调时钟,我不太信。倒不是觉得他在骗我,可能是他没找到正确的设置菜单。毕竟网上同款车机的视频里,菜单中明明就有时钟设置这一项。

由于 SD 卡里的地图显然已经非常过时、根本用不了,我决定不要,前任车主就把它扔了。

我也没觉得少了什么。中控屏上确实显示着一大行“No maps found!”,占了屏幕的大半,但我也没太在意。

车机屏幕显示爱沙尼亚语的“No maps found!”

和大多数欧洲国家一样,爱沙尼亚实行夏令时。最近我们刚从夏令时(UTC+3)切换到“冬令时”(UTC+2),中控屏上的时间就差了一个小时。我钻进设置菜单,指望能轻松找到“设置时间”这一项。

并没有。

本该有“设置时间”的菜单里根本没有这个选项。我拿菜单和网上视频里的对比了一下,这个选项就是不见了。中控台上有两个“Settings”按钮,其中一个能用,但只能改各种蓝牙和电话簿设置,时间日期设置连影子都没有。第二个 Settings 按钮完全没反应,每次按下去只会弹出一条“No maps detected!”。

我大概花了一个小时翻遍各种菜单,按各种按钮、试各种组合键,指望能误打误撞进到正确的菜单,又上网搜这款车机的信息,只因为我不肯相信居然没有改时间的选项,而且我肯定是漏掉了什么明摆着的东西。

那就直接逆向吧

我还是不甘心,决定把固件拉下来翻一翻,指望能找到点线索,告诉我该怎么打开正确的设置菜单。

好在走这条路的不止我一个,已经有人对这款机器做过逆向工程(https://github.com/andronedev/carminat-tomtom-live-re/blob/main/README.md)。文章是法语的,而且看起来大量借助了 LLM,但它确实给我指对了下载更新文件本身的 URL。

解压之后,更新文件里有一个 ttsystem 文件,实际上是一个 ext3 文件系统,装的就是设备的根文件系统,正是我要找的东西。这台机器(TomTom Carminat LIVE,代号 beaumont)基于 Samsung S5P6440 SoC,跑的是 TomTom 基于 Linux 2.6.28.10 定制的系统。

逆向了一段时间后,我得出结论:前一位机主说得完全对。不插导航 SD 卡,根本没有任何办法进入正确的设置菜单。事实上,指向 Settings 小程序的菜单定义本身就是从 SD 卡里读出来的;没有一张写着正确菜单定义的卡插在里面,机器压根不知道怎么进入 Settings 小程序。

TomTom(或者 Renault)做出这种设计,我到现在也想不通。

更糟的是,这还必须是一张正版 SD 卡。系统有 DRM 校验,会拒绝 CID 不对的 SD 卡(CID 是烧在卡里的唯一 ID),文件签名也绑定在这个特定 CID 上,所以我连把文件拷到空白 SD 卡上都做不到。我猜这套 DRM 是为了防止盗版地图。

我不要地图。我有手机可以导航。我只想改个时间,就这么点事。

我在网上查了查,发现有些第三方在卖我大概需要的那张卡,但算上寄到爱沙尼亚的运费,差不多要 60 欧元,这钱我实在不想掏。

行吧。那我就自己 root,自己动手。

有没有捷径?

我在网上找不到任何人越狱过这款机器。只翻到几个老论坛帖,里面的图片链接早就失效了,内容也只是些关于这台机器的泛泛之谈,普遍的共识是:很难 root。

我本以为 2011 年的 Linux 2.6 系统不会难搞到哪里去,就这么天真地进去找软柿子了。

第一个想法是直接改 ext3 更新镜像本身。看上去挺简单:挂载文件系统,改几个 init 脚本让它干我想干的事,再拷到一张空白 SD 卡上,让设备读到改过的镜像并刷进去。

系统对原版、未修改的 ttsystem 镜像会正常弹出确认提示,安装毫无问题;但我改过的镜像怎么都更新不上去,也完全不知道为什么。我查了更新文件里自带的更新逻辑,找不到任何会拒绝我这个镜像的理由。

于是我搭了个 QEMU 模拟器,配了一个同样基于 2.6 版本的自定义内核、一些补丁,还有一个很简单的 CAN 模拟器,好让系统真的能启动。虽然它启动得很勉强,每 3 秒就想重启一次,我干脆忽略 reboot 系统调用把它 patch 掉了。

我用这套环境测试安装改过的更新镜像。然后才意识到,设备启动去做更新的那个 recovery 分区我其实没有,于是推断中间一定还有一层我没看到的校验。

再仔细看看 ttsystem 镜像,基本也印证了这一点:后面追加了一大段高熵数据,接着是一些全零块,再接着是 40 个高熵字节。我猜这是某种签名(40 字节可能是 DSA with SHA-1),但拿不到真正做校验的二进制,也就没法搞清楚该怎么攻击,甚至不知道能不能攻击。

回到起点

之前我一直不太愿意花力气去搭模拟器,结果它出乎意料地好用:各种攻击都能在上面试,也能借此摸清系统的整体行为。

比如我发现大部分分区要么以只读方式挂载,要么是 noexec,要么两者兼有。以下是运行中系统的挂载情况:

rootfs on / type rootfs (rw)
none on /sys type sysfs (rw,nosuid,noexec)
none on /proc type proc (rw,nosuid,noexec)
/dev/mmcblk0p5 on /content type ext3 (rw,nosuid,nodev,noexec,noatime,nodiratime,errors=continue,data=ordered)
/dev/loop0 on / type ext2 (ro,noatime,nodiratime,errors=continue)
none on /dev type tmpfs (rw,nosuid,noexec,size=1024k)
none on /tmp type tmpfs (rw,nosuid,noexec,size=2048k)
none on /var type tmpfs (rw,nosuid,noexec,size=5120k)
none on /dev/pts type devpts (rw,nosuid,noexec,gid=5,mode=620)
none on /var/log type tmpfs (rw,nosuid,noexec,size=1024k)
/dev/mmcblk0p3 on /mnt/flash type ext3 (rw,sync,nosuid,nodev,noexec,errors=continue,data=ordered)
/dev/mmcblk1p1 on /media/sdcard type vfat (rw,sync,nosuid,nodev,noexec,noatime,nodiratime,fmask=0022,dmask=0022,codepage=cp437,iocharset=iso8859-1)
/dev/loop1 on /media/loopback type ext3 (rw,nosuid,nodev,noexec,noatime,nodiratime,errors=continue,commit=3600,data=journal)
none on /mnt/movi type unionfs (rw,nosuid,nodev,noexec,noatime,nodiratime,dirs=/media/loopback=rw:/media/sdcard=ro)

值得注意的是,SD 卡上基础的 FAT32 文件系统挂载到了 /media/sdcard,系统又在它之上于 /mnt/movi 挂载了一个 unionfs,其可写层指向一个 ext3 文件系统,而这个文件系统以文件形式存放在外置 SD 卡上。我不完全确定这套安排用意何在(也许是为了用上 data=journal,它在断电时更不容易丢数据?),但无论如何,这些挂载全都是 noexec。

对 2011 年的设备来说,这样的安全性强得有些出人意料。

模拟器还帮我弄清了另一件事:系统会尝试从外置 SD 卡读取哪些文件。其中之一是 master.script,这是一门自定义语言写的脚本,用于自动化某些 UI 导航操作。它每次启动都会加载,我指望能借它打开设置时间所需的那个菜单。

车机显示屏提示用户执行 master.script 的截图

我逆向了负责解析该脚本的二进制文件 ttn,边分析边记录它的功能,但遗憾的是,我没能找到任何打开所需菜单的途径。

又一次白费功夫

我仍然认为该把精力放在 master.script 上,因为它功能很多,可能成为攻击面。

和往常一样,我检查了是否存在可用于命令注入的 system 或 popen 调用。遗憾的是,没有发现任何由用户输入构造的调用。

逆向解析器时,我碰到一个 SHEX/SHELLEXECUTE 命令,一度以为捡到宝了。它把任意参数传给 launchserver,而后者会用 execve 启动传入的任意命令行。

这里有两个障碍。第一,仍然没有办法让它直接执行自定义可执行文件,因为 SD 卡是以 noexec 挂载的,这使卡上的可执行文件和脚本无法运行。不过可以绕过:不直接执行脚本,而是执行类似 /bin/sh /media/sdcard/payload.sh 的东西,这样会走 rootfs 上的 /bin/sh,而 rootfs 没有以 noexec 挂载。

然而,第二个障碍更大。事实证明,虽然 launchserver 乐意启动任何东西,但 SHEX 只接受一个以空白分隔的 token。这意味着我只能控制执行哪个二进制文件,却无法给它传任何参数。结合前一个障碍来看,这似乎同样是死路一条,因为我只能运行已经在 rootfs 上的脚本或二进制文件。我确实翻遍了其中大部分,但没发现什么特别有用的东西。

走投无路之下,我开始研究文件写入。也许如果我能把文件写到本不该能写的地方,就能获得某种筹码。

果然,我发现了 LOG 命令,它可以截断一个文件(用 #define LOGFILE log.txt 指定),并向任意文件写入一行内容。这个命令倒是允许空格和常规 ASCII 字符,但仅限于 0x20 到 0x7E 的字节,也就是说写入二进制数据完全不可能。由于 ttn 以 root 身份运行,任何写入也会以 root 身份完成。

它还有一个经典的路径遍历漏洞。在 LOGFILE 中填入 ../,就能写到预期的日志目录之外,理论上可以把可打印 ASCII 写到系统上的任何位置。

然而,我又一次在挂载点的设置上撞了墙。rootfs 以只读方式挂载,我无法覆盖上面的任何东西;其余的一切都以 noexec 挂载,而且根本不会被执行。我也查过有没有什么有用的配置文件可以覆盖,但大多数文件是自定义的二进制格式,用 LOG 写不了,剩下的也没什么用。这看起来同样是死路一条。

在其他方面,master.script 可以在屏幕上显示图像(GIF/BMP)。我花了一些时间用 Unicorn 搭建 fuzzing 环境,想看看能不能触发图像解码器里导致内存破坏的 bug。虽然确实发现了不少越界读取,但没找到任何有用的写入。

借用内核漏洞利用的技巧

我研究、逆向并尝试想出各种执行任意代码的策略,已经有一段时间了。令我沮丧的是,没有一个策略奏效。于是我退后一步,从更大的图景来看,想弄清楚能不能把手上已有的拼图碎片拼到一起。

我知道自己能做到这些:

  • 执行任意二进制文件,但不能带参数,而且只能用 rootfs 上已有的
  • 向自己选定的路径写入 ASCII

就在这时我灵光一闪,想起内核漏洞利用里常用的一招:与其想办法让用户态执行我们的脚本,为什么不直接让内核来执行?

Linux 内核漏洞利用在能任意写内存时,常常会覆盖 core_pattern 字符串。这个字符串本来决定进程崩溃时 coredump 存到哪里,但它也接受一种特殊语法,把 coredump 通过管道传给某个进程(|/bin/my-coredump-handler)。崩溃发生时,内核会启动 core_pattern 里指定的进程,并把数据通过管道送进去。

core_pattern 还支持给被启动的进程传命令行参数。这里我运气不错:这个支持在 2.6.24 就有了,而这台机器的内核是 2.6.28。

关键在于,你还能从用户态把模式写入 /proc/sys/kernel/core_pattern 来设置 core_pattern。

用这个把 LOGFILE 指向 ../../proc/sys/kernel/core_pattern,再 LOG 字符串 |/bin/sh /media/sdcard/payload.sh,轻而易举。这样就会运行 rootfs 里的 /bin/sh,以 root 身份执行我们的脚本。

能执行任意二进制文件这一点,即使不能带参数也派上了用场,因为我总得想办法让某个程序崩溃,才能触发 coredump。用 DoS 漏洞去打崩脚本解析器或者主 UI 本身效果不好,因为这会立刻让整个系统重启,把我的脚本一起干掉。

我用模拟器环境把系统上几乎每一个二进制文件都跑了一遍,发现 /bin/dbus-monitor 在连不上系统总线时会抛出 SIGABRT(这台机器上并没有运行系统总线),从而触发 coredump。完美。

拼起来

最终的 master.script 相当简单:

#define LOGFILE ../../proc/sys/kernel/core_pattern
LOG |/bin/sh /media/sdcard/payload.sh
SHEX /bin/dbus-monitor

我最初只是在 payload.sh 脚本里调用 reboot 来确认成功。插上 SD 卡启动机器,在提示 Execute master.script? 时按下“Yes”,机器随即重启,说明代码执行成功,这让我喜出望外。

我做的第一件事,是用 dd 给内部 eMMC 做了一份完整备份。拿到更完整的系统转储之后,我很快定位到,我关心的日期和时间设置存放在 /mnt/flash/settings.dat 里,那是一种自定义的二进制格式。又逆向了一阵,我找到了文件中保存当前时区偏移量的确切位置,并写了个脚本把它改成 UTC+2。

我就是这样最终修好了车上的时区。

来源: nns.ee← 返回首页