欢迎光临散文网 会员登陆 & 注册

[中文]使用 QEMU 模拟 iPod Touch 1G 和 iPhoneOS 1.0(第一部分)

2022-12-31 11:37 作者:-小白之家-  | 我要投稿

大约一年前,我开始使用QEMU仿真软件模拟iPod Touch 1G。经过几个月的逆向工程,弄清楚各种硬件组件的规格,以及使用GDB进行无数次调试运行,我现在有了iPod Touch的功能模拟,其中包括显示渲染和多点触控支持。模拟设备运行Apple为iPod Touch发布的第一个固件:iPhoneOS 1.0,build 3A101a。模拟器运行iBoot(引导加载程序),XNU内核,然后执行Springboard。Springboard渲染主屏幕,并负责启动其他应用程序,例如Safari和日历。我没有对正在加载的引导加载程序、内核或其他二进制文件进行任何修改。所有源代码都可以在我的QEMU分支中找到。注意:模拟器需要自定义 NOR 和 NAND 映像(本文稍后将详细介绍)。我的目标是很快发布另一篇博客文章,其中包含有关如何生成这些自定义图像的详细说明。

下面的视频显示了在启动设备和浏览各种应用程序时运行的模拟器:

QEMU RUN IOS_哔哩哔哩_bilibili

为了实现上述目标,我以其他人🚀之前在iOS / Apple设备仿真方面的一些工作为基础:

  • @zhuowei最初的这篇博客文章最初激发了我开始这个项目的灵感。

  • Johathan Afek的后续工作建立在@zhuowei的工作之上。

  • S5L8900 SoC仿真的早期工作。

  • 这种带有 QEMU 的 iPhone 11 仿真 - 提供了完整的内核仿真功能。

  • openiboot项目一直是理解iPod Touch硬件组件的宝贵资源(我希望在某一时刻,我们可以在iOS设备上运行Android)。

  • 我用来反汇编引导加载程序/内核映像和其他二进制文件的 Ghidra 逆向工程工具。

  • 此 iPod Touch 设备树的转储@dizima提供了 iPod Touch 1G 中包含的硬件组件的概述和规范。

这个项目最复杂的部分是模拟iPod Touch中包含的许多硬件组件。我必须操作的大多数组件的规格都是专有的,没有文档记录,因此有时很难正确模拟它们。然而,我确实认为这是第一个模拟的苹果产品,它不仅是开源的,而且还具有完整的显示支持和多点触控操作(尽管Correllium也提供虚拟化iPhone,但Correllium是商业和闭源的)。在这篇博文中,我将概述我遇到的一些挑战,描述在启动过程中采取的步骤,并列出一些可以使仿真更好的未来任务。我确实喜欢在这个模拟器上工作,并学到了很多关于移动设备内部的新东西。

我特别决定专注于模拟运行有史以来第一个iOS版本的iPod Touch 1G。我这样做有两个原因:首先,旧设备的硬件组件比新设备少,这使得构建有用的设备仿真器更容易。当代 Apple 设备包含许多其他硬件组件,例如神经引擎、安全飞地和各种传感器,这将使此类设备的仿真变得更加困难和耗时。第二个原因是较旧的iPhoneOS / iOS版本几乎没有实施安全措施,例如信任缓存。通过专注于最原始的iPhoneOS版本,我不必绕过任何安全机制。

当前项目状态

执行iBoot,XNU内核,Springboard和预安装的iPhoneOS应用程序所需的所有硬件组件都可以正常工作。这些硬件组件是:

  • AES 加密引擎

  • SHA1 哈希引擎

  • 芯片识别模块

  • 硬件时钟和计时器

  • GPIO 控制器

  • LCD 显示屏和帧缓冲器

  • NAND 控制器和纠错码 (ECC) 模块

  • 闪存控制器(FMC),用于与NAND存储器通信

  • 多点触控设备

  • 电源管理单元和集成实时时钟

  • SDIO 控制器

  • SPI 控制器

  • I2C 控制器

  • 矢量中断控制器 (VIC) 和 GPIO 中断控制器

  • 直接内存访问 (DMA) 控制器

  • UART控制器

以下硬件组件尚无法正常工作,但也不是完全启动 iPod touch 所必需的:

  • USB OTG/Synopsys 设备

  • 音频设备

  • 802.11 无线网络控制器

  • PowerVR MBX图形处理器

  • 视频编码器/解码器引擎

  • 加速器和光传感器

iPod Touch的启动过程

下图显示了将 iPod Touch 启动到用户应用程序时的所有五个步骤:

Bootrom 和低级引导加载程序

iPod Touch 1G 使用 ArmV6(Little Endian)指令集。该项目的验证第一步涉及设置带有CPU的QEMU机器,以便我们可以执行一些代码。幸运的是,QEMU 支持 CPU 和所需的指令集。初始化 QEMU 机器并初始化一些内存后,我们准备将二进制文件加载到内存中并执行一些代码!ARM1176

打开iPod Touch电源时执行的第一个代码是bootrom代码,可能是三星在iPod Touch 1G推出时设计的。引导在设备中融合,只读,无法通过软件修改。因此,bootrom中的漏洞受到高度追捧,因为此类漏洞无法通过软件修复(Checkm8是此类漏洞中的最后一个)。可以从本网站下载引导代码的转储。我最初尝试在我的 QEMU 机器中加载和执行引导代码。但是,我很快发现 bootrom 跳转到一些代码,这些代码可能也在设备中融合并且从我使用的 bootrom 转储中丢失(丢失的代码似乎位于内存中的偏移量)。由于我在这个项目开始时没有实体iPod Touch 1G,所以我无法获得这个缺失的代码。低级引导加载程序(LLB,上图中的步骤 2)也跳转到了这个神秘的代码,所以我将注意力转移到执行 iBoot 上(上图中的步骤 3)。0x22000000

iBoot 引导加载程序的乐趣

iBoot 引导加载程序的主要功能是初始化设备外设以及加载和执行内核映像。iBoot还可以进入恢复模式,允许使用iTunes重新安装iPhoneOS。幸运的是,openiBoot 项目已经做了很多工作来重新实现 iBoot 提供的大部分功能。此源代码有助于我理解 iBoot 中的主要逻辑和过程。由于 iBoot 初始化并与各种硬件组件通信,因此我还必须专注于启动并运行这些组件才能运行 iBoot。

我工作的第一个硬件组件是矢量中断控制器(VIC)。此组件注册来自其他硬件组件的中断请求,并在发生中断时通知 CPU。iPod Touch 1G似乎配备了PL192,这是有据可查的。在 VIC 启动并运行后,我致力于将内核生成的打印语句重定向到 QEMU 控制台,这在调试过程中有所帮助。下面你可以看到iBoot的控制台输出,直到iBoot加载和解密XNU内核:

iis_init() spi_init() power supply type batt battery voltage Reading PMU register 87 error SysCfg: version 0x00010001 with 4 entries using 200 of 8192 bytes BDEV: protecting 0x2000-0x8000 image 0x1802bd20: bdev 0x1802b6a8 type dtre offset 0x10800 len 0x7d28 image 0x1802c170: bdev 0x1802b6a8 type batC offset 0x18d40 len 0x101e1 image 0x1802c5c0: bdev 0x1802b6a8 type logo offset 0x29a80 len 0x1c3a image 0x1802ca10: bdev 0x1802b6a8 type nsrv offset 0x2bfc0 len 0x4695 image 0x1802ce60: bdev 0x1802b6a8 type batl offset 0x30d00 len 0xc829 image 0x1802d2b0: bdev 0x1802b6a8 type batL offset 0x3e240 len 0xe9d2 image 0x1802e888: bdev 0x1802b6a8 type recm offset 0x4d780 len 0xb594 display_init: displayEnabled: 0 otf clock divisor 5 fps set to: 59.977 SFN: 0x600, Addr: 0xfe00000, Size: 0x14001e0, hspan: 0x500, QLEN: 0x140 merlot_init() -- Universal code version 08-29-07 Merlot Panel ID (0x71c200):   Build:          PVT1   Type:           TMD   Project/Driver: M68/NSC-Merlot ClcdInstallGammaTable: No Gamma table found for display_id: 0x0071c200 power supply type batt battery voltage error power supply type batt battery voltage error usb_menu_init() vrom_late_init: unknown image crc: 0x66a3fbbf ======================================= :: :: iBoot, Copyright 2007, Apple Inc. :: :: BUILD_TAG: iBoot-204 :: :: BUILD_STYLE: RELEASE :: ======================================= [FTL:MSG] Apple NAND Driver (AND) 0x43303032 [NAND] Device ID           0xa514d3ad [NAND] BANKS_TOTAL         8 [NAND] BLOCKS_PER_BANK     4096 [NAND] SUBLKS_TOTAL        4096 [NAND] USER_SUBLKS_TOTAL   3872 [NAND] PAGES_PER_SUBLK     1024 [NAND] PAGES_PER_BANK      524288 [NAND] SECTORS_PER_PAGE    4 [NAND] BYTES_PER_SPARE     64 [FTL:MSG] FIL_Init [OK] [FTL:MSG] BUF_Init [OK] [FTL:MSG] VFL_Init [OK] [FTL:MSG] FTL_Init [OK] [FTL:MSG] VFL_Open [OK] [FTL:MSG] FTL_Open [OK] Boot Failure Count: 0 Panic Fail Count: 0 Delaying boot for 0 seconds. Hit enter to break into the command prompt... HFSInitPartition: 0x1802b8f0 Reading 8900 header with length 2048 at address 0x0b000000 Will decrypt 8900 image at address 0x0b000000 (len: 3319392 bytes) Loading kernel cache at 0xb000000... data starts at 0xb000180

从上面的日志中可以看出,iBoot 首先初始化各种硬件组件;然后,它从NOR闪存读取多个映像,初始化LCD屏幕,初始化电源管理单元(PMU)以读取电池状态,然后从NAND闪存读取内核映像。最后,它将执行释放到内核。如果启动因任何原因失败,iBoot 将跳入恢复模式,该模式允许通过 UART 接口执行多个调试命令。

iPod Touch 1G包含两种持久内存:NOR和NAND。NOR 内存是一个相对较小的块设备。主文件系统保留在 NAND 内存中,iPod Touch 1G 的大小为 8-32 GB,具体取决于型号。为了使模拟器正常工作,我们需要模拟这些块设备,并确保引导加载程序/内核可以正确读取它们。

构造 NOR 映像

在启动期间,iBoot 引导加载程序会读取存储在 NOR 闪存中的多个文件。例如,这些文件是设备启动时显示的Apple徽标,恢复模式屏幕,低电量屏幕和设备树。NOR 内存还包含 NVRAM 和 SysCfg 分区,用于存储各种设备属性,例如序列号、MAC 地址、内核的引导参数和崩溃日志。我编写了一个自定义工具,用于从 IPSW 文件中包含的文件构造有效的 NOR 内存映像,并在启动 QEMU 时提供了此自定义内存映像。构建此 NOR 映像的源代码可在此 GitHub 存储库中找到。

构建 NAND 映像

iBoot 的职责之一是将 XNU 内核加载到内存中并将执行传递给它。iBoot 可以通过两种方式加载内核映像:它从 NAND 内存中的文件系统读取映像,或者加载位于特定内存偏移量的映像。由于我希望仿真尽可能接近实际的引导过程,因此我专注于启动并运行 NAND I/O。乍一看,这听起来很简单,因为 NAND 存储被分成不同的页面,并且每一页都有编号。因此,我们的模拟器可以在 iBoot 或内核请求时简单地在页面中返回适当的数据。然而,在引擎盖下,NAND设备比这复杂得多,主要是因为NAND存储器需要磨损均衡算法。这是必要的,因为NAND中的每个物理块只能在性能下降之前可靠地擦除和写入多次。NAND 驱动程序还包含其他算法,例如,用于纠错代码、坏块管理和垃圾回收。因此,NAND存储器中页面的物理布局与这些页面的逻辑组织完全不同。

幸运的是,Openiboot包含了iPod Touch 1G中的NAND驱动程序的实现。这不仅帮助我了解了NAND存储器的物理布局,还帮助我了解了与NAND存储器的I / O交互。我还查看了包含NAND驱动程序源代码的iBoot源代码的泄露版本。与 NOR 映像类似,我编写了各种脚本来构造可由 NAND 驱动程序读取的 NAND 映像。源代码可以在此 GitHub 存储库中找到。NAND 映像是从 IPSW 固件文件中包含的根文件系统构建的。

解密和加载内核映像

此时,iBoot 会从 NAND 存储(位于文件系统中的 )正确加载内核映像。但是,此内核映像使用专有的 8900 加密方案进行加密,并且 iBoot 跳转到内存中的解密过程,我没有指令。为了仍然能够解密映像,我在跳转到 QEMU 逻辑中的加密函数的开头实现了回调并解密内核映像。然后我将解密的内核映像保留在内存中,之后 iBoot 跳转到内核映像的进入方法。/System/Library/Caches/com.apple.kernelcaches/kernelcache.s5l8900xrb

在 iBoot 加载内核之前,我必须启动并运行其他一些硬件组件。这些组件包括电源管理单元 (PMU)、DMA 控制器、硬件定时器和时钟以及 LCD 显示屏。

模拟 XNU 内核

我的大部分逆向工程工作都集中在理解 XNU 内核和模拟内核使用的硬件组件上。尽管XNU内核大部分是开源的,但苹果似乎为iPod Touch和iPhone等苹果设备中包含的内核维护了一个私有分支。将 iOS 中附带的内核与开源内核代码进行比较,似乎苹果对 iOS 内核进行了各种更改,以确保它可以在 ARM CPU 上运行。此外,在开源内核实现中没有硬件组件的设备特定驱动程序的源代码。

XNU 内核首先初始化几个 BSD 子系统,包括内存管理逻辑、调度程序和线程支持。随后,内核读取 NOR 映像中包含的设备树。设备树是一种数据结构,用于描述属于特定设备的所有硬件组件。内核使用设备树为所有这些组件加载相应的驱动程序,并使用正确的设置初始化这些组件。iPod Touch 1G使用的设备树的转储可以在这里找到,正如你所看到的,它包含了相当多的信息!设备树还可以显示有关不同组件之间依赖关系的信息。例如,它表示与多点触摸屏的通信通过由SPI控制器控制的SPI接口进行。

也许设备树节点中最重要的字段是组件的内存地址。大多数硬件组件使用一种称为内存映射 IO 或 MMIO 的技术。使用 MMIO 时,相同的地址空间用于寻址主内存和 I/O 设备。因此,内核可以简单地读取和写入主存储器,以与硬件组件进行通信。事实证明,在 QEMU 中实现对内存映射 I/O 的支持相对简单。但是,某些硬件组件不使用MMIO,必须使用不同的硬件通信协议(如SPI,I2C或SDIO)进行访问。

初始化 BSD 子系统后,内核启动 IOKit 框架并开始加载设备树中包含的硬件组件的驱动程序。由于内核加载了相当多的驱动程序(大约 30 个),确保所有这些驱动程序正确启动花了我几个月的时间。启动过程偶尔会卡住,因为它正在等待我尚未正确模拟的硬件组件以给出特定响应。下面您可以看到一些反编译驱动程序的屏幕截图:

以及我的 QEMU 存储库中的一些文件:

在执行过程中的某一时刻,内核开始从 NAND 中的文件系统读取二进制文件。即使我已经有完整的NAND支持来使iBoot满意,内核通过闪存控制器或FMC从NAND存储读取。事实证明,这是我必须模拟的最具挑战性的硬件组件之一。FMC也是我必须模拟的第一个硬件组件,没有任何可用的文档或源代码。破译FMC执行的不同I/O操作并确保读取正确的NAND页面花了我几个星期的反复试验。目前,FMC 的 NAND 读取操作应该可以正常工作,但我尚未添加对 NAND 写入操作的支持。

初始化完所有驱动程序后,内核就可以执行应用程序了。 是内核启动的第一个程序,顾名思义,它负责启动其他应用程序和启动脚本(它也使用 PID 1 运行)。启动时,内核引导被视为完成。从这一点开始,执行的应用程序在用户空间而不是内核空间中运行。当正常运行时,下一步是启动管理iPod Touch主屏幕的标准应用程序:Springboard。launchdlaunchdlaunchdlaunchdlaunchd

启动跳板

应用程序在文件系统的目录中查找启动脚本并执行这些脚本。例如,这些启动脚本包括用于音频控制的守护程序、地址簿和蓝牙支持。其中一个启动脚本 包含启动应用程序的说明。不幸的是,Springboard 在启动后不久就卡住了,因为我还没有实现显示渲染。launchd/System/Library/LaunchDaemonscom.apple.SpringBoard.plistSpringboard.app

让有展示

Springboard.App包含用于呈现主屏幕的逻辑,包括应用图标、对话框屏幕和状态栏。iPod Touch(或任何移动设备)上的显示渲染通常由硬件图形处理器加速。从逆向工程中,我已经可以看到这个硬件组件非常复杂,内核和图形处理器之间的通信协议很复杂。作为替代方案,我开始寻找一种暂时禁用图形处理器的方法。幸运的是,启动脚本允许我添加一个成功禁用图形处理器的环境变量。使用此选项,所有显示渲染都由内核执行,这也比在专用硬件上执行渲染要慢得多。尽管没有硬件加速渲染操作,但模拟设备中的动画非常流畅,如博客文章开头的视频所示。Springboard.AppLK_ENABLE_MBX2D=0

此时模拟设备成功启动 Springboard 并呈现主屏幕 🎉🎉🎉

实现对多点触控的支持

我的下一步是添加对通过触摸屏幕导航用户界面的支持。我的想法是使用与Xcode中包含的iPhone模拟器相同的方法,其中鼠标点击转换为屏幕上的触摸。看似一个相对简单的问题 - 检测用户按下屏幕的位置,将此触摸转换为(x,y)坐标对并将其传递给内核 - 实际上是一个非常具有挑战性的问题。这项专利于 2007 年授予 Apple 描述了准确注册用户触摸和手势所需的一些步骤。总之,多点触控设备生成由内核中的多点触控驱动程序读取的帧。每个帧包含一个触摸事件,该事件以省略号的形式包含有关触摸的详细信息(例如参见链接专利中的图3)。

在某一时刻,内核开始初始化 HID 设备,其中还包括多点触控设备。多点触控设备的初始化过程大致如下:

  1. 上传校准数据:内核将校准数据上传到多点触控设备并校准设备。此校准数据包含在文件系统中,也嵌入在设备树中。

  2. 上传固件数据:内核将一些Zephyr2固件数据上传到多点触控设备。此固件数据包含在文件系统中,也嵌入在设备树中。

  3. 读取设备信息:内核从多点触控设备获取各种状态报告。这些报告包括有关多点触控设备的多个方面的信息,例如版本控制信息和触摸表面水平/垂直方向上的触摸点数。

内核通过 SPI 接口与多点触控设备通信。为了确保多点触控设备生成的帧成功传输到内核,我必须启动并运行SPI控制器。多点触控设备生成 GPIO 中断,以通知内核帧的可用性,例如,是否有触摸或其他事件需要处理。为了获取有关包含触摸事件的帧结构的更多信息,我修改了 openiboot 以初始化多点触控设备,对其进行编译,并在帧中记录所有字段,如下面的屏幕截图所示:

通过仔细分析各种触摸和滑动生成的帧,我想出了如何将 QEMU 窗口中的鼠标单击转换为多点触控设备的触摸和帧。与触摸事件相关的每个帧还包括有关轻扫速度的信息。例如,在滚动垂直列表或调整水平滑块时,使用此速度。为了确保这些滚动操作正常工作,我还必须在触摸生成的每个帧中提供水平和垂直速度。我通过将前一个鼠标事件的 x/y 坐标与当前鼠标事件的 x/y 坐标进行比较来计算这些速度。

最后,我添加了对主页按钮(按“H”键激活)和电源按钮(按“P”键激活)的支持。这一步非常简单。在这一点上,我有一个功能齐全的iPod Touch,它可以启动到主屏幕,并且可以通过鼠标单击和键盘进行导航。

我还发现一些应用程序崩溃是因为缺少关键资源文件。这些丢失文件的原因是我正在从IPSW中提供的根文件系统生成NAND存储。但是,在恢复或安装 iPhoneOS 时,这个干净的文件系统填充了各种文件。在我的仿真中,我没有执行还原脚本。我还必须从实际设备复制激活记录以绕过设备激活。

浏览预装的iPhoneOS应用程序时的其他一些屏幕截图:

已知问题和后续步骤

虽然我现在有一个功能性的iPod Touch模拟器,但仍有很多问题:

  • 设备在尝试显示键盘时崩溃。这似乎是因为(负责 Unicode 支持的库)没有正确加载到内存中,但我还没有弄清楚为什么会发生这种情况。libicucore.dylib

  • 有一些与USB驱动程序和闪存控制器相关的罕见崩溃。我怀疑它们是引入的竞争条件,因为 QEMU 中的硬件通信比实际设备上的通信快得多,这可能会违反内核逻辑中的一些基本假设。

  • 不支持高级手势,例如捏合和放大。

  • 亮度控制也尚未工作。

  • NAND存储器没有持久性。

  • 当设备断电或进入自动锁定模式时,会出现各种故障。

有时很难调试并找出设备上发生的情况。大多数调试是通过将 GDB 调试器附加到 QEMU 客户机来完成的。运行交互式 shell 会很有帮助。我尝试在模拟设备上编译和运行,但尚未使其运行。bash

努力建立一个统一的基础设施来模拟其他几代的iPhone,iPod Touch,Apple TV甚至Apple Watch也会很好。但是,所有这些设备在硬件和软件规格上都有差异,并且模拟它们可能非常耗时。下一步,我想尝试让iPod Touch 2G功能正常。

我希望这篇博文能为模拟iPod Touch 1G的过程提供一些见解。有很多细节我没有写过,但我可能会在其他博客文章中写到它们。在我的下一篇博客文章中,我将提供有关编译 QEMU、生成自定义 NOR/NAND 映像和运行 QEMU 仿真的说明。同时,如果您对这个项目有任何想法、建议或问题,请告诉我!


[原文地址]

使用 QEMU 模拟 iPod Touch 1G 和 iPhoneOS 1.0(上部分)|Martijn de Vos (devos50.github.io)

[中文]使用 QEMU 模拟 iPod Touch 1G 和 iPhoneOS 1.0(第一部分)的评论 (共 条)

分享到微博请遵守国家法律