使用Raspi配置
raspi-config是一个用户空间工具,它允许我们配置树莓派的各个方面,其中之一是启用各种外部接口。我们将使用raspi-config来启用UART接口,首先启动工具,如下所示:
sudo raspi-config 这将导致出现以下结果:
接下来,我们将选择Interface Options,然后是Serial Port,如下图所示:
选择这个选项后,我们会遇到两个问题:
1.你希望通过串行方式访问登录shell吗?
2.你想启用串行端口硬件吗?
我们现在已经在树莓派上启用了UART,接下来,我们需要将它连接到我们的机柜。我们将机柜的Tx连接到Pi的Rx,将机柜的 Rx 连接到 Pi 的 Tx:
UART工具
使用树莓派上的UART接口,我们可以尝试连接到目标设备上的这个Serial Port。为了与这个Serial Port交互,我们将使用屏幕实用程序。当与UART接口时,屏幕要求我们传递一个设备和波特率,由于我们知道上一段的波特率,我们将按如下方式运行:
sudo screen -L -Logfile cabinet-bootup.log /dev/ttyS0 115200
-L -Logfile cabinet-bootup.log——将会话记录到cabinet-bootup.log文件;
/dev/ttyS0——要使用的串行设备;
115200——波特率;
我们现在可以在配置我们的 UART 和启动屏幕后打开机柜。当我们打开机柜电源时,我们看到以下内容:
最终,我们发现自己在一个控制台:
我们现在有一个根控制台,可以探索文件系统,查看目标上正在运行的内容,并了解有关它是如何构建的更多信息。如果可能,我们的下一步将是对分区进行映像;让我们先看看挂载了哪些分区:
我们的根文件系统是以只读的形式挂载的,并且使用的是squashfs格式。此外,还挂载了标记为userdata的另一个分区。如果我们检查可用的block设备,我们会看到以下内容:
我们可以看到,SPI flash设备大概位于/dev/rkflash0。要获得这个block设备的图像,我们可以将USB插到机柜的USB端口并使用dd实用程序。当我们插入一个USB闪存驱动器时,它在 /dev/sda 中被枚举,我们可以用以下命令将SPI闪存的内容映射到USB驱动器:
Sudo dd if=/dev/rkflash0 of=/dev/sda status=progress
如果我们将 USB 驱动器插入 Pi 并检查分区表,我们会看到相应的分区已映射到驱动器。
现在我们有了闪存的备份,这是我们尝试修改嵌入式系统时应该采取的第一步;在我们尝试修改任何内容或重新刷新任何分区之前,我们应该确保我们有办法恢复它们;为此,我们将调查引导加载程序。作为起点,让我们注意启动日志开头的以下行:
如果我们在为机柜供电时在屏幕提示中按住 Ctrl-c,我们会看到以下内容:
我们现在有一个 UBoot 提示符,在深入探讨之前,让我们先谈谈 UBoot 及其工作原理。
UBOOT
UBoot是嵌入式系统中常用的开源引导加载程序。它支持各种架构和CPU类型。但是,UBoot 的职责通常是为嵌入式系统加载操作系统内核或主应用程序。
UBoot 还包括在你的逆向工程工作中有用的调试实用程序;最值得注意的是 UBoot 命令提示符。
UBoot命令
UBoot 控制台可以包含大量的内置实用程序,这些实用程序可以在标准引导过程中(通常通过环境变量)或在 UBoot 命令行中使用。可用的命令将根据UBoot映像的构建方式而有所不同。
现在我们已经发现了一个 UBoot 控制台,让我们首先通过运行 help 命令查看哪些命令是公开可用的。
在查看每个命令之前,让我们重新审视一下我们的主要目标,即能够从引导加载程序读取和写入根文件系统分区,以防我们以后需要恢复这个机柜。下面的命令非常醒目,因为它们涉及内存读取和写入:
接下来,我们可以使用 printenv 命令查看此引导加载程序配置的环境变量。这将为我们提供更多关于该平台如何启动、正在使用的内存地址以及其他可用接口的背景信息。
UBoot环境变量
在为设备构建或配置UBoot映像时,可以配置各种环境变量。这些环境变量控制启动时执行的操作。存储这些变量的方法有很多种。有时它们被硬编码到二进制中,它们也可以驻留在闪存分区上,允许用户从UBoot提示符修改它们。
我们可以使用 printenv 命令检查环境变量:
我想在下表中指出一些有趣的变量:
此时,如果我们交叉引用我们在硬件检查期间收集的信息,我们会看到一致的结果。在我们的硬件检查之后,我们假设SPI flash是主要的存储方法,这个假设在UBoot环境变量和可用的命令中得到验证。
让我们从检查rksfc命令开始,通过搜索,这是RockChip的SPI SFC(串行flash控制器)接口工具。该命令包含以下子命令:
可以通过以下命令获取SPI flash的信息:
使用这些命令,我们可以了解更多关于SPI flash的信息。我们可以看到块大小为512,该芯片总共包含220672 (0x35E00)块,被分成5个分区:
uboot ——可能包含我们的 UBoot 映像/第一阶段引导加载程序;
trust ——可信执行环境映像;
boot ——内核映像/ ramdisk;
rootfs ——我们最大的分区,内核的根文件系统;
user data——用户特定数据,可能用于高分、用户设置等;
注意,该数据与我们之前在根控制台提示符中看到的内容相匹配。我们现在了解了闪存是如何分区的以及可能有哪些数据可用,但是我们如何在不向板上焊接额外线路的情况下读取/写入这些数据呢?如果我们检查 usb 命令,我们会看到以下内容:
使用机柜侧面的 USB 端口,如果我们插入设备并运行 USB start 后跟 USB info,则会生成以下输出:
这样,我们就可以看到USB堆栈成功枚举并检测到我们的大容量存储设备。
在继续之前,让我们回顾一下我们对 UBoot 环境的了解:
通过检查环境变量,我们可以在RAM中找到可用的地址;
使用rksfc读取实用程序,我们可以读取SPI闪存扇区到RAM;
使用USB命令,我们可以枚举一个USB设备并写入它;
我们可以将SPI flash读入RAM,连接USB设备,然后使用USB写入命令将SPI flash数据写入USB设备。如果这种方法有效,我们还应该能够通过反向步骤恢复闪存图像,从USB驱动器读取数据,并使用rksfc write写入闪存。让我们从测试读取开始。
首先,我们将尝试使用以下命令将整个 SPI 闪存读入 RAM 以获取目标地址,我们将尝试存储在 $ramdisk_addr_r 中的地址,即 0x6a200000:
这不起作用,我们以某种方式触发了未定义的指令异常。我们可能破坏了 UBoot 正在使用的一些东西,让我们看看当我们尝试另一个内存较低的地址时会发生什么:
移动到 RAM 中的较低地址允许读取完成而不会破坏任何内容,让我们看看我们现在是否可以将这些数据写回 USB 驱动器:
现在我们来看看这个驱动器的内容,把它插入树莓派,看看我们有什么:
此时,我们可以看到USB驱动器上的分区表与rksfc part 0命令的输出相匹配。接下来,我们将使用dd实用程序提取用于分析的各个分区。
到目前为止,该数据与我们在查看正在运行的系统上的挂载输出和 UBoot 菜单中的分区表时看到的数据相匹配。因此,我们可以通过 unsquashfs 提取 squashfs 分区并尝试挂载 ext2 分区以确认它们是有效的:
看起来我们有一个有效的根文件系统,现在我们可以开始逆向工程软件了,并了解更多关于我们如何修改这个系统来玩更多游戏或运行自定义固件的信息。
现在我们已经确认可以读取flash,让我们测试一下,看看我们是否可以使用上面描述的方法将这张图片写回flash:
现在我们重新启动,希望我们重新刷新的图像仍然有效。
成功!我们现在可以使用我们的 USB 驱动器从 UBoot 读取/写入 SPI 闪存;这对于测试补丁和固件修改很有用!
现在我们可以用 UBoot 读/写这个机柜的 flash,如果我们可以自动擦除 flash 的各个分区和段,而不需要每次手动输入范围,那就太好了。为此,我们将使用 Depthcharge 实用程序来自动化我们的 UBoot 交互!
使用 DEPTHCHARGE 编写 UBOOT
在使用UBoot环境时,我们经常需要自动化交互。例如,在我们的示例中,我们可能希望自动重写特定的闪存分区,而不必每次都手动输入地址偏移量。对我们来说幸运的是,NCC集团的人已经组装了一个工具来帮助我们,这个工具叫做深度充电。我们可以使用这个自动化的过程,从我们的闪存芯片和外部USB驱动器读取数据。我们的脚本将需要执行以下操作:
连接到UART并识别UBoot提示符;
通过rksfc读写命令对SPI flash进行读写操作;
通过USB读写命令对USB驱动器进行读写;
首先,我们需要安装模块,可以通过执行命令sudo pip install depthcharge.o在Pi上安装depthcharge。
连接到UART并识别UBoot提示符
我们可以使用以下python代码连接到UBoot提示符:
在上面的函数中,我们创建了一个Console对象,它要求我们提供一个到Serial Port和波特率的路径。然后这个控制台对象被用来制作Depthcharge上下文,我们将使用它来访问Depthcharge所提供的功能。depthcharge文档中有一个很好的例子,详细描述了安装过程。
Flash 通过 depthcharge 读写
现在我们已经连接到接口,我们需要实现rksfc读写命令。我们可以使用depthcharge的send_command() API来做到这一点。这个API调用允许我们生成UBoot命令并将其发送到命令提示符并返回响应。在下面的例子中,我们在cmd_str变量中构造了read命令,并确保参数被正确格式化,然后使用send_command() API发出命令。
现在我们已经实现了对闪存的读写,接下来我们需要枚举USB堆栈,然后从闪存驱动器中读写。
USB 通过 depthcharge 读写
与我们实现 rksfc 命令的方式类似,接下来我们将实现 usb 命令。该过程将类似于用于 rksfc 命令的过程:
使用Depthcharge 转储闪存
现在我们已经定义了适当的函数,我们可以尝试以下操作:
如果我们运行这个脚本,就会看到如下输出:
当我们将u盘插入Pi时,就会会看到以下分区:
成功!我们已经使用 Depthcharge 将 SPI 闪存提取到 USB 设备!
文件系统内容
现在我们有了一种可靠的读取和写入flash的方法,让我们简要地检查一下内容。有趣的文件位于/moo文件夹中。此文件夹包含仿真器及其相关资源。Moo是一个使用自定义ROM格式的自定义模拟器,2020年,一些研究人员在模拟器的另一个版本上进行了测试。然而,如果我们查看目录内容,会发现一些有趣的现象:
在这个系统上肯定不可能有 PE32 Windows 可执行文件,如果我们将此文件复制到 Windows 机器上并尝试执行它:
它运行了!显然,这是一个构建工件,开发者没有意识到它存在于系统中。
使用本文介绍的方法,我们现在可以使用 UBoot 将 SPI 闪存读写到 USB 驱动器。我们已经提取了根文件系统并确定了核心模拟组件。我们的下一个目标将是对该目标上的一些二进制文件进行逆向工程,以确定运行自定义固件的困难程度。
总结
我们在本文中介绍了如何使用我们的万用表/逻辑分析仪执行嵌入式设备的初始拆卸和识别潜在的调试头。然后进一步详细介绍了如何分析未知的 UART 流量并使用带有 Raspberry Pi 的屏幕连接到Serial Port。连接到Serial Port后,我们发现可以通过按Ctrl-C来访问UBoot控制台。在查看了 UBoot 控制台之后,我们编写了一个 depthcharge 脚本来将每个 SPI 闪存分区提取到一个外部闪存驱动器中。所有使用的脚本和工具都可以在github上找到。
本文翻译自:https://voidstarsec.com/blog//2022/01/27/uart-uboot-and-usb如若转载,请注明原文地址