微控制器利用指南-全-

微控制器利用指南(全)

原文:zh.annas-archive.org/md5/6d73ec50d55d655c27ee1aad1adc0318

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

大家好,

微控制器是单片计算机。你信用卡里有一个,你的笔记本电脑和汽车里有几十个。医疗设备、电子游戏、电力表和对讲机都在使用它们。每个微控制器内部都有一些非易失性存储器用于存储计算机程序,一个最基本的 CPU 来运行程序,还有足够的 RAM 来存储全局变量,也许还有堆栈和调用栈。

我一直对微控制器的读取保护特性充满兴趣,这些特性可以保护芯片的固件不被提取和逆向工程。在这段时间里,许多聪明的同行想出了许多巧妙的方法来提取这些固件,但当我想要分享它们时,我经常发现自己在啤酒渍的餐巾纸上勾画大致的细节,因为缺乏一个集中的收集。

所以我开始写这本书,作为记录我可能发现的这些技巧的一种方式,按技巧和明确的型号编号组织,并附上原始出版物的引用。这些是真实的漏洞,从真实的芯片中提取代码。

你将学习如何通过 nRF51 的保护模式使调试能够禁用其通过 JTAG 的保护,以及 nRF52 系列的保护稍好但易受电压故障攻击的方式。你将探索 STM32F0 如何在每次复位后允许导出一个字节,STM32F1 的异常处理如何在一小时内逐渐泄漏固件,以及 STM32F2 和 STM32F4 的 USB 引导加载程序如何容易受到任意代码执行的攻击。你还将了解如何通过相机闪光灯提取德州仪器 MSP430 的固件,以及如何通过接地 Freescale MC13224 的一个引脚来禁用所有保护,从而允许外部调试器。

对于这些漏洞,你将学习如何在自己的实验室中重现这些结果,导出芯片的固件。旁白会引导你参考相关芯片,并说明一种攻击如何预测另一种攻击,这在你尝试从新的设备中提取固件时非常有用。并且在可能的情况下,你将被引导到源代码和该技术首次发布的资料。

编号章节详细解释了某些技巧或如何破解特定的芯片。这些章节大致按引入某种技巧的类型进行分组。字母章节尝试快速分组目标,简明扼要地描述先前的研究。提供了内存映射,以帮助你将内存地址视为特定的位置,在可能的情况下,我还包括了自己实验室中的 X 射线和芯片照片。

使用这本书,我建议你首先快速浏览一遍,以获得如何提取芯片固件的概览,然后使用书后的索引,在需要时找到针对特定型号的技巧。当你没有实践时,你是无法真正掌握的,所以即使你的目标是防御这些攻击,也一定要亲自实现一些这些攻击。

你的学校图书馆员提醒你去查找一些参考书目中的引用是对的,但如果她告诉你不要在页边写字,那就是错的。我把页边留得很宽,就是为了方便你在需要的时候写下笔记。

来自 EM85AX,

特拉维斯·古德斯皮德

第一章:1 内存提取的基础

在我们深入讨论从锁定的微控制器中提取固件的漏洞利用之前,让我们先回顾一下基本内容。我们将简要地浏览可能有效的许多方法,然后在后续章节中详细学习这些攻击。

首先,收集关于芯片、其调试机制和启动加载程序的所有可用文档非常重要。

对于公开文档的芯片,你需要获取数据手册、系列指南、一些参考设计和一个可用的交叉编译器。只有首先了解芯片在工厂中是如何被编程的,才能找到那些会将固件转储出来的 bug。

或许我应该稍微解释一下这些术语。数据手册是芯片的简短描述,通常不到一百页,介绍了你需要构建电路板的内容。系列指南有不同的名称:程序员指南、集成指南、用户指南,或者供应商那一周愿意使用的名称。它们通常描述一整个相关部件的系列,并且会指向更多的文档。参考设计包括原理图、源代码和 CAD 文件,芯片供应商鼓励工程师复制这些内容,以便将他们的芯片嵌入到成品中。

对于未公开文档或未标识的芯片,你只能利用你能获得的少量线索,比如相关芯片的设计或从开发者那里泄漏的文档。运气好的话,这些线索可能会引导你找到有用的内容。在逆向工程 Tytera MD380 的专有无线电芯片 HR C5000 时,曾通过 DocIn.com 找到了一份中文的机密开发者指南。^(1) 在逆向工程现代的 Tamiagotchi 玩具时,Natalie Silvanovich 浏览了成百上千张芯片焊接照片,识别出一个未标识的微控制器是 General Plus GPLB52X,然后找到了其数据手册。^(2) 虽然 Freestyle Libre 血糖监测仪中的 RF430TAL152 RFID 芯片没有公开文档,但公开文档中的 RF430FRL152 除了 ROM 内容等细节外几乎完全相同。^(3)

很容易直接跳到攻击芯片,而不先作为开发者使用芯片,但你会发现这本书几乎每个漏洞利用都从理解目标的细微差别开始。对于任何新的芯片,花时间绘制出其内存映射,使用调试器探索未锁定的芯片,并真正理解芯片是如何使用的。如果有可能,请不要跳过在目标上编译并运行 Hello World 这一步!

JTAG

对于调试和故障分析,大多数芯片在硬件中实现了某种变种的 JTAG 协议。经典的变种使用四条信号线:TDI、TDO、TCK 和 TMS。第五条信号线 TRST 有时会包括在内,并且存在多种两线变种,以便更容易布线,例如 cJTAG、单线调试(SWD)和间谍双线(spy-bi-wire)。

这些线路各有其目的。TDI 和 TDO 是串行输入和输出信号,由 TCK 信号时钟控制。TMS 选择模式,让调试器将目标状态机在不同寄存器之间切换。所有这些细节都被调试器的硬件和软件抽象化,直到你需要编写自己的代码时,才需要深入了解这些内容。

如果你幸运的话,你有一颗解锁的芯片,只需连接一个 JTAG 适配器,并使用调试器将完整的闪存范围导出到磁盘。开发人员通常会为了故障分析的原因而将设备保持解锁状态,这样他们可以更容易地提高生产良率并保持装配线的正常运作。有些设备甚至不支持锁定,这些设备总是很容易读取!

如果你运气不佳,JTAG 端口将被完全或部分禁用,以防止读取,这通常是通过熔丝或非易失性内存标志来配置的。

完全 JTAG 锁定通常可以通过某种故障注入方式绕过,其中芯片的电气、光电或电磁要求被短暂违反,从而绕过保护机制。例如,许多 STM32 芯片的完全锁定可以通过重置后的电源电压波动降级为部分锁定。^(4) 许多 MSP430 芯片在遭遇闪光灯时会从完全锁定状态变为解锁状态。^(5)

部分 JTAG 锁定稍微复杂一点,主要是因为它们种类繁多。通常,部分锁定允许进行某种形式的调试,用于故障分析,同时对闪存施加限制。STM32F0 的部分保护在 JTAG 连接后会断开闪存与数据总线的连接,但它发生的时机稍微晚了一些,因此你可以通过反复重新连接来转储内存,从而提取出单个 32 位字。^(6) 同样,STM32F1 的部分保护可以通过意识到中断处理程序是通过指令总线获取的这一点来打破,因此通过使用向量表偏移寄存器(VTOR)重新定位表,就可以在单步调试时触发中断并观察寄存器,从而泄漏闪存中的字。^(7)

ROM 引导加载程序

许多微控制器配备了掩膜 ROM。这些 ROM 的内容和格式差异巨大,但如果存在,通常至少包含一个引导加载程序,也可能包含一些便捷功能,就像旧款 IBM PC 的 BIOS 一样。这些 ROM 的位来自制造时的光刻掩膜,通常你可以拍摄它们的照片来查看并解码这些位。

就像我们试图从闪存中提取的应用程序代码一样,ROM 代码也可以被反编译和逆向工程。如果这段代码中存在可利用的漏洞,修复它可能是困难的或不可能的,导致整个芯片系列的固件被转储。

以 STM32F2 和 STM32F4 的 ROM 为例,包含了三个引导加载程序,使得芯片可以从 USB、串口和 CAN 总线启动。这三个引导加载程序包含了三种不同的 JTAG 锁定功能的重新实现,而且 USB 设备固件更新(DFU)引导加载程序中的软件漏洞允许从任意地址执行代码,从而能够提取所有受保护设备的固件。^(8)

在大批量生产的芯片中,你可能会发现定制的 ROM 镜像。这些镜像与消费型号的芯片不完全相同,但通常是从相同的代码中分叉出来的,这可以在成功提取固件之前提供一些线索。^(9) 因为 ROM 中的某些位在显微镜下是可见的,我们可以借助一定的耐心和软件工具,直接读取这些位。^(10)

闪存引导加载程序

我们已经讨论过来自芯片制造商的 ROM 中的引导加载程序,但许多设备制造商会添加他们自己的引导加载程序,可能是从头编写,或者是从参考设计中分叉出来的。

以 Tytera MD380 为例,它是一款双向无线电,其固件被提取并修改,以为业余无线电社区添加新功能。它的 STM32F405 包括了上述提到的 ROM 引导加载程序,但还包含第二个闪存引导加载程序,具有 DFU 协议的自定义变种。这个闪存引导加载程序允许读取和写入无线电的 SPI 闪存芯片的明文数据,而内部闪存区域则只能进行写入,且仅限于加密固件更新。此引导加载程序中的未初始化指针允许提取前 48kB 的内存,其中包含引导加载程序。

通过修改这个引导加载程序使芯片保持解锁状态,可以通过 JTAG 随意提取明文固件!^(11)

无论你的目标是什么,方法是什么,最终的目标都是从受保护的芯片中提取代码。掌握了正确的技巧,并且深入理解保护机制的工作原理,几乎任何芯片都能被专注的逆向工程师攻破。

第二章:2 STM32F217 DFU 退出

根据 Goodspeed(2012)私下向 STMicroelectronics 报告,本章是首次公开描述 STM32F217、STM32F407 以及其他系列芯片在采用 USB 设备固件更新(DFU)协议的掩码 ROM 实现时的远程代码执行漏洞。这个漏洞的好处在于它非常简单:DFU 实现限制了对加密芯片内存的读取和写入,但更改目标地址和执行应用程序则是完全允许的。

要提取加密芯片的内存,我们首先使用 JTAG 将一些 Shell 代码放入未使用的 SRAM 中,然后重置芯片并通过 USB 使用 DFU 执行该 Shell 代码,从 GPIO 引脚输出所有内存内容。引导加载程序使用的 DFU 协议方言在 STMicro(2010)中有文档说明;在阅读本章节时请务必随时查阅。

Image

图 2.1:STM32F217

Image

图 2.2:简化版 STM32F217 内存映射

JTAG 和引导加载程序

与本书讨论的大多数 STM32 芯片一样,STM32F217 也有三个保护级别:0、1 和 2。级别 0 无保护,如果设备处于此级别,你可以直接读取固件并关上这本书。级别 2 不允许任何形式的调试,通常攻击者会首先通过降级保护到级别 1 来对该级别的设备进行攻击。

级别 1 是一个折中的方式,也是你在生产设备中最常见的模式。在此模式下,连接 JTAG 调试器将禁用对闪存的访问,但仍然可以访问 CPU、RAM 和 ROM。还可以从级别 1 降级到级别 0,但这会导致闪存被全部擦除,并摧毁其中的所有数据。开发者喜欢这种模式,因为失败分析仍然是可能的,同时他们也被告知固件将保持安全,防止被提取。^(1)

STM32F217 还在 ROM 中包含了三个引导加载程序,分别用于通过 UART、USB DFU 和 CAN 总线接受固件更新。这三个引导加载程序之间几乎没有共享代码,它们在软件中实现了级别 1 的保护,而不是依赖于连接 JTAG 调试器时存在的硬件保护。这对我们来说是有利的,因为这意味着如果我们能够诱使这三个引导加载程序中的任何一个读取闪存,我们就可以选择该引导加载程序并提取芯片的固件。

USB DFU 引导加载程序

本章的漏洞出现在通过 USB 访问的 DFU 引导加载程序中。我首先编写了一个与芯片兼容的 DFU 客户端,然后用它提取了位于 0x1fff0000 的 ROM 进行逆向工程,以便了解所有规则。^(2)

我将在此简要介绍 DFU 协议,但要真正理解或实现该协议,应该阅读 Henry 等人(2004)的原始文档。

首先需要了解的是,DFU 支持以下七个请求:DetachDownloadUploadGet StatusClear StatusGet StateAbort。寻址是通过块索引来处理的,而不是地址,这个块索引相对于地址指针。

大多数高级命令是通过调用UploadDownload实现的,接着使用Get Status来了解事务的结果。

块索引从 2 开始用于数据事务,而不是我们预期的 0 或 1。如果你将 32 个字节上传到索引 2,它们将被写入地址指针。将 32 个字节上传到索引 3 将把它们写入地址指针之后的 32 个字节,上传 64 个字节到相同的索引将把它们写入地址指针之后的 64 个字节。

索引 1 从不使用。索引 0 表示一个特殊块,其中第一个字节是几个秘密命令之一。下载[0x41]将会批量擦除所有闪存。一个空字符串[]将断开 DFU 会话,并执行目标地址的应用程序。下载[0x21, 0x1c, 0x32, 0x00, 0x08]将把目标地址指针设置为0x0800321c。下载[0x92]将首先批量擦除所有内存,然后还会解锁芯片到 RDP Level 0。^(3)

Image

图 2.3:DFU 会话,来自 Henry 等人(2004 年)。

Image

图 2.4:STMicro 的零块 DNLOAD 扩展(2010 年)

你可以通过将[0xFF, 0xFF]下载到目标地址0x1fffc000来锁定芯片。在这种情况下,索引为 2,我们正在写入指定的地址,而不是写入特殊的零块。

一旦芯片被锁定到 RDP Level 1,连接到 DFU ROM 的权限将受到以下限制:除非从某些特殊地址,否则不能进行UploadDownload操作。索引 0 处的特殊命令将单独允许或拒绝。特别需要关注的是,你仍然可以设置地址指针,并且你可以退出 DFU ROM。

错误

在了解了所有这些背景信息后,错误本身并不复杂。首先,JTAG 允许我们将应用程序写入未使用的 SRAM 中,并且该应用程序在芯片重置后仍然会保留,在此时闪存被重新连接并开始从 ROM 执行 DFU 引导加载程序。其次,DFU 引导加载程序允许我们设置地址指针,尽管存在锁定,当我们退出引导加载程序时,执行将继续到指针指向的目标应用程序!

从实际角度来看,这意味着如果地址指针设置为0x20003000,引导加载程序将在退出时跳转到存储在0x20003004的值。选择这个地址是因为它恰好位于 SRAM 中,并且 DFU 引导加载程序未使用该地址,因此它不会被引导加载程序的堆栈或全局变量覆盖。

我们从 SRAM 执行的 shellcode 相当简单。它通过 SPI 协议循环传输所有闪存数据,使用 PG6 引脚作为 MOSI,PG8 引脚作为 CLK。这很容易通过逻辑分析仪捕获,如图 2.6 所示。如果这些引脚上也有 LED,它们将闪烁以表示成功利用。

由于我们的输出格式本质上是 SPI 总线流量,我们可以使用逻辑分析仪的 SPI 解码器从记录中提取固件镜像。

利用

ST Micro 已经在最近的修订版本中修复了这个漏洞,因此对目标 ROM 进行一些逆向工程可能是一个不错的主意,以验证该漏洞是否存在。通过将 2KB 加载到 USB 帧缓冲区中,然后执行其中未被较短命令覆盖的部分,应该能够实现更好的利用。

虽然这个特定的漏洞只适用于 RDP Level 1,但如第 E.5 章中所描述的故障攻击可以将保护从 Level 2 降级到 Level 1。

Image

图 2.5: STM32 Shellcode

Image

图 2.6: STM32F217 固件转储

第三章:3 MD380 空指针,DFU

尽管利用芯片厂商的 ROM 引导加载程序是非常有效的,但许多设备厂商会在闪存中添加第二个引导加载程序。这个故事最早在 Goodspeed(2016b)中讲述,讲述了我如何通过空指针读取漏洞 dump 了一款双向无线电的固件。它也是如何破解固件更新加密的故事,来自 Rütten 和 Goodspeed(2016)。

Tytera MD380 是一款手持无线电收发器,支持模拟 FM 或数字移动无线电(DMR)。DMR 提供了 GSM 的一些功能,如短信和中继塔的时分复用,而无需 SIM 卡的麻烦。许多人购买 MD380 用于业余无线电;它实在太诱人了,无法不拆掉固件并为业余无线电社区打补丁,增加新功能。

这款无线电的 CPU 是 STM32F405,采用 LQFP100 封装,配备一兆字节的闪存和 192KB 的 RAM。^(1) STM32 具有 JTAG 和 ROM 引导加载程序,但这些在最安全设置下被读取设备保护(RDP)功能保护,JTAG 连接完全禁止。

读取空指针

我并没有直接从第二章介绍的 STM32 漏洞入手,而是先为无线电编写了一些自己的 USB 驱动程序。正如我们很快会看到的,这并不是浪费时间。

Image

图 3.1:Tytera MD380 无线电

Image

图 3.2:STM32F405

MD380 有个独立的 USB 设备固件更新(DFU)协议实现:一个在 ROM 中,一个在闪存的开头用于固件更新和恢复,另一个在主无线电应用程序中。第二个和第三个协议大致相同,我们可以用差不多的方式利用它们。

我通过在 VMWare 中运行厂商的 Windows 应用程序,逆向工程了协议,然后通过在图 3.4 中的代码行修补.vmx文件,将 USB 流量写入日志文件。现在,我可能会在 Linux 主机上使用usbmon,同时在 Qemu 虚拟机中运行 Windows。

日志显示,MD380 的 DFU 变体包括非标准命令。特别是,LCD 屏幕在官方客户端应用程序中会显示“PC Program USB Mode”,但在任何第三方应用程序中则不会。在我能够进行适当读取之前,我需要找到能够进入此编程模式的命令。

DFU 实现通常会在UPLOADDNLOAD命令中隐藏额外的命令,当块地址小于 2 时。要擦除一个块,DFU 主机会下载0x41,后跟一个小端地址到块零。要批量擦除所有内存,主机只需发送0x41,不带任何额外字节到块零。要设置地址指针,主机发送0x21,后跟一个小端地址。有关以这种方式调用的 STM32 标准扩展的列表,请参见图 2.4。

除了那些已记录的命令,MD380 还使用了若干个两字节(而非五字节)的 DNLOAD 事务,这些命令在标准 DFU 协议中都不存在。我在 图 3.5 中观察到了这些命令,其中许多我至今仍然只理解了一部分。

修改 Michael Ossmann 的 Ubertooth 项目中的开源 DFU 客户端,以便读取和写入无线电配置并不难。这个配置被无线电用户称为“代码插头”,存储在 SPI 闪存中,并不包含任何固件。相反,它保存了无线电频道设置和频率。

Image

图 3.3:简化的 STM32F405 内存映射

Image

图 3.4:在 VMWare 上进行 USB sniffing

Image

图 3.5:MD380 的 DNLOAD 扩展

Image

图 3.6:闪存内存转储

Image

图 3.7:MD380 的中断表

如果在读取之前没有发送任何来自 图 3.5 的扩展命令,读取出的将是一个非常有趣的模式,如 图 3.7 所示。你可以认为这只是没有选择内存源。

以小端模式解读,开始出现的字是 0x2000-1a300x080056150x08005429,以及一堆指向 STM32 闪存地址的奇怪指针。这是闪存开头的中断表,而我看到的是从 0x08000000 开始的闪存引导加载程序的前一千字节!

内部发生了什么?嗯,每个 DFU 事务都会尝试从内存中读取一个块,但因为没有发送自定义命令来选择内存源,未初始化的缓冲区从未被填充。那么在 STM32F4 上,未初始化位置的缓冲区到底包含了什么呢?嗯,0x00000000 会巧妙地映射到芯片启动时所在的内存,因此从那里读取一千字节,实际上就得到了来自 0x08000000 的一千字节,这就是为什么我们会看到引导加载程序的前一千字节。

阅读第一个块之后,我们发现每个块都包含相同的一千字节。这是因为 DFU 是按块号寻址的,但缓冲区仍未初始化,因此所有块地址都会被重定向到闪存的起始位置。尽管改变块索引没什么用,但我们可以通过 dfu-util--transfer-size 选项增加块大小,获取超过一千字节的数据。最大传输大小取决于操作系统和 USB 控制器,但我的 iMac 能够拉出 0xC000 字节,即恢复引导加载程序的完整长度!

破解保护

所以现在我们有了恢复引导加载程序,但在 0x0800C000 的内存中还没有随之而来的应用程序。我们可以通过修改恢复引导加载程序来禁用读取保护,然后使用 STM32 的 ROM 引导加载程序通过 USB 转储所有内存来获得这段代码。

要将图像加载到逆向工程工具中,如 IDA Pro 或 Ghidra,只需设置 ARM/Cortex 指令集和 0x08000000 的基地址。有时将图像标记为没有写权限有助于反编译器,它会知道代码不会自我修改。还需要将 0x40000000 的 I/O 区域标记为易失性,以防止反编译器优化掉大部分中断处理程序代码。

通过搜索 IO 地址 OPTCR_BYTE1_ADDRESS0x4002-3C15),我们迅速发现 STM32 示例中的 FLASH_OB_RDPConfig() 被包含在 0x08001fb0。它从 main() 中被调用,并在 0x0800-44A8 的指令中传递了 0x55 的参数。

Image

图 3.8:此函数设置 RDP 保护级别。

Image

图 3.9:按下 BOOT0 引脚

然后,我们可以修补一个字节,使其不再写入 0x55(用于 RDP 级别 1 读保护),而是写入 0xAA(用于 RDP 级别 0 无保护)。

Image

现在我们有了一个不会锁定芯片的引导加载程序,但仍然需要安装它。我们通过在重启期间将 CPU 的 BOOT0 引脚拉高,按照 图 3.9 中的硬件修改,启动 ROM 引导加载程序。此时,我们仍处于 RDP 级别 1(读保护),但我们可以通过发送批量擦除命令降级到级别 0,擦除闪存中的所有内容并让无线电没有固件。

然后,我们将修补后的引导加载程序写入闪存,并在重启无线电时按住无线电右侧的上下按钮来启动它。LED 灯将开始红绿交替闪烁。此时,设备已准备好接受更新,但仍没有应用程序镜像,因此我们使用厂商的 Windows 应用程序安装加密的固件更新。这为我们提供了一个正常工作的无线电!

我们再次通过在重启时将 BOOT0 引脚拉高,进入 第二章中的 ROM 引导加载程序。这次,我们处于 RDP 级别 0(无保护),可以自由地转储所有闪存,其中无线电固件从 0x0800C000 开始。由于设备保持解锁,我们还可以修补应用程序镜像并将其写回到无线电中。

Image

图 3.10:反编译后的解密函数

破解更新加密

到此为止,我们已经获得了恢复引导加载程序和应用程序的明文转储,以及应用程序的加密固件更新。剩下的就是破解加密,这正是我的好朋友 Christiane Rütten 在 Rütten 和 Goodspeed(2016)中贡献的技巧。

不同形式的密码学当然需要不同的技术。如果供应商使用公钥加密签名更新,我们可能就无能为力了。如果使用像 AES 这样的标准对称加密算法,我们或许能通过搜索常量表来找到线索,然后追踪引用直到找到解密固件的代码。

相反,Rütten 发现加密固件更新中有重复的序列,如果加密做得正确,这种情况是不应该发生的。她随后将加密的固件更新与我从内存中提取的明文应用程序做了异或(XOR)操作。

果然,用明文与更新文件做异或操作,产生了一个 1,024 字节的重复模式!请参阅 第 38 页 了解使用这些字节将固件块包装成加密更新的 Python 代码,它与制造商自己的工具兼容。

执行这个异或操作的固件函数如 图 3.10 所示。请注意,无论写入的块大小如何,1,024 字节都会与 firmwarekey 的字节进行异或,但复制的数量是作为参数传递的。

这些漏洞使得 MD380Tools 项目成为可能,这是一个开源补丁集合,用于破解 MD380 固件,添加了混杂模式、所有注册的业余 DMR 操作员的电话簿,以及原始数据包捕获。^(2) 它也使得 Goodspeed(2016a)成为可能,在该项目中,我将固件重新链接成 ARM/Linux 可执行文件,用于在桌面或服务器上自由地编码和解码 DMR 的 AMBE+2 音频编解码器。

ImageImage

第四章:4 LPC1343 调用栈

NXP 的 LPC800、LPC1100、LPC1200、LPC1300、LPC1500、LPC-1700 和 LPC1800 系列 ARM 微控制器容易受到引导加载程序内存损坏的影响。此问题首先在 Herrewegen 等人(2020)中描述,针对的是 LPC1343,这是一款具有 32KB 闪存和 8KB RAM 的 Cortex M3 处理器。在本章中,我们将探讨引导加载程序的协议和漏洞,然后逐步编写自己的攻击代码。

LPC 微控制器有五个代码读取保护(CRP)级别,每个级别都对 ISP(引导加载程序)和 SWD(调试器)访问施加了进一步的限制。级别 0(NOCRP)是未保护的,允许通过引导加载程序或 SWD 调试器自由读取和写入内存。CRP 1 完全禁用 SWD 调试,同时禁止 ISP 读取,并限制 ISP 写入,以便在保护其余部分的同时进行现场内存更新。在 CRP 2 中,大多数命令被禁用。CRP 3 是最安全的,禁用所有功能。第五种模式 NOISP 禁用 ISP 接口,同时保留 SWD 启用,从而使内存仍然暴露。

引导加载程序同时作为 UART 串口和 USB 大容量存储磁盘呈现,其中磁盘的一个文件代表芯片的固件。Herrewegen 的攻击特定于 CRP 级别 1 中的 UART 接口,但作者指出,大容量存储接口可能是进一步寻找漏洞的一个好目标。有关在更高保护模式下对这些芯片有效的故障攻击,请参见第十五章。

Image

图 4.1:LPC1343

入门

掩码 ROM 引导加载程序位于 0x1fff0000,大小为 16KB。32KB 的闪存从地址 0x00000000 开始,8KB 的 SRAM 映射在 0x10000000

引导加载程序不允许直接读取 ROM,因此我首先使用 SWD 调试器和 OpenOCD 转储了 ROM。我还需要一份 SRAM 的副本,以便在调试时查看全局变量和栈值,因此我首先使用调试器将 SRAM 清零,然后进入引导加载程序。通过引导加载程序读取 RAM 转储,可以让我获得引导加载程序的读取 RAM 函数中的状态,所有未初始化的字节都保持为 0x00

保护级别通过写入闪存内存地址 0x02fc 的 32 位字来配置。CRP 1 的值是 0x12345678,CRP 2 是 0x87654321,CRP 3 是 0x43218765。所有其他值都使芯片处于未保护状态,这使得它成为第十五章中故障攻击的好目标。

RAM 从0x10000000开始,并为引导程序提供一个受保护的区域作为工作内存。引导程序将拒绝对此区域的写入。根据文档,前 768 字节(直到0x10000300)应当被保护,但实际上只有前 512 字节(直到0x10000200)被保护。在未受保护的范围内存在一些全局变量(尽管应该被保护),但目前没有发现这些全局变量是可被利用的。图 4.3 展示了这个布局。

设计师似乎保护了他们的 .data 部分,却忽略了调用栈对攻击者来说是一个更具诱惑力的目标。引导程序的调用栈从 0x10001fdc 向下增长,完全位于写保护区域之外!Herrewegen 的漏洞通过反复使用写 RAM 函数修改这个栈来触发返回到原本无法到达的读取内存函数,先转储一些字节的闪存,然后重复这个过程。

UART 协议简介

UART 协议在 NXP (2012) 的第二十一章中有详细说明。它是一个 ASCII 协议,可以自动同步到你的波特率,如果你愿意的话,可以在终端仿真器中手动缓慢输入大部分协议内容。

通过拉高 BLD_E 引脚来启用引导程序,并通过将 P0_3 拉低来选择具有 USB 的型号的 UART 模式。启动引导程序后,你以 57,600 波特率发送一个问号。芯片会发送 Synchronized 这个词,你将其返回以确认一切正常。

每个命令以一行文本的形式发送,并会被回显。数值参数始终是十进制的;不支持解析十六进制。读写操作以uuencode格式的行进行保护,每二十行有一个校验和。(每行解码 45 字节,即每 900 字节一个校验和。)

由于我找不到可以打补丁的开源引导程序客户端,我使用 Golang 和 go-serial 库编写了一个新的引导程序客户端。

Image

图 4.2:LPC1343 内存映射

Image

图 4.3:LPC1343 引导程序 SRAM

Image

图 4.4:LPC1343 引导程序命令

Image

图 4.5:代码保护字面量

逆向工程引导程序

下一步是逆向工程引导程序。我在 Ghidra 中执行了这个操作,加载了 0x1fff0000 处的 ROM 转储和 0x10000000 处的 SRAM 转储。

在第一次尝试时,我加载了一个未被清零的芯片的 SRAM 转储。SRAM 在没有电源时会丢失状态,因此它将大部分未使用的内存填充了杂乱无章的内容,这使得逆向工程变得更加困难。在运行引导程序之前清零 SRAM,然后通过引导程序将其转储,给我提供了一个初始化了所有全局变量并且拥有活动调用栈的镜像,帮助我理清思路。

这些转储当然是从一个解锁的芯片上获取的。除非解锁芯片不可用,例如智能卡只能在保密协议下使用,否则最好先在解锁芯片上开发漏洞,并在以后再将其用于锁定目标。

加载了固件和 SRAM 转储后,我花了一个下午的时间寻找并命名函数。有关函数用途的好线索来自于它访问的 I/O 地址以及是否读取或写入这些地址。

地址的第一个字节(nybble)通过查看图 4.2 中的内存映射告诉我它是什么类型。以1开头的是该芯片上的 SRAM,而以0开头的是闪存存储,并且是有效的常量。如果以4开头,则是 I/O 外设,我可以在芯片的数据手册或头文件中查找外设的名称。

大型switch语句也很有用,例如图 4.4 中解释命令的循环。请注意,这些命令中的TU在 NXP 的文档中是缺失的。

我跳过了大容量存储的实现,因为我已经知道从 Herrewegen 等人(2020 年)的详细信息中将利用的漏洞。当寻找原始漏洞时,而不是重新实现已有技术,最好探索所有在芯片锁定时可以访问的代码。如果没有手动发现可利用的漏洞,可以特别关注解析器代码,并考虑在仿真中进行模糊测试固件。

控制程序计数器

实现基本的引导加载程序命令后,我们可以读取和写入一个锁定芯片上0x10000200以上的 SRAM,所以控制程序计数器就像在该地址以上的堆栈中找到返回指针一样简单。如果我们覆盖该地址然后返回,芯片将跳转到我们的新地址,而不是合法的调用函数。

在我的 Ghidra 项目中,我查看了位于0x1fff0000的引导加载程序的中断表。第一个字,0x10000ffc,是堆栈的初始顶部,而我想覆盖的返回指针应该位于该位置以下的内存中。

我的第二个线索是,当我将引导加载程序停止并将其归零时,程序计数器是0x10001f88。深度会根据调用的函数有所变化,但这表明我处于正确的区域。

第三个线索同样来自 Ghidra,我可以在这个区域中探索有效的代码指针。偏移量会略有变化,因为我正在查看 Read 命令的堆栈,而我的利用将会破坏 Write 命令的堆栈,但对齐通常是相似的。

最终,我得到了0x10001f94作为有效的返回指针,它会在 Read 命令发送确认后恢复到程序计数器。在这里,我写入我的 shellcode 地址以触发其执行。

Image

特权提升的 Shellcode

Herrewegen 的漏洞利用不仅仅重写了返回指针。相反,他修补了栈,使写操作变成读操作,将文本转储回他的客户端。我比较懒,所以我采取了更直接的方式:从 RAM 中运行 C shellcode,而不是从 ROM 中重用现有代码。

获取能够从 SRAM 中运行的 shellcode 的字节只需要一个最小的链接脚本,为了简化起见,我使用了ENTRY(main)指令将main()方法设置为入口点,并将.text.data紧挨在一起放入 RAM 中。第一个字节是入口点,任何全局变量都直接与镜像一起加载,而不是从代码内存中复制。

从 Herrewegen 的论文中,我知道 SRAM 中有一个全局变量缓存着 CRP 锁定字。Flash 中的永久位置在0x000002fc,在 Ghidra 中稍作搜索,发现缓存版本位于0x10000184。所以,我的 shellcode 做的第一件事就是用更高权限的值(例如零)覆盖这个值。

我还需要确保栈已经恢复,以免引导加载程序的解释器循环崩溃。这可以通过运气完成,或者通过在栈上构造正确的字节实现,但因为我希望我的 shellcode 在第一次就能成功,所以我选择了一个更简单的解决方案:它直接调用命令解释器的主循环,后者期望在权限缓存后被引导加载程序调用。这个循环是一个无限的while()循环,永远不会返回,并且栈深度足够。这样就能干净地继续执行,而不需要任何额外的工作。¹

这是我的符号文件。它仅定义了包含保护级别的全局变量和引导加载程序的命令解释器循环。

Image

这是我的 shellcode,使用 C 语言编写,而不是汇编语言。它仅仅禁用了保护并直接跳回到命令循环。

Image

将这些内容结合起来,这是解锁芯片的 Go 方法,然后干净地继续执行任何标准引导加载程序命令,不受麻烦的读取保护干扰。

Image

第五章:5 Ledger Nano S, 0xF00DBABE

Ledger Nano S 是一款电子加密货币钱包,由 STM32F042 微控制器和 ST31H320 安全元素提供支持。在启动时按住一个按钮,会触发 STM32F0 闪存中的引导加载程序,通过 USB 采用 APDU 协议进行通信。大部分 STM32 固件是开源的,而 ST31 在封闭源代码的监督程序内运行 applets。

本章将讨论一个漏洞,最早由 Roth (2018) 发布,该漏洞通过双重映射闪存使得在写入固件时可以绕过一致性检查,从而让引导加载程序错误地认为代码签名已经被验证。

我们还将简要介绍 Rashid (2018) 中的一个技术,利用该技术可以欺骗设备的加密固件认证。通过用跳转回引导加载程序等效功能的分支替换编译器内建函数,我们可以为补丁腾出一些空间。这使得 STM32 能够向 ST31 撒谎其代码,悄悄通过验证进行小补丁。

Ledger Nano S 将其代码分布在 STM32F042 和 ST31H320 之间。该设备并没有使用贴纸封条来防止篡改,而是采用了一个易于打开的外壳和软件认证。ST31 智能卡通过严格的时序要求读取 STM32 的固件来验证其合法性。

从攻击者的角度来看,一次成功的攻击需要同时将新代码写入 STM32 芯片,并伪造证明过程,使得主机 GUI 软件相信固件是正版的。我们将涵盖两者的技巧,但首先让我们简要了解一下平台,以便知道我们正在使用的是什么。

Image

图 5.1:反汇编的 Ledger Nano S

Image

图 5.2:STM32F042 内存映射

虽然 ST31 固件保持机密,但 STM32 固件是开源的,包含文档和开发套件。为了防止恶意修补,主机软件会验证 ST31 对 STM32 固件的证明,且为了防止恶意应用程序,需要输入密码来批准可能被闪存写入设备的应用程序和签名密钥。

第三方应用程序采用 C 语言编写,并在 ST31 的受保护模式下运行。大多数例子是加密货币钱包应用,但也有少数游戏,例如 Parker Hoyes 移植的 Snake。^(1) 在安装时,Applet 固件会由 ST31 进行验证,并且 GUI 必须调用才能运行由不受信任的机构签名的 applet。STM32 固件现在已经被验证,但在设备的早期版本中并没有进行验证。

与 Nano S 的通信通过 USB 封装的 APDU 命令进行,客户端示例在 ledgerblue 包中免费提供。该包中的一个示例显示在 图 5.3 中。

拥有完整的开发套件、准确的固件源代码以及对第三方应用程序的合法支持,给攻击者提供了许多自由度。在 Saleem Rashid 的例子中,知道官方应用程序的预期字节允许它被压缩、打补丁并重放,从而伪造安全元件的认证。正如我们将在 Thomas Roth 的例子中看到的那样,错误可以在从开发模式下的应用程序转储引导加载程序后找到。

Rashid 的认证漏洞

在 Ledger Nano S 的早期版本中,STM32 固件及其引导加载程序都是开源的。主机软件会要求 ST31 通过快速通过内部 UART 总线传输 STM32 代码来认证 STM32 固件。

Rashid 首先通过更改启动屏幕创建了一个恶意固件补丁,使得在钱包创建恢复密钥时,memset会被调用,而不是cs_rng函数。这样,客户将总是得到相同的密钥,而这个密钥可以被外部知晓。

这远不是一个隐蔽的后门,因此他接下来通过将他的代码隐藏在也存在于引导加载程序中的应用程序函数副本中,伪造了认证。例如,memset既存在于应用程序地址0x08006310,也存在于引导加载程序地址0x08002a9c。他可以通过将函数调用从一个重定向到另一个来释放 124 字节。

Image

图 5.3:Python 示例客户端脚本

他接着可以用一个修补过的包装函数填充这些字节,该函数将内存块发送到 ST31 进行验证,注意发送伪造字节来隐藏他的钩取和打补丁操作。

Roth 的引导加载程序漏洞

在 Rashid 发布后,Ledger 关闭了其 STM32 引导加载程序的源代码,并修补了它,在启动之前立即验证应用程序区域。然而,他们保留了 STM32 JTAG 端口,因此 Roth 打开了设备,接线并转储了闪存的副本。然后他进行了逆向工程,目的是找到一个漏洞,允许他刷写并执行未经认证的代码。

Ledger Nano S 的引导加载程序通过 APDU 协议进行操作。命令在图 5.4 中描述,首先使用选择段选择一个基本地址,然后使用加载将数据接受到工作段,最后将每个块刷新回闪存。当完整的更新安装完成后,你可以调用引导命令或重新启动设备来执行映像。

所有这些对于引导加载程序来说都相当标准。棘手的部分在于,这个引导加载程序会验证应用程序映像的签名,而不是实现锁定。因此,你可以在锁定的生产设备上调用所有这些命令,但如果映像没有使用制造商的生产密钥签名,你不应该能够执行引导命令或启动你的映像。

通过读取引导加载程序的转储,Roth 了解到,它在验证签名后,将 0xf00dbabe(以小端字节序表示为 be ba 0d f0)放置在 0x0800-3000 处。如果发现这个标签,它不会重复验证。所以,将该值写入该位置就足以通过引导加载程序注入外部的、未经认证的代码。

Image

图 5.4:APDU 引导加载程序命令

Image

图 5.5:Roth(2018)提供的 APDU 加载处理程序伪代码

Image

图 5.6:APDU 引导加载程序漏洞概念验证

从他在图 5.5 中反编译的处理程序伪代码来看,似乎可以在魔术字之前开始一个段并覆盖它,但在 STM32 上进行闪存写入时有严格的页面对齐规则,这会阻止此类攻击。同样,它们会检查是否对禁止写入的页面进行了写操作,并清除缓冲区中的四个字节,以防止我们绕过限制。

使这一漏洞得以利用的原因在于,在许多 STM32 微控制器中,包括本例中的这一款,闪存不仅映射到其默认位置 0x08000000。此外,在 0x00000000 处还有一个第二个位置,这是一个镜像或幽灵地址,因为它默认映射到引导内存。Roth 观察到,虽然有一个明确的检查来防止写入 0x0800C000,但没有任何机制来阻止写入 0x0000C000。由于镜像的存在,这两个地址实际上指向同一个位置!

Roth 的有效载荷

概念验证漏洞的示例显示在图 5.6 中。这证明了该漏洞的存在,但让我们反汇编他的有效载荷,看看它到底做了什么。

写入操作发生在 0x3000,但我们知道这是 0x08003000 的镜像地址,因此为了保持一致性,我们绕过这个目标位置进行操作。在 Radare2 中,我们会像这样打开它。

Image

文件开始时包含两个 32 位字。0xf00dbabe 是引导加载程序密码,0x080030c1 是代码执行的复位向量。

Image

记住要丢掉最低有效位,我们可以反汇编目标字以找到无限循环。^(2)

Image

但其余的代码是做什么的呢?为什么不直接使用十个字节(0xf00dbabe0x08003009b 0x08003009)在第一条指令上循环永远运行呢?嗯,Roth 似乎在最后一刻把一个几乎可用的漏洞当作彩蛋插入,通过改变入口点将其转变为无限循环。

第六章:6 NipPEr 是个舔屁股的家伙

在本章中,我们将讨论 Dish Network 智能卡中的缓冲区溢出漏洞,该漏洞曾是 EchoStar 与 NDS 之间著名诉讼的主题。这个漏洞的首次公开解释是一篇简短的论坛帖子,NipperClauz(2000),但由于审判的原因,我们通过一份秘密的 NDS 内部技术报告 Mordinson(1998)获得了更详细的文档。

首先,让我们设定场景。此智能卡曾在北美用于 Dish Network 的卫星电视服务,它将为接收器计算一个短时有效的解密密钥。芯片内部是 STMicroelectronics 的 ST16CF54 芯片,当时称为 SGS Thomson。指令集大部分与摩托罗拉 6805 兼容,除了额外的指令 TSA(0x9E)和 MUL(0x42)。该芯片包含 16kB 的用户 ROM、8kB 的系统 ROM、4kB 的 EEPROM/OTP 和 480 字节的 SRAM。用户 ROM 由 Nagra 开发,而系统 ROM 由 SGS Thomson 开发。

图 6.1 显示了芯片的内存布局,图 6.2 显示了 EEPROM 的布局。请注意,EEPROM 被镜像到三个额外的地址范围,因此每个 EEPROM 字节可以从四个独特的地址中读取。类似的镜像效应,有时称为幽灵效应,稍后在本章中将变得非常重要,就像在第五章中一样。

EEPROM 补丁由一个补丁编号的单字节和一个补丁处理程序地址的字节对组成。它们会在开关表中的敏感功能之前被调用,但没有补丁处理程序调用前无法修补 ROM 错误的机制。

Image

图 6.1:ST16CF54 内存映射

Image

图 6.2:Nagra1/Rom3 EEPROM 映射

Image

图 6.3:去除延迟的 ST16CF54A

这张卡的文档宝库可以在 Guy(2000b)中找到,完整 ROM 的注释反汇编版本可以在 Guy(2000a)中获得。唯一的公开文档曾经是一个三页的营销简报,但在 STMicro(1996)的法庭记录中曝光了真实的数据手册副本。除了缺少描述系统 ROM 的附带文档外,它是完整的。

漏洞

漏洞本身是一个静态分配的字节缓冲区溢出,它首先保存传入的 APDU 数据包,随后被重新用于传出的回复。这是一个典型的缓冲区溢出,但有一些复杂的情况需要绕过。

首先,缓冲区位于0x019C,它是 SRAM 中的最后一项。智能卡数据包最大可以达到 255 字节长,但在 SRAM 结束(0x01FF)之前,只有 100 字节。之后,官方内存映射显示系统 ROM 之前有一个较大的空白区域。

这里的技巧使得该漏洞可被利用,原因在于 SRAM 在内存中有幽灵效应。超出 SRAM 末尾 132 字节并进入我们 100 字节缓冲区时,写入0x0220的效果与写入0x00200x0420相同。因此,尽管我们溢出的缓冲区在全局变量和调用栈之后,我们仍然可以利用幽灵效应从内存的开头开始循环,并破坏有用的数据。

对于从0x000x1F的寄存器,没有幽灵效应,因此我们不需要像尝试保护 SRAM 一样仔细选择这些值。

另一个值得关注的效果是,SRAM 中较早位置的全局变量保存了接收缓冲区的索引。数据包是逐字节接收的;当该变量被覆盖时,目标位置将在其余字节拷贝时发生跳转。这对于削减数据包的字节数很有用,但如果忽略它,漏洞可能会失效,导致程序崩溃或调整 UART 时序。

Image

图 6.4:NipperClauz(2000)论坛帖子

NipperClauz 漏洞

现在我们已经覆盖了理论部分,接下来深入探讨第一个公开示例,NipperClauz(2000)。论坛帖子在图 6.4 中重现,在本节中我们将对其进行反汇编,理解它是如何工作的。

这前三个字节是事务头,其中0xC4表示长度。

Image

之后是很多看似无意义的字节计数行,有时被更有意义的字节打断。这些字节大部分无关紧要,但后面的字节确实会覆盖全局变量,若这些变量的值错误,可能会导致漏洞失效,甚至导致应用崩溃或调整 UART 时序。

Shellcode 从第 35 行中间开始,它回调 ROM 中的字节传输函数,地址是0x42d7,以保持较短的长度。

ImageImage

漏洞的结束部分包含一些填充和一个校验字节。

ImageImage

NDS 主机端漏洞

Mordinson(1998)附录 F 描述了相同漏洞的另一种利用方式。以下是该报告中的原始漏洞代码,采用nasm汇编格式,注释做了些微的修改。

注意评论的清晰度,几乎解释了每一条指令,并提供了加载到内存中的确切地址。它没有调用 ROM 中用于传输字节的函数,而是实现了自己的传输函数,地址是0x01c8

ImageImage

Go 语言中的现代漏洞

这两个漏洞都可以成功地转储卡片的 EEPROM。本书的内容是编写漏洞,而不是运行漏洞,因此我订购了十几个卫星接收器和各种卡片,直到我找到一些易受攻击的卡片。在本节中,我们将介绍 Goodspeed(2022),我为这些卡片编写的漏洞,它可以在现代计算机上运行,配合 USB 智能卡适配器,不仅转储 EEPROM,还包括用户 ROM 和它没有损坏的 SRAM。

要获得你自己的卡片,只需收集一堆卡片,然后读取卡片的复位应答(ATR)。你要找的是那些 ROM 显示为DNASP003(表示 ROM3)并且其 EEPROM 版本显示为Rev272或更早的卡片。我的一些卡片错误地显示了较新的 EEPROM 版本,以假装它们已经被修补,因此不要总是相信版本号告诉你卡片不是易受攻击的。

这些卡片当然已经被破解用于电视盗版。被破解的卡片也可以通过电子序列号与打印的序列号不一致来识别。

第一个复杂问题是,Headend 和 NipperClauze 漏洞会在一次交易中转储所有 EEPROM。智能卡交易有一个字节的长度字段和一个校验和,因此响应数据比长度字段应允许的更多,并且校验和总是错误的。当这些漏洞是在 90 年代编写时,这并不是问题,但现代智能卡适配器使用 USB 而不是串口。USB 的智能卡标准(CCID)抽象了数据包,要求所有长度和校验和都正确。

为了解决这个问题,我将交易缩减为 64 字节,并编写了一个接受转储基础地址的 shellcode。像其他漏洞一样,我的代码不支持干净的续传。我发现通过在每次交易时重置卡片来避免续传的麻烦是很方便的。

Image

图 6.5:修订版 369 EEPROM 转储

你会注意到,我的 shellcode 没有包括其他示例中的三个字节的头部或一个字节的尾部。这是因为 PCSC 守护进程会自动将头部和校验和应用到交易中。由于 shellcode 每次执行只转储 64 字节,因此必须将起始地址写入循环中的ld a, (target+1, x)指令中,其中0xFFFF位于清单中。

为了将回复传回主机,shellcode 跳转到0x757f的用户 ROM 函数。这是 ROM 用于传输消息的正常函数,比像 NipperClauz shellcode 那样重新利用传输一个字节的函数要小一点。它也比实现一个完全自定义的传输函数要小,正如 Headend 漏洞所做的那样。

ImageImage

第七章:7 RF430 后门

发现一个未列出的芯片实际上是一个带有自定义 ROM 的商用芯片并不罕见。RF430TAL152 就是这样的芯片,它基本上是一个 RF430FRL152,具有一个掩模 ROM,该 ROM 实现了在 Freestyle Libre 品牌下销售的血糖监测传感器。

在本章中,我们将讨论 RF430TAL152 中的一个后门,这一后门最早由 Goodspeed 和 Apvrille(2019)记录。我们将从公开的 FRL152 变种芯片开始,然后探索 TAL152 变种、其自定义命令以及一个后门。

RF430FRL152,商用变种

TAL152 和 FRL152 都在 0x4400 处具有 7KB 的掩模 ROM,用于传感器应用。这两款芯片都不包含闪存;相反,它们使用一种新型的存储技术,称为铁电 RAM(简称 FRAM)。像闪存一样,它是非易失性的,内容在没有电源的情况下依然会保存。像 SRAM 一样,它在写入时非常省电。

针对 ROM 的小补丁被加载到 0xF840 处的 2KB FRAM 中。另有一个小的 FRAM 区域位于 0x1A00,存储着序列号和校准值。

FRAM 是一种奇特的存储器,让我们快速回顾一下它的特性。在最低层级,写入操作需要的功率非常小,大多数位在没有电源的情况下能持续几十年。像 DRAM 和磁芯存储一样,读取操作是破坏性的。

Image

图 7.1:RF430TAL152 表面

Image

图 7.2:RF430FRL152 去层化

破坏性读取和偶尔的位错误会导致致命缺陷,因此内存控制器通过自动写回、错误更正和缓存来纠正这些问题。在更高层级,程序员可以假装它是 RAM,唯一与之相矛盾的证据就是有时读取比写入稍微需要更多的时间和功率。不是很有趣吗?

该芯片拥有比你预期的更多 SRAM,其中 0x4400 位置有 4KB 的 SRAM。SRAM 在 MSP430 架构上是可执行的,它可以映射到 ROM 的一半位置,用于开发自定义 ROM。开发人员还可以将普通代码存储在 SRAM 中,但这样做的风险是,电源故障时这些代码可能会被完全删除。

因为 ROM 的修改需要昂贵的掩模修订和重新制造,所以商业版和自定义 ROM 都支持在 FRAM 中打补丁。这些补丁会挂钩一个函数指针表中的条目,将来自 ROM 版本函数的调用重定向到 FRAM 中的替代函数。

由于 FRAM 不仅用于代码,还用于数据,它可以看作是芯片剩余地址空间的一个窗口,也是进行完整数据转储的第一步。稍后在本章中,当我们开始利用一个被锁定的 TAL152 芯片时,你将看到这一点。

FRL152 可以通过 JTAG 以令人沮丧的现代电压 1.5V 进行读取和写入。德州仪器(Texas Instruments)提供了一个开发套件,零件号 RF430FRL152HEVM,其中包括电平转换,支持其调试工具的 3.3V 电压。这使得可以从芯片的商业版本中提取 ROM 并进行反汇编。

Freestyle Libre 血糖传感器中的 RF430TAL152 采用不同的 ROM,JTAG 连接无法使用,但它支持相同的 NFC Type V 协议,标准化为 ISO 15693。该协议在 Android 上得到了良好的支持,但在 Linux 上的 USB 读取器支持较差,因此它处于一个尴尬的位置:相比笔记本电脑,更容易通过手机应用程序被利用!

Image

图 7.3:RF430FRL152 内存映射

Image

图 7.4:RF430FRL152 承载板

来自 Android 的 NFC-V

让我们暂停一下,讨论 NFC 标签在 Android 中是如何工作的,以及如何编写工具与 RF430 进行无线通信。

在 Android 中,NFC Type V 标签通过类 android.nfc.tech.NfcV 进行访问,其中的 transceive() 函数将字节数组发送到标签并返回结果。由于标签的命令集、块大小和寻址模式等属性差异很大,因此使用这些原始命令而不是更高层的封装函数。

NFC-V 交易以一个选项字节开始,通常是 02。接下来是一个命令字节和可选的命令参数。如果选项字节指示,可以在中间插入显式地址。命令字节大于 A0 时,需要跟随厂商编号,对于 TI 来说是 07。参见 图 7.5 获取一些示例命令。

你可以在 NFC Tools 应用中自己尝试低级命令,应用的 Other/Advanced 标签页在显示警告声明后接受原始命令。只需将 I/O 类设置为 NfcV,然后发送以下示例命令,再使用它们来实现我们自己为芯片编写的高级功能。

我们稍后会介绍更多命令,但现在你需要注意一般格式。在这里,20 是用于从 8 位块地址读取块的标准命令,而 C0 是用于从 16 位块地址读取块的秘密厂商命令。每个回复的第一个字节为零表示成功,非零表示失败。

ImageImage

图 7.5:NFC-V 命令动词

Image

图 7.6:TAL152 示例命令

C0(读取)命令和匹配的 C1(写入)命令接受 16 位地址,但它们仍然局限于 FRAM 和 SRAM 的子集。在下一部分中,我们将看到如何将一些 shellcode 写入 FRL152,并执行它以实现真正的任意读取。

FRL152 上的 Shellcode

FRL152 上的 FRAM 可能包含一个命令处理程序表。如果找到该表,其条目将被复制到接近 SRAM 开始部分的函数指针数组中。此外,C0C1 命令允许我们自由读取和写入 SRAM,因此可以为远程代码执行提供充分的控制。

虽然我们可以覆盖调用堆栈,但覆盖早期 SRAM 中的函数指针表,用指向我们函数的指针要容易得多,因为我们一次只能写入 4 或 8 字节。

有许多函数可供选择,一个理想的钩子是不会被正常函数忽略的。我们还希望尽可能有续接功能,以便执行代码时不会使目标崩溃。

我们将要覆盖的函数指针位于 SRAM 中的 0x1C5C,指向 ROM 中 0x4FF6 处的 rom_rf13_senderror()。为了正确的续接,Shellcode 必须向 RF13MTXF 外设写入两个字节,然后返回。如果没有这两个字节,协议将被违反并触发 Java 异常。要取消挂钩,只需将 0x4FF6 写入 0x1C5C,恢复原始处理程序。

图 7.7 展示了我用 Java 编写的方法,用于在任意地址执行 Shellcode 并将两个字节返回给调用者。这两个字节恰好是续接所必需的,但从漏洞利用中获得一些反馈总是不错的。

图片

图 7.7:在 RF430FRL152 中执行 Shellcode

RF430TAL152,医疗版

TAL152 葡萄糖传感器在布局和外观上与现成的 FRL152 非常相似,唯一的不同是掩模 ROM 的内容和 JTAG 配置。在本节中,我们将追溯从首次检查该芯片到最终转储其 ROM 并将自定义固件写入 FRAM 的漫长过程。

在首次实验该芯片时,我们发现 NFC 曝露了一个额外的 FRAM 块。每一页的最后一页都被写保护,我们无法通过标准写命令 21 修改它们。FRL152 的 C0C1 厂商命令在这里不存在,因此我们也没有一个方便的方法来处理越界内存。

但一切并未失去!在最后一页有一个函数指针表,内存最后的复位向量值告诉我们,这个 ROM 与 FRL152 的不同,因此我们知道这两个设备的 ROM 中的软件是不同的。

这个表位于 NFC 可读的内存部分,因此我们可以使用方便的智能手机读取它。然而,它是写保护的,所以我们还无法写入补丁到该表。目前我们无法读取 FRAM 的较低部分,或任何 ROM 或 SRAM。

我们看到图 7.9 中的表格,它从0xFFCE开始,魔术字是0xABAB,然后向下扩展到较低地址的相同字0xFFB8。^(1) 该表中的每一项都是自定义的厂商命令,我们看到,类似于在 FRL152 中非常实用的C0C1命令,TAL152 也有A0A1A2A3A4命令。A1A3的处理程序位于 FRAM 中,我们至少可以读取它们的部分代码。

Image

图 7.8:RF430FRL152 FRAM 命令表

Image

图 7.9:RF430TAL152 FRAM 命令表

当然,表格很早就结束了,E0E1E2被禁用,因为E0的命令号已被表格结束标记覆盖。这些命令在制造过程中的某个时刻是可用的,我们可以从 FRAM 中读取它们的命令处理程序,但无法执行它们。

调用这些函数有些令人失望。A1返回某种设备状态,但其他Ax命令甚至没有给我们返回错误信息。部分汇编代码难以看出原因,但我们后来得知,它们需要一个安全密码。

在尚未能够运行A3命令时,我们读取了它的反汇编代码。该函数首先调用0x1C20处的另一个函数,然后读取原始地址和长度,接着将请求的数量的 16 位字发送到 RF13M 外设到读卡器。^(2) 如果我们能调用这个命令,就能转储 ROM 并逆向工程其他命令的行为!

嗅探读卡器

为了在没有固件转储的情况下获得密码,我们必须嗅探合法读卡器尝试调用除A1以外的任何Ax命令,以便我们能够了解密码,并使用A3转储原始内存。我们通过触摸制造商专用硬件读卡器的 SPI 总线以及通过 Frida 观察厂商的 Android 应用程序分别找到了这个密码。^(3)

32 位密码C2AD7521A0命令的一个参数,该命令在注射到患者的手臂后初始化葡萄糖传感器。在A3中尝试相同的密码,并跟随地址和长度,使我们能够读取原始内存。循环发送此命令可以完全转储 ROM 和 SRAM,以及完全转储 FRAM 区域。这些区域无法通过标准读取命令20访问,该命令需要块地址。

TAL152 ROM 内部

将这个完整的转储加载到 Ghidra 中显示,ROM 与 FRL152 的 ROM 相关,但它们已经有了很大的差异。TAL152 没有直接实现厂商命令;相反,必须通过补丁表添加这些命令。

我们也没有写入 FRAM 的能力,因为它是写保护的。果然,A2命令会写保护通过 NFC 暴露的每个 FRAM 页面,而A4则解锁这些页面!命令列表见图 7.5。

调用A4命令后,我们可以解锁页面并开始修改。简单地写入0xFFB8将重新启用Ex命令,允许我们实验恢复旧传感器。或者我们可以编译自己的固件运行在 TAL152 中,将一个葡萄糖传感器变成完全不同的东西。

其他解锁技巧

在尝试提取 TAL152 的内容时,我们遇到了一些死胡同,但这些方法可能对你在其他目标上有效。

我们无法建立连接,但 TAL152 的 JTAG 似乎已经解锁,如果它遵循与 FRL152 相同的约定。这可能是由自定义激活密钥引起的,但无论是不同的锁定机制还是不同的密钥,我们都未能建立连接。我后来听说,TAL152 的接线不同,需要调整接线才能建立连接,但我尚未在我的实验室确认这一点。

我们尝试通过将芯片加热到其居里点以上,将其恢复到出厂设置。我们的理论是,热量可能会抹去 FRAM 中的数据,同时保留 ROM 中的数据,这样 ROM 就可以自由读取。

德州仪器应用报告 SLAA526A,MSP430 FRAM 的质量与可靠性,让我们相信这个温度接近 430°C。短时间的热风枪和强磁铁实验没有成功,但我们希望有一天能将芯片在窑中烘烤几个小时,以寻找位错误。

芯片上的测试引脚引起了我们的好奇心,因为其他芯片使用它们进入引导加载程序,而这些芯片可能也用它们来恢复到出厂状态。这种方法可能和过热 FRAM 一样有效,而且避免了极端温度带来的麻烦。

值得注意的是,我们成功的方法——使用A3命令和制造商的密码——可以通过接触硬件读取器的 SPI 总线来完成,或者通过从制造商的 Android 应用中读取相同的密码来完成。在逆向工程中,任何有效的技巧都是好技巧,而且通常有不止一种方法可以完成任务。

第八章:8 JTAG 和 ICSP 基础

JTAG 接口是一种与微控制器通信的低级方式,通常用于调试或设备工厂的初始编程。

JTAG 由四个必需信号组成:TDI、TDO、TCK 和 TMS。TDI 和 TDO(测试数据输入/输出)负责将数据传入和传出芯片,而 TCK 提供数据的时钟信号,TMS(测试模式选择)则指示芯片的状态。一个可选的第五个信号 TRST 可以重置测试逻辑。

还有一些减少引脚数的 JTAG 变种,如 ARM 的单线调试(SWD)和 MSP430 的 spy-bi-wire。这些变种的优势在于它们需要的引脚更少,有时比 4 引脚的协议变种更容易实现。

我暂时不会深入探讨这些协议的复杂细节,但了解一些历史背景是值得的。JTAG 最初作为一种测试 PCB 连接性的方式,后来才被扩展为用于调试微控制器。对芯片的调试访问通常是非常底层的,并且必须为不同版本的芯片以不同方式实现。

除了 JTAG,许多微控制器供应商还提供自己的串行接口用于编程或调试。Microchip 的 PIC 和 AVR 系列称之为在线串行编程(ICSP)。

JTAG 适配器和软件

JTAG 最初只是一个物理层,但在它之上已经建立了一个完整的软件和工具生态系统。其中一些有文档记录,一些则是机密或专有的。这就是为什么工具选择如此令人困惑的原因。

就像大多数嵌入式开发者不能立刻说出他们最喜欢的微控制器的流水线阶段数一样,他们也很少需要从头开始实现 JTAG。为了提取固件,我们应当记住使用现成适配器与从头编写新适配器之间的区别。

在硬件方面,大多数流行的微控制器供应商提供自己的半专有适配器。这些适配器可能很昂贵,但有一个漏洞是,开发板上也包含了相同的适配器,通常一个非常便宜的评估套件(EVK)可以被重新布线用于调试任何芯片,而不仅仅是与之一起出厂的型号。

还有一些供应商专门提供适用于各种板子的 JTAG 适配器。Segger 的 J-Link 特别受欢迎,提供从廉价学生套件到极其昂贵的型号。高端适配器不仅能调试代码,还能实时追踪代码,几乎不影响性能。

最后,还有一些开源适配器,如我以前为 MSP430 制作的 GoodFET。一个流行的解决方案是使用 FTDI 芯片进行大爆炸 IO 调试,支持多种目标。你也可以使用树莓派的 GPIO 引脚,因为这些引脚的延迟比 USB 适配器低得多。

在软件方面,既有专有软件也有开源软件。专有软件通常在记录功耗和执行追踪方面具有优势,有时与商业开发工具集成得更好。虽然专有软件可以通过开发者 API 进行定向,但开源替代方案包括适用于各种芯片的脚本,并且通常可以非常快速地适应新的目标。OpenOCD 并不是唯一的开源适配器,但它通常是获取 GDB 调试会话的一个良好目标,适用于新的芯片。

发现引脚布局

对于一个已知的、带有良好文档的方便封装的芯片,追踪 JTAG 引脚并不困难,这些引脚应该在数据表上有明确标记。但当引脚布局未知,或者芯片本身没有文档时该怎么办呢?幸运的是,我们有一些选择。

为了方便起见,许多 PCB 设计师为其架构使用行业标准的 JTAG 连接器。如果你看到芯片附近有一个两排的引脚头,包含 10、14 或 20 个引脚,那么很可能是 JTAG。如果接地引脚符合标准并且数据引脚直接连接到你的芯片,这个猜测就更准确。如果是 PIC 和 AVR 芯片,则不支持 JTAG,但它们有自己的六针标准。有关示例,请参见 图 8.1。

当然,也会出现违反标准的情况。在像 第十二章 中的 HID iClass 读卡器这类安全主题设备中,这可能是为了防止逆向工程。你还会看到由于其他原因偏离标准布局的情况,例如引脚被意外交换,或者 PCB 设计师在各种 14 针调试器标准之间混淆。

Heinz(2006)描述了一种 AVR 固件、GTK GUI 和算法,用于识别候选引脚中的 JTAG 信号,它通过使用 1 位的 BYPASS 寄存器将信号从目标回传。这一项目已不再维护,但 Grand(2014)描述了 JTAGulator,这是一种现代的开源 JTAG 引脚布局查找器,基于 Parallax Propeller 芯片构建。

图片

图 8.1:常见的 JTAG 和 ICSP 引脚布局

图片

图 8.2:Grand(2014)的 JTAGulator

如果我们能够自动找到引脚,并且如果 JTAG 真的只是一个交换一些寄存器的方式,那么应该可以枚举这些寄存器,导出一个列表以供进一步调查。Domke(2009)提供了一个算法和实例,正是用来做这些的。

在工厂中,JTAG 不仅用于编程芯片,还用于验证它们之间的连接,确保所有引脚已被焊接。Skowronek(2007)描述了一个算法,用于恢复多个芯片之间的引脚连接,这一算法成功地被用于逆向工程他从废料堆中救回的视频处理板,使他能够在大约一天的时间内构建一个用于搜索 SHA-1 和 MD5 的 8 字符密钥空间的破解工具。

总 JTAG 锁

现在我们已经介绍了 JTAG 的工作原理、如何找到其引脚以及使用哪些 JTAG 硬件和软件,接下来让我们讨论特定芯片中使用的保护机制。在本书的后面章节,我们将专门介绍如何绕过各个保护机制。

MSP430 是一个很好例子,它采用了完全锁定的 JTAG。早期的芯片,如 MSP430F1xx、MSP430F2xx 和 MSP430-F4xx,通过烧录保险丝来启用保护模式。在 JTAG 调试器连接后,保险丝检查序列会测量芯片的保护状态。在后来的芯片中,电迁移保险丝被一个特殊的闪存字取代,但完全锁定的概念仍然保留。这些细节在德州仪器(2010)中有描述,基本足够实现一个从头开始的 JTAG 编程器。

乍一看,完全锁定似乎并没有给我们留下太多操作空间,也没有多少攻击面可以探索。我们怎么才能解锁一个只暴露无用的 BYPASS 寄存器的芯片呢?

一种方法是通过攻击其引导加载程序完全避免它。MSP430,像许多其他芯片一样,具有一个掩码 ROM 引导加载程序,即使在 JTAG 被锁定后也会保持启用。第 E.8 章描述了一种攻击方式,通过这种方式,可以在 JTAG 完全禁用的情况下,通过故障注入引导加载程序,泄露 MSP430F5172 的固件。

另一种选择是故障注入,以伪造保险丝检查的结果。我们可以在保险丝检查的时刻通过注入故障,使检查在应该失败的时候通过。有关通过注入照相机闪光灯光源来故障注入较旧 MSP430 芯片的保险丝检查的详细信息,请参见第二十章。

部分 JTAG 锁定

完全 JTAG 锁定很容易实现,但它们让设计师感到紧张,因为它们几乎不留下任何用于故障分析的空间。如果 Bob 的小工具发生故障,他希望尽快知道是硬件故障还是固件问题,没有调试器他将没有太多线索。所以,与其让 Bob 实现自己的自定义后门,许多芯片制造商允许进行部分锁定,既保护了 Bob 的知识产权,同时仍然允许将新的固件写入芯片。

Nordic Semiconductor 的 nRF51 芯片是一款非常受欢迎的蓝牙低能耗(BLE)芯片。它使用一种基于内存保护单元(MPU)的部分保护机制,禁止调试器进行任何内存访问。你可以逐步执行现有代码,随心所欲地读取和写入 CPU 寄存器,但一旦你尝试直接从 RAM 或闪存中获取一个字,处理器将在下一时钟周期断开连接。Kris Brosch 发现了一个漏洞,虽然你无法直接从闪存中读取,但你可以在闪存中找到一个小工具,让它为你完成这项工作。有关详细信息,请参见第九章。

STM32F0 系列还提供了部分调试锁定。在 JTAG 开始调试 CPU 后,每当对闪存执行任何访问操作时,无论是由调试器本身还是 CPU 代码引起的,闪存都将从总线上断开。你不能重新使用闪存代码来提取指令,因为从闪存执行代码也会在连接调试器时触发锁定。幸运的是,对于攻击者而言,这个锁定发生得只晚一个时钟周期,因此可以在每次 JTAG 连接后读取恰好一个字的闪存内容,并且通过数千次连接,整个固件都可以被提取出来。详细信息请见第十章。

一些其他 STM32 设备具有部分锁定功能,这种功能不会受到 STM32F0 的首字暴露漏洞的影响。在这些设备中,存在一个巧妙的漏洞,即在中断调用过程中,使用单独的内存总线来访问中断向量表(IVT)。通常,这个表位于闪存的最开始位置,但攻击者可以利用向量表偏移寄存器(VTOR)来滑动中断表,通过触发中断调用并读取程序计数器,从而转储受保护的内存!详情请见第十一章。

即使我们没有针对特定芯片的 JTAG 漏洞,部分 JTAG 锁定也可以用于其他目的。通常,当闪存被锁定时,SRAM 可以被自由读取,或者可以将 shell 代码写入 SRAM 的未使用部分,并在下次启动时通过软件漏洞执行。而现代 CPU 的复杂性,即使是微控制器,也使得总会存在一些巧妙的角落案例,只要我们足够仔细,就能够发现它们。

第九章:9 nRF51 ROM 中的工具

本章首次在 Brosch(2015)中记录,描述了尽管有代码保护功能,如何从 nRF51822 中提取受保护的内存。其漏洞在于,尽管调试器不能直接读取受保护的内存或将 Shellcode 写入 SRAM,但它可以逐步执行闪存中的受保护代码。

虽然此版本是针对 nRF51 系列描述的,但 Obermaier、Schink 和 Moczek(2020)在描述 CKS32F103 和 GD32VF103(这两款是流行的 STM32F103 的克隆)时也提到了一种类似的漏洞。Kovrizhnykh(2023)指出,Sonix 的 SN32F248B 也曾被相同的技术利用。

学习所有规则

nRF51 的保护机制在 第九章 中有详细记录(Nordic,2014),它是内存保护单元(MPU)的扩展。MPU 类似于内存管理单元(MMU),不过它的粒度较粗,并且不支持虚拟内存。

该芯片最常见的读取保护方式叫做“Protect All”(PALL),其配置方法是将零写入 I/O 端口 UICR.RBPCONF.PAL。此保护旨在防止 SWD 调试器访问代码区域 0、代码区域 1、RAM 或任何外设,除了 NVMC 外设、POWER 外设中的 RESET 寄存器,以及 MPU 外设中的 DISABLEINDEBUG 寄存器。你通常会看到引导加载程序在每次启动时执行此保护,但该保护是持续有效的。只需应用一次保护即可。

Image

图 9.1:NXP nRF51822

Image

图 9.2:nRF51822 内存映射

Image

图 9.3:Brosch 的 PoC nRF51822 漏洞利用

还有一些较低级的保护模式,这些模式限制代码区域 1 访问代码区域 0。此类模式的目的是保护软设备,这些设备是通常需要商业授权的二进制广播驱动程序,但仍允许自定义代码与其并行运行。这些二进制代码在较低区域自由运行,尽管上层区域可以调用下层区域,但不能将该区域作为数据读取。

参考手册还提到,不论保护模式如何,CPU 从代码内存中获取指令时不会被拒绝,并且从 0x000x80 的中断表不受保护。

绕过规则

在我们已经了解了保护的已记录行为之后,有必要做一些实验并学习未记录的规则。Kris Brosch 发现,通过将调试器连接到锁定的芯片,他获得了相当大的自由度来引导 CPU。他可以读取和写入寄存器,包括程序计数器。他还可以读取一些内存映射寄存器,如 0x10001004 处的读取回保护配置(RBPCONF)。

最重要的是,尽管他不能直接通过调试器从受保护区域读取,但他可以单步执行现有代码,控制指令前后的寄存器值(作为输入和输出)。

他重置了芯片,这会从中断向量表加载程序计数器和堆栈指针,然后将程序计数器的值读取回来为0x000114cc。因此,他知道0x00000004处的复位向量值应该是0x000114cd。(奇数指针表示 ARM 的 Thumb2 模式,但程序计数器本身并不持有奇数值。相反,那个状态位被保存在状态寄存器中。)

知道内存中的一个词后,他反复将所有寄存器加载为0x00000004,并将程序计数器跳转到新的地址,直到看到r3的值变化为0x000114cd,这表明出现了一个任意读取的小工具!

这个小工具是ldr r3, [r3, #0],它出现在复位处理程序的第二条指令中。通过不同的r3值反复跳入这个小工具将暴露所有内存。

Brosch 的概念验证可以在图 9.3 中找到。telnet 连接的是 OpenOCD,假设小工具出现在复位处理程序中。如果小工具出现在目标的其他位置,你需要调整相应的设置。

第十章:10 STM32F0 SWD 字词泄漏

许多微控制器允许某种部分锁定模式,在这种模式下,调试器可以附加上去,但代码仍然受到保护。在 STM32 系列中,这对应于 RDP 级别 1,在此级别下,调试器连接后闪存会被断开。本章描述了 STM32F0 系列中的一个漏洞,其中闪存断开发生得比预期晚了两个时钟周期。经过精心设计的调试器可以每次连接转储一个字。

该漏洞首次在 Usenix WOOT 上描述,出现在 Obermaier 和 Tatschner(2017)论文的结尾处。

漏洞

如我们在第二章中讨论的,STM32 的读出设备保护(RDP)功能有三个级别。级别 0 是无保护的,而级别 2 则是完全的 JTAG 锁定,拒绝所有连接尝试。级别 1 是大多数商业设备采用的中间设置;它通过在 JTAG 连接时断开闪存与总线的连接来工作。其目的是允许进行故障分析或重新编程,同时仍然防止提取闪存用于克隆或逆向工程。

你可以使用 OpenOCD 或其他 JTAG 调试器验证这一点。描述是正确的:连接到被锁定的芯片是可行的,但无法从闪存中读取任何有用的信息。你可以读取 RAM,或将内容写入 RAM,但代码不能从闪存中读取或执行代码。

Image

图 10.1:STM32F042

Obermaier 的独特观察是,大多数 JTAG 调试器在连接时执行多个事务,而第一次内存访问负责锁定闪存,但该读取操作通常在锁定应用之前就已完成!

为什么是通常而不是总是?细节对于漏洞利用并不重要,但原文论文做出了有力的论证,认为这是一种总线争用问题。作为一种解决方法,似乎在访问失败后重试是足够的,且在顽固的情况下,添加随机延迟可能会有所帮助。

漏洞利用

Obermaier 的漏洞利用程序作为独立固件运行在一颗 STM32 上,它实现了 SWD 协议来转储目标芯片的内容。完整源代码可用,以下是他用 C 语言编写的从受保护内存中转储一个 32 位字的函数。SWD 比 JTAG 更容易实现,在这个漏洞利用中,你会看到 SWD 的实现不到六百行代码。

注意,每次尝试时,代码必须在新的调试会话中重新连接,因为读取后闪存会被断开。由于单个尝试经常失败,因此必须重试直到事务成功。

ImageImage

第十一章:11 STM32F1 中断拼图

STM32 系列的 RDP Level 1,其中允许 JTAG 调试但会立即断开闪存连接,是一个具有吸引力的内存提取漏洞目标。STM32F1 系列似乎不易受到 Obermaier 的 STM32F0 漏洞攻击(见第十章)或 DFU 引导程序漏洞攻击(见第二章),但在本章中,我们将讨论一个不同的漏洞,这个漏洞最早由 Schink 和 Obermaier(2020)描述,针对的是 STM32F1 系列,随后 Obermaier、Schink 和 Moczek(2020)也描述了针对其两个克隆版本——APM32F103 和 CKS32F103 的漏洞。作为额外说明,STM32F1 系列不支持 RDP Level 2,因此该系列的所有部件可能都容易受到攻击。

当启用保护时,调试器连接时,闪存会与主内存总线断开连接。你无法将其作为数据获取,也不能将其作为代码执行。这里的技巧是,尽管闪存在代码和数据获取时与主内存总线断开连接,但中断仍然可以触发。尽管断开连接,中断地址仍然可以从中断向量表(IVT)准确获取!这个表也是可以移动的,通过缓慢地将表步进过内存,我们可以将大部分内存字移入程序计数器,以便调试器捕捉。

Image

图 11.1:STM32F103

前两个字

Image

Schink 的论文从这个简单明了的例子开始,在该例子中,他首先通过 SWD 连接一个 Segger J-Link 适配器,然后在 OpenOCD 的 telnet 会话中输入reset halt,从而揭示了0x08000268是复位向量的高 31 位,即闪存中的第二个字。0x20005000是初始堆栈指针,位于第一字。

程序计数器的低位(1)被设置为所有真实处理程序地址的指示,表示为 Thumb2 模式,但它也可能清除为(0),因此我们需要恢复该位以进行真正的攻击。这是因为,与真实的中断表不同,假中断表大多由非中断处理程序地址的指令或数据组成。Schink 通过首先读取程序计数器(其低位被强制清除),然后从ESPR中获取 Thumb2 模式来恢复缺失的位。

Image

这将为我们提供闪存的前两个字,但通过阅读代码,你会看到这是一种特殊情况,因为触发复位也会将中断表移动回闪存的开头。

Image

剩余内存

对于所有其他地址,必须缓慢地将整个中断表步进过闪存,然后必须人为地触发单个中断,以便将表项移动到程序计数器。

第一个复杂性是列表中的七个条目是无法使用的。我们已经讨论过 0(MSP)和 1(复位)不能重新定位,因此除了开始时,其他地方都不能使用这些条目。异常 7、8、9、10 和 13 是保留的,我们无法触发它们。异常 16 及更高的是外部中断,我们可以触发它们,但数量会根据芯片型号不同而有所差异。

第二个复杂性在于我们使用向量表偏移寄存器(VTOR)来重新定位表格。这个寄存器通常被自定义的引导加载程序使用,比如第三章,以便芯片可以先使用一个中断表,然后再切换到应用程序的中断表。

如果我们能一次移动一个字来滑动中断表,我们就可以重用一个中断来转储内存中的所有字,但正如图 11.2 所示,我们有一个 128 字对齐的限制,妨碍了这一点。我们需要按块移动表格,然后触发单独的中断从表格中提取字。

这个对齐规则意味着,虽然我们可以移动 VTOR,但由于禁用的异常,我们的表格会存在空白,每个表格都缺少七个字!Schink 发现,虽然你确实需要根据表格大小进行对齐以保证正常操作,但如果表格对齐到其大小的一半,并且触发一个超出表格末尾的中断,表格会有点“环绕”。

图片

图 11.2:STMicro 的 VTOR(2005)

图片

图 11.3:IVT 的重新定位

因此,在具有 64 个条目的 STM32F103 中,表格从 0x08000000 开始,我们可以正式使用 VTOR 将其重新定位到对齐的地址:0x080001000x080002000x08000300,依此类推。在这些偏移量下,我们无法读取偏移 0x000x040x1C0x200x240x280x34 的七个字,因为这些中断是禁用的或无法触发的。但通过将表格设置为 0x080000800x080001800x08000280 等的 32 字对齐,我们可以利用表格环绕来填补空白,触发中断 32 而不是 0 来转储偏移 0x00,或者触发中断 39 而不是 7 来转储偏移 0x1C。图 11.3 说明了这一点。

使用这个非法偏移技巧,我们仍然错过了每个偶数 32 字节块的七个字,但我们收集了所有来自奇数 32 字节块的字,这使得我们能够覆盖 STM32F103 固件的 89%。因为我们仅仅错过了偶数块的字,所以在具有更大中断表的芯片上,我们的覆盖率会更好。

触发中断

现在我们已经讨论了 Schink 利用的理论,接下来讨论触发特定中断的实际细节。在连接到 OpenOCD 服务器后,他的脚本首先会暂停目标并禁用异常屏蔽。

图片

然后它将四个半字指令写入 SRAM 的起始位置0x20000000,用于在无法直接触发中断时触发异常。其中一个是svc #0用于触发监督调用,第二个是nop,第三个是用于触发总线故障的加载指令,第四个是0xFFFF,即非法指令。许多这些中断默认是禁用的,因此代码必须首先启用这些功能,然后再执行非法操作。

每个中断首先通过向 OpenOCD 发送reset halt,写入 VTOR 地址,然后通过其独特的触发方法触发各个中断。

首先是标准中断:异常 2 是 NMI,通过设置ICSR寄存器的第 31 位来触发。异常 3 是 HardFault,通过从 SRAM 执行0xFFFF来触发。异常 4 是 MemFault,通过设置SHCSR寄存器的某个位并跳转到0xe0000000处的不可执行 I/O 内存来触发。异常 5 是 BusFault,通过设置SHCSR的另一个位并跳转到 SRAM 中的ldr指令来触发。异常 6 是 UsageFault,通过设置SHCSR的适当位并跳转到 RAM 中的非法指令来触发。异常 11 是 Supervisor Call,通过从 SRAM 执行svc #0来触发。异常 12 是 DebugMonitor 异常,通过设置DEMCR寄存器的第 17 位来触发。异常 14 是 PendSV 中断,通过设置ICSR寄存器的第 28 位来触发。异常 15 是 SysTick 中断,通过设置ICSR寄存器的第 26 位来触发。

从异常 16 开始,直到表格末尾,我们有外部中断。每个中断都有一个异常编号,从异常 16 的 0 开始,并且每个中断都可以通过设置NVIC_ISER0NVIC_ISPR0寄存器的相应位来触发。

除非明确指定特定指令,否则在触发这些中断时,您可能需要执行nop,以避免提取过程中出现任何不可预测的错误。

计数外部中断

在漏洞利用准备就绪之前,还剩下最后一件事。我们迫切需要知道中断表的大小,以便知道何时将其滑动到新位置以及何时可以使用环绕技巧处理半对齐的表位置。

对于演示来说,简单地将一些已知型号的值硬编码是完全可行的,但为了便于移植,Schink 的漏洞利用方法通过依次触发外部中断来进行计数,直到程序状态寄存器(PSR)不再指示异常为止。

统计中断时还发现,对于某些型号,文档错误地将一些外部中断列为保留中断,而实际上它们的功能与其他中断完全相同。

性能

Schink 的论文以一张 STM32F1 芯片的表格作为结尾,表格列出了外部中断计数、提取时间和提取 128kB 闪存时的覆盖率。(见图 11.4 和图 11.5。)

提取覆盖率严格受到中断数量的限制,因为有七个烦人的中断无法在对齐的中断表中触发。

如果仅凭大部分指令进行固件逆向工程变得过于令人沮丧,尝试转储多个目标固件版本可能会有所帮助。不同版本的固件应该在不同位置出现空白,从而允许用另一个版本来填补其中一个版本缺失的部分。(当然,这种技术会因为源代码和对象大小的差异而导致错误,但大多数单词应该能够被正确提取。)

Image

图 11.4:来自 Schink 和 Obermaier (2020) 的代码覆盖率

Image

图 11.5:计数中断处理程序

Schink 使用了一个 Segger J-Link 调试器,工作频率为 3500 kHz,提取时间可能通过减少往返次数或提高时钟频率来改善。当然,这对单个设备的转储影响不大,但如果需要转储多个不同的芯片,以填补不同版本固件中遗漏的字节时,这可能变得至关重要。

第十二章:12  PIC18F452 ICSP 和 HID

在 2010 年,针对存储对称密钥的 RFID 标签的利用引起了广泛关注。这些卡片本身在内存提取方面有一定的保护,而且密钥可能是每个客户安装的唯一密钥,因此研究人员通常会攻击读卡器。这些读卡器通常使用商用微控制器,并依赖它们的读出保护来确保对称密钥的安全。

在本章中,我们将介绍两个利用 ICSP 从 HID iClass 读卡器提取密钥的漏洞。它们都利用了 ICSP(Microchip 的在线串行编程标准)中的细微差异。第一个漏洞在 2010 年的 27C3 大会上作为 Meriac 公开,涉及通过 ICSP 擦除受保护的闪存页面,并用 shellcode 替换它,从而转储其余数据。第二个漏洞,Huffstutter(2011),则利用相同的 ICSP 协议转储 RAM,而不是闪存,因为该芯片没有为 RAM 设置保护位。

本案例中的目标读卡器是 HID RW400,Meriac 选择它是因为它是支持 iClass 卡片的最旧编程器。这个设备如图 12.1 所示,图中透明的环氧树脂封装覆盖了我们可以在图 12.2 的 X 射线图像中看到的电路。

ICSP 协议有许多小的变种,每个变种都在《FLASH 微控制器编程规范》文档中进行了说明,该文档涵盖了十个或二十个部件号。

较旧的芯片需要外部施加高电压进行擦除,而现代芯片也支持低电压模式,在这种模式下,编程电压是内部生成的。如果没有其他漏洞可用,这里尝试错误电压和时序肯定是值得一试的。章节 H.1 描述了 90 年代的类似攻击。

图片

图 12.1:HID RW400 卡片读卡器

图片

图 12.2:HID RW400 X 射线图像

PIC18 比早期的 PIC 架构稍微强大一些,但它仍然遵循尽可能多地复用组件的理念,以保持晶体管数量尽可能少。

ICSP 协议看起来很像 SPI,唯一不同的是它有一个单一的数据引脚,其方向会根据需要变化。请参见图 12.3,该图显示了图 12.1 中压电蜂鸣器左侧六个引脚的引脚排列。所有的传输都是 20 位数据,由 4 位操作码命令和 16 位参数组成。

在 ICSP 中,命令0000将参数作为原始的 PIC18 指令执行。0010读取TABLAT(表格锁存器)寄存器,而10001011是表格读取命令,11001111是表格写入命令。这是一种相对迂回的读取代码内存的方式,但大体上符合 PIC18 汇编语言中的表格指针操作。编程规范中包含了擦除内存和将代码写入内存的命令和指令示例对。

Flash(代码)、RAM 和 EEPROM 在不同的地址空间中,并且一系列配置字描述了保护设置,以及振荡器、定时器、欠压保护和其他可配置功能的设置。这些 16 位字从闪存中的0x300000开始。对于开发者来说,这些设置被定义为#pragma行,如图 12.4 所示。

代码内存被分成了大小略显笨拙的多个页面。第一个页面是位于0x0000的 512 字节引导程序页面,接下来是从0x02000x1fff的 7,680 字节的页面 0。剩余页面每个为 8kB。请参见图 12.6 以查看布局图。

Image

图 12.3:HID RW400 的自定义 ICSP 引脚布局

Image

图 12.4:HID RW400 读卡器的配置字

每个页面都有一个CP位。清除此位可以启用代码保护,WRT位启用写保护,EBT位启用表格读取保护,以确保来自其他页面的代码无法通过表格接口读取此页面作为数据。这些位通过擦除相应页面来设置。

Meriac 的引导块漏洞

当 Milosch Meriac 想要从 HID RW400 读卡器中转储该芯片时,他发现CPWRT位已被清除,因此 ICSP 上下文中执行的指令无法读取或写入任何闪存块。他选择这些读卡器是因为它们是最早支持 iClass 卡的,而你可以在图 12.4 中找到读卡器的配置位。

幸运的是,EBT位没有被清除。如果它们被清除,来自一个闪存页面的代码将无法对任何其他页面执行表格读取操作。由于这些位仍然被设置,因此可以从任何页面上运行的代码转储整个芯片的内存。

Meriac 观察到,通过擦除一个页面,他可以设置该页面的CPWRTEBT位^(1)。这使他能够将一段 shellcode 写入该页面,这段 shellcode 会将其余内存通过串口输出。

他将这个过程包装成一个 Windows 的 C++应用程序,通过 FTDI 芯片的 GPIO 引脚将 ICSP 传输到调试接口,然后通过同一个芯片的 UART 读取固件。他的 shellcode 如图 12.5 所示;它只是简单地将固件转储到 UART。

Image

图 12.5:Meriac 的 PIC18 Dump 源代码

Image

图 12.6:PIC18F452 闪存映射

Image

图 12.7:Meriac 的 PIC18 Dump Shellcode

Image

图 12.8:Microchip PIC18F452

Image

图 12.9:Huffstutter 的 ICSP RAM 提取

对于他的目标,只需擦除并重写 512 字节的引导程序页面,并用 shellcode 二进制文件替换,因为这个页面恰好没有值得保留的内容。其他目标可能会在引导块中有重要内容,对于这些目标,必须使用第二个受害设备。这个第二个设备会擦除除第一页以外的每一页。这些页面随后会被一系列重复的 NOP 指令覆盖,最终将 shellcode 写入内存的末尾。其思路是引导块最终会在其他块中分支,而几乎每个合法地址都会滑动到 shellcode,转储第一块内容。

Huffstutter 的 ICSP SRAM 漏洞

Carl Huffstutter 在 Huffstutter(2011)中描述了针对同一芯片和同一固件映像的不同漏洞。他发现,尽管每个闪存和 EEPROM 存储区都有各自的保护保险丝位,但没有用于保护 RAM 的保险丝位。果然,图 12.9 中的 ICSP 交易能够干净且无破坏性地提取被锁定微控制器的所有 RAM。

在 RAM 中,他找到了 64 位 HID 主身份验证密钥、两个 64 位的 Triple DES 密钥(用于加密读卡器和卡片之间的通信)、用于高安全性卡的 128 字节密钥表,以及最后一次读取的卡片的所有详细信息。这些信息在使用后并未被擦除,但即便被擦除,机器也可能在读取过程中被中断,从而揭示正在使用的内容。

许多其他设备在保护闪存的同时暴露 SRAM,因此每当你需要从芯片中获取数据而不一定需要代码副本时,考虑这种攻击是值得的。在防御方面,将任何重要的密钥和表声明为const可能会有所帮助,这样它们只会存储在闪存中,而永远不会复制到 RAM 中。

第十三章:13  故障注入基础

亲爱的读者,请允许我讲一段小小的神话故事。之后,我们将继续讨论现代时钟和电压故障攻击。

很久以前,故事是这么说的,一张卫星电视智能卡存在内存损坏的漏洞。人们为此欢欣鼓舞,因为通过内存损坏漏洞足以解锁所有频道并提取卡内的所有数据。然后,天上传来了一个信息——一个 EEPROM 更新,而不是预言——这些卡被修补成在无限循环中运行,而不是解码皮卡德船长与博格族的最新战斗。确切的补丁和确切的卡片已经随着时间的推移而失传,但在 C 语言中,我们可能会说代码看起来像下面这样。

Image

因为卡片在无限循环中运行,而不是执行其正常功能,黑客们称之为“被循环”。从中,他们发明了“解循环”技术,即通过操控卡片的电压或时钟信号跳出无限循环。今天,我们称这些技术为“故障注入”或“故障攻击”,它们依然非常有效地去除芯片的保护。

这个技巧是短暂地降低芯片的电压,或者向时钟供应线路引入一个非常简短的额外周期。就像让芯片运行得太快或电力不足一样,这会导致指令执行错误。但由于这种违例非常短暂,可能仅仅一个指令就会被破坏,而其他部分保持正常。

在我们的示例中,智能卡将永远执行第 3 行的while循环。优化和汇编语言会以不同的方式表示,但可以想象它变成以下伪汇编代码。

Image

当设备被循环时,微控制器将永远按顺序执行第 2 行和第 3 行。如果我们缩短时钟周期,使得第 3 行的“等于跳转”指令不会覆盖程序计数器,那么执行将继续到第 5 行,调用主方法,就好像这个芯片没有被锁定一样。由于循环持续运行,芯片在每次重启前都会给我们提供许多尝试机会。

另一个好的目标是复制循环。在启动时,智能卡通常会呈现其复位响应(ATR)字符串。如果发送该字符串的for循环像这样,那么我们可能会通过故障攻击在最后一个字节之后,比较i16时泄露额外的内存。当比较是精确的(i ≠ 16)而不是范围比较(i <* 16)时,这可能会导致大量额外的内存泄露!

Image

在 2000 年代初,专门的解锁硬件开始在商店里销售给业余爱好者,家用设计的原理图也在论坛上传播。大多数方案使用 Atmel AT90 微控制器和 7400 系列芯片,在时钟和数据线上插入故障,以对抗 DirecTV HU 卡。^(1) 请参见图 13.1 了解示例,如果你想购买现成的,可以在 eBay 上搜索“Mikobu”。

Image

图 13.1:PLC77 的智能卡解锁器(2001)

就软件而言,这些解锁器设计中的大部分都需要通过 MAX232 芯片将固件加载到 AT90 中,且是通过串口进行的。尽管许多故障程序以源代码或黑盒二进制的形式共享,但也有一种传统是以注释过的 VBScript 形式与名为 WinExplorer 的程序共享它们。

时钟故障

当设计微控制器时,会涉及到时序闭合的问题。对于任何给定的芯片,都会有一个最大时钟频率,低于这个频率,设计可以按预期正常工作。在这个速度下,所有组合逻辑都会在正确的时间得到结果,并能被顺序逻辑锁存。

超过这个频率,事情会失败,但并非一下子就全完。也许乘法运算是时钟频率的瓶颈,稍微超过这个频率就会导致一些乘法运算失败,而其他的功能依然正常。如果你不需要乘法运算,你可能会超过这个时钟频率,从而提升其他功能的性能。

当芯片从外部引脚获取原始时钟输入,而没有通过锁相环平滑时钟时,我们就有机会进行时钟故障操作。我们通过插入一个短暂的时钟脉冲,单个边缘或周期,远远超出芯片的最大时钟频率来实现这一点。

在多周期设计中,可以将其看作是一个指令的一部分被给予时间以完成。也许在指令的第一周期中,错误的操作码被锁存,或者也许在指令结束时,跳转没有写回程序计数器。

我通常从固件做出重要决策的时间范围开始,然后尝试在这个范围内故意制造随机错误,直到让芯片出现异常。由于我们控制着时钟本身,这个时序可以非常准确和可靠。

电压故障

当原始时钟输入不可用时,电压故障仍然可能是一个选项。其思路是突然改变电压,迅速升高或降低电压,持续的时间极短,以至于芯片不会崩溃,但也无法正确执行指令。

降低电压会产生许多影响。其中之一是晶体管翻转速度变慢,从而导致一个本来在时序闭合范围内的设备,突然无法及时计算其结果,类似于时钟故障。例如,Atmega328P 在 4.5V 时可安全运行 20MHz,而在 2.7V 时则只能运行 10MHz。其他影响包括存储器故障和指令解码错误。

电压故障的校准可能很棘手。第一个轴线将是从可观察触发器(例如引脚上升)到发生故障的时间偏移。变化持续时间和目标电压将是另外两个轴线,时钟漂移会导致我们在触发后等待更长时间,故障发生的可靠性降低。

为了简化操作,许多现代的故障攻击通过直接将核心电压短接到地面,并依赖非常短的时序来防止崩溃。^(2)

无论你如何安排,重要的是一次只校准一个轴向的故障。我会在一个开发板上进行此操作,开发板上的芯片与我的目标芯片相同,首先运行一个紧凑的while循环,计算一堆数字,并在它们不一致时打印警告。然后,我可以寻找一个合适的持续时间和电压,使警告出现,而不必担心何时应用故障。我会移除大部分去耦电容器,然后如果情况变得不稳定,再逐个加回。

只有在成功注入故障到这个简单目标之后,我才会切换到真正的目标。在那里,我的触发器和偏移量才会变得重要,并且最好是其他参数已经调好。

第十四章:14 MC13224,最简单的故障注入

让我们看看我在 Goodspeed(2011)中的一个漏洞,在这个漏洞中,通过在重置期间将 Freescale MC13224 的一个引脚接地来解锁它。这需要定制的 PCB 和一些热风焊接,但非常可靠,而且不需要任何复杂的软件。

MC13224 是一个系统封装(SiP),配备了 32 位的 TDMI ARM7 CPU 和一个 802.15.4(Zigbee)无线电。它具有 128KB 的 SPI 闪存、96KB 的 RAM 和 80KB 的 ROM 用于实现 15.4 MAC 功能。这个芯片曾被用于 Defcon 18 忍者徽章(Wozniak 和 Creighton,2010)。它的卖点是,2.4GHz 调谐的 50Ω轨迹天线是你所需的唯一天线链,其他所有组件(除了晶体)都已内部集成。

系统封装是一种使 PCB 设计师的工作更轻松的好方法,但从图 14.1 中解封的照片可以看到,这个封装实际上是三颗小芯片穿着风衣,试图像成人一样行事。^(参见 1) 最小的芯片是一个射频变压器,最大的芯片是一个结合了射频功能的 CPU,第三个芯片是闪存。

由于闪存位于单独的芯片上,并且 MC13224 没有原地执行功能,它无法直接从闪存中执行代码。相反,ROM 引导加载程序会将一个工作镜像从闪存复制到 RAM 中。如果镜像的开始位置看到安全字“OKOK”,则在引导加载程序跳转到 RAM 之前会启用 JTAG 访问。如果安全字设置为“SECU”,则不会启用 JTAG 访问,芯片将保持在默认的锁定状态。

Image

图 14.1: 解封的 MC13224

Image

图 14.2: SST25WF010

仔细观察闪存芯片,我们可以从芯片上的文字找到型号,见图 14.3。这是一款标准的 SST25WF010 低电压 SPI 闪存芯片。读取此芯片的一种方式是解封目标芯片,然后将这个 SPI 闪存芯片重新焊接到一个新的封装中,并使用低电压 SPI 适配器读取它。这样当然可行,但我们更倾向于不需要像焊接机这样昂贵设备的解决方案。

Image

图 14.3: MC13224,133 号引脚加粗显示

更好的技术利用了这样一个事实:虽然 SPI 总线没有连接到外部引脚,但第 133 号引脚(NVM_REG)是闪存芯片的电压调节器输出,该引脚被暴露出来,允许外部电压调节器替代内部电压调节器。在低功耗应用中,可能通过在启动后关闭此引脚来节省电力。

当我们通过将此引脚接地切断 SST25WF010 闪存的电源时,会发生什么?Freescale(2010 年)在第 93 页的图 3-22 中解释到,当在闪存中找不到魔法字时,MC13224 将启用 JTAG 访问。然后,它将尝试从 UART1(作为串口)、SPI 从设备、SPI 主设备或 I2C 主设备启动。如果这些方法都不起作用,芯片将陷入无限循环,但它会在 JTAG 启用的情况下挂起!

因此,恢复 MC13224 闪存副本所需的仅仅是一个在复位时将引脚 133 拉低的电路板,然后将一个新的可执行文件加载到 RAM 中——在允许引脚变高后,该文件将从最近通电的 SST25WF010 中读取固件,并通过 I/O 引脚将其导出。

为此,我制作了一小批经过修改的 Econotag 电路板,见图 14.4,这些电路板将此引脚暴露给跳线。然后,可以使用镊子在重新启动时将线路保持为低电平,以解锁 JTAG。移除镊子后,可以通过电路板内置的 OpenOCD 实现使用内部 SST25 SPI 闪存芯片的客户端来转储固件。

要了解更复杂的针对双核微控制器的攻击,请参阅第 D.2 章中的 GD32F130 漏洞或第 D.4 章中的 MT1335WE 漏洞。

图片

图 14.4:修改版 Econotag

第十五章:15 LPC1114 Bootloader 故障注入

除了第四章中讨论的软件漏洞外,LPC1114 和 LPC1343 还容易受到 Gerlinksy(2017)、Nedospasov(2017)和 Dewar(2018)文献中记录的电压故障攻击的影响。这是一种初学者的故障注入攻击,适合作为学习故障注入的第一个目标。

在开始之前,查看图 4.5,并复习第四章中关于锁定功能的解释。当锁定级别为 CRP1 时,我们可以使用该章节中的内存损坏漏洞来转储芯片的内存,但在 CRP2 和 CRP3 中,bootloader 命令被严格限制,我们无法触发该漏洞。这时电压故障注入就派上用场了。

你还应该在图 4.5 中看到,一个单词的闪存内存控制着保护模式。0x12345678将我们置于 CRP1,此时远程代码执行漏洞有效。0x4321-8765将我们置于 CRP3,此时 JTAG 和 ISP 编程模式完全禁用。0x87654321也同样糟糕,允许 ISP,但允许大规模擦除命令。

表格的最后一行是关键,说明了这些芯片为何如此容易成为故障注入的目标。如果 32 位字的值与表中所列的任何值都不同,它会默认完全开放,允许 JTAG 调试和 ISP 编程。虽然0x432187650x87654321会锁住我们,但单个比特的错误可能会将它们更改为0x432087650x87654331,任意一个都会为我们提供完全访问权限。我们故障注入的目的是破坏那个字,从而产生这样的变化。

Image

图 15.1: LPC111x

Image

图 15.2: Olimex LPC-P1114 电路图

硬件修改

Gerlinsky、Nedospasov 和 Dewar 各自对图 15.2 中的 Olimex 开发套件进行了稍微不同的修改,但其基本原理是相同的。

首先,我们要移除电路图中的 100nF 去耦电容 C4。这个电容的作用是防止电压瞬间下降导致芯片故障,而我们移除它是因为我们的目的是引发这种瞬间故障。如果它留在那儿,电压故障注入将变得更加困难。

该芯片的去耦电容设计位于 VSS 和 3.3V VDD 线路之间,但在许多其他芯片上,你会发现有多个去耦电容,或者电容会放置在一个专用引脚上,电压较低,即 CPU 核心电压。

该板上还有两条可能需要切割的线路,我们需要切割这两条。3.3V_IO_E将 C1 和 VDDIO 引脚连接到 3.3V 电源轨,而3.3V_CORE将 VDD 引脚连接到 3.3V 电源轨。我们将切割这两条线路,然后用一个 12Ω的电阻将切割后的3.3V_CORE线路两侧重新连接。这让我们可以测量芯片的功耗,因为电流消耗会在电阻上产生非常小的电压降。此类测量在已知时序后进行 glitch 并非必需,但对于发现时序非常有帮助。

P0_3短接到地面将启用引导加载程序模式。我们还将添加一个 SMA 连接器,将地面和 3.3V 电源轨暴露给我们的电压 glitcher。glitch 硬件本身只是短暂地将这两个引脚短接在一起,虽然 Dewar(2018)使用 ChipWhisperer,而 Gerlinsky(2017)使用微控制器板,但你可以用晶体管和几乎任何能在复位后以可预测时序给该晶体管发送短脉冲的东西来短接它们。

如何进行 Glitch?

现在我们有一个 SMA 连接器,通过它我们可以对芯片进行 glitch,短暂地将电压轨短接到地面,而不需要去耦电容器来保存电压。在我们能解决时序问题之前,我们至少需要大致了解应应用多大的 glitch。过大的 glitch 会导致目标崩溃或重启,而过小的 glitch 则完全没有效果,因为电压降会被自然的电容和线路长度衰减,直到什么都没有发生。

如果我们把这个引脚的空闲状态想象成一个平坦的 3.3V 电压,我们将其拉低,那么在我们的 glitch 中有两个基本参数:深度持续时间

Glitch 的深度是我们将引脚拉低到的电压。它通常是从 glitcher 的一侧进行测量,理解为目标不会立即降到该电压,并且可能不会完全降到该电压。一个“crowbar”glitcher,如 ChipWhisperer,简单地通过 MOSFET 将两个电源轨短接在一起,所以它的深度实际上是地面。

你通常会在较新的设备上找到 crowbar glitcher,因为时钟频率足够高,使得 glitch 不会导致目标崩溃。它们也非常容易放置在电路板上,只需要一个由攻击微控制器的 GPIO 引脚控制的 MOSFET 晶体管。常见的 MOSFET 选择包括 IRLML6246 和 IRF8736。

在电视盗版的年代,更常见的是使用 74HC4053 多路复用器在全电压和深电压之间切换。在开发过程中,这两者都可以由实验室电源提供,尽管 glitches 会稍微宽一些,但不会那么深。

有了一个维度(持续时间)或两个维度(持续时间和深度)来校准,我们更倾向于在涉及时间的额外维度之前,先找到正确的值。这通常通过运行一个从闪存或 RAM 中加载的程序来完成,这个程序故意设计为一个容易成为攻击目标的程序。

当设置大致正确时,这段代码将开始向串口输出数据。需要确保这三个变量都是易变的(volatile),这样 C 编译器就不会优化掉它们之间的差异。

Image

当然,我们只能在这段代码上训练我们的参数,因为我们攻击的芯片也作为解锁部件供商业使用。当对智能卡或其他没有解锁样本的设备进行故障注入时,通常的做法是故障注入某种其他行为,比如读取设备的序列号。

何时进行故障注入?

现在我们知道了故障注入的宽度(持续时间)和深度(电压)来引起故障,我们仍然需要知道何时触发故障注入。我们首先选择一个触发点作为时间的起点,然后选择一个时间度量,计算从该触发点开始的时间,最后寻找可能正在运行易受攻击指令的时间范围。

这通常是以微秒数或时钟周期数来衡量的,计量从某个特定事件之后的时间,例如复位线升高。需要注意区分目标的时钟信号,它与内部 CPU 时钟紧密耦合,而攻击者的时钟信号则耦合较松散,实际上只是另一种测量墙时的方法。

目标的时钟输入引脚曾经是精确定位特定指令的一个非常好的方法,但现在许多芯片,如 LPC11,默认使用内部振荡器作为引导加载程序的系统时钟,只有在主应用程序中才会切换到外部晶体。其他芯片使用内部锁相环(PLL)来倍增外部时钟的频率,提供某种关联性,但分辨率较低。在本章中,我们将忽略外部振荡器,而是使用墙时(wall time)。

现在我们已经选择了时间的度量,并且将复位引脚的上升沿作为零时间,我们需要知道何时应用故障来解锁引导加载程序。在其他目标上,我们可能通过功率分析来执行此操作,将 SMA 连接器连接到一个 T 型接头,接到故障注入器和示波器上。在这个目标上,我们有更好的方法:引导 ROM 的转储,这是我们为了编写 Shellcode 而在第四章中制作的。

回想一下那一章,引导加载程序会多次检查其锁定状态,但它始终检查的是在启动序列早期就创建的 SRAM 中的副本。这就是为什么软件漏洞的 Shellcode 仅仅重写 SRAM 中的 CRP 级别副本,并直接跳回引导加载程序的主循环,重新利用其代码进行权限提升的原因。

图片

在这种故障攻击中,我们当然没有写操作,但我们知道有一条或两条指令在执行复制操作。也许我们在从闪存读取数据时翻转一个位,或者在写入 SRAM 时翻转一个位,或者也许我们翻转一个操作码位,让它变成另一个指令。

在 8 位 CISC 芯片上,我们可能仅通过计算指令和它们的周期成本来实现这一点。由于 LPC11 是一个流水线 RISC 芯片,这个过程就显得有些繁琐,因为任何故障都会同时影响多个指令。对于一些 ARM 芯片,另一种选择是使用嵌入式跟踪宏单元(ETM),它允许外部调试器跟踪每条指令的执行过程。我们也可能运行从 RAM 加载的修改版启动 ROM,修补后通过 GPIO 引脚暴露其时序。

即使不使用这些花哨的技巧,我们仍然可以通过一些时序线索来推测。我们知道 ROM 在复位线高电平之前不能开始执行,也知道当它接受我们的第一个命令时,必须已经跳过目标指令。如果我们足够耐心,可以遍历这个范围,直到芯片解锁,然后通过已知的偏移量在更短的时间内重复这一效果。

这种方式利用芯片并不罕见,通常攻击者会将芯片放在机架或储物间里,几天或几周后,恰当的时机才会出现。

Dewar(2018)建议,从 100MHz 时钟开始攻击时,在 5,100 到 5,300 个周期之间大约出现了十次故障。一个板子在 5,211 个时钟周期时,十次脉冲效果最好,而另一个板子在 5,181 个时钟周期时,十一脉冲效果最好。这个差异很可能来自目标芯片的内部 R/C 振荡器或房间温度,针对不同目标解锁的时刻有所不同并不奇怪。

图片图片图片图片图片

第十六章:16 nRF52 APPROTECT 漏洞

访问端口保护(APPROTECT)是 nRF52 替代 nRF51 系列 MPU 基于保护功能的实现,这在第九章中有介绍。它修复了旧平台的漏洞,为解锁芯片提供了调试接口,但对锁定芯片则提供了非常有限的接口。在锁定芯片上,调试器几乎无法做什么,除了擦除所有内存,解锁芯片,但同时摧毁了可能曾经存储在闪存中的任何机密数据。对 APPROTECT 进行的 glitch 攻击首次在两篇文章中描述:Results(2020a)和 Results(2021b)。这些文章的具体目标是 nRF52840,但预计整个系列都会存在漏洞。

由于这些芯片没有引导 ROM,所有外设在复位后都会在硬件上初始化,包括保护功能。通过在示波器上使用简单的功耗分析,确定内存控制器在启动时禁用解锁芯片上的 APPROTECT 的时间偏移,然后他可以在这一时刻进行 glitch 攻击,欺骗锁定芯片像解锁芯片一样禁用保护功能。

随着 Apple AirTag 的流行,以及 O'Flynn(2021)中公开的引脚图(图 16.1),nRF52 开始取代 LPC11 系列,成为文献中最常被攻击的微控制器。它已经作为视频教程发布(Roth(2021)),而由 STM32 开发板制作的 glitcher(Melching(2021))几天内就出现了。实践出真知,我最喜欢的 nRF52 glitcher 是作为 36 行 Arduino ESP32 代码在 Christophel(2021)中发布的,并且以推文形式发布!

Image

图 16.1:O'Flynn(2021)提供的 Apple AirTag 测试点

Image

图 16.2:Nordic nRF52840

Image

图 16.3:Twitter 上的 nRF52 Glitcher

第十七章:17 STM32 FPB 故障

在 Obermaier、Schink 和 Moczek(2020)中可以找到许多精彩的攻击,但我最喜欢的是 STM32F103 的 RDP Level 1 逃逸攻击,以及它的克隆之一,Geehy 的 APM32F103。这个攻击涉及许多复杂的环节,所以大家聚精会神地听好!

首先,回想一下 第二章,RDP Level 1 会在连接 JTAG 调试器时禁用闪存,但连接仍然被允许,所有 SRAM 对调试器是可用的。重置芯片会断开调试器并重新连接闪存,但不会擦除 SRAM。

其次,STM32 芯片可以根据启动时 BOOT0 和 BOOT1 引脚上的值,从 SRAM、ROM 或闪存启动。闪存可以完全访问内存,ROM 包含一个引导加载程序,具有其自身的软件实现的访问限制,但从 SRAM 启动时,代码的限制与连接 JTAG 时相同。也就是说,闪存不可访问。这个限制适用于从 SRAM 启动,但从 ROM 或闪存启动后执行 SRAM 时则不受此限制。

有时需要对闪存进行小的修补而不重写内存,STM32 的 Cortex M3 内核支持闪存补丁和断点单元(FPB)。这个单元在修改掩码 ROM 时也非常有用,尽管掩码 ROM 当然不能就地重写,但仍然可以进行补丁。 图 17.2 显示了这个单元的寄存器,注意指针以 E 开头,因此这个外设来自 Cortex M3 内核,并非 STM32 独有。

Image

图 17.1:简化的 STM32F103 内存映射

Image

图 17.2:Cortex M3 闪存补丁和断点(FPB)单元

所以 Obermaier 编写了一段两阶段的 shellcode,它作为可启动映像加载到 SRAM 中。第一阶段不能读取闪存,因为存在访问限制,但它可以重新配置 FPB 设备,以补丁形式将复位向量 0x00000004 指向第二阶段。然后,启动引脚被更改为选择闪存作为启动源,并且一个电源电压故障被与重置同步,以作为触发器。

重置会恢复对闪存的访问,如果故障在合适的时刻成功,FPB 的复位向量补丁将不会被重置序列清除。这会导致执行立即返回到 SRAM 中第二阶段的 shellcode。然后,这个阶段可以自由地导出所有内存内容。

就可移植性而言,我已经指出 FPB 单元来自 ARM,而非 ST Micro。这个单元在本书的其他漏洞攻击中也有使用,具体见 C.4 章 和 C.5 章。

Image

图 17.3:Geehy APM32F103,STM32 克隆

第十八章:18 芯片解封装

到目前为止,我们已经讨论了多种可以通过电气手段利用的漏洞,这些漏洞可能是通过软件错误或外部触发的故障注入来利用的。一旦去掉封装,暴露出芯片下面裸露的玻璃,还可以进行更多的攻击。在这一章中,我们将讨论用于拆解芯片的化学方法,接着我们将看到如何用激光照射芯片、拍摄其掩膜只读存储器(ROM),以及如何利用紫外光擦除其电子可擦除可编程只读存储器(EEPROM)、一次性可编程存储器(OTP)或闪存。

在我们开始之前,了解一下芯片是如何被放入封装中的很重要。微芯片首先通过光刻工艺在称为晶圆的圆盘上制造。不同的层依次被放置并蚀刻,掩膜和光照控制着哪些部分保留,哪些部分被洗去。最终,晶圆被锯成单独的芯片,然后进行测试和分类。

那些通过测试的芯片被放入各种各样的封装中。具有引脚的封装,如双列直插封装(DIP)和小型外形集成电路(SOIC),最初是作为金属引线框架制造的。芯片被粘在这个框架上,并且芯片上的引脚与框架上的引脚通过微小的金属线进行连接。然后,环氧树脂将芯片和引脚固定到位,之后引脚被弯曲成形。

请参见图 18.1 中的两个例子。上面的 X 射线图像是 TO92 晶体管封装在塑料封装后的框架。下面的 X 射线图像则是 DIP16 芯片裸露的框架,在芯片连接之前。封装完成后,工厂会将每个引脚切开,然后将其弯曲成适合分配的形状。

Image

图 18.1:TO92 和 DIP16 引脚框架

芯片的封装方式也可以非常不同。系统级封装(SiP)设备将多个芯片粘接到同一个电路板上,然后像处理引线框架一样将电路板用环氧树脂封装。晶圆级芯片尺度封装(WLCSP)则将焊球直接放在芯片上,这样就可以将其焊接到电路板上,而不需要被封装在环氧树脂中。当这些封装成为我们的障碍时,就该去化学实验室了。

实验室用品和设备

让我们从购物清单开始。在实验室设备方面,你需要一个通风柜、加热板和超声波清洗机。30 mL、50 mL 和 100 mL 的 Pyrex 烧杯可以用来装化学药品。塑料移液管用于从容器中取出酸液。(玻璃移液管手感很好,但它们的橡胶泡沫往往会变硬并开裂。)另外,购买一些电脑气雾清洁剂和一些非常锋利的镊子。

为了安全起见,你需要穿实验室工作服、戴手套和眼镜。长发应该束起来,除非你想了解在开放式鞋子下走路的酸烧伤体验,否则不要穿开口鞋。

对于溶剂,您需要丙酮和异丙醇(IPA)。我还备有蒸馏水,您可以以较低价格购买到作为 CPAP 水的蒸馏水。至于化学品,您需要首先准备 65%的硝酸(HNO[3])和 98%的硫酸(H[2]SO[4])。我建议在熟悉了浴法之后再购买更多特殊的化学品,因为其中一些对健康有害且难以处置。

Image

图 18.2:DIP40 的 X 射线图像

Image

图 18.3:HNO[3]和 H[2]SO[4]浴法

HNO[3]浴法

这种方法是我们许多人学到的第一个方法,它仍然是我实验室中最常见的日常去封装方法。

这种方法最适用于表面贴装芯片,因为它们的封装与芯片本身的尺寸相差不大。对于较大的封装,例如在图 18.2 中 X 射线拍摄的 DIP40,过程变得非常缓慢。几乎所有这些芯片的结构与 X 射线中的相同,芯片位于中心引脚之间。用带锯快速切割可以去除大部分塑料,从而减少处理时间并节省硝酸。

首先切断 CPU 的引脚,将其从电路板上取下,然后放入一个装有 65%硝酸的烧杯中,烧杯的液面大约为一半。您会看到硝酸与引脚残留物反应时发出微弱的绿色烟雾,但我们还需要一些热量来燃烧掉塑料。

在加热硝酸时,您希望它变热,但不要让它沸腾。小心地提高温度,直到您看到反应开始,但当看到液体中冒出气泡时,立即将温度降下来,而不是来自芯片的气泡。

初次尝试时,早期的反应可能会让人有些失望,液体变成了淡淡的绿色,仅有丝网印刷部分被从塑料上烧掉。这是由于任何金属的外表面在酸的作用下发生氧化,这种情况会持续,直到温度足够高,塑料才能分解。(金属可以是引线框架、连接线,或者在旧芯片中是裸露的芯片顶部金属层。)缓慢提高温度,以免不小心让烧杯里的液体溢出来。

当包装与硝酸反应时,小块的物质会像从奥利奥饼干中掉落一样脱落。您需要继续反应,直到微芯片的芯片和其引线框架从塑料封装中解放出来。

芯片是通过胶水附着在引线框架上的。有时,这种胶水在去封装过程中变得较弱,导致两者分离;有时框架会在酸中溶解。如果它们没有分离,并且框架没有溶解,你可以通过一个巧妙的化学方法解放芯片。只需向新鲜酸液中加入少量蒸馏水,并用镊子刮擦引线框架。框架的氧化表面正是阻止酸液伤害框架的原因。刮擦会打破这个氧化层,随着水冲走新形成的氧化物或铁锈,整个框架将在稀酸中溶解。金属最适合用大约 20%的硝酸攻击,稍后你会看到本章中引线框架和键合线在强硝酸中并不会溶解。

一旦芯片被解放出来,将其在一个干净的蒸馏水烧杯中煮沸,以去除任何金属盐,然后进行两次超声波清洗:首先是丙酮,然后再次是异丙醇。丙酮在溶解或分解污垢方面要好得多,但这意味着在丙酮清洗后芯片上可能会有污垢颗粒,所以第二次用异丙醇清洗能将这些污垢清除掉。

最后,将芯片放在显微镜载玻片上,在其仍然湿润时,用电脑吹尘器轻轻地吹掉表面的酒精,而不是让它自然干燥。(如果让它干燥,虽然比丙酮留下的杂质少,但仍可能有一些杂质需要吹走。)在此过程中要稳稳地握住芯片,并使用较少的气压,因为看着芯片飞入尘土飞扬的实验室深处是一种令人沮丧的浪费。

H[2]SO[4] 浴法

除了 65%的硝酸,你可能还想使用硫酸去封装芯片,无论是化学供应商提供的 98%浓度硫酸,还是用于清洁排水管的较低等级硫酸。整个过程大致相同,因此在本节中,我将重点介绍不同之处。

硝酸会导致封装开裂并粉碎。这让你能够看到反应的进展,但也意味着一些封装碎屑可能仍然附着在玻璃上,丙酮可能无法把它们刷掉。硫酸在加热过程中会变黑,它将封装溶解成非常细小的颗粒,从而留下一个更干净的表面。这样做的代价是液体完全不透明;你无法看到进展,直到样品被从酸液中取出。

王水用于金

塑料 DIP 封装比较麻烦,但本章前面介绍的技巧足以从中提取芯片。然而,一些低体积陶瓷封装的框架上有一层金属涂层,防止了硫酸或硝酸将芯片解放出来。由于陶瓷本身对这些酸是不可渗透的,而且盖子很容易被去除,我们可以考虑用王水去除金属涂层,从而解放芯片。

王水是由盐酸和硝酸混合而成,摩尔比为三比一:HNO[3] + 3 HCl。该混合物在常温下会产生烟雾,初始时是透明的,但随着氯气和氧化氮烟雾溶解回液体中,它会迅速变为橙色或红色。

我发现,对于我需要溶解的薄层来说,比例并不特别重要。即使盐酸不是特别浓烈,在加热下滴入一点硝酸和一点盐酸也足够了。

RFNA 滴加法

在之前的章节中,我们了解到,硝酸在较低浓度时对键合线和框架的腐蚀性更强,因为水作为催化剂将金属盐带离金属。我们可以利用这一点,通过滴加极少量的红色烟雾硝酸(RFNA)在封装中开一个坑,而不损坏键合线。这样芯片仍然能够正常工作,这是进行光伏攻击和探针针头操作所必需的。

RFNA 是非常强的硝酸,含有 90%以上的 HNO[3]和不到 2%的 H[2]O。这需要特别的运输限制,就像我当时收到不到半升的订单时一样,它是用五加仑桶固定在运输托盘上送来的!

打开芯片时,首先将其焊接到一个小载体板上,载体板的另一面没有任何东西。你需要在热板上加热至 100°C 以上。

在你的排风柜的其他位置,但要确保不会碰倒,放置几毫升冷 RFNA 在一个小烧杯中。拿一个尖细的移液管,吸取一点酸液到管尖,然后在封装的正上方,芯片的上面,滴下小线条。让它燃烧一会儿后,使用纯丙酮清洗酸液和一些封装物料到一个大烧杯中。

一些注意事项:不要误用异丙醇(IPA)或水进行清洁。异丙醇在与 RFNA 接触时会发生爆炸,少量时会发出小的爆裂声,大量时会造成相当大的尴尬。水会加剧硝酸对键合线的腐蚀,任何水或含水化学品都必须严格避免,以确保操作成功。

Image

图 18.4:在 PIC16LC74 上使用 RFNA 滴加法

在第一次暴露后清洗干净,仔细检查样品。你应该能看到一个小的凹槽,以及酸接触到的地方去除了丝网印刷,且不应该看到封装引脚或载体 PCB 的腐蚀。如果发现酸液滴落到侧面,说明你使用的量太多了。最初使用的量应该远小于一滴。

我曾警告你要保持酸液在凹槽内,并将凹槽保持小一些,但你会做几次这种操作,以理解原因。如果凹槽变得太宽,铅框的引脚可能会断裂,带走它们的连接线。你还应该看到,酸液更倾向于浸入之前已被蚀刻掉环氧树脂的芯片表面;如果酸液溢出凹槽,它会使更多的表面吸引酸液。

重复此过程很快就能得到一个可以容纳更大酸滴的凹槽。不要轻易让酸液煮沸至干,通常在接近玻璃时缩短暴露时间是个好主意,这样可以减少表面残留物。图 18.4 展示了早期的滴液和最终结果,其中暴露了 PIC16LC74 芯片。

一旦表面完全暴露并且你预计不会再有酸滴落时,可以安全地用蒸馏水和 IPA 冲洗芯片。不要在过程中早些时候这么做,否则水可能会损坏连接线。

松香或松脂

我住在美国,对欧洲读者来说,这里可能看起来是一个没有监管的边疆,枪手山地居民可以私下持有与工业故障分析实验室使用的化学品相同的物质。这些读者并非完全错,但让我们花点时间考虑一下他们如何在没有硝酸或硫酸的情况下去封装芯片。

Schobert(2010)描述了一种来自 Beck(1988)的技术,其中使用松树树脂或松香去除封装^(1)。将封装在 350°C 的松树树脂中煮五到十分钟,以释放芯片。当然,树脂在冷却时会固化,但溶解在丙酮中可以再次释放芯片,便于拍照。

这种方法很凌乱,但利用美容和音乐商店的物资进行去封装是相当酷的。

其他技术

在本章中,我们学到了多种提取微芯片玻璃芯片的方法。第二十二章将通过去层和 Dash 蚀刻扩展这些化学技术,以揭示扩散层并突出 P 型和 N 型硅的差异。它还将解释如何将 ROM 位提取成 ASCII 艺术,并将其从物理顺序重新排列为适合仿真和反汇编的逻辑顺序字节。

第十九章:19 紫外线解锁 PIC

使用 Microchip 的 PIC 微控制器设计有很多限制,但在 90 年代初期它们非常方便。它们有点像那个时代的 Arduino,既用于业余项目,也用于商业产品。它提供了掩模 ROM、(E)PROM、EEPROM 和闪存变体,至今仍在使用。有很多方法可以解锁这些芯片,但在本章中,我们将重点介绍如何利用紫外线光清除保险丝位,同时保护我们希望读取的主要程序内存。

在 EEPROM 和闪存设备普及之前,开发人员会购买带有石英晶体窗口的芯片,就像图 19.1 中所示的 EPROM 变体。这单个字母 E 表示这是可擦除的可编程只读存储器设备,而双字母 E 表示这是一种电擦除设备。从电气上讲,你可以将位从 1 清除为 0。要将位从 0 擦除为 1,则需要将其暴露在紫外线灯下 15 到 20 分钟,之后该芯片即可写入新程序。

相同的芯片将以标准的不透明包装形式作为 PROM 或 OTP(一次性可编程)变体出售。这些芯片出厂时已经擦除,但由于没有窗口,无法方便地擦除以进行新程序写入。如我们在第十八章中所见,我们可以使用红色烟雾硝酸(RFNA)在不损坏芯片或连接线的情况下打开外壳上的孔。这就是所有这些攻击的基础,技巧通常在于擦除某一部分,同时保护另一部分。

Image

图 19.1:紫外线可擦除的 PIC16C74

保护由配置位控制,通常被称为保险丝。这些位控制代码保护(CP)、看门狗定时器(WDTE)和振荡器(FOSC)。在 PIC 中,它们是通过与生产 EPROM 相同的浮动栅极技术实现的,但需要理解的是,配置位并不位于程序内存中。它们位于芯片的其他地方。

早期的芯片,如图 19.2 中的 PIC16C56,是最容易破解的,因为它们的配置位与程序内存一起被设计为可以擦除。通过 RFNA 滴水法去除封装后,只需用红色指甲油涂抹程序内存,并将其放入 EPROM 擦除器中加热,直到设备变得可读取。严格来说,你不需要知道配置位的具体位置,因为只需保护那些更容易识别的程序内存即可。

在 PIC16C56 中,EPROM 内存位于左图右侧靠近的深色矩形区域,在右图中被一滴指甲油覆盖。这款样品来自 Parallax BASIC Stamp,我能够在紫外线消毒箱中经过 151 秒后读取其固件。消毒箱内部的 USB 集线器使得在熔丝被擦除后,读取芯片变得方便,而一个 Shell 脚本则让我在芯片解锁的瞬间就能读取数据。过渡期持续了三秒钟,之后每次读取的结果都一致。

图片

图 19.2:PIC16C56,裸片与涂了指甲油的对比

PIC16C620 PIC16C621 PIC16C622 PIC16C62A
PIC16C63 PIC16C64A PIC16C65A
PIC16C710 PIC16C711
PIC16C72 PIC16C73A PIC16C74A
PIC16C83 PIC16C84A PIC16C923 PIC16C924
PIC17C42A PIC17C43 PIC17C44

表 19.1:最早的带有熔丝保护的 PIC

位数据损坏可能会成为一个问题,因为不完美的遮蔽会擦除未被遮盖的位。零始终可靠为零,但一有时会变得模糊,因为它们可能是被损坏的零。Caps0ff(2017a)指出了一种方法来帮助测量这种损坏。PIC16 允许读取每个 14 位指令的两个半部分的 7 位 XNOR,即使芯片被锁定。通过先转储所有校验和,然后解锁芯片,最后转储代码,作者能够识别损坏的数据字。^(1)

紫外线擦除熔丝的问题引起了 Microchip 的关注,到 1996 年,表 19.1 中的设备已经有了针对这种技术的防护,首先是使用遮挡紫外线的外壳,后来则增加了额外的冗余熔丝。Tarnovsky(2008)记录了这种情况,特别是在 PIC16C558 的例子中,其中一些配置位在顶部金属层有一个保护屏蔽。这两个配置位控制着代码保护,并通过与门确保两个位都必须擦除才能解锁设备。Tarnovsky 并没有通过光学方法绕过这一保护,而是使用激光切割机将与门的输出桥接到 VDD。

到目前为止,我们已经讨论了带有 EPROM 或 EEPROM 内存的设备。同样的技术也适用于更现代的带有闪存内存的设备,例如黄(2007)中,Bunnie 解锁了一个 PIC18F1320。他使用紫外线光以非常锐的角度照射金属下方,擦除保护熔丝。电工胶带遮盖了代码内存,以防止其被擦除。

Caps0ff(2017b)对 PIC16C74 进行了重复攻击,并确认了一些细节。首先,对于包含保险丝位上方罩壳的设备,光照射芯片的角度必须是非常锐角的。从表面到光源的 90角度没有效果,即使是 45*°*角度也似乎没有太大变化,但非常锐的入射角和更长时间的照射确实成功解锁了芯片。(2)他使用了红色指甲油代替了 Bunnie 例子中的电工胶带。由于担心胶带可能会损坏连接线,我个人实验室更倾向使用指甲油方法。

另一个复杂因素是,紫外线可能会在面罩下方散射,最终到达保险丝。这也许就是为什么锐角有效的原因,它能够将光散射到保险丝遮蔽层下方的顶部金属层。当这种情况发生时,可能会损坏一些代码存储器中的位,需要繁琐的逆向工程来找出哪些位本应是零。

在攻击真正的目标之前,最好先在一个没有重要内容的测试芯片上定位保险丝(或多个保险丝)。

Schaffer(2018a)描述了两次尝试使用紫外线解锁 Intel 8752 微控制器,一次成功,一次失败。像 PIC16 一样,这个设备的保险丝位是一个漂浮栅极晶体管,位于主存储区外。失败的尝试使用了一个稍微大一点的面罩,预计保险丝位应位于该区域。每当解锁芯片失败时,保存每次尝试的照片并将它们合成,以便了解保险丝可能的位置。

Schaffer(2018b)描述了 Altera EP900 EPLD 的解锁过程,这是一种现代 CPLD 的早期祖先。这个芯片的保护位存储在主 EPROM 内存中,与比特流一起存储。通过有选择地遮罩所有角落,直到最终测试芯片在紫外线照射下解锁,找到了这个位。

Skorobogatov(2005)使用显微镜内置的卤素照明装置重置 PIC16F84 的保险丝,聚焦在最大功率和高倍率下,照射在未遮蔽的保险丝上。卤素灯泡确实会发出紫外线,但从描述中不清楚机制是部分紫外线穿过玻璃镜头,还是其他部分的光也有紫外擦除的效果。在一次简单的测试中,使用桌面显微镜高倍率暴露 24 小时并没有改变我的 PIC16 位。

Skorobogatov 还描述了在对 USB 加密狗中使用的 CY7C63001A 芯片进行紫外线攻击时的成功经验。对于保险丝位位于主 EEPROM 之外的情况,他建议它们通常使用类似的结构。主 EEPROM 中的存储单元形状也将是芯片上其他位置保险丝单元的形状,这可以帮助找到它们。

第二十章:20 MSP430 攻击方法

早期的 MSP430 系列,如 MSP430F1xx、F2xx 和 F4xx,容易受到一种半侵入性攻击,这种攻击首次由 Thomas(2014)公开记录,在这种攻击中,使用相机闪光灯伪造保险丝检查,而 JTAG 调试器则在一个紧密的循环中尝试连接。

这些芯片有两个访问控制。JTAG 受到金属迁移保险丝的保护;这是一条位于芯片上的薄金属线路,当通过它的电流过大时,会永久断开。与保险丝无关的是一个 32 字节的密码,它是访问串行引导加载程序(BSL)所必需的。这个密码是位于内存末尾的中断向量表(IVT),没有它,BSL 只能进行擦除内存的操作。由于 BSL 无法读取保护保险丝,你可以通过首先提取闪存的最后 32 个字节,然后将它们提供给引导加载程序,从而利用该芯片。

首先要理解的是,芯片中的所有晶体管实际上都是光电晶体管。如果足够强的光照射到这些晶体管之一,它将导电,即使它在电气上应处于非导电状态。CMOS 技术通过将每个导电晶体管与非导电晶体管进行平衡来实现其功率效率,而强光照射的闪光灯会将所有设计约束抛诸脑后。树莓派 2 因其暴力崩溃而闻名,这通常是由于 PCB 上的裸露芯片在拍照时造成的^1。

图片

图 20.1: MSP430F449

第二个要理解的事情是,MSP430 的 JTAG 端口被硬件保险丝锁定,至少在 MSP430F5xx 系列之前的设备中是这样。当你连接 JTAG 调试器时,它会通过 TDI 引脚向保险丝输入小电流来测试保险丝。如果测试成功,JTAG 将解锁,芯片可以被读取。如果测试失败,一项“无害无损”的政策允许在所有但最早期的芯片上进行更多的保险丝读取尝试。

为了解锁这些芯片,我们将首先通过使用红色烟雾硝酸(RFNA)滴定法进行现场去壳。去除封装后,我们将芯片连接到 GoodFET 进行 JTAG 调试,并修改 GoodFET,使其反复尝试 JTAG 保险丝检查,直到成功。通过对裸露的芯片进行拍照,我们将绕过保险丝检查并启用被锁芯片的调试功能,从而可以自由地提取固件。

使用 RFNA 进行现场去壳

这里介绍的现场去壳程序在概念上类似于我们在第十八章中讲解的完全去壳,但有一些关键的区别,以保持连接线和部分封装完整,使芯片在裸露的情况下仍能正常工作。如果你没有化学实验室可用,也不愿意自己动手制作,你可以雇佣一个故障分析实验室来为你执行这一过程。

我们将使用红烟硝酸(RFNA),而不是 65%的硝酸,后者有时会溶解连接线。RFNA 至少含有 90%的硝酸和最多 2%的水。这是非常强烈的化学物质,会与腈纶手套和异丙醇剧烈反应,所以一定要在通风柜中工作,并穿戴全套安全装备。

从将目标芯片焊接到载体 PCB 开始,不要有其他组件。将其加热至约 100°C,远低于焊料的熔点,但足够高以使酸液能够侵蚀封装。

Image

图 20.2:去封装后的 MSP430F2418

你的目标是暴露芯片中央的裸片,而不让酸液溅到引脚或 PCB 上。一开始,芯片的封装表面是平的,所以任何大量的酸液都会溢出。从一个冷的烧杯中取少量 RFNA,并使用带有非常窄尖的移液管,将最小量的酸液滴在芯片封装的正中央。

关于酸液量的快速但重要的提示:如果移液管尖端形成了一个水滴,说明你即将使用过多的酸液。你希望第一次滴落的酸液尽可能少。想象一下,你正在用移液管像钢笔一样在纸上书写。

酸液首先会看起来渗透到芯片的表面,然后开始稍微冒泡。在允许一些气泡分解封装材料后,使用一喷丙酮清除酸液,留下剩余的封装。重复这一过程几次,会在封装内形成一个类似碗形的空腔,你可以开始使用更多酸液来加速刻蚀过程。

每次使用丙酮冲洗后,都要在强光下仔细检查封装。当你开始看到连接线在黑色封装中闪光时,说明你已经接近裸片。在这个阶段,你应该更早一点冲洗,以确保酸液不会煮干,留下遮掩裸片的难看塑料痕迹。

如果这个过程成功,你应该得到一个引脚及其周围封装完好无损的封装,同时裸片及其连接线被暴露出来。裸片的清洁程度不如浸泡法处理的裸片干净,但表面残留的少量污垢不会妨碍后续操作。

一定要小心地先用丙酮、然后用异丙醇和去离子水冲洗芯片和板子,以防剩余的酸液溶解板上的线路或氧化引脚。这一最终清洗应该是实验中唯一一次使用异丙醇,因为异丙醇会与 RFNA 剧烈反应,实验室爆炸通常不被欢迎。同样,水会去除金属盐,这些金属盐保护连接线和框架免受 HNO[3]的侵蚀,因此你应该避免在最后清洗之前使用水。

熔断器检查序列

既然我们已经打开了目标芯片的封装,接下来的步骤是触发故障。要自行完成此操作,您需要一个可以获取源代码的 JTAG 编程器,如 Good-speed(2009),以及 MSP430 芯片的 JTAG 规格说明书,德州仪器(2010)。

我想我们可以使用激光器,进行精细的脉冲控制,精确地在正确的时间和位置射击。(^(2))感谢科技进步,如果我们稍微修改一下我们的 JTAG 编程器,这样的设备就不再是必须的。在这个示例中,我们将使用我开源的 GoodFET 编程器,尽管它有点过时。

图 20.3 显示了 MSP430F1xx、F2xx 和 F4xx 设备的硬件保险丝检查序列。该检查通过至少两次切换 TMS 引脚来完成;如果保险丝未被烧毁,2 毫安的电流将流入 TDI 引脚。图 20.4 是我在 GoodFET 项目中用 C 语言实现的 JTAG 保险丝检查序列示例。

配备原始 MSP430 CPU 和 CPUX 扩展的设备存在一个错误,可能在上电时未通过保险丝测试,因此需要进行另一次电源循环才能重新测试保险丝。CPUXv2 设备在重置 JTAG TAP 时会清除保险丝检查结果,这可能会使得当您通过相机闪光模拟保险丝检查时变得更加复杂。

图片

图 20.3:MSP430 JTAG 保险丝检查序列

图片

图 20.4:在 Goodspeed(2009)中的 MSP430 保险丝检查

MSP430F5xx 和 F6xx 设备已经取消了硬件保险丝检查,取而代之的是通过软件机制实现读取保护。本章的攻击方法预计不适用于这些设备。

当目标芯片正常工作且裸芯片暴露时,利用此环境进行攻击包括反复尝试保险丝检查,并观察是否已通过,同时对裸芯片施加相机闪光。可以通过两种方式修改图 20.3 中的序列:要么重复序列直到检查成功,要么通过延长 TMS 引脚上的周期数来增加尝试通过测试的次数。

在硬件方面,当相机闪光照射到裸芯片上时,目标芯片会消耗相当大的功率。我们并不是在进行电压故障攻击,所以瞬态功率消耗应由去耦电容器和可能的台式电源来处理。

当整个布局完成后,大约每四次相机闪光就应解锁目标并建立 JTAG 连接。请在设置过程中非常小心,以确保保持连接打开,切勿以会要求重新检查保险丝的方式重置芯片。

你还应该预期,在连接建立后,闪存可能会因为相机闪光灯的影响而出现读取错误,直到它稳定下来并存储永久值。我通过反复读取所有闪存几次来解决这个问题,将早期的读取结果保存下来以备不时之需,但依赖后续的读取结果来获取真实的程序内容。内存被暂时“冻结”的效应也可能被用来临时破坏常驻在 ROM 中并忽略 JTAG 保护保险丝的串行引导加载程序(BSL)的密码。

第二十一章:21 CMOS VLSI 插曲

很早在第十八章中,我们就暂时不讨论破坏芯片,而是快速研究了晶圆如何被封装。我们看到,芯片被锯开后,会被粘在一个引线框架上,并通过导线与引脚连接。整个框架随后被包裹在环氧树脂中,然后引脚被弯曲成正确的形状,框架的多余部分则被切除。在本章中,我们将深入探讨芯片是如何设计和制造的。这不会像关于超大规模集成电路(VLSI)的专门书籍那样详细,如果你需要详细了解,可以阅读相关书籍。

超大规模集成电路(VLSI)技术是将数百万甚至数十亿个金属氧化物半导体(MOS)晶体管集成到微芯片中的技术。这些晶体管被组合成几百个独特的逻辑单元,称为基本模块,这些模块是实现特定功能的小型晶体管集合,如逻辑门或存储单元。这些模块被放置并连接形成 VLSI 芯片的知识产权(IP)模块。较大的 IP 模块包括 CPU、SRAM、掩膜 ROM 和闪存 ROM。IP 模块可以手动设计,也可以通过高级语言如 Verilog 或 VHDL 进行设计。

这个解释在高层次上是有效的,但重要的细节被省略了。逻辑的基本模块是什么样的,存储器也是由这些模块构建的吗?让我们来看看。

工艺层

我们在第十八章中学到,光刻技术用于将化学层放置到硅片上,然后选择性地将其蚀刻掉。这些层按给定工艺的顺序堆叠,并且在本节中,我们将介绍这些层在制造后按顺序堆叠的方式。这与它们的制造顺序有所不同,因为在制造过程中,晶圆厂有时会通过一层材料向下挖掘,以便将另一层放置在芯片的更低位置。

该过程从一块由硅制成的大硅片开始。层被堆叠到硅片的正面,而硅片的背面保持为空白。在大多数封装芯片中,正面朝向远离电路板的一侧,但也有例外,例如我们将在第二十四章中讨论的 MYK82 芯片。一些采用晶圆级芯片规模封装(WL-CSP)的设备没有封装,它们将焊球放置在芯片的正面。

纯硅在做很多事情时并不非常有用,因此,尽管我们开始时使用纯硅,但我们通常会将其掺杂成n 型p 型。这些名称与它们的电荷类型有关,n 型带负电荷,而 p 型带正电荷。

在最底部,我们有一层 p 型衬底,它覆盖了整个硅片的表面。NMOS 晶体管可以直接放置在这个衬底上,但 PMOS 晶体管必须放置在一个n-well中,这个 n-well 是挖掘到衬底中的。稍后我们将回到 NMOS 和 PMOS 晶体管之间的区别。

在 p-基片和 n-井上方,我们有一个扩散层,它将 n 型和 p 型硅大致保持在相同的深度。这些通过掩膜将带电离子注入到暴露的 p-基片或 n-井中,形成掺杂层。

在扩散层上方,我们有多晶硅。多晶硅最重要的作用是作为 NMOS 和 PMOS 晶体管的输入。无论何时你看到两个相同扩散类型(p 或 n)之间的多晶硅轨迹,那就是一个晶体管。在数字逻辑中,最容易将晶体管看作开关;通过多晶硅上的输入,电流在扩散层之间的流动被打开或关闭。

在多晶硅上方,我们有金属层,用于将芯片的各个部分连接起来。在 70 年代,通常只有一层金属。开放源代码的 SKY130 工艺有五层金属,而 MOSIS 200 纳米工艺有六层。从 2003 年开始,具有九层和十层的工艺变得常见。在旧的芯片中,这些金属通常是铝(Al),但现在铜(Cu)已经相当普遍。

SKY130 和 MOSIS 都是相对开放的工艺。这种情况是例外而非常态,对于许多芯片来说,你无法享受低级别的工艺文档。

具有多层金属的芯片将像印刷电路板一样进行布线,但对于只有一层金属的芯片,通常可以看到金属被布置到一段短的多晶硅上而没有晶体管。这是一种跨越电路而不连接它们的方式。

这不是一个完全独立的层,但你会注意到,有时金属在多晶硅或扩散层上方会显得更暗。这是接触过孔,用于连接各层之间。

我跳过了一些层,专注于反向工程中重要的部分,并保持对许多代工厂的通用解释。这些包括用于隔离各功能层之间的氧化层,用于由金属层构建电容器的封装层,以及其他在 CMOS 中并不基本但在实际芯片制造中非常有用的元件。要了解任何实际工艺中的更多信息,你需要查找相关代工厂的工艺开发工具包中的文档。

Image

图 21.1:SKY130 NMOS 晶体管横截面

Image

图 21.2:SKY130 PMOS 晶体管横截面

Image

图 21.3:CMOS 反相器电路图

NMOS 和 PMOS 晶体管

现在我们理解了各个层次以及它们的堆叠顺序,让我们来看一下如何用这些组件构建有用的逻辑。CMOS 逻辑是由两种类型的晶体管构建的:NMOS 和 PMOS。

NMOS 晶体管在输入高电平时导通,将输出拉低至低电压。PMOS 晶体管在输入低电平时导通,将输出拉高至高电压。任何给定的栅极都会包含这两种类型的晶体管,它们之间的平衡使得 NMOS 晶体管在输出为高时拉高电平,而 PMOS 晶体管在输出为低时拉低电平。^(1)

为了制造一个晶体管,首先将一条多晶硅放置在一个扩散区上,将其分成两半。多晶硅是输入或栅极连接,控制电流在扩散区的两半之间流动,我们将其称为源极漏极。这种结构,n 型扩散区位于 p 型衬底上,构成了 NMOS 晶体管,而同样的结构,p 型扩散区位于 n-well 上,则构成 PMOS 晶体管。

请参见图 21.1 和 21.2,其中展示了 SKY130 工艺中的晶体管横截面,包括一些在此解释中我跳过的额外细节。在这些图中,NP描述了扩散区,它们分别成为晶体管的源极和漏极。晶体管的栅极是位于它们之间的多晶硅线。

在特别老的芯片上,你会发现仅使用 NMOS 晶体管,并且用上拉电阻代替 PMOS 晶体管。按照现代标准来看,这种做法效率不高,但在当时非常实用,并且节省了在制造过程中需要放置 n-well 层或 p 型扩散区的步骤。

这两种晶体管类型足以构建任何形式的数字逻辑,但还有第三种类型,称为浮动栅极晶体管,它存在于 EPROM 和闪存中。浮动栅极晶体管与 NMOS 非常相似,唯一不同的是它们有两层多晶硅堆叠在一起。上层多晶硅是控制栅极,而下层则是浮动栅极。所谓浮动,意味着它在电气上是断开的,并且持有可以通过源极和漏极读取的电荷。

为了强调一切都会随着工艺发生变化,我应该告诉你,浮动栅极有时是由氮化硅制成的,这种技术称为 SONOS。这对闪存的质量和密度非常重要,但这是一个复杂因素,在本书中我们不会过多关注。

浮动栅极晶体管被编程为零或擦除为一。编程通过在源极和漏极保持低电平,同时将栅极设置为高电平进行;这样会向浮动栅极添加电子,使得晶体管在源极和漏极之间的导电性降低。擦除则是相反的过程,通过将两个扩散区设置为高电平,同时控制栅极为低电平,从而使电子从浮动栅极流出,使得晶体管在两个扩散区之间的导电性增加。

漂浮栅极晶体管也可以通过紫外线光进行擦除,就像我们在第十九章中看到的那样。在早期,设备会使用这种方式进行唯一的擦除,没有石英窗口的设备基本上是一次性的。后来,芯片加入了电擦除电路,消除了在开发过程中需要紫外线擦除的需求。

基本模块

现在我们理解了特定的形状会构成晶体管,CMOS 由两种互补类型的晶体管构成,整个微芯片的行为由晶圆上的微观形状定义。

芯片设计师通常首先选择一个将要制造芯片的公司,然后从工厂或工艺厂支持的工艺列表中选择一个过程设计套件(PDK)。对于任何给定的工艺,都必须编写 PDK 来描述该工艺的基本模块,并提供关于其特性(如时序和电压范围)的仿真数据。

一些设计套件被发布,用于大学课程或像 MOSIS 这样的多项目晶圆。最近,Cypress 大约在 2001 年使用的 130 纳米工艺作为 SKY130 PDK 开源。如果你曾想知道一个单元可能是什么样子,渲染一些来自这些 PDK 的单元看看它们是如何实现的非常方便。当然,无法保证你的工艺会相似。

图 21.4 是简化版的 CMOS 反相器示意图,采用的是 SKY130 工艺,由 Matt Venn 提供的示例所示。输入A位于块左侧的小金属片上,输出Q位于右侧较长的金属片上。电压来自顶部,地来自底部,就像图 21.3 中相同反相器的电路图一样。这个单元是从上方观察的,如果仔细看,你应该会看到,顶部的 PMOS 晶体管与图 21.2 中的截面相匹配,底部的 NMOS 晶体管与图 21.1 中的截面相匹配。

Image

图 21.4:CMOS 反相器布局

PDK 将包括成千上万的这些单元,以表示数字逻辑门、触发器以及像电阻和电容这样的无源元件。许多这些单元是为了降低功耗或提高反应速度而设计的,因此只有几百个单元会出现在给定的设计中。它们通常排列在规则的行列中,以便于电源轨的连接,金属层的电线将它们连接在一起。当这些安排由 VLSI 软件处理时,看起来没有明显的规律,我们称之为门海

大型结构

最后,我们应该考虑大型结构的情况。基本模块可以放置并布线以形成任何你想要的逻辑,但在实现像内存这样的功能时,结果远非高效。

相反,芯片设计师会使用某种编译器来生成正好符合需要的内存尺寸。这不仅有助于密集地打包内存的位,还可以确保内存满足时序和电气要求。请参见 Guthaus 等人(2016)的开源 RAM 编译器示例,以及 Walker(2023)对该编译器的扩展,支持掩模 ROM。

阅读这些论文时,你会看到内存往往扩展性差,在某个尺寸下工作正常,但当其尺寸稍微增大时,性能就会急剧下降。当你看到一些微控制器有多个小内存尺寸时,比如 TMS320 系列的一些成员,原因就在这里。

在 第二十二章 中,我们将看到如何通过化学方法揭示 ROM 的内容,然后处理这些照片。别担心,这比反向工程芯片的其他部分容易多了。

反向工程

到这时,你应该明白一个芯片的逻辑是由标准单元构成的。这些单元被排布在设计中,然后在金属层中连接在一起,也许还会用到一些多晶硅。如果我们能拍摄这些并做标注,为什么不反向工程芯片的逻辑呢?

反向工程芯片逻辑通常从识别去层化芯片照片上的基本模块开始。一旦基本模块被反向工程,便可以在芯片上识别相同形状,从而识别所有该模块的副本。模块识别完后,就可以追踪基本模块之间的连接,并解码成其实现的数字逻辑。

Degate 是一个开源 CAD 工具,用于执行此类工作,首先构建一个基本模块库。它有演示项目,分别是 DECT 电话的控制芯片和 Legic Prime RFID 标签,每个项目都被分解成与设备行为相匹配的 Verilog 代码。

也可以使用像 Inkscape 这样的分层图像编辑软件进行反向工程。6502 的分层图像可以在 Visual6502(2010)中找到,从中该项目恢复了所有门并生成了准确的仿真。对于 Yamaha 的 OPEN 系列 FM 音频合成器芯片,Raki(2024)提供了描述标准单元和布线的 SVG 文件,以及反向工程的电路图。

第二十二章:22 掩膜 ROM 摄影

有些芯片将它们的程序位存储为掩膜上的标记,这些掩膜是光刻绘制微芯片的。我们称这种 ROM 为掩膜 ROM,以区别于 EEPROM、闪存 ROM 以及其他现场可编程技术。本章将讨论拍摄这些 ROM 以提取其位的理论,接下来的章节将以从头到尾的实例来解析真实的目标。

掩膜 ROM 有三种类型:通孔、扩散和植入。这些在化学和物理上差异很大,但在提取它们时,我们只需要足够了解它们,以便使位变得可见。表 22.1 列出了许多微控制器及其 ROM 过程。

通过接触ROM 使用通过层之间的通孔来标记一个位。这些在布局空间方面效率不高,但它们非常容易解码,因为当你找到它们时,位是清晰可见的。许多 ROM,例如任天堂游戏机的 Game Boy,甚至可以在不去除层次的情况下直接从表面看到!

扩散 ROM 位于芯片较低的位置。这里的位通过扩散池的存在或其缺失来标记,从而使晶体管工作或破坏晶体管。由于它们位于较低的位置,你几乎总是需要去层才能看到它们,但在这个过程中损坏芯片的风险很小。

嵌入式 ROM 是这三种中最让人沮丧的。位是通过在一个正常工作的晶体管中附加离子植入来编码的,而由于某种该死的巧合,损坏和未损坏的晶体管颜色完全相同!这些 ROM 通常需要去层到位的内部,然后使用 Dash 蚀刻法将其着色差异显现出来,我们稍后会讨论这一点。

模型 ROM 类型
TMS1000 通孔
Game Boy 通孔
T44C080C 通孔
TMS320C15 通孔
MSP430F1, F2, F4 通孔
6500/1 扩散
EMZ1001 扩散
MYK82 扩散
Tengen Rabbit 扩散
TMS32C10NL 扩散
HCS300 扩散
Z8 扩散
SM590 嵌入式
MK3870 嵌入式
TLCS-47 (TMP47) 嵌入式

表 22.1: ROM 类型示例

当然,有很多方法可以编码位,就像硅工匠发明的独特形状一样。我使用这些宽泛的类别来描述提取位所需的努力,但当然也有一些 ROM,其标记在金属层上而不是通孔层上,这些也能从表面看到。就像逆向工程中的一切一样,我们先使用这个抽象概念,直到它不再有用,再深入挖掘,看看里面的情况。

显微镜学

一旦芯片准备好,我们需要拍摄它。

你需要一台冶金显微镜,它的光柱通过镜头垂直照射到芯片上并反射回来。那种将光从样品上方发送的显微镜非常适合生物学,但对于拍摄不透明的微芯片毫无帮助。

还需要一台相机。虽然通过单目显微镜的镜头适配器可以获得不错的图片,但使用三目显微镜要更容易,这样你的眼睛可以找到目标,只有在拍照时才需要用到相机。

通常不可能将图像缩小到足够小的程度,以便同时保持图像的细节和整体框架,所以我们通常会拍摄一系列相互重叠的照片。拍摄后可以使用全景软件(如 Hugin)将这些照片合并。

这种摄影在显微镜能力的极限下可能会非常繁琐,所以我通常会先在最低放大倍率下拍摄整片芯片的全景,然后再用高倍率拍摄我感兴趣的区域,例如 ROM。非常感谢 John McMaster,感谢他卖给我一台带有电动舞台的显微镜,因此我现在的所有照片都具有一致的间距和文件名,文件名显示了行和列。

使用氟化氢酸去层

要去除芯片的层,我将它加热到稀释的氟化氢酸(HF)中,在美国可以通过 Whink 或 Rust-Go 品牌的生锈去除剂买到。

氟化氢酸对你的骨骼有危险,它会在没有太多皮肤疼痛感的情况下损害你的骨骼。如果你处理这种物质,请非常小心,并且不要忽视安全措施。^(1) 氟化氢酸的另一个麻烦是,我们使用它是因为它会攻击玻璃,所以我们不能用玻璃烧杯来容纳反应。此时塑料烧杯或塑料离心管非常重要。

当氟化氢酸攻击目标时,你会看到一些气泡,这是因为它与金属发生反应。第一波气泡通常表示顶部金属层,在需要达到特定深度的反应中,通常很有帮助的是同时去层多个芯片,然后在事后对它们进行排序,以找到适当的深度。

你可能会注意到金属层会从芯片上剥离,而不是溶解成液体。轻轻搅动有助于将这些金属层从芯片上移开,以免它们遮挡去层反应。

Dash 蚀刻用于植入型 ROM

对于植入型 ROM,其中的位是相同颜色的,我们需要为它们赋予不同的颜色以便拍摄。这是通过使用 Dash 蚀刻在去层后完成的,最佳描述可以参考 Beck(1998)和 McMaster(2019)。

这里的去层可能会让人感到困惑,因为你无法真正看到我们距离想要拍摄的植入体有多近。你可以尝试去层多个样本,并将那些没有充分去层的样本送回浴槽。

Dash 蚀刻由三部分组成。氟化氢酸和硝酸攻击硅,而醋酸(HAc)则缓冲反应并减缓其速度。当比例合适时,p 型掺杂将稍微偏向氧化反应,导致 p 型硅比 n 型硅更快地变为棕色。

我使用 John McMaster 的 Rust-Go 溶液进行此操作,该溶液由 3mL 的 65% HNO[3]、4mL 的 12% HF(Rust-Go)和 8mL 的醋酸组成。最终的比例大约是 4.3%的 HNO[3]和 3.2%的 HF;溶液的其余部分是 HAc 和 H[2]O,用于缓冲反应。

Beck 推荐使用的溶液由 3mL 65% HNO[3]、1mL 48% HF 和 10 mL 至 12 mL 98% HAc 组成。McMaster 自己已经转向了这种混合物,而我仅仅因为 HF 是一种有毒的毒药而犹豫不决。^(2)

无论使用哪种方法,已经去层的芯片会被放置在明亮的光源下进行观察,例如来自卤素光纤灯的光。光照一两分钟后,芯片的某些部分会变暗,如果幸运的话,数字“1”会比“0”显得更加暗。

图片

图 22.1:TMP47C434N 植入 ROM

在这些反应过程中,保持金属含量低是至关重要的。你不能让去层反应中的任何金属盐留在玻璃器皿上,也不能让任何残留的铅框在芯片下方留下。通常,你甚至可以看到反应过程中镊子在芯片边缘留下的痕迹。

图 22.1 展示了 TMP47C-434N 的字体植入 ROM 的数据表描述,并附有我实验室拍摄的芯片照片,这些位通过 Dash 蚀刻处理后被染色。请注意,Dash 蚀刻导致了对比度的不均匀;有些部分比其他部分要暗得多或亮得多。

从照片到位

在拍摄了位图像后,有必要将这些位数字化提取。做到这一点的一种方法是仔细手工写下每一位,耐心地标记每一个零或一,同时不丢失位置或失去耐心。另一种方法是让软件来做这项枯燥的工作。正如 Crigger 教练在高中时常对我说的:“聪明地工作,而不是拼命工作。”

一个早期的公开示例是 Laurie(2013)的 Rompar,这是一个用 Python 编写的应用程序,用于标记汽车钥匙遥控器中 MARC4 微控制器的位。最近,Gerlinsky(2019)的 Bitractor 和我自己在 Goodspeed(2024)发布的 Mask ROM Tool,都是用 C++编写的。这三种工具在实现和使用上有很大差异,但它们的基本原理是先得出一个位位置矩阵,然后采样每个位的颜色,以确定“1”和“0”之间的区别。

图片

图 22.2:MYK82 在扩散层中的位

图片

图 22.3:MYK82 ROM 中的颜色分布

当你自己尝试时,你会发现简单的采样策略出奇有效。大多数位可以通过一个颜色通道的阈值来正确解码,通常是红色或绿色。通过绘制每个颜色通道的样本直方图来提高这种成功率,以确保“1”和“0”之间有清晰的双峰分离,并确保你的阈值设定在这两组之间。

以一个具体的例子,图 22.2 展示了 NSA 的 MYK82 比特的特写,我们将在第二十四章中详细讨论并提取它的 ROM。在图中,你会看到中央方块和水平导线之间的比特,呈现为一个比周围更暗的矩形框。图 22.3 展示了红色通道中,1 与 0 之间的对比有多明显,完全分离,阈值处没有比特,且几乎没有比特接近阈值。绿色通道的分离几乎同样好,而蓝色通道则没有明显的分离。

在任何颜色通道中如果没有干净的二分法分隔,图像预处理或通过采样多个像素来创建分隔可能会有所帮助。在我的工具中,我有一些采样技术,它们返回每个颜色通道中最暗的部分,通常是薄的水平或垂直条带。这对过度蚀刻的扩散 ROM 特别有效,在这些 ROM 中,比特的边缘有颜色,但比特中心的任何颜色差异已经被蚀刻掉。

虽然现有的比特标记工具之间存在许多差异,但它们都统一使用 ASCII 艺术作为导出格式。通常,较宽的轴被任意定义为 X 轴,以适应计算机显示器的尺寸,你可能需要进行一些旋转才能解码这些比特。

从比特到字节

在从物理顺序提取比特后,你需要将它们重新排列成逻辑顺序的字节。在我们开始讨论一些可以减轻这个过程痛苦的工具之前,先让我们来讨论一下比特最初为什么会以如此混乱的顺序排列。

在自然语言中,我们的书写方式有很大的多样性。有些语言从左到右书写,而有些则从右到左。有些语言通过字母组合来表示单词,有些则使用表意符号,还有一些语言混合了这些概念,通过将小符号组合成更大的符号来表示一个单词。

ROM 也有一些常见的规则和无限多样的排列方式,但几乎有一个概念是它们从未实现过的。它们几乎从不将一个字节的比特聚集在一起,而是更倾向于将它们分散到八列中,以便于物理上的便利。

要弄清楚比特的排列顺序,一种方法是非常仔细地研究 ROM 的比特,并尝试不同的模式,直到它们有意义为止。例如,在一个 16 位的微控制器中,如果你看到 16 列,你可能会猜测每列提取一个比特来构成一个字。检查顶部行和底部行的所有字,可能会揭示出程序的入口点,从而使整个布局变得有意义。

Gerlinsky(2019)介绍了 BitViewer,这是一个工具,见图 22.4,它以图形方式显示比特,调整它们的组织方式,以便操作员能够探索其含义。加载比特流后,你可以选择比特像素的高度和宽度、它们之间的间距以及每列中的比特数量。比特可以单独选择或按列和行分组,这使得操作员能够发现揭示 ROM 内容的模式。这比在图表纸上做同样的事情要轻松得多。

Image

图 22.4:来自 Gerlinsky(2019)的 BitViewer

Image

图 22.5:来自 Goodspeed(2024)的 MaskRomTool

| cols-downl | 第一个比特位于左上角,然后向下移动,

然后向右移动。 |

| cols-downr | 第一个比特位于右上角,然后向下移动,

然后向左移动。 |

| cols-left | 第一个比特位于右上角,然后向左移动,

然后向下移动。 |

| cols-right | 第一个比特位于左上角,然后向右移动,

然后向下移动。 |

squeeze-lr byte&0xAA 使用 cols-left,byte&0x55 使用 cols-right。

表 22.2:Zorrom 解码策略

McMaster(2018)采用了另一种方法,称为 Zorrom。它实现了多种已知芯片的解码策略,并提供了一系列转换功能,如 X 轴翻转、比特矩阵旋转以及比特反转。幸运的时候,大约一半的时间,它可以仅凭比特流和对几个比特或字节的猜测正确地解码。

Zorrom 的解码策略列出在表 22.2 中。要应用策略,首先将比特列分成八组,然后从每一组中取出一个比特组成一个字节,最不重要的比特位于左侧。所以,对于 cols-downr 策略,第一个字节将由每组的右上角比特组成。第二个字节的比特位于第一个字节的下方,在从每组的每行取出一个比特后,你会向右移动一列比特并从顶部重新开始。

它不支持从组的底部开始解码或将最重要的比特放在左侧的解码策略。这些由现有策略处理,在旋转和 X 轴翻转后执行。

我自己的比特解码解决方案叫做 GatoROM,它既可以作为一个 CLI 工具运行,也可以作为一个 C++ 库运行。随后,使用该库编写了我从 Goodspeed(2024)获得的 Mask ROM 工具的 GUI 扩展。它无耻地借鉴了 McMaster 的工具,实现了所有必要的转换,以便与他的解码器兼容。

作为库使用时,void* 指针可以实现物理顺序的位与逻辑顺序的字节之间的双向关联。你可以在十六进制查看器中选择字节,然后让软件在图形用户界面(GUI)中高亮显示它们。当为不完全符合现有解码策略的芯片实现新的解码策略时,这非常方便。

无论你使用什么工具解码 ROM,最终结果都是一个包含字节的平面二进制文件。当你首次得到有意义的解码结果时,应该对其顺序保持一定的怀疑,因为小的顺序错误可能直到 ROM 被反汇编并进行逆向工程时才会被发现。^(3)

第二十三章:23 Game Boy 通过 ROM

任天堂的 Game Boy,内部被称为点阵图形游戏(DMG),并未配备我们将在第二十五章中讨论的 CIC 保护芯片。游戏卡带需要包含任天堂的 logo,而不是一个锁定芯片。

这一过程由第一阶段启动 ROM 强制执行,它将自己的 logo 与卡带中的 logo 进行比较。如果两个 logo 匹配,则会播放一个简短的动画和声音,随后 ROM 会禁用自己并跳转到游戏卡带。在本章中,我们将以上一章的理论为基础,提取 ROM 内容并进行反汇编。

或许你已经意识到,任何人都可以将任何 logo 放入卡带中,而且在制作非授权游戏时,logo 比较并不是一个技术挑战。这个强制执行机制并不是技术性的;相反,是任天堂的法律顾问,他们会愉快地将任何未经许可使用其商标的人告得一塌糊涂。如果你,亲爱的读者,恰好是任天堂的律师,请不要起诉我。

Neviksti(2005 年)描述了 ROM 的提取过程。我在自己的实验室中重复了这个过程,并制作了图 23.5 中的 ROM 照片。位元在芯片的表面照片中清晰可见,没有任何去层或染色,使其成为一个极好的首次目标。

如同任何化学实验,请小心不要伤到自己。慢慢而安全地做这些麻烦事,是值得保住你的眼睛和手指的。

Image

图 23.1:来自 Neviksti(2005 年)的 Game Boy ROM 末端

去封装

要获取 ROM,我们首先需要牺牲一台 Game Boy。CPU 标记为DMG-CPU B,可以在靠近设备背部、远离 LCD 的板子上找到它。

(Game Boy Color 和 Super Game Boy 的 ROM 从表面上并不清晰可见。请参见第 E.4 章,了解一种在执行来自卡带内存的代码时,使 ROM 保持可见的故障攻击。)

去封装是通过使用第十八章中的 HNO[3]浴法进行的。位元是表面可见的,因此不需要涉及更危险化学品的去层程序。我们基本上只是将整个 QFP 封装浸泡在 65%的硝酸中,直到包装脱落,然后用丙酮和异丙醇清洗,以便拍摄。

摄影

我们要寻找的 ROM 在 CPU 中,其表面芯片的照片展示在图 23.2 中。在这种放大倍率下,位元是无法看到的,因此请查看图 23.3 以获得更近的特写。

要找到 ROM,首先需要找到内存总线,它是位于芯片大约中间的水平电缆。 从西边缘开始,沿着总线向东走,直到它在东侧的门海处终止。ROM 是位于总线正北方、门海正西的薄型水平结构。在适当的放大倍数下,位会显现出来,远距离看几乎像是无法辨认的外文。

黑色点是连接各层垂直的通孔,而亮点则是没有通孔的位置。这使得点的颜色暗示了位的值。当然,并非所有的通孔都是位,但在图 23.3 中,您应该能看到两列八个位和前六行。位于较长金属线中的通孔,那些到达图像顶部电源轨的通孔,不是位,不应该被提取。为了确保您理解什么是位,什么不是位,请花点时间从照片中制作 ASCII 艺术表。

Image

图 23.2:来自 Game Boy 的 Nintendo DMG-01-CPU

Image

图 23.3:DMG-01-CPU 位的特写

Image

图 23.4:位于 0xA8(ROM)和 0x104(卡带)的 Nintendo 标志

在找到 ROM 及其位之后,我通过冶金显微镜以 50x 放大倍数拍摄了它的全景,拍摄了 22 张图像。这些图像通过 Hugin 和 Panotools 拼接在一起,形成了一张宽 9000 像素,高 2249 像素的全景图。您可以在图 23.5 中查看低分辨率版本,或者查看数字文件^(1)。

位提取

拿到芯片的照片后,下一步是将位提取到文本文件中。

我使用了 Mask ROM Tool 来完成这项工作,为每一列和每一行画了线。这个 ROM 相对较小,拼接的图像对齐得很好,因此我可以绘制贯穿 ROM 整个长度的行列线。

软件会在每一行和每一列交点处标记一个位,它还会帮我绘制一个位的直方图,供我选择一个零和一之间的阈值颜色。红色和绿色两个色彩通道之间对零和一有明显的分离,但我发现绿色的间隔更宽,因此绿色是最适合采样的通道。我使用的颜色是位中心像素的颜色,不需要更复杂的采样策略。

Image

图 23.5:DMG-01-CPU 位的 ASCII 艺术

Image

图 23.6:Game Boy 内存映射

位解码

在提取了图 23.5 中的物理顺序 ASCII 艺术位之后,接下来的挑战是解码它。我们来看看三种解码方法。

McMaster(2018)以此芯片为例,自动解决已知明文下的位解码问题。Game Boy 使用一款 Sharp LR35902 CPU,类似于 Z80。与 Z80 一样,LR35902 代码通常在第一条指令中使用0x31操作码来设置堆栈指针。因此,McMaster 使用他的 Zorrom 工具搜索所有在其中第一个字节为 0x31 的解码。

Image

这些文件名包含了解码参数,其中两个参数都被旋转了 180 ^°C 并沿 X 轴翻转。位被反转,唯一的区别是一个使用 cols-left 策略,而另一个使用 cols-downr 策略。

然后,他使用 MAME 的 unidasm 反汇编器检查每个文件的第一条指令。cols-left 变体以 31 11 47 开头,设置堆栈指针为 0x4711,而 cols-downr 变体以 31 fe ff 开头,设置堆栈指针为 0xfffe。从 图 23.6 中的内存映射可以看出,后者是一个更合理的值,位于高 RAM 的尾部,而不是在卡带 ROM 的中间一个随机地址。

我们也可以使用 GatoROM 执行相同的解决方案。

Image

自动化工具在工作时非常棒,但我们应该始终对我们不理解的工具保持怀疑。cols-downr 模式并不复杂;它意味着字节被编码成由两个 8 位物理列组成的 16 位逻辑列。最左侧的列包含最重要的位,行的第一个字节位于最左侧。要获得下一个字节,首先向下移动,然后将所有内容向右移动一步。

ROM 的尾部,如 图 23.1 中的反汇编所示,在 0x00fe 通过向 0xff50 寄存器写入 1 禁用了读取访问,然后继续进入 0x0100 的卡带内存。这就是为什么直接通过构建一个卡带来显示 ROM、通过链接端口导出或通过扬声器发出哔声来倾倒 ROM 并不那么简单。

第二十四章:24 克利珀芯片扩散 ROM

在九十年代,克林顿政府对密钥托管加密有一种执念。他们希望为美国公民提供一种美国政府自己也能破解的加密方式,但这种方式排除了外国政府享有同样的特权。这种加密方式通常被称为“克利珀芯片”,在这一章中,我们将重点介绍该芯片的 PCMCIA 版本,称为 MYK82 或 Fortezza 卡。我们将提取其固件并将其转化为有用的数据。

它大致是这样工作的:假设莫妮卡打电话给比尔进行私人对话。当她按下加密按钮时,两部电话会执行密钥交换。密钥交换完成后,她的电话会向比尔的电话发送一个名为“执法访问字段”(LEAF)的数据包,其中包含(1)使用莫妮卡个人密钥加密的会话密钥,和(2)会话密钥的校验和。LEAF 本身使用一种“家庭密钥”进行加密,这个家庭密钥是每个克利珀设备都包含的,但不会提供给消费者。每个设备都有这个家庭密钥,但只有持有令状的特殊机构才能查找莫妮卡的个人密钥并解密会话密钥。

精明的读者会注意到这些密钥都是对称的,并且这种方案并不适用于拥有固件控制权的攻击者。如果你有家庭密钥,事情可能会有所不同。比尔可以给莫妮卡打电话,进行密钥交换,然后发送一个篡改过的 LEAF,里面包含(1)一个随机数,和(2)真实会话密钥的校验和。她的电话会验证校验和并允许通话继续,但任何监听的特殊机构都无法将随机数解密为会话密钥。莫妮卡的电话没有访问密钥托管数据库的权限,因此无法知道当局正被欺骗。

Image

图 24.1:MYK82 芯片在 Fortezza PCMCIA 卡中的位置

Image

图 24.2:MYK82 芯片

还值得注意的是,比尔并不严格需要知道家庭密钥。在没有篡改设备的情况下,比尔可能会反复拨打莫妮卡的电话,几万次后通过篡改 LEAF 数据包,直到 16 位校验和发生碰撞,令她的电话认为 LEAF 数据包没有被篡改。Blaze(1994)描述了这种攻击,并详细解释了托管加密标准。

MYK82 芯片嵌入在 Fortezza 卡中,执行此协议,并提供了便于在 Windows 和 Solaris 中使用该卡的库函数。图 24.1 展示了该芯片在 QFP 封装中的样子。这个封装有些特别,因为引脚框架位于芯片上方,而芯片则面朝下与 PCB 接触。这可能是为了减少射频辐射,因为芯片位于两个接地平面之间。

芯片的整体结构展示在图 24.2 中。CPU 位于西南部,其中包括一个 ARM6 的标志,告诉我们可以期待 32 位的 ARM 指令,而不是后来的 ARM7 中加入的精简 Thumb 指令集。该芯片上有三个 ROM,其中最大的一个位于东侧,存储代码。两个较小的 ROM 位于芯片南部,稍微偏东的地方,存储相同的 Skipjack F-Table;这两个 ROM 的大小正好是 256 字节,并与 Skipjack 文档中的内容完全一致,该文档已经解密。

MYK82 芯片,像它的前身 MYK78 一样,使用扩散 ROM。这些 ROM 形状扩散层位于晶体管下方,使得工作中的晶体管输出 1,而损坏的晶体管输出 0。

由于位是不可见的,需要像第二十二章中那样的去层处理来去除覆盖扩散层的上层。我通常对这个芯片的处理流程是,首先用 63%的硝酸烧掉封装,然后用 5%的氟化氢酸去层。这两种操作都在热板上进行,并且我在我的排风罩里做 HF 反应时,会将其放入密封的塑料试管中,以尽量减少有害气体。

在去层之前,位仅能在低倍放大下看到总的轮廓。这与光学和少量曝光有关,因为单个位几乎无法看到。去层后,位会显著地浮现出来,无论在高倍还是低倍放大下都能清晰可见。

图 24.3 展示了 ROM 的整体结构,由于印刷版中这仍然有些难以看清,图 24.5 展示了 ROM 最右侧六个位。 图 24.4 展示了位的特写。为了破解这些数据,我在一次飞往波哥大的航班上拍了这两张照片,那时我没有当地的朋友和责任。离开时,我已经将 ROM 解码成 32 位的字,并交到了几个朋友。^(1)

我们的第一个线索是芯片上其他地方的 ARM6 标志。ARM6 出现在 Thumb 之前,因此所有指令都是 32 位宽并且对齐到 32 位。我们可以看到 ROM 的底部相当稀疏,每个位都填充了相同的颜色。这些正好是 0,它们正确地暗示了代码是从 ROM 顶部的行构建起来的。

ARM 逆向工程师会告诉你,32 位代码很有特色,因为大多数指令的最显著四位(nybble)是E。如果你查看图 24.5 中的右侧六个位,你会看到每一列的两位。 (你也许可以从图 24.3 推断出这一点,那里 16 列代表 32 位。)最右侧的主列大多是 1,而其左侧的主列在右半部分是 1,左半部分是 0。这就是我们的E nybble,从右到左依次是 1, 1, 1, 0!

Image

图 24.3:MYK82 代码 ROM

Image

图 24.4:MYK82 ROM 位

Image

图 24.5:MYK82 代码 ROM 的右侧六位

确实,我们可以通过从每一列的 32 个小列中取一位来找到 32 位字——即从每个大列中取两个字节——最有效位在最右侧,最不有效位在最左侧。我们已经知道程序从第一行开始,因为最后有空的、清零的行。剩下的就是理解给定行中单词的顺序。

每一行都有 512 位,因此我们知道每行包含 16 个字。为了了解顺序,我简单地编写了一个解码器,它按顺序输出这些位,并将其传送到反汇编器中。正确的顺序是从右到左,就像位最好是从右到左读取一样。

到此为止,我们已经清楚如何将 ROM 解码为 32 位字,但要将其转化为字节,我们希望了解字节序。最高有效字节是先还是后?这就是事情变得复杂的地方。

字节序(Endianness)并不是指字中的字节顺序,而是指字是如何看作字节,或者字节如何看作字。内部 ROM 仅由 32 位字组成,这些字从不以更小的尺寸提取,因此它没有字节序。ARM6 CPU 没有指令从 ROM 中提取字节,但外部 EEPROM 存储器有一个布线决定。这个 EEPROM 存储了以大端字节序存储的代码,只有通过这个,我们才能说整个机器是大端的。

第二十五章:25 任天堂 CIC 及其克隆版

回到七十年代末,曾有一家公司制造家庭电视视频游戏机,名为 Atari。Atari 的游戏机拥有一些由 Atari 制作的优秀游戏,同时也有一些来自众多临时公司的糟糕游戏。到 1983 年,后者已经充斥市场,导致市场崩溃,Atari 将超过 50 万的游戏卡带丢弃在新墨西哥州的一个垃圾填埋场。Atari 不仅因为这些糟糕的游戏声誉受损,而且由于这些游戏只是电路板上的 ROM 芯片,Atari 通常不会因此类低劣的第三方游戏收取任何授权费用。

在任天堂准备于 1985 年在北美市场推出任天堂娱乐系统(NES)时,他们需要一种方法来避免重蹈覆辙。他们的解决方案是检查集成电路(CIC),这是一种每个 NES 卡带都必须包含的锁定芯片,任天堂通过限制 CIC 的供应来控制卡带的生产许可。通过为 NTSC 和 PAL 市场分别设计不同版本,他们还可以实现区域锁定,防止英国的贫困儿童发现,在外面的世界里,《忍者神龟》里的青少年变种英雄竟然是忍者,而其中名叫米开朗基罗的角色还使用非法的双截棍。

这个方案的工作原理是在 NES 主机中安装一个 CIC 芯片,在游戏卡带中再安装另一个 CIC 芯片。从重置开始,每个芯片会生成一串伪随机比特,任何比特不匹配都会导致主机重启并重新尝试。

鉴于任天堂严格控制游戏内容,任何能够制造不含 CIC 芯片的游戏的生产商都会获得可观的利润。在这一章中,我们首先讨论了为干扰主机的 CIC 芯片而设计的模拟电路,这些电路能使 CIC 芯片在预期的序列未能到达时,不进行重置,从而使其“失效”。接着,我们会讨论 Tengen 公司如何逆向工程 CIC 芯片、制作其克隆版,以及 21 世纪出现的开源克隆。

Image

图 25.1:任天堂 NES 的 CIC 芯片

另一种较为简单的绕过方法是直接重新使用合法但便宜的游戏卡带中的 CIC 芯片。你也可以制造一个“中间人”卡带,它可以接受任何合法授权的卡带,从而暂时借用其 CIC 芯片。

干扰主机的 CIC 芯片

在 CIC 芯片的兼容假冒品出现之前,曾有一种引人注目的替代方案:卡带可以发送一串疯狂的脉冲,通过卡带边缘连接器来震晕主机的 CIC 芯片,目的是防止该芯片的固件重置主机并结束游戏。

这个方法之所以有效,是因为主机的芯片完全独立于 CPU 运行,游戏继续运行,直到 CIC 芯片重置主机。如果 CIC 芯片崩溃,它的固件就不会运行,主机也永远不会重置!

对于这种故障技术,最好的,也是唯一的文献资料可能就是 Horton(2004)。Horton 描述了由 Camerica、Color-dreams 和 AVE 制造的七种不同类型的故障电路。这些电路中的每一种都会在 35 号或 70 号引脚上产生负电压故障,这些引脚直接连接到 CIC 芯片。这样,CIC 芯片会崩溃,从而使其 ROM 代码无法重置 CPU。图 25.2 展示了其中一种卡带,后面有一个故障配置开关,并且没有任天堂的质量认证标志。

Image

图 25.2:没有 CIC 的未经授权卡带

任天堂最终通过在 35 号和 70 号引脚上加入电阻器和保护二极管,结束了卡带故障时代,从而防止了卡带崩溃主机的 CIC 芯片。

天真的兔子:CIC 克隆

随着故障漏洞的关闭,未经授权的游戏制造商不得不提供切割主机锁定芯片引脚的说明,或者想出能够生成类似真实 CIC 芯片的伪随机序列的解决方案。天真,作为 Atari 的子公司,成功地做到了后者。

这里的故事大多属于民间传说,所以如果有时候我不让真相妨碍好故事,请耐心听我讲。根据我的理解,有一个三到四人的工程师团队,他们通过拍摄 Nintendo CIC 芯片的掩膜 ROM 并深入研究夏普(Sharp)关于该芯片家族的文档,进行反向工程。这支团队经常加班加点,最终制造出了一个功能齐全的 CIC 芯片克隆版,天真公司将其打包为“兔子”芯片,见图 25.3,后来又将其与一个映射器芯片合并,形成了被称为 Rambo 的芯片。^(1)

当然,任天堂对于天真公司(Tengen)破解他们的补贴锁定、未经授权生产游戏,甚至未经授权生产他们授权的游戏感到非常愤怒。他们在著名的案件Atari Games Corp.诉 Nintendo of America Inc中起诉了天真公司并索赔。

Image

图 25.3:天真的兔子

Image

图 25.4:天真的兔子扩散 ROM

Atari 有一个相当不错的辩护理由:他们仅复制了与兼容性相关的部分,且没有复制任何创意部分,且反向工程是通过洁净室方法进行的。不幸的是,Atari 的律师们太急于赚取费用了。他们在任天堂起诉他们之前,向版权局谎称他们已经被起诉,并请求了任天堂的 CIC 固件副本。哎呀!

任天堂因 Atari 的不洁手段获胜,本可能成为兼容性反向工程的商业成功案例,结果却被历史的废料箱遗弃。嗯,至少有十多年是这样。

现代兔子克隆

细节散布在论坛帖子中,但到 2006 年,Rabbit 芯片的 ROM 转储已出现在 Neviksti(2006)的#nesdev论坛上。Fox(2006)随后发布到论坛,作为 ROM 反汇编到 C 语言的翻译。你可以在第 245 页找到它的复印件。

阅读这个论坛帖子的过程非常吸引人,不仅仅是因为它来自一个社交媒体参与度指标还未破坏长篇讨论的时代。到第三页时,Zack S 已经将两个 CIC 连接在一起,在没有主机或游戏的情况下重现了检查和重置电路。

到第七页,Neviksti 的 ROM 照片开始被解码成比特,伴随着从晶圆照片逆向工程出的 ROM 电路的解释。

这是本书中的一个独特案例,因为固件保护的商业性利用本身被利用提供了一个漏洞,这个漏洞与对原始芯片的全新破解一样有效!CIC 被克隆到 Rabbit 中,然后 Rabbit 在论坛上被克隆,早于 CIC 本身被公开转储。

Image

图 25.5:任天堂 CIC(SM590)引脚图

克隆任天堂的 CIC

到 2006 年底,Tengen 的 Rabbit 芯片已经通过晶圆照片进行逆向工程并克隆,但任天堂的原始 CIC 芯片除了 Tengen 之外没有被克隆。这个空白由 Segher(2010)填补,他撰写了一篇优秀的文章,提供了 Neviksti 的图像和 ROM 转储,并描述了芯片使用的 Sharp SM590 架构。

说到 SM590,它是一个 4 位的 CPU,而这并不是它最疯狂的地方。10 位程序计数器被分为 1 位字段、2 位页面和 7 位步进。步进是按多项式顺序计数的,而不是线性顺序,因为 LFSR 比线性计数器使用的晶体管更少!像 PIC 一样,硬件调用栈与 RAM 分开存储。

Sharp SM590 后门

在完成了这么多工作来转储 CIC 的 ROM 后,是否有更简单的方法呢?Riddle(2019)记录了一种后门测试模式,在这种模式下,SM590 的 ROM 可以通过 I/O 引脚进行转储。

根据图 25.5 中的引脚图,通过按顺序降低引脚 7、14 和 13 来激活后门。ROM 数据将以 508 字节为一组,每 2,032 个时钟周期在引脚 12–19 和 4–1 上重复一次。

启动位置有些不可预测,但 Riddle 建议可以通过在引脚 7 降低后计算时钟周期,或通过在转储的末尾同步一长串零来进行同步。

Riddle 指出,SM591 和 SM595 可能需要更改字段才能获取所有数据,因为并不是所有内存都被覆盖。我们将在第 G.4 章看到这些是如何被转储的。

ImageImageImageImage

第二十六章:A 更多启动加载程序漏洞

A.1 PN553 签名绕过

Wade (2021a) 和 Wade (2021b) 记录了 PN553、PN547、PN548、PN551 和 PN5180 系列 NFC 芯片在启动加载程序中的内存损坏漏洞,这些芯片出现在 Pixel 3 和 Xiaomi MI Note 3 等消费类智能手机中。这些芯片实现了 NFC 通信,操作系统可以调用高级抽象。对芯片的原始控制将有助于执行原始的 NFC 交易,这就是此漏洞利用的价值所在。

在手机中,Wade 发现 Linux 将设备呈现为 /dev/nq-nci。这个字符设备允许标准 NCI 命令和系列特有的自定义命令。启动加载程序命令如下,他从 ELF 库中提取了这些命令。

c0 写入内存
a2 读取内存
a7 写入 64 字节到配置
e0 校验和和配置

c0 命令执行固件写入,但具有一种奇怪的签名结构。这些命令中的第一个包含一个版本号、一个 SHA256 哈希值和该哈希值的签名。哈希值本身是 下一个 区块的哈希,而下一个区块将包含下一个区块的哈希。通过这种方式,更新可以从头到尾线性进行,逐个验证和写入区块,而无需将整个镜像保存在内存中。

图片

图 A.1: NXP PN553 NFC 控制器

最后的区块稍有不同,没有哈希值,因为没有后续的区块来继续链条。Wade 注意到最后一个区块可以多次发送而不会报错,他推测这个命令并不会替换掉即将到来的哈希值。如果有可能用任意哈希值覆盖预期的值,那么下一个区块可能会用任何东西,而不管签名和哈希链如何。

现在,写入大多数区块的 c0 命令比写入最后一个区块的 c0 命令稍长。Wade 发现发送一个非法长的 e0 命令会在返回错误之前替换掉预期的哈希值。这个哈希值的损坏会打破链条,允许后续的区块像已经签名一样被写入。

拥有修补固件的权限后,他实现了一个没有范围限制的读取命令,并愉快地将所有内存转储以进行逆向工程。他还提到,SN100 芯片虽然与其他系列相似,但对其固件更新进行了加密,这使得利用该漏洞变得更加困难。

A.2 Tegra X1,Fusée Gelée

任天堂 Switch 使用 Nvidia 的 Tegra X1 处理器,该处理器严格限制设备仅能启动任天堂授权的内容。Temkin (2018) 提出了针对 X1 芯片 USB 堆栈的漏洞利用。该漏洞被报告给 Nvidia 并标记为 CVE-2018-6242,更广为人知的是 Fusée Gelée。

漏洞存在于设备进入 USB 恢复模式(RCM)时的引导 ROM 中,当某些引脚接地且外部引导存储器不可用时,设备会进入该模式。在 Switch 上,这是通过从插座中取出 eMMC 板,按住音量下按钮并将右侧摇杆连接器的第 10 引脚接地来执行的。然后,Switch 会作为一个 USB 设备出现,等待一个签名的可执行代码有效负载。

图片

图 A.2:Fusée Gelée memcpy

Temkin 描述了这个 bug,指出当从设备读取时没有检查长度。USB 控制请求包括一个 16 位长度字段,用于指示设备在回复中可能传输给主机的最大数据量。例如,主机可能会请求设备的状态,而设备可能只回复几个字节,而不是主机允许的最大字节数。她发现了三个例外情况,在这些情况下,X1 的 USB 堆栈会传送主机允许的最大数据量:

  • GET_CONFIGURATION 请求与 DEVICE 接收者。

  • GET_INTERFACE 请求与 INTERFACE 接收者。

  • GET_STATUS 请求与 ENDPOINT 接收者。

读取超出缓冲区末尾的数据对于内存转储很有用,但缓冲区使得这个问题更加严重。当主机请求 65,535 字节的状态时,超出的字节会从状态变量的地址复制到其中一个 DMA 缓冲区以进行 USB 传输。由于 DMA 缓冲区较小并且位于调用栈下方,这种复制过程中的溢出可能会覆盖整个调用栈!

方便的是,状态变量之后的内存也由主机控制。大部分内存用作缓冲区,用来存储最多 0x30000 字节的 RCM 命令。命令有一个我们无法伪造的签名,但它会在签名检查之前被存储在内存中。

图 A.2 显示了内存布局,Temkin 的漏洞将待处理的 RCM 命令复制到调用栈上。这里没有堆栈保护或地址空间布局随机化(ASLR)来增加复杂性,并且调用栈本身是可执行的。Trust-Zone 在这里也不是问题,因为 RCM ROM 以最高特权级别作为安全监控程序运行。

A.3 LPC55S69,K82 USB 过度读取

除了我们将在 C.4 章 中看到的 NXP 的 LPC55S-69 中的 TrustZone-M 漏洞外,NXP 的 LPC55S69 和 Kinetis K82 芯片中还存在一个 USB 过度读取的 bug。可以在比实际缓冲区小得多的空间中读取到数千字节的内存。这个 bug 已在 LPC55S69 的 A3 版本中修复,但怀疑相同的 USB 堆栈及其漏洞被应用在多种微控制器中。

Alaudeen (2021) 为 LPC55S69 提供的漏洞利用在 图 A.3 中展示,它在设备重置之前从芯片中转储 16KB 数据。 图 A.4 中的 K82 漏洞利用涉及更复杂的事务,但成功地从芯片中转储了 64KB 数据。

由于 libusb 中MAX_CTRL_BUFFER_LENGTH的值,这两个漏洞每个都被限制在 4kB。显然,在许多 Linux 平台上,只需简单地将该#define修改为 65,536,就可以解决这个问题。

Image

图 A.3:Alaudeen 针对 LPC55S69 的 USB 漏洞

Image

图 A.4:Alaudeen 针对 K82 的 USB 漏洞

Alaudeen 提供了来自这两款芯片的示例转储,但我似乎找不到有关转储中发现内容的详细信息。由于这款芯片有数百 KB 的 SRAM,我预计你可能会在转储中找到一些来自先前引导的字节,但不应指望此技术揭示闪存内容的太多信息。

A.4 CH552 验证命令

CH552 是南京青恒微电子生产的价格便宜、功能实用的 8051 微控制器,具有 USB 外设。Christophel 和 Thomas(2018)最初在一个德国论坛线程中讨论这款有用的芯片,但讨论很快转向了反向工程引导加载程序,以便在没有文档的情况下编写新客户端。

引导加载程序已预先写入这些芯片的闪存中,但它不在遮蔽 ROM 中,因此软件补丁是可能的。有 11 条命令支持读取、写入、擦除和验证闪存。与 8051 的哈佛架构一致,访问分开的代码和数据存储器有不同的命令。

这里的可利用漏洞存在于命令0xA6中,它验证代码闪存区域。你需要提供起始地址和一些 XOR 编码的字节,^(1),如果匹配,它会返回零,否则返回非零值。Thomas 将这个脆弱的函数重新编写成了图 A.6 中的 C 代码。

该代码的目的是通过要求字节数为八的倍数,防止攻击者利用验证功能暴力破解内存的内容。虽然一次猜测八个字节确实需要很长时间,但引导加载程序的作者忘记强制对地址进行对齐!

Image

图 A.5:W.CH CH552

Image

图 A.6:反编译 CH552 验证

要利用这个漏洞,攻击者可以将地址设置为七个已知字节,后面跟着一个未知的第八个字节,然后暴力破解第八个字节。一旦找到了这个字节,窗口就可以每次滑动一个字节来破解下一个字节。

利用此漏洞的直接方法是从已知的引导加载程序开始,然后每次向前滑动一个字节进入应用程序。Cheron(2019)中使用的一种更通用的技术是假设固件以八个0xff字节结束,然后从应用程序镜像的起始位置向后推算。

Image

图 A.7:BCM61650 中的堆栈缓冲区溢出

A.5 BCM61650/PRC6000 头文件

Broadcom 的 BCM61650,之前在其收购 Percello 之前被称为 PRC6000,是一款用于 3G 微蜂窝的 MIPS CPU,作为流行法国品牌 DSL 和光纤调制解调器的插件使用。

Xilokar(2022)描述了针对芯片 TFTP 启动映像头部格式的利用。他首先修补模块硬件以暴露以太网引脚,然后通过 TFTP 网络启动映像中的暴露密码获取 root shell。获得这个立足点后,他编写了图 A.8 中的快速内核模块,将 ROM 转储到内核日志中。

拥有 ROM 转储后,他在引导加载程序的头部解析例程中发现了一个解析错误,如图 A.7 所示。这里的错误是 fm_sig_len 是直接从攻击者控制的引导加载程序头部读取的,而其目标缓冲区 0xbf40-090c 离初始堆栈位置 0xbf403ff0 不远。一个非常长的头部将覆盖堆栈变量和返回指针。

图片

图 A.8:BCM61650 的 Linux ROM 转储工具

通过制作一个极长的签名长度,可以利用 Percello 引导加载程序跳过签名验证。然后,可以自由修补 FM 加载程序以允许任意的内核和初始 ramdisk。

A.6 PSoC4 闪存加倍器

Cypress 的 PSoC4 系列 ARM Cortex M0 微控制器具有一个受保护的 ROM,称为 SROM,实施了许多启动功能。它又使用一个隐藏且受保护的闪存,称为 SFLASH,用来存储设置,如芯片的保护级别和闪存容量。

在 Grinberg(2017a)中,Dmitry Grinberg 发布了通过用户闪存触发的 ROP 链转储 SROM 的详细信息,修补 SFLASH 通过重新实现 SROM 的闪存库,并通过修补 SFLASH 的两个字节将 CY8C4013SXI-400 的容量从 8kB 扩展到 16kB。

作为后续,Grinberg(2017b)尝试彻底记录额外寄存器及其含义,以帮助将这些攻击移植到其他芯片。

A.7 i.MX53 引导加载程序溢出

在第一代 USB Armory 设备中使用的 i.MX53 芯片具有堆栈缓冲区溢出漏洞,如 Delugré 和 Szkudlapski(2017)所述,允许绕过代码签名和安全启动限制。更多细节见 Barisani(2017)。

第一个漏洞,CVE-2017-7932,是 X.509 解析器中的堆栈缓冲区溢出。证书在验证之前就被解析,因此漏洞可以在没有正确签名的情况下触发,概念验证可以在 USB Armory Git 仓库中的 usbarmory_csftoolhab_poc 函数中找到。

第二个漏洞,CVE-2017-7936,通过滥用不正确的内存检查,在 ROM 中的串行下载协议(SDP)实现中允许远程代码执行。

A.8 M16C 引导加载程序定时攻击

瑞萨 M16C 芯片具有一个 ROM 引导加载程序,容易受到简单的时序攻击,至少在引导加载程序的第四个版本之前是如此。在 Bazanski 和 Kowalczyk(2018)中,利用这一点作为一种方式,来提取作为嵌入式控制器的三菱 M306K9FCLRP 芯片,该芯片用于东芝 Portégé R100 笔记本电脑。

固件提取漏洞本身是一个简单的时序攻击,针对密码检查。通过枚举每个可能的第一个字节,其中一个会比其他 255 个字节快 3 微秒。对每个字节重复此操作,平均需要 900 次猜测即可得到预期的密码,之后七个字节就能全部知道。有了这七个字节,你就可以自由地读写闪存。

Bazanski(2017)提供了这个漏洞的利用程序。它作为一个 Python 主机应用程序运行,匹配一个使用开源 Icestorm 工具链编程的 ICEStick FPGA 开发板。

A.9 IC204 通过魔术数字绕过

Lim(2021)描述了一款梅赛德斯-奔驰 ECU 的内部工作,其型号为 IC204。Lim 的具体示例来自 2011 年的 C300,但 2007 年至 2013 年间的许多车辆应该都容易受到同样的漏洞影响。

图片

图 A.9:2011 年梅赛德斯仪表盘上的 Nyan Cat

这里的技巧是,瑞萨 uPD70F3426 被编程为带有 ROM 引导加载程序链,验证每个部分的签名,随着引导过程的进行。Lim 逆向工程该 ROM,发现签名检查每次固件更新时只进行一次,每个块的成功验证会被缓存为一个 32 位的魔术数字。

在这种情况下,神奇的数字是0x5a5a5a5a。通过将该数字写入0x0f1f800x16ef800x1b3f800x1f4f800x1f5f800x0fff800x1fff80,这些位置都被 ROM 允许,签名检查可以被绕过,任意代码可以自由执行。

在获取 ECU 固件控制权后,他将 Nyan Cat 添加到 ABS 和 SYS 故障消息中,如图 A.9 所示。

A.10 Zynq 7000 引导加载程序转储

很多时候,芯片首先通过笨拙且劳动密集的方式被利用,接着从第一次利用的转储中进行逆向工程,找到一种更简单的方法。Xilinx Zynq 引导加载程序就是这样,在第 E.16 章中通过故障攻击被转储后,才发现了这一点。

Schretlen(2021a)描述了这样一个 UART 引导加载程序,你可以通过拉高两个引导模式引脚来启用它。只需要图 A.10 中的 Python 代码,就能上传并执行一个有效的镜像。在自己实现这个时,要小心像代码中那样延时;这样做是为了避免 ROM 中的可靠性问题。

到此阶段,我们已经明确可以上传镜像,但什么镜像值得上传以提取 ROM 呢?一个好的首选目标是复制 ROM 到 RAM 中,以便稍后提取。Schretlen(2021c)提出了一种利用 Zynq 7000 应用程序头的漏洞,利用引导加载程序从不验证镜像源地址这一事实。

如 图 A.10 所示,漏洞载荷仅是一个镜像头,它将 ROM 从原地址复制到 0x00000000 的 RAM 中。在执行漏洞攻击后,攻击者通过附加 JTAG 调试器并将该内存范围的内容转储到磁盘来恢复镜像。JTAG 调试器无法读取原始数据,但可以自由读取 ROM 拒绝启动的复制数据。

A.11 Zynq 7000 NAND/ONFI

Schretlen(2022a)描述了一种针对 Zynq ROM 的 NAND/ONFI 接口以及 embeddedsw 硬件抽象库(HAL)漏洞,适用于 xilinx_v2021.1 之前的版本。

ONFI 规范(开放 NAND 闪存接口)是 NAND 芯片的标准,定义了它们的封装、引脚配置及其他各种模式,使得来自不同厂商的芯片可以互换、兼容。

除了标准化引脚配置(图 A.12)和信号之外,ONFI 还提供了标准化的“参数页面”及匹配的数据结构。参数页面是 NAND 芯片的一页,可以通过设备代码读取,以便 NAND 芯片报告其一些特性。参数页面结构以 4f4e4649(“ONFI”)开始,并包括协议版本号、十三个可选功能和命令、JEDEC 制造商信息和内存组织等字段。^(2)

Image

图 A.10:来自 Schretlen(2021a)的 Zynq 启动加载程序客户端

Image

图 A.11:来自 Schretlen(2021c)的 Zynq 7000 漏洞头

Image

图 A.12:标准化的 NAND/ONFI 引脚配置

ONFI 参数页面的第 80 到 99 字节描述了内存的组织方式,包括每页的数据字节数、每页的备用字节数、每个块的页面数以及每个 LUN(逻辑单元号)的块数。这些值的验证较差,且每页备用字节过多会导致获取坏块表时发生溢出,坏块表会被加载到一个 0x200 字节的本地栈变量中。溢出此缓冲区会使得多个有用的栈变量的控制权被获取。

由于目前已知的任何商用 NAND 闪存芯片的参数页面均不可写,因此触发此漏洞需要使用 FPGA 模拟 NAND 芯片。Galan Schretlen 在编写此攻击时的优势在于他之前已经通过 章节 E.16 和 A.10 的技术转储了 ROM;若盲目编写漏洞攻击则会更加具有挑战性!

以下是他在 ARM 汇编中的 shellcode,将解锁赛灵思 Zynq 上的 JTAG,并将几个有用的寄存器值转储到 UART。

ImageImageImageImage

A.12 Zynq 7000 BOOT.BIN 解析

当有物理访问权限,NAND 引脚已断开,并且有一个 FPGA 模拟器的 NAND 芯片易于获得时,赛灵思 Zynq 7000 的漏洞利用在第 A.11 章非常好用,但这些限制可能令人厌烦,并且许多高端板卡不使用 NAND 芯片,因此它们不会断开必要的引脚。在本章中,我们将讨论 Schretlen(2022b),这是 BOOT.BIN 文件解析器中的内存破坏漏洞,该文件可能在 SD 卡上找到。

此漏洞利用不需要复杂的模拟器硬件,并且在签名检查之前触发,因此不需要单独破解加密。非常适合越狱设备。

Schretlen 首先使用 Unicorn 的 Python 绑定来模拟先前提取的 ROM。一旦功能正常,模拟器就可以用来探索 BOOT.BIN 的注册初始化列表(RILs)中允许的地址范围。

当解析 BOOT.BIN 时,ROM 根据 RILs 将部分加载到 RAM 中。只有在图像完全加载后才会检查签名。这样做可以防范时机检查到使用(TOCTOU)攻击,但这也意味着在签名检查完成之前可能会利用解析器漏洞。

Schretlen 发现,虽然 SDIO DMA 控制器的基本寄存器不可写,但由于机器正在从 SD 卡引导,它已经被引导 ROM 设置。在许多嵌入式漏洞利用中,你会看到相同的技巧,即不会费心配置被利用软件已经配置的 I/O 端口或寄存器。

以下是一个 Python 脚本,用于生成触发漏洞的有效负载头部。由于竞争条件的原因,它需要一个相当快的 SD 卡,这些原因最好在原始论文中进行解释,并且头部必须跟随适合于覆盖引导加载程序的 shellcode 块。

ImageImageImage

A.13 TMP91 密码

東芝的 TLCS-900 系列,以其前缀 TMP91 而闻名,是 2000 年代初的 16 位微控制器。其引导加载程序具有两种保护措施:密码和保护标志。这些保护措施是冗余的,因此如果设置了标志,则仅密码并不是非常有用。

至少对于 TMP91FW27 和 TMP91FW60 设备,O’Flynn(2023)描述了成功使用功耗分析恢复引导加载程序密码,以及针对保护标志的不太成功的故障注入攻击。

ROM 引导加载程序仅包含五个命令,其中需要密码才能用0x60锁定芯片,并用0x10从 RAM 执行代码。启用保护标志可确保即使有密码,也无法从 RAM 运行新的程序。

在 O’Flynn 的情况下,他想提取自己厨房烤箱的固件,以便绕过恒温器的一个 bug。烤箱会加热到大约正确的温度,但温度计总是显示目标温度,而不是实际温度。这导致了一批精美的饼干被毁,科林决定通过固件提取和修补来报复。

他的烤箱使用的是 TMP91FW60 芯片,但他针对 TMP91FW27 进行了攻击原型开发,因为该芯片在 eBay 上更为常见。这里的思路是首先攻击一个便宜的目标,然后再回过头来攻击那个稀有的目标。

为了进行功率分析,他在 VCC 引脚上添加了分流电阻,并用外部时钟源替换了石英晶体,以便让功率分析与目标同步。通过向芯片发送密码猜测并测量每次猜测时的电压降,他能够逐字节地揭示猜测的正确性。他还确定了一个潜在的目标,通过电压或时钟故障跳过引导加载程序中的标志检查,这对于在启用保护标志时运行 RAM 程序是必要的。

Image

图 A.13:TMP91 引导加载程序命令

Image

图 A.14:O’Flynn 的 TMP91 目标板

Image

图 A.15:TMP91FU62F0

到此为止,他的 FW27 演示板一切顺利,于是他转回了烤箱中的 FW60 芯片。功率分析揭示了密码是samsungoven0,但在调整电压故障时,他不小心擦除了所有内存。他辛苦提取的固件消失了!

给三星客服打了几通电话后,他收到了替换品,但这个板子与原始烤箱有一个关键的区别。虽然两者使用相同的密码,但替换品并没有启用保护标志!知道密码后,他可以自由地从 SRAM 运行 shellcode 来提取程序内存。如果你没有这么幸运,得到一个没有锁定位的目标,O’Flynn 建议从搜索窗口的末尾向后查找你的故障参数。

第二十七章:B 其他调试器攻击

B.1 STM32 克隆

GD32F103 是 STM32F103 的克隆,继承了其前辈的安全模型,其中 RDP 级别 1 允许 JTAG 连接,但会断开闪存连接。Obermaier、Schink 和 Moczek(2020)描述了一个巧妙的利用这一点的攻击。

作者注意到,当 DHSR 寄存器的 C_DEBUGEN 位被设置时,即启用 CPU 调试模块以暂停 CPU 或访问处理器寄存器时,会启用闪存访问限制。但当通过 JTAG 访问系统组件(如外设)时,这些限制不会生效。挑战在于如何在不触及 CPU 寄存器的情况下,仅通过外设触发代码执行。

他们的一个攻击手段是这样的:首先,JTAG 调试器控制 CPU,将 shellcode 写入固件未初始化的 SRAM 区域。然后重置目标设备,这会恢复对闪存的访问,但断开调试器连接。重新连接后,JTAG 被用来调整向量表偏移寄存器(VTOR),指向 SRAM 中的 shellcode,同时小心避免任何调试操作,以免启用 CPU 限制。由于新的 VTOR 值,下一次触发的中断将触发 SRAM shellcode 中的处理程序,进而转储所有闪存内容。

同篇论文描述了如何使用 JTAG 调试 GD32F103 和 CKS32F103 芯片的其他外设,同时小心避免任何针对 CPU 的调试操作。在这种情况下,目标是 DMA 引擎,而不是上一节中提到的 VTOR。

在 CKS32F103 上,DMA 引擎始终被允许从闪存读取,即使 CPU 的访问权限被撤销,因此你可以简单地使用 DMA 在内存到内存模式下将闪存内容复制到 SRAM。CPU 调试被用来暂停 CPU,命令 DMA 引擎从闪存复制到 SRAM,并获取 SRAM 的内容。

在 GD32F103 上,我们仍然可以使用 JTAG 读取缓冲区,但不能通过 JTAG 暂停 CPU,因为那样会启用 DMA 引擎的闪存访问限制。由于必须暂停 CPU 以防止内存访问冲突,他们使用 B.1 章中的 VTOR 技巧,将中断向量表移到 0xF0000000,这是一个非法地址,导致在下一个不可屏蔽中断(NMI)时 CPU 崩溃。这会暂停 CPU,但不会暂停 DMA 引擎,防止总线冲突影响正在传输的其他内存的可靠性。

论文中的另一个攻击方法影响 CKS32F103 和 GD32VF103,其中后者使用 RISC-V 内核,而非原始 STM32 芯片及其其他克隆的 ARM 内核。该攻击并不是通过 JTAG 将 DMA 外设指向闪存复制,而是利用了闪存访问在 CPU 执行来自芯片某些区域的代码时不会被禁用这一事实。

在 GD32VF103 中,从闪存或 SRAM 执行的固件可以读取闪存,即使芯片是只读保护的,调试器也不能直接读取闪存。因此,要转储内存,你只需将一些 shellcode 写入 RAM,运行它来执行从闪存的复制操作,然后使用调试器读取 RAM 中的缓冲区。

CKS32F103 也有类似的漏洞,但仅限于从 ROM 运行的代码,而不是从 RAM 运行的代码。利用这种漏洞的一种方法是盲目地在代码存储器中搜索合适的小工具,正如我们在第九章中看到的 nRF51 一样。Obermaier 采取了不同的方法,将解锁芯片的引导程序转储,以找到在所有 CKS32F103 设备中存在于可靠地址的小工具。

B.2 GD32 Giga 漏洞

Kovrizhnykh(2023)通过扩展 Obermaier、Schink 和 Moczek(2020)的研究,提出了三种新的 GD32 微控制器漏洞。这些漏洞影响不同的设备;请参见表 B.1 找到适用于你感兴趣的芯片的漏洞。

在这些芯片中,保护级别大致与真正的 STM32 相同。RDP 级别 0 为无保护,级别 1 允许调试,但会禁用闪存,级别 2 应该禁止所有调试。这里的调试协议是 SWD,而不是 JTAG。

这些攻击中的每一种都依赖于一个奇怪的观察,即在芯片处于复位状态时,SWD 调试是可能的。SRAM 和闪存始终读取为零。外设可以读取,但只能读取它们的复位值。SWD 缓冲区,例如读取结果或即将被读取的地址,似乎不会被擦除。

这三种漏洞中的第一个是,在一些芯片中,如 GD32L23x、GD32E23x 和 GD32E50x,在复位期间排队的读取可以在芯片退出复位时执行。Kovrizhnykh 发现,他可以通过这种方式泄漏 SRAM 中的数据。

当!RST 引脚为低电平时,他发送“W AP4 0x20000008”来准备读取 SRAM。然后,!RST 引脚被拉高,芯片退出复位并开始启动。仅仅 1.45 微秒后,他发送“R APc”来执行读取,并在读取命令发送后不久将!RST 引脚拉低。总的来说,芯片仅在复位外停留了 55 微秒。当芯片重新进入复位状态时,他发送“RDBUFF”,然后芯片愉快地提供了0x0800186c,这是预期地址的值。

图片

图 B.1:GD32F130,底层芯片

图片

表 B.1:Giga 漏洞成功表

这里的机制是竞态条件。如果给芯片足够的时间完全启动,调试限制会生效,读取操作将被拒绝。这种技术无法提取闪存,可能是因为闪存比 SRAM 在复位后可用的时间要长。

第二个漏洞依赖于完全断开调试器,因为当启用调试域时,读取保护会被触发,方法是使用CDBGPWRUPREQ。通过将一个转储应用程序加载到 SRAM 并启动应用程序,然后使用chip.dap dpreg 0x4 0x0在 OpenOCD 中清除调试域位来利用此漏洞。内存愉快地通过 UART 泄漏出来,SRAM 和闪存都可以直接以这种方式提取。

大多数测试的设备都容易受到这种攻击,但 GD32F3x0 是一个顽固的例外,既不容易受到第一种方法也不容易受到第二种方法的攻击。

第三个变种涉及该系列 F 系列芯片的上电复位序列中的竞态条件。!RST 引脚高电平后,SWD 无法工作,但可以通过断电、将!RST 引脚拉低并重新供电来使用它。功率分析表明,Kovrizhnykh 发现该系列的竞态窗口要比 E 和 L 系列更宽,达 1600 微秒,而 E 和 L 系列为 20 微秒。

这个第三个变种还有两个复杂因素。SRAM 因失去电源而内容丢失,因此我们无法像第一个变种那样暴露其内容。另一个复杂因素是,尽管 SWD 被允许,但无法调试 CPU,因此任何闪存的读取必须由外设执行。由于不能使用 CPU,Kovrizhnykh 改为配置 DMA 外设,将所有闪存直接转储到 UART。

B.3 赛灵思比特流解密 oracle

来自赛灵思的 7 系列 FPGA 在运行过程中将比特流存储在 SRAM 中,这要求它们从外部存储芯片或微控制器加载配置。为了在不增加非易失性存储器成本的情况下保护这些比特流,赛灵思允许通过 AES-256 CBC 模式加密比特流,使用已烧录到 FPGA 有限 eFuse 内存中的密钥。

通过 JTAG 读取比特流被加密功能禁用,但 Ender、Moradi 和 Paar(2020)描述了一种漏洞,可以一次泄漏 32 位明文比特流。他们注意到,在 HMAC 错误之前,WBSTAR寄存器被加载了一个解密的比特流字。然后他们可以复位 FPGA 并读取此寄存器的内容,因为该寄存器在复位时不会被清除。

该攻击虽然缓慢,但非常有效,能够在三小时 42 分钟内解密 Kintex-7 XC7K160T 的比特流。Virtex 6 系列也容易受到此攻击,唯一的限制是每个 32 位字中的两位在复位期间会被破坏并丢失。

B.4 CC2510,CC1110

德州仪器的 CC2510 和 CC1110 是最早将非易失性存储器、射频收发器和微控制器集成到一个封装中的芯片之一。这一代使用的是 8051 作为 MCU。

Devreker(2023)描述了一种电压故障攻击,用于从这些芯片中提取固件,灵感来自它们在电子墨水价格标签中的应用,且该标签使用的是尚未公开的无线协议。Devreker 首先通过 Raspberry Pi Pico 实现了调试接口,然后通过在 DCOUPL 引脚上加装 IRLML6246 MOSFET 为其添加了故障支持,这一引脚直接连接到用于连接去耦电容器的内部 1.8V 线路。他的代码是开放的。[1]

他在文章中提到了一些有用的小技巧。将 Pi Pico 的时钟频率从默认的 125MHz 超频到 250MHz,可以提高故障的精度。该芯片有多个核心,将故障操作分配到与 USB 栈不同的核心上,可以避免 USB 中断影响计时。增加故障引脚的驱动强度可以提高其跃升速率,使故障信号的边缘更加锐利。通过 Pi Pico 的 GPIO 引脚直接为 CC2510 供电,可以在故障发生后轻松重启目标设备。这些小技巧虽然可能并非严格必要,但它们为他的论文增加了可移植性,即使在处理非常不同的目标时也能提供很好的参考。

图片

图 B.2:德州仪器 CC2510

至于故障本身,攻击调试协议的状态机与攻击引导加载程序的软件解析器可能是完全不同的。每当调试器命令芯片执行指令时,都会检查芯片的锁定状态。通过在DEBUG_INSTR调试命令之后制造一个故障可以绕过这个检查,但最少需要两条指令,首先将 16 位地址MOVDPTR寄存器,然后通过MOVX@DPTR处的字节读取到累加器中。为了读取一个字节,两个故障都必须成功。

由于需要进行双重故障,Devreker 的漏洞利用速度相当慢。他报告了每次故障大约 5%的成功率,因此双重故障的综合成功率为 0.25%。这意味着每二十秒他能获得一个字节,或者在四天内获得完整的 32kB 固件镜像。

第二十八章:C 更多特权升级

C.1 Game Boy Advance BIOS

与其前身 Game Boy 一样,任天堂的 Game Boy Advance 包含一个在重置时执行的掩码 ROM,用于启动游戏卡带,且会验证卡带是否包含有效的任天堂商标 logo 以保护商标。在 Game Boy 中,ROM 会在跳转到游戏卡带之前取消映射,而 Game Boy Advance 则保持 ROM 映射在内存中。我们称之为BIOS,因为它像 IBM PC 中的 BIOS ROM 一样,包含作为中断调用的便捷功能。

与本书中的许多目标不同,攻击者拥有在设备上运行任意机器码的特权。这是通过一个简单的 EEPROM 芯片连接到游戏卡带的正确引脚来实现的,在任天堂发布 GBA 后,爱好者很快就可以使用它。

BIOS ROM 的转储对于模拟平台非常有用,因此设有访问限制。当从其范围外的地址作为代码读取时,ROM 会被禁用;而当从其范围内的地址作为代码读取时,则会重新启用。这一过程是在硬件层面发生的,在访问发生的瞬间。

本章中,我们将看到三种方法来欺骗 Game Boy Advance 允许读取 BIOS ROM。其中一种利用了没有源地址限制的 BIOS 调用,一种预先中断 BIOS 调用以在验证后更改源地址,第三种通过从未映射的内存执行指令,以使流水线解锁 ROM 进行读取。

Image

图 C.1:任天堂 GBA CPU

MidiKey2Freq 方法

Fader(2001)是经典的 BIOS ROM 转储漏洞,如图 C.3 所示。它是 Unix 中经典的内核内存暴露技术的变种,其中一个系统调用未能验证源地址,导致调用者可以在内核权限下查看内存。

MidiKey2Freq作为 ROM 中断0x1f实现。它接受一个指向 MIDI 样本的指针,读取该地址的四个字节,并对这四个字节执行音频处理功能。然而,这个音频功能没有范围或对齐限制,并且它保留了最高字节不变。Fader 的漏洞通过 ROM 地址空间进行循环,每次抓取返回值中的最高字节。

Endrift 方法

曾一度认为MidiKey2Freq方法是唯一的 BIOS ROM 转储方式,但 Vicki Pfau 觉得这不对。在 Pfau(2017)中,她提出了两种不同的黑盒技术来转储 BIOS ROM。她的两种技术都依赖于 ARM7 的中断优先级,在 BIOS 调用的软中断进行时触发硬件中断。

嵌套中断无法直接读取 BIOS,但它拥有完全的权限来读取和写入 BIOS 调用中的软件中断的调用栈。

Vicki 的黑盒示例注册了一个定时器中断,与软件中断调用 CPUFastSet 重叠。CPUFastSet 处理程序在 BIOS 地址空间内执行快速复制,但它验证源地址,因此调用者不能仅仅用它导出 BIOS。在 BIOS 软件中断运行时,它会被她的 bbTest 处理程序打断,然后扫描软件中断调用栈中的源指针(位于 CPUFastSet 的栈帧中)。在返回之前将源指针覆盖为 ROM 地址,然后会导致 BIOS 执行非法的复制操作,因为源地址只在中断处理程序开始时验证一次,而不是对每个字都重复验证。

0x00 SoftReset
0x01 RegisterRamReset
0x02 Halt
0x03 Stop
0x04 IntrWait
0x05 VBlankIntrWait
0x06 Div
0x07 DivArm
0x08 Sqrt
0x09 ArcTan
0x0A ArcTan2
0x0B CPUSet
0x0C CPUFastSet
0x0D BiosChecksum
0x0E BgAffineSet
0x0F ObjAffineSet
0x10 BitUnpack
0x11 LZ77UnCompWRAM
0x12 LZ77UnCompVRAM
0x13 HuffUnComp
0x14 RLUnCompWRAM
0x15 RLUnCompVRAM
0x16 Diff8bitUnFilterWRAM
0x17 Diff8bitUnFilterVRAM
0x18 Diff16bitUnFilter
0x19 SoundBiasChange
0x1A SoundDriverInit
0x1B SoundDriverMode
0x1C SoundDriverMain
0x1D SoundDriverVSync
0x1E SoundChannelClear
0x1F MIDIKey2Freq
0x20 MusicPlayerOpen
0x21 MusicPlayerStart
0x22 MusicPlayerStop
0x23 MusicPlayerContinue
0x24 MusicPlayerFadeOut
0x25 MultiBoot
0x26 HardReset
0x27 CustomHalt
0x28 SoundDriverVSyncOff
0x29 SoundDriverVSyncOn
0x2A SoundGetJumpList

表 C.1:Game Boy Advance BIOS 中断

图片

图 C.2:Game Boy Advance 内存映射

图片

图 C.3:来自 Fader(2001)的 MidiKey2Freq ROM 转储器

图片

图 C.4:来自 Pfau(2017)的优化 GBA BIOS 转储器

图片

图 C.5:来自 Hearn(2017)的 BIOS Peek 功能

黑盒方法特别好,因为它不要求作者已经拥有一份 BIOS,并且时序校准不需要特别精确。Vicki 还提供了一个优化实现,直接在 CPUFastSet 中间进行 bx 调用,因为 BIOS 入口点不强制执行,并且那段代码始终可以从 BIOS 中读取。请参见 图 C.4。

执行缺失的内存

虽然 Fader 直接使用 BIOS 调用来泄漏内存,Pfau 则通过伪造栈或在嵌套中断中修改真实栈来重用 BIOS 代码,Hearn(2017)则达到了极致的复杂性。她设法在地址空间的最远端执行来自未映射内存的代码,因此,来自内存起始位置的预取指令在从流水线中被丢弃之前解锁了 BIOS。我不是在开玩笑!

回想起你本科时的计算机科学课程,像 ARM7TDMI 这样的 90 年代 RISC 芯片采用了流水线架构。这个具体的例子有三个流水线阶段:取指、解码和执行。在 CPU 执行指令的同时,它会解码下一条指令并取出下一条指令。如果取出的和解码的指令没有价值,它们会被直接丢弃。

CPU 通过总线与外设进行通信,如内存和 I/O。在 ARM7TDMI 上,有一个有趣的现象:该总线的数据线保持它们的最后一个值,每当一个未映射的地址被取出时,它会返回这个值。^(1) 如果你从任何地方读取0xdead-beef,或者将其写入任何地方,然后再从一个未使用的地址读取,比如0x100000000x4bidb10c,且两次读取之间没有其他总线访问,你将会读取到0xdeadbeef。这是架构的一个特性,许多其他情况下会触发故障或返回不同的值。^(2)

结合这些观察,Hearn 意识到,如果她能够将两条 Thumb 指令写成单个 32 位字并跳转到0xfffffffd,那么第一条指令可能会在 ROM 的第一条指令(从0x0000-0000取出)执行之后执行,从而解锁 ROM。图 Figure C.5 中的几行 Thumb 汇编完成了这一点,它们简直是一件艺术品。

在阅读代码时,别忘了 Thumb 寻址是如何工作的。0xfffffffd表示 Thumb 模式,但 16 位指令是从0xfffffffc取出的。每次取出的都是 32 位数据,第二条指令不会单独取出。

第 7 行将她的指令对写入内存末尾,第 8 行则跳转到内存末尾执行它。ldr指令会将作为参数给定的任何 BIOS 地址读取到返回值中,而bx lr指令则返回给调用者。“等一下,”你可能会问,“如果我们还没有从 ROM 中执行任何指令,第一条指令怎么能够读取 BIOS ROM 呢?”

CPU 流水线就是答案。在ldr指令从 ROM 加载一个字之前,流水线已经从0x00000000取出一个 32 位的字进行解码,最终准备执行。这为数据提取解锁了 ROM,且即使这些流水线指令会在接下来的bx指令中被丢弃,也无关紧要。

C.2 MSP432 IP 封装

IP 封装(IPE)是德州仪器部分 MSP430 和 MSP432 设备的一个特性,目的大致与 TrustZone-M 或其他可信执行环境(TEE)相同。其思想是,你可能会购买一个带有无线电库的微控制器,你可以使用这个库,但不能读取这个库来进行逆向工程或克隆。

就像本章其他特权升级漏洞一样,防御方处于明显的不利位置。攻击者能够运行本地代码,附加调试器,并进行故障注入。防御方只能希望 TI 的限制足以防止提取受保护的库。

Sah 和 Hicks(2023)详细描述了这个特性,并指出了一些设计错误,这些错误暴露了封装的固件。有两个事实对于利用这一漏洞特别重要:首先,IPE 特性并未强制执行进入受保护代码的特定入口点,因此当从用户程序内存调用时,gadget 可以被重用。其次,IPE 特性并未禁用大部分中断源,尤其是定时器中断,它们在攻击者代码中非常有用,能够让攻击者在封装库中间执行代码,从而获得有关库的信息。

利用细节在 16 位的 MSP430 架构和 32 位的 MSP432 架构之间有所不同。在这两种情况下,都使用一个非常小的计时器来触发受保护库内的异常,然后非保护应用程序中的异常处理程序观察寄存器状态,以便做出关于代码状态的合理猜测。

例如,如果处理程序观察到某些额外的返回指针被压入栈中,这些指针将揭示 MSP430 上的call指令或 MSP432 上的bl/blx指令的位置。类似地,攻击者可以通过在 ARM 上调用这些指令并设置链接寄存器,或者在 MSP430 中加载返回指针到栈来定位ret指令。

最终,攻击者会发现一个 gadget,它能够将任意地址读取到寄存器中。也许 gadget 会返回,在这种情况下就不需要定时器。也许它不返回,在这种情况下可以使用定时器的倒计时来反复调用这个 gadget,然后跳出。无论是哪种情况,重复使用 gadget 可以提取所有受保护的内存。

C.3 BCM11123 U-Boot 和 TrustZone

Cisco 的 8861 型号 IP 电话使用了带有 TrustZone 的 Broadcom BCM11123 CPU。TrustZone 芯片有两种模式,安全代码拥有非安全代码所不具备的特权。并不是说非安全代码易被利用,而是它不被信任。两种模式之间的通信通过中断处理程序进行,类似于用户空间到内核的系统调用。

在这款手机的情况下,U-Boot 在非安全内存中运行,通过调用 TrustZone 监控程序来验证并启动 Linux 内核。Cui 和 Housley (2017) 主要讲述 EMFI 攻击,但该论文的附录描述了一种针对这种配置的巧妙攻击。

Image

图 C.6: Cui 和 Housely 针对 BCM11123 的漏洞利用

作者们首先在启动时故意破坏手机的 NAND 闪存,以便进入 U-Boot 的命令行,这就像在 第十四章 中提到的 Freescale MC13224 的 ROM 启动加载程序一样。这个启动加载程序提供了读取、写入和执行内存的便捷命令,但由于它位于非安全区,因此不足以转储或控制 TrustZone 中的安全区域。接下来的任务是找到 TrustZone 监控程序中的漏洞,并从 U-Boot 中利用它。

涉及的漏洞位于 _ssapi_public_decrypt 函数中,该函数缺少必要的长度检查,并未确保源地址和目标地址位于 TrustZone 障碍的适当一侧。通过精心选择正确的参数,Cui 和 Housley 成功地将小块数据从安全区复制到 U-Boot 可以访问的非安全内存中,进行反向工程和转储。

他们随后利用同样的漏洞反向操作,覆盖了安全区中的返回指针,并将 U-Boot 本身提升到 TrustZone 内运行。

C.4 LPC55S69 硬件和软件

LPC55 系列微控制器采用 ARM Cortex-M 架构,并通过 TrustZone-M 来保护密钥材料,例如每个设备唯一的密钥,免受用户编程应用程序的影响。理想情况下,这将使板设计者能够在芯片上安装使用该密钥材料的软件,但即便该应用程序软件存在严重漏洞,攻击者也无法控制受信任区域、其软件或其密钥。

一些 Cortex-M 设备包括一个闪存补丁和断点(FPB)单元,它允许对内存中的几个字进行修补,用选择的值覆盖其真实值。在像 LPC55 这样支持 TrustZone-M 的设备中,ARM 明确禁止该 IP 模块,以防在重新映射地址空间时,TrustZone-M 保护可能被失效。

在为 LPC55S69 反向工程一个应用程序时,Laura Abbott 发现存在一个自定义模块,类似于被禁用的 FPB 单元,可以对内存中任何地址的几个 32 位字进行小规模修补,包括 ROM 中的字。她在 Abbott (2021) 中记录了该模块,并描述了如何利用它伪造 ROM 补丁的签名验证,从而允许恶意补丁被安装并在下次启动时继续存在。

该模块作为 APB 外设存在于0x4003e000的非安全内存和0x5003e000的安全内存中,这一地区在 LPC55S6x 用户手册的内存映射中缺失。由于它存在于特权模式和非特权模式下,因此非特权代码可以利用它对特权 ROM 代码的行为进行修补,从而实现特权升级!

图片

图 C.7: LPC55S69

该补丁模块的配置会在重置时被清除,但如果攻击者希望补丁能够持续存在,比如禁用安全启动认证,怎么办呢?Abbot 描述了一个受保护的闪存区域中的补丁条目表,其结构如下。三种支持的命令包括单字更改、svc入口点更改和对 SRAM 的补丁。

图片

除了未记录的补丁模块外,还有第二个软件漏洞可用于提升权限进入安全世界。Abbott(2022)中描述了固件更新头部解析中的软件漏洞,它允许从非安全世界进行特权升级,并在下次重置后持续控制。

错误位于头部结构中,如图 C.8 所示。按设计,m_keyBlobBlock 应该是紧接着头部之后的块号。每个块为 16 字节,因此第 8 块应该紧随 128 字节的头部之后。

安全启动解析器不是仅仅复制头部,而是继续复制数据块,直到计数到m_keyBlobBlock。当这个数字大于 8 时,这种复制就会成为典型的缓冲区溢出。

另请参见第 A.3 章,了解引导加载程序 USB 堆栈中的缓冲区过度读取问题,以及第 E.2 章,了解针对芯片的故障攻击集。

图片

图 C.8: LPC55 SB2 更新头部

C.5 FM3 闪存补丁

英飞凌的 FM3 系列 Cortex M3 微控制器至少在索尼的 Playstation 4 Dualshock4 控制器的某些型号中被使用。Enthusiast(2018)描述了一种闪存补丁和断点(FPB)技巧,类似于第十七章和 C.4 章中的方法,这种技巧可以通过在重置后持续补丁来提取闪存。

该芯片具有启动模式引脚,标记为MD,在复位时会读取这些引脚以执行来自闪存的应用程序或来自 ROM 的串行引导加载程序。USBDirect 是制造商的编程工具,其通过将本地代码块加载到 SRAM 中来操作。该代码块的开源替代方案已发布,通过对其打补丁,您可以自由地调整编程环境。

这是一个不错且简单的开始,但代码运行在受限的环境中,直到进行全擦除之前无法访问闪存。任何尝试从闪存读取数据的行为都会返回垃圾数据,这也适用于像 DMA 传输这样的复杂读取方法。

通过更多的实验,作者发现 SRAM 在重置后仍然保持数据。正如我们在第二章中看到的,这是一种很好的方法,可以在后续攻击中留下 shellcode。

了解到 SRAM 没有被重置后,作者开始查看其他外设,最终找到了 FPB 单元。FPB 保存了六对地址,将从第一个地址获取的代码映射到第二个地址的获取。这一模块的配置在重置时也不会被清除!

最终的攻击利用包括一个用于串行引导加载程序的 SRAM 数据块,该数据块启用了 FPB,通过它修补闪存中的用户应用程序,以重新进入串行引导加载程序。此时,可以呈现正常的 SRAM 数据块。由于设备是从闪存启动的,读取限制没有启用,因此这个数据块可以导出所有闪存。大规模擦除并重写固件后,便能解锁目标,就像我们在第十七章中看到的一样,只是这次不需要在重置时引发电压故障。

第二十九章:D  更具侵入性的攻击

D.1 Atmega、AT90 背面 FIB

Helfmeier 等人(2013)描述了针对 Atmega328P 和 AT90SC3232 的背面探测攻击。这两款芯片使用相同的 AVR 核心,但 Atmega 使用浅槽隔离(STI)来分隔晶体管,以防止电流泄漏,而 AT90 则在其顶部两层金属之间有一层安全网格。

在这两个芯片中,作者能够通过 IC 背面挖掘出一个槽沟,暴露出熔丝位,然后通过聚焦离子束(FIB)篡改熔丝来设置或清除熔丝。改变与读保护相关的位后,芯片便可以外部读取。

论文中记录了熔丝位置,以及关于 STI 特性如何影响 FIB 槽沟工作的说明。你可以在图 D.1 中找到大致的熔丝位置。

D.2 GD32F130 QSPI 嗅探与注入

GD32F103、GD32F130 和一些 STM32 的克隆芯片是双芯片设备,其中闪存芯片堆叠在 CPU 上方,通过 QSPI 总线连接。如图 D.2 所示,可以看到这两个芯片通过线键合直接连接在一起。上面的小芯片是存储器芯片,下面的大芯片是 CPU。

图片

图 D.1:Helfmeier 等人(2013)提供的 Atmega328P 熔丝

图片

图 D.2:GD32F130 与 QSPI 闪存芯片的键合

Obermaier、Schink 和 Moczek(2020)记录了去除包装以暴露连接两个芯片的引线、使用逻辑分析仪嗅探 4MHz 总线流量、逆向工程一些地址和数据混淆,然后重构固件镜像的过程。此外,他们还能够向总线注入数据故障,通过引入单个位错误将 RDP 等级从 2 降级到等级 1。通过翻转地址的两个比特,还可以将 RDP 降级到等级 0。

D.3 STM32 紫外线降级

Obermaier 和 Tatschner(2017)的大部分内容讨论了在第十章中,STM32F0 系列的 JTAG 调试中的一个令人愉快的漏洞,允许使用自定义 JTAG 调试器从 RDP 等级 1 提取固件。这些芯片中的许多都被锁定在 RDP 等级 2,论文还讨论了如何通过实时去壳和紫外线光降级芯片。Garb 和 Obermaier(2020)扩展了这一研究,提供了关于 STM32F0 系列闪存布局和激光故障注入的具体笔记。

总结本书中多个章节所解释的内容,RDP 等级 0 完全解锁,等级 2 完全锁定,不允许调试。等级 1 是中间地带,允许使用调试器,但连接调试器会禁用对闪存的访问。由于调试器的访问对攻击者非常有用,例如用于放置 shellcode 或利用保护中的漏洞,从等级 2 降级是一件非常有价值的事情。

保护级别存储在选项字节中,作为一对 16 位字,分别命名为RDPnRDP。这些字对 Level 0 和 Level 2 有固定值,所有其他值为 Level 1。因此,虽然我们需要一个非常特定的值才能降到 Level 0,但翻转任何一个单独的位就足以降到 Level 1。

图片

图 D.3:STM32F051 顶层金属

图片

图 D.4:STM32F051 闪存布局

了解到紫外线能够将闪存的位从 0 提升到 1,Obermaier 功能性地去除了 STM32F051 的封装,并将紫外线 C 射线照射到其上,同时反复尝试连接调试器。几个小时后,调试器成功连接,并且RDP/nRDP选项字节中的一个 0 位变成了 1。不幸的是,其他的存储位也发生了变化,因此需要进行掩蔽,以最小化对其他存储的损坏进行解锁。与第十九章中的 PIC16 一样,掩蔽可以通过直接在芯片上涂抹指甲油来完成。

对位损坏的显而易见解决方案是掩蔽存储器,但首先我们需要知道哪个物理区域包含选项字节。他们将所有闪存存储器在解锁芯片中填充为零,然后在紫外线光线穿过塑料掩蔽层照射的同时,反复使用调试器重新读取存储器。实际上,这将芯片变成了一个图像传感器,所有的 1 位表示存储器中被掩蔽区域外的地方。

这表明图 D.3 中的 STM32F051 闪存具有 1024 位行和 512 字行,组织成 32 位列的 32 位行。位线垂直于芯片最近的边缘,最重要的位在左侧,最不重要的位在右侧。选项字节位于字行 0 下方,RDPnRDP位于闪存单元区域的右半部分,因为它们是 32 位字的下半部分。图 D.4 展示了闪存位列和 RDP 字位置的大致布局。

他们最好的解决方案是一个移动的塑料掩蔽层,只暴露闪存的右下角边缘。这实现了几次解锁没有损坏固件,并且许多解锁只损坏了几百个固件位,而两个损坏的固件图像进行按位AND运算通常足以生成一个干净且准确的图像。

D.4 MT1335WE 神风

来自第十四章的 MC13224 并不是唯一一款将没有非易失性存储器的 CPU 芯片与标准 SPI 闪存芯片结合的系统封装(SiP)。

联发科技的 MT1335WE 可以在 Xbox 360 的 DVD-ROM 驱动器中找到,其固件负责区分商业光盘和消费者 DVD 刻录机制作的 DVD-R 光盘。盗版者发现,可以将这些光盘补丁使其接受刻录的光盘,但只有在重写了 MT1335WE 的 SPI Flash,并且固件被补丁化的情况下才行。复杂之处在于,SPI Flash 芯片是内置于 MT1335WE 封装中的,因此没有外部引脚可供接触,也没有可以替换的封装。

写保护通过芯片的 !WP 引脚实现,就像它在一个单独的封装中一样。为了绕过这个控制,我们可能会通过键合线连接 SPI Flash 芯片的 !WP 引脚。这个过程在 sQuallen(2012)中有所描述,并引用了 Geremia 和 Carranzaf 作为共同发现者。

这个想法是,图 D.6 中显示的键合线在不同芯片之间的位置是一致的,即使丝印标注稍有偏差。因此,通过图 D.5 中显示的定位方法,使用钻头准确击中键合线是可能的,知道钻头最终会与键合线碰撞。如果仔细观察 SPI Flash 右侧的第二根键合线,你会发现它已经被钻头干净利落地切断了一半。

为了执行解锁操作,钻头通过一个上拉电阻松散地连接到 3.3V 引脚。早期的说明建议在封装的东侧画一条线,离东侧五个引脚、北侧八个引脚的位置,这通常位于“Mediatek”字母 K 的东南方。后来的工具包使用了一个柔性 PCB 作为模板,模板上有一个小孔用于放置钻头。

Image

图 D.5:MT1335WE 钻孔点

Image

图 D.6:MT1335WE 键合线

缓慢旋转钻头,施加较小的压力,直到钻透封装并触及到键合线,而在此过程中,PC 反复尝试重写 SPI Flash 内容。这个过程一开始会失败,因为钻头尚未拉高 !WP 线,但最终钻头会接触到键合线,SPI Flash 就解锁了!

sQuallen 还提到了一种利用点燃打火机的压电火花攻击,将其放置在键合线附近。据我所知,这不是为了执行初始解锁,而是为了将高阻抗输入引脚“漂移”回高电压状态。这允许在切断键合线后进行重新编程,而无需进一步钻孔。

D.5 Xilinx XCKU040 背面激光注入

Lohrke 等人(2018)描述了一种针对翻转芯片封装的 Xilinx XCKU040-1FBVA676 的红外激光刺激攻击,这是一种具有加密比特流的 FPGA。

这个 20 纳米芯片的背面在封装上暴露,且芯片的基板对红外光透明。这意味着可以从外部对芯片芯片进行拍照,非侵入性地进行!如果你想在不解封的情况下拍摄自己的背面照片,请参考 Huang(2022)中的设备列表。

XCKU040 是一个 FPGA,其位流在启动时从外部存储芯片加载。为了保护该位流不被复制或逆向工程,它使用一个密钥进行加密,该密钥保存在电池备份的 SRAM(BBRAM)中或在 eFuse 中。BBRAM 的缺点是需要备份电池,但它提供了一些额外的安全性,因为破坏备份电源的侵入性攻击也会摧毁密钥,防止其恢复。

因此,意识到硅背面暴露并对红外光透明,Lohrke 使用红外激光照射电池备份区域中的 SRAM 单元,并在每个点上绘制功耗图。果然,CMOS 功耗泄漏突出了每个比特单元,在一种方向上为 1,而在相反的方向上为 0,揭示了关键!

第三十章:E 更多故障注入

E.1 Java Card 无效字节码

Java Card 是一个简化版的 Java,旨在运行于微控制器和智能卡上。它是那种只能在九十年代发明的疯狂设备,允许 Java 开发固件小程序。在这里,我们将讨论 Mostowski 和 Poll(2008)以及其他地方描述的类型混淆问题,以及 Barbu、Thiebeauld 和 Guerin(2010)在此方案中绕过保护的一种方法。

实现这个操作需要做很多权衡。在 Java Card 小程序中,你会发现使用原始数据类型的情况比普通 Java 软件更多。可用的库有限,你必须通过调用硬件加速库来进行加密,而不是完全通过软件实现自己的加密功能。

Java Card 3 于 2008 年发布,增加了强制的片上字节码验证(OCBV)。之前的卡片仅仅信任开发者的工作站生成并签名有效的字节码。这意味着任何具有签名权限的人都可以编写非法字节码,将一个类转换成另一个类,然后利用错误解释的类的数据字段转储整个 ROM。

虽然你可能没有签名密钥来提取你想要的卡片密钥,但通常可以从 eBay 购买一张“白卡”,它接受开发者密钥。在这些卡片上,可以利用这种漏洞来转储 JVM ROM,这是攻击锁定卡片时非常有用的工具。

Image

图 E.1:捕获误转指令

我们已经提到过 Java Card 3 修补了这个漏洞,所以接下来我们来讨论一个技巧,可以在运行时执行类型混淆,而不会触发字节码验证器的警告。这个方法最早在 Barbu、Thiebeauld 和 Guerin(2010)中描述过。

这个想法是使用 Java 的try/catch结构,其中非法转换的错误会被捕获,但不会使机器崩溃。可以应用许多漏洞,应用程序将帮助掩盖那些失败的漏洞,直到某个幸运的漏洞成功。

Barbu 提供了图 E.1 中的具体示例,其中SecurityException被悄悄捕获并忽略,但如果转换没有触发异常,则该对象可以重新使用。这个过程会无限循环,除非出现故障注入,因为异常总是会发生,但幸运的故障会跳过异常并允许转换。一旦成功转换,错误类型的对象可以在不触发另一个异常的情况下使用几个小时。

E.2 L11, M2351, LPC55 CrowRBAR

Roth(2019)描述了针对 NuMicro 的 M2351 芯片和 NXP 的 LPC55S69 的故障攻击。这很快被 Results(2020b)跟进,后者描述了这些故障的实际影响。Roth 的论文涉及针对归属单元的电压故障攻击,归属单元定义了内存区域的信任级别。

他首先描述了 ARM 的标准化安全归属单元(SAU)。这是一个外设,用于将内存区域描述为安全、非安全或可调用非安全。某些芯片还支持一个实现定义的归属单元(IDAU),它可能是定制的,而不是继承自 ARM 的标准设计。

他的第一个目标是 Microchip 的 SAM L11,这是首批搭载 TrustZone-M 的微控制器之一。此芯片不包含 SAU,只有一个由启动 ROM 配置的 IDAU,该配置来自闪存中的一行数据。^(1)

故障攻击的目标是读取安全世界中的数据,同时在非安全世界中运行。故障攻击没有触发欠压检测器(BOD)外设,这让他感到担忧,因为该外设应该在电压过低时重置芯片。

因为他还没有获得启动 ROM 的转储,他不得不假设一个合适的目标,而不是通过反汇编来学习正确的时机。他使用了 ChipWhisperer 来揭示安全模式在复位后 2.18 毫秒时首次被设置;这一点表现为功耗的明显差异。然后可以写入一个自定义固件镜像,立即显示在那个时间点附近发生故障的成功与否,从而缩小攻击黑盒目标前的参数范围。

Image

图 E.2:Nuvoton M2351

SAM L11 既可以作为裸片购买,也可以配备密钥和 Trustonic 的 Kinibi-M 商业可信执行环境库。这一变种被称为 SAM L11 KPH,用户只能写入和调试非安全世界的内容。Roth 从 Digikey 购买了一些,并对芯片进行故障攻击,直到 OpenOCD 报告成功读取,之后他可以读取 Knibi 进行逆向工程,甚至替换它以进行供应链攻击。

Roth 的第二个目标是 Nuvoton M2351。与 SAM L11 不同,这款芯片包含了 SAU 和固定的 IDAU。其市场营销明确宣传防止电压故障攻击的防护。

他最初认为对这款芯片进行故障攻击会很简单,因为 SAU 或 IDAU 中更安全的设置将会覆盖另一个。不幸的是,针对他的攻击,这款芯片使用了一条特殊指令,blxnsbxns,用于从安全世界跳转(并链接)到非安全世界。

目标地址的最后一位也会被这些指令检查。安全代码指针是奇数,在旧的芯片中,这意味着使用 Thumb 指令集。当安全世界希望调用非安全世界时,必须首先清除指针的一位,以使其与这些指令兼容。

因此,最小的攻击方式可能是首先故障攻击设置 SAU->CTRL=1 的指令,然后故障攻击在 blxns 之前清除位,以便使正常世界的代码在安全世界的上下文中运行。这是可行的,但要做到稳定非常困难。

Roth 针对该芯片的更好攻击叫做 CrowRBAR。其思路是 IDAU 将每个区域映射两次,第一次映射为安全区域,第二次在不同的位置映射为非安全区域。位 28 区分镜像,安全映射时该位为 1,非安全映射时该位为 0。SAU 的RBAR寄存器描述非安全区域的起始位置,如果该值为零,整个区域都将是非安全的。

故障攻击RBAR寄存器的写入大约需要三十秒,暴露整个区域到非安全世界!Roth 无法在这种状态下读取 SAU 寄存器,无法准确知道故障的具体影响,但他能够读取整个闪存中的代码,暴露给非安全世界。

Roth 还考虑了 NXP 的 LPC55S69,其布局与 M2351 非常相似。与 M2351 相比,这个目标的复杂性在于MISC_CTRL_REG寄存器的ENABLE_SECURE_CHECKING字段,该字段检查归属单元的安全状态是否与内存保护检查器(MPC)的状态匹配。这也可以通过故障攻击来绕过,但需要多个故障。

尽管 Roth 主要关注的是提升这些芯片中到安全世界的特权升级,Results(2020b)描述了针对 M2351 的 ROM 库(MKROM)中密码学函数的三种攻击。这些攻击依赖于这样一个事实,即非安全代码可以在调用 ROM 之前暴露一个 GPIO 引脚的时序,因此故障攻击者能非常准确地预测时序,且漂移非常小。

第一个攻击通过跳过XAES_SetKey()将 AES 密钥设置为零,将时序提前 2.5 微秒。第二个攻击通过将XAES_SetDMATransfer()的输出值设为零来制造故障。

你经常会听到说 AES128 或其他算法在跳过某些轮次时容易受到密码分析攻击,而在我年轻的时候,我曾经好奇这种情况到底有什么用。Limited Results 的第三个攻击方法通过故障跳过最后一轮 AES。将两个故障的密文输入到 Philippe Teuwen 的 PhoenixAES 工具中进行差分故障分析,可以揭示出K[10],从中可以提取出整个密钥调度,包括原始的 AES 密钥K[00]。

E.3 68HC705 和 6805

摩托罗拉的 68HC705 是一款早期的 6800 微控制器,内置 EEPROM,通过一个选项位保护防止读取,但可以通过故障攻击绕过该保护。6805 与之相关,但它具有一个可以拍照的掩模 ROM,以及一个可以电气方式转储的测试模式。

Pemberton(2022)是一款基于 Arduino Mega2560 和 Altera MAX7000S CPLD 的定制故障攻击工具,后者因其方便与老旧微控制器配合使用的 5V I/O 引脚而被选择。他的 CPLD 在故障攻击目标供电电压和 2 MHz 时钟时提供 32 MHz(31.25 ns)分辨率。

电源故障通过一个或四个 2N7000 FETs 施加,并且 5V 轨的供电电流通过一个 10Ω至 220Ω的电阻限制。

彭伯顿使用了摩托罗拉(1995)作为启动 ROM 源代码的便捷来源,但他承认他并没有选择目标指令,而是通过强行尝试时序来完成。他描述了一种巧妙的技巧,在将芯片从复位状态中拉出之前,使看门狗定时器过期。这样,看门狗中断就不会干扰周期计数的规律性。

对于 68HC705 带 EEPROM 的版本和带掩膜 ROM 的旧款 MC6805 芯片,都有一个未公开的测试模式来转储内存。瑞德尔(2016)主要是通过摄影提取 ROM,但也包含了这个电气提取的描述:

我通过非用户模式(NUM)引脚成功地电子方式转储了 ROM。我在 EXTAL 引脚上使用了 1 MHz 的时钟,XTAL 接地,!RST、!INT 和 TIMER 引脚接高电平,NUM 接+5V。我将 Port A 引脚通过八个 1K 电阻接到+5V 和地,将其设置为0x9D,即nop的操作码,并将 Port C.3 接高电平。ROM 的内容通过 Port B 输出,我使用逻辑分析仪捕获了这些字节。

图片

图 E.3:68HC705C8A

瑞德尔的页面描述了当 MC68705P5 没有被加密时,基于 EEPROM 的电气转储,这个过程与上面相同,只是 Port C.0 通过 1K 电阻拉到七伏。MC68705P3 和 ST 微电子的 EF6805U3 是相同的,只是它们不支持防止电气转储的加密。他提到,转储通常从复位向量的目标地址开始,而不是从地址零开始。

请不要将他的方法与自检模式混淆,自检模式用于转储内存的校验和,而不是其内容。它位于 MC6805P2 ROM 的0x784处,通过将九伏电压加到 TIMER 引脚来激活,从而将中断向量表向上偏移八个字节。连接到 Port C 的 LED 在校验和失败时会闪烁。

图片

图 E.4:Game Boy Color CPU

图片

图 E.5:来自 Sideris(2009a)的 Game Boy Color Shellcode

E.4 超级 Game Boy 与 GB Color

虽然 Game Boy(DMG)的 ROM 可以通过摄影读取,就像我们在第二十三章中看到的那样,超级 Game Boy 和 Game Boy Color 的 ROM 则有一些位无法从表面直接看到。或许通过 Dash 蚀刻可以暴露这些位,但电压故障使得这一切变得不再必要。

在 Sideris(2009a)和 Sideris(2009b)中描述的技巧是对 ROM 的最后一条指令进行电压故障,从而禁用 ROM 访问,直到下次重启。通过跳过这条指令,已编程的闪存卡带可以自由地从内存中读取代码,转储 ROM 内容。

Sideris 通过让 FPGA 替代 CPU 的时钟和卡带来制造故障。它以正常速率计数时钟周期,直到执行0x00FE处的锁定指令,然后停止时钟并切断电源几秒钟,以清除芯片的一些状态。希望内部 ROM 不会被禁用,并且 CPU 将在稍后的地址处恢复运行,地址位于卡带内存中某个位置。

在故障成功后,卡带 ROM 会执行一长串 nop 指令,进入图 E.5 中的 shellcode。该 shellcode 会读取所有内存,并在每次读取到内存中的字节x时,将其写入0xA100|x。这些写入会被静默忽略,但由 FPGA 生成的访问日志会按顺序记录控制台内存中的每个字节。

Super Game Boy 将其 ROM 从0x0000映射到0x00FF,与 Game Boy 一样。Game Boy Color 有一个 3kB 的 ROM,该 ROM 被映射到这个区域,并且还映射到0x02000x08FF的范围,这与卡带 ROM 重叠,但为卡带 ROM 头部留出了一个从0x01000x01FF的空隙。shellcode 必须从这个空隙中运行,或者在0x0900之后运行。

E.5 STM32F2 Chip.Fail 与 Kraken

Roth、Datko 和 Nedospasov(2019)描述了 STM32F2 引导 ROM 的故障,利用该故障可以从 RDP Level 2(完全保护)降级到 Level 1,在 Level 1 下闪存被保护但 SRAM 没有被保护。通过扩展此故障并进行第二次故障,Uncredited(2020)展示了从一个完全锁定的芯片中提取固件。

其他细节中,Roth 指出,最好是与复位引脚的上升沿计时,而不是电源的接通。用于电源分析的旁路电阻显示,包含保护模式的选项字节作为第一个可见的电源尖峰。^(2)

使用 FPGA 和 MAX4619 模拟开关,他们成功地将 STM32F2 故障降级至 RDP Level 1,延迟为 17,900 个周期,并在 100MHz 时产生 50 个周期的脉冲。RDP Level 1 不暴露闪存,但早期版本的 Trezor 加密货币钱包将密钥材料转移到 SRAM 中,从而可以通过精确计时提取出来。Grand(2022)描述了如何利用这一攻击方法对旧版加密货币钱包进行攻击,以记录本来丢失的内容,因为这些设备在保险箱中被遗忘,更新没有部署到这些设备上。

像第 D.3 章中的 RDP 降级一样,这种故障也可以用于后续提取需要 RDP Level 1 的 STM32 漏洞内存,例如第二章中的漏洞。

Uncredited(2020)首先重现了 Roth、Datko 和 Nedospasov(2019)的 RDP 降级故障。像 Roth 一样,他未能找到使芯片完全降级到 Level 0 的故障,他的兴趣在于提取仅存储在闪存中并未复制到 SRAM 中的秘密。为了实现这一目标,他开始了一些观察。

首先,他指出,在复位后大约 170 µs 进行故障攻击将启用 STM32F205 的 JTAG 和 SWD。复位后 180 µs 进行故障攻击将重新启用引导程序 ROM。JTAG/SWD 和 ROM 的行为就像处于 RDP Level 1 中一样,但有一个关键区别:当访问尝试发生时,JTAG 和 SWD 会在硬件中禁用对闪存的访问,而引导程序则通过命令处理程序内执行的软件检查来禁止访问。

这意味着,您可以通过在启动时首先进行故障攻击,将芯片降至 RDP Level 1,开始引导程序会话,然后在执行读取内存命令处理程序时进行第二次故障攻击,从而转储被锁定芯片的闪存。

E.6 STM8 引导程序和 SWIM

STM8 系列 8 位微控制器广泛应用于汽车防盗系统和其他有用的目标中。芯片的锁保护形式为代码读取保护(CRP)位,由引导程序进行检查。

还有一个掉电重置(BOR)功能,当电压低于阈值时,它会重置芯片。BOR 并不完全是一个故障防御机制,但它可能要求任何故障攻击都必须非常短且精确,以避免不必要的重置。

Herrewegen 等(2020)在第四部分中描述了一种针对 STM8L152 和 STM8AF6266 的双重故障攻击。第一次故障攻击使对 0x8000 的读取出错,使引导程序误以为芯片是空的,从而启动引导程序而不是应用程序。第二次故障攻击使对 0x4800 的读取出错,使芯片误以为 CRP 没有启用。

Image

图 E.6:STM8L152

对这两个目标进行故障攻击很困难,因为没有反馈机制让你知道是否有一次故障攻击的时机正确,直到两次都成功故障攻击。在被锁定的芯片中,无法区分一次差点成功的故障与一次完全失败的故障。为了解决这个问题,他们修补了引导程序,使其从闪存运行,从而允许在转向复杂的双重故障攻击之前进行部分反馈实验。

比引导程序更容易故障攻击的目标是 SWIM 调试接口,这是 STM8 的 JTAG 等效接口。在 Fritsch(2020)中,STM8S103 在复位后通过一次故障攻击成功进入了未受保护的 SWIM 会话。Rainier(2022)最近也成功复制了这一结果,仅使用了一对高速 LMC555 定时器!两者都报告了通过用非常短的脉冲将 VCAP 引脚接地进行故障攻击时的成功。

E.7 STM32F1/F3 故障形状调整

Bozzato、Focardi 和 Palmarini(2019)报告了针对 STM32 的两次故障攻击,在这些攻击中,作者使用信号发生器来控制每个电压故障的形状。

针对 STM32F1 系列,他们报告了通过故障注入“读取内存”命令,绕过引导加载程序的读取保护检查。成功时,这个故障的检查会返回ACK和一块内存。如果不成功,则会迅速返回NAK并且没有内存,但对未来的攻击没有任何惩罚。

对于 STM32F3,他们在复位时执行一个故障操作,将 RDP Level 2 降级到 RDP Level 1,在 Level 2 下不允许使用引导加载程序或 JTAG 连接,而在 Level 1 下则可以有限地使用引导加载程序和 JTAG 访问,芯片也因此容易受到其他攻击。他们注意到故障时序的复杂性,因为启动过程需要一些时间,而目标的时钟会偏离故障发生器的时钟。

但为什么他们选择将故障注入降级到 Level 1,而不是直接降到 Level 0 呢?原因在于,Level 2 在保护配置字中定义为0xCC33,而 Level 0 则是0xAA55,因此将这些值损坏为任何其他值都会导致降级到 Level 1。基于这个原因,将故障注入降到 Level 0 比简单降到 Level 1 要困难得多。

其他 STM32 的故障注入攻击遵循类似的模式。例如,Uncredited(2020)在 Chapter E.5 中执行的读取操作,通过在运行时故障注入保护级别检查,而不是在启动时进行。

E.8 MSP430F5172 每字故障注入

MSP430F5 系列的串行引导加载程序(BSL)需要一个固件的中断向量表(IVT)形式的密码,才能使读取命令生效。一般而言,如果你知道中断表的内容,那你就已经拥有了固件的副本,因此芯片无需进行防御。

故障注入很让人沮丧,因为存储密码比对成功的位会对每个通过 TX 数据块命令读取的字节进行检查,但 Bozzato、Focardi 和 Palmarini(2019)记录了一个成功的攻击案例,该攻击可以转储单个字节。这个攻击一旦校准完成,速度非常快,几乎是每分钟两千字节。

作者们还将此攻击实施到了铁电 RAM(FRAM)设备 MSP430FR5725 上。FRAM 是闪存的潜在替代品,但由于在最低级别时位错误频繁出现,因此它包括一个 ECC 机制来修正预期的位错误,从而使不可靠的内存看起来非常可靠。他们指出,这种错误修正使得攻击变得更加缓慢,大约每六分钟才能读取一千字节。

E.9 CC2640 CC2652 电子熔丝

Wouters、Gielichs 和 Preneel(2022)描述了对德州仪器 SimpleLink 系列中 CC2640R2F 和 CC2652R1F 2.4GHz 无线微控制器的故障注入攻击。他们的商业目标是特斯拉 Model 3 的钥匙扣,后者使用了 CC2640。

通过反向工程启动加载程序 ROM 的转储,他们识别出了两个有效的故障目标,分别是从客户配置(CCFG)和工厂配置(FCFG)eFuse 页面提取的设置。为了便于实验,他们构建了一个不依赖硬件的 ROM 仿真器。

他们首先通过在一个人工目标程序中故障一个紧凑的循环,表征了触发故障但不崩溃的故障宽度,从而让他们可以暂时搁置故障偏移的问题。CC2640R2F(Cortex M3)在 100 纳秒时最易发生故障,而 CC2652R1F(Cortex M4)则在较长的 610 纳秒时最易发生故障。他们将这种差异归因于微架构的不同。

客户配置(CCFG)

第一个故障目标是客户配置(CCFG)eFuse 解析,其中 ROM 读取CCFG:CCFG_TAP_DAP_x寄存器以了解哪些 JTAG 功能将被启用。通过对具有有效固件和无效固件的芯片之间功耗差异的侧信道分析,估算了 ROM 解析 CCFG 位的“最后时刻”。潜在的故障目标时间是从该偏移量反向探索的。

图片

图 E.7:德州仪器 CC2640

在这里他们遇到了一些困难:每次故障尝试可能会启用 JTAG,但 JTAG 速度较慢,他们只能每 2.5 秒尝试一次故障!为了加速,他们编写了一个小程序,将JTAGCFG寄存器的状态输出到 UART。这使得可以在没有等待 JTAG 连接的情况下,以每秒十次的速度对测试芯片进行故障时序尝试。经过表征后,从测试芯片得出的故障偏移可以用于真实目标芯片。

在 200 MHz ChipWhisperer 周期中,经过复位后的故障偏移,成功的 CCFG 故障以启用 JTAG 的偏移量在 CC2640R2F 之间为 188,300 到 188,4000 周期,成功率为 5%。CC2652R1F 在复位后的 161,700 到 162,000 周期之间发生故障,成功率为 1%。

工厂测试模式(FCFG)

到这时,两个芯片的成功故障已经确认,但它们比较慢。一个更好的目标出现了,它是一个未记录的工厂测试模式,在启动过程的早期触发,由工厂配置(FCFG)eFuse 引发。

如果你记得故障 CCFG 的原理限制是检测到开放的 JTAG 连接,那么你可能会希望找到其他信号来确认故障是否成功。最理想的信号就是 GPIO 引脚,这正是通过反向工程 ROM 中的早期检查发现的。

检查 GPIO 引脚状态允许每秒进行一百次尝试,比 UART 指示的速度快十倍。因为指示的代码存在于 ROM 中,它不仅适用于实践尝试,还能在面对未知固件的真实目标时正常工作!

成功的故障攻击会将 GPIO 引脚 23 置高。CC2640R2F 在重置后的 161,100 到 161,200 次时钟周期之间进入这个状态,故障宽度为 115ns,成功率为 10%。这一过程不到一秒钟!CC2652R1F 则在 129,700 到 129,900 时钟周期之间发生故障,但与之前 610ns 的故障宽度相比并没有改善。其成功率为 0.1%,使得他们能够在不到几秒钟的时间内启用所有调试功能。

E.10 LC87 通过 USB 解开循环

本书中我最喜欢的资料来源之一是 Scott(2016)。她描述了针对 Wacom CTE-450 平板中 Sanyo/ONsemi LC871W32 微控制器的 USB GET_DESCRIPTOR 请求的故障攻击。她的文章非常有趣,最后成功使用平板的扫描线和纯软件的内存损坏漏洞读取了一个 125 kHz 的 RFID 标签。对于本书的目的,我将重点讨论她最初通过故障攻击其 USB 处理程序提取设备掩模 ROM 的过程。

LC87 是一款 8 位微控制器,销售量非常大,并且不支持业余爱好者或低容量使用。对于这些手写板,Wacom 最初使用了该芯片的闪存变种,后来改用了掩模 ROM 变种。

当她第一次接触到这款平板时,LC87 的调试端口拒绝了所有连接,而且没有串行启动加载程序,因此 USB 成为了她进行内存损坏攻击的最佳选择。

当时,关于 USB 故障攻击的公开资料非常少,因此她设计了 FaceWhisperer,这是 Colin O’Flynn 的 Chipwhisperer 的一个扩展。^(4) 像我的 Facedancer 板一样,她使用了 Maxim MAX3241E USB 控制器,但她还提供了一个 12MHz 的时钟输出和一个带可调电压阈值的故障触发输入。

虽然在 USB 中定时故障比在 UART 启动加载程序中更难,但确实存在所有 USB 设备都实现的通用命令。她并没有针对 Wacom 协议中的独特部分,而是针对所有 USB 设备都实现的通用 GET_DESCRIPTOR 处理程序。它返回一个定义设备提供的接口和端点的结构体。虽然这个结构体可以动态生成,但许多设备只是将其静态副本存储在代码内存中,并在请求时返回。

在平板的情况下,USB 配置描述符长度为 34 字节,并且在一个数据包中返回。一次成功的交易看起来大概是这样的。

Image

当时机恰到好处时,故障可以破坏传输的长度,导致更多字节被返回。这个例子显示了 268 字节,其中 234 字节位于 真实描述符的 34 字节之后。经过几次类似时机的故障后,她最终碰巧成功获取了一个 65,534 字节的交易,其中包括了所有 32kB 的掩模 ROM!

Image

在提取 ROM 后,她进行了逆向工程,找到了一个未文档化的后门,一个人机接口设备(HID)请求,可以将 16 个字节写入任意地址的 SRAM 中。虽然在该平台上 RAM 不可执行,但这足以让她加载并执行 ROP 链,从而实现任意行为。

通过一点模拟魔法和大量经验,她能够以正确的方式脉冲平板的感应线,既为 EM4100 RFID 标签供电又进行读取。这个目标虽然很奇怪,但考虑到她的最终目标没有进行任何硬件修改,实在令人印象深刻。

E.11 78K0 故障校验和

Renesas 78K0 的第一次故障利用在 Bozzato、Focardi 和 Palmarini(2019)中有所描述。他们的利用方式是通过故障 Checksum 和 Verify 命令,使其操作在四个字节上,而不是最小的 256 字节。

后来的攻击在 Herrewegen 等人(2020)中进行了,利用了从逆向工程的 ROM 中获得的知识,以提供更精确的时序,泄露了单个字节。因为每次读取字节时必须绕过完整性检查,所以成功的提取大约需要十个小时,前提是设备已经过校准。

最著名的攻击在 Wouters 等人(2020)中有详细描述,主要是关于现代汽车的德州仪器 DST80 防盗系统。他们没有尝试从防盗芯片中提取固件,而是对丰田 ECU 中的 Renesas 78K0/KC2 芯片进行了故障攻击。^(5)而不是尝试故障 Checksum 或 Read 命令,Wouters 故障了 Set Security 命令。此命令包括一个安全检查,以确保新的安全状态不低于旧的安全状态,绕过此检查可以通过一次成功的故障解锁芯片。

故障参数可以在他们的论文第 105 页中找到,其中一个 16 MHz 目标的安全设置命令从 2.7V 被故障至 0V,宽度为 100 纳秒,偏移量为 596.78 微秒或 818.05 微秒,发生在安全设置消息的第一个位之后。他们认为时序差异来自于保护选择,因为他们的一个目标启用了比另一个更多的保护。

E.12 RX65 引导加载程序故障

Renesas RX65 芯片允许为内存范围设置读取保护,并通过安装 ID 码来实现。范围限制用于防止读取引导加载程序 ROM,而 ID 码则是保护闪存读取的密码。

Julien(2021)描述了一种针对 Renesas RX65N 的电压故障攻击,首先通过逆向工程未文档化的 FINE 协议,该协议包装了文档化的串行通信接口(SCI)协议命令。然后,他移除了目标的去耦电容,并通过 VCL 引脚上的晶体管产生故障,暴露了内部核心电压。他的故障脉冲由一个运行在 180MHz 的 Nucleo-F429L 板产生,源脉冲的宽度不到 100 纳秒。

尽管他最初的故障攻击是在没有获得引导加载程序 ROM 转储的情况下进行的,但该故障使他能够转储内存中的保留区域。大多数区域返回全零,但最终在 0xfe7f-90000xfe7fffff 的范围内找到了引导加载程序 ROM。这个范围有点奇怪,因为它位于一个整数的下面,而不是从整数开始。

E.13 GPLB52X Tamagotchi

许多 Tamagotchi 玩具使用 GPLB52X,这是 General Plus 的一款 LCD 控制器,配有 6502 微处理器和定制掩码 ROM 的应用程序。我们将在这里讨论三种获取远程代码执行并进行固件转储的方法,其中一种技术似乎可以移植到其他具有攻击者控制的 SRAM 缓冲区的 6502 机器上。

Silvanovich (2013a) 描述了一个可靠的软件漏洞利用,针对 Tamatown Tama-Go 玩具中 switch 语句未处理的情况,shellcode 被作为艺术作品加载到 LCD 帧缓冲区中。这个 exploit 非常巧妙,因为她不得不在没有已经获得掩码 ROM 转储的情况下盲目编写它进行逆向工程。

从 第 343 页 的芯片照片开始,她通过查阅 General Plus 的连线文档,直到文档中的焊盘与玩具芯片上的焊盘匹配。这告诉她芯片的型号,并允许她编写 shellcode,但她仍然需要一种方法来执行她的 shellcode。

Image

图 E.8: 通用 Plus GPLB52X

Image

图 E.9: 简化版 GPLB52X 内存映射

执行 shellcode 是一项复杂的任务,因为攻击者只能控制外部 EEPROM 内存。这个外部内存不可直接执行,因此需要等待设备读取外部 EEPROM,然后将其部分数据复制到内部 SRAM 中,后者是可执行的。幸运的是,玩具在外部 EEPROM 中保存了精灵图形,这些图形通过内存映射的帧缓冲区显示在玩具的 LCD 屏幕上。

所以她将带有长 nop sled 的 shellcode 放入 LCD 缓冲区,作为来自外部 EEPROM 的插件图形,然后对 EEPROM 中所有可用的配置字节进行模糊测试,直到 shellcode 运行并转储内部 ROM。获取 ROM 后,她逆向工程,发现 switch() 语句中的解析器漏洞,并编写了一个干净的 exploit,可靠地触发相同的代码执行,且副作用最小。

后来发布的 Tamagotchi Friends 玩具没有支持内存芯片配件或红外通信,但支持一个小型 EEPROM 用于持久数据存储,并支持 NFC 外设。Silvanovich (2014) 描述了一次成功的故障攻击,她成功地将执行重定向到她的 54 字节 shellcode,这段代码从 EEPROM 作为数据复制到 LCD 帧缓冲区。

与本书中许多其他故障攻击尝试跳过特定指令不同,她更倾向于将目标故障得足够严重,以至于程序计数器被破坏。6502 CPU 没有非法指令,并且未使用的大部分内存读取为0x00,在调试器附加时是brk指令,但在其他情况下是nop,形成了一个 nop 滑槽,基本上将其引导到她的 shellcode,如图 E.10 所示。

YKT(2023)中还有一个关于 Mitsubishi M37409M2 的 6502 核心的掉电故障的例子,该例子将从 SRAM 缓冲区运行 shellcode。像 Natalie 的攻击一样,这也使用了具有长 nop 滑槽的 shellcode,并依赖于长时间电源故障随机化程序计数器,而不是尝试故障单个指令。

图片

图 E.10:GPLB52X(6502)Tamagotchi Friends 的 Shellcode

YKT 描述了这样的攻击:

通过电压故障转储了 SC-55mkII 的次级 MCU(Mitsubishi M37409M2)固件。向其 RAM 注入了特洛伊木马,并使用故障来破坏 PC 计数器以执行它成功。

禁用芯片的电源将导致 PC 寄存器损坏为随机值。由于这是一个非常简单的 8 位 MCU,内存占用非常小——仅 8KB——因此非常有可能将 PC 指向 RAM 地址,并在多次重试后执行它。

Silvanovich(2013b)描述了一个测试程序,驻留在 GPLB52X 系列的 ROM 中的0xC000位置。Natalie 在与 Tamagotchi 一起转储时,它就位于应用程序开始之前的0xCC00位置。查看图 E.9 获取内存映射和表 E.1 获取测试程序列表。测试模式由芯片的测试引脚启动,然后通过端口 A 采样程序号。她对程序0314特别感兴趣。

程序03是一个 ROM 校验和例程。默认情况下,当端口 B 未设置时,校验和覆盖整个 ROM。设置端口 B 允许钟区域被时钟输入,但遗憾的是,对于转储单个字节来说,这不是可利用的。范围必须至少为 255,并且 ROM 中的一个错误在事务后将端口 B 保留在输入模式,因此在选择了有限范围时,无法读取校验和。

00 睡眠模式?
01 RAM 测试
02 压力测试
03 ROM 校验和
04 LCD 测试
05 未知
06 端口压力测试
07 定时器中断测试
08 另一个 LCD 测试
09 未知
0A 未知
0B 类似于09
0C 类似于00
0D 类似于04
0E 未知
0F SPI 测试
10 未知
11 LCD 测试
12 类似于16
13 ROM 校验和
14 代码执行!
15 中断测试?
16 跳转到0x0200的 RAM
17 设置0x300b0x300c

表 E.1:来自 Silvanovich(2013b)的 GPLB52X 测试代码

程序14更为有用。它通过端口 B.7 接收程序的各个比特,每次一个比特,同时端口 2 和 4 上的比特信号表示下一个比特是否已准备好。该程序从0x0200加载到0x05ff,然后在加载完最后一个比特后直接执行。图 E.11 列出了该程序处理程序。

图片

图 E.11:GeneralPlus 测试程序14

E.14 MC9S12 复位故障

像 Freescale 的 MC9S12 芯片这样的 HCS12 芯片在汽车 ECU 中非常流行。它们经常被汽车芯片调优行业破解,用于调整燃油喷射发动机的空燃比。

Stephen Chavez 和 Specter 在 Chavez 和 Specter(2017)中提供了一些破解提示,从私人通信中我确认他们通过给复位线施加一个非常短的脉冲将其拉高,从而混淆 HCS12 复位状态机,成功转储了芯片。

VVDI Prog 是一款商业芯片编程器,其特殊功能是内置对多种汽车微控制器进行内存提取攻击的支持,用于性能调优或密钥复制。从版本 4.9.5 开始,它宣传针对 MC68HC(9)08、MC68HC(9)12 和 MC9S12 系列的一些成员进行攻击。

E.15 Nvidia Tegra X2

虽然 Tegra X1 在 Nintendo Switch 中的应用广为人知,但 X2 则出现在更昂贵的设备中,如自动驾驶单元和现代汽车中的信息娱乐系统。关于 X2 的电压故障注入在 Bittner 等人(2021 年)的研究中有描述。

X2 的引导分为三个阶段:(1)iROM 从掩码 ROM 运行,解密并验证(2)Nvidia 的 MB1 引导程序的签名,从 eMMC 加载,接着运行(3)OEM 的 MB2 引导程序从 eMMC 加载。MB1 是加密的,其签名密钥由 Nvidia 严格保护,但 MB2 可以通过开发工具包自由修改。

Bittner 的第一个挑战是编写一个 MB2 镜像,用于转储 iROM 以进行逆向工程。这得到了泄露的 X1 BootROM 源代码的帮助,该源代码周期性地在线出现,然后在一阵 DMCA 通知后消失。

图片

图 E.12:X2 UART 引导程序中的保险丝检查

逆向工程 iROM 揭示了该芯片支持“故障分析模式”,在该模式下,一个提示会被发送到 UART,然后代码通过该 UART 接收并执行。这个模式通过引导过程早期的保险丝检查来选择,因此保险丝检查是一个很好的故障注入目标。复位引脚可用作故障时序的触发信号,UART 提示的出现表示故障成功。

对于故障注入本身,Bittner 使用了 IRF8736 MOSFET 来故障 X2 的电压轨,通过 MAX4619 电平转换器控制 MOSFET,通过 FPGA 的 GPIO 引脚进行控制。故障的目标大致是图 E.12 中的代码,第 3 行或第 11 行是故障指令的良好候选。

通过 UART 引导加载程序执行代码后,他们加载了利用 X2 内部密钥解密 MB1 引导加载程序的 shellcode。

E.16 Zynq 7000 ROM 转储故障

Xilinx 的 Zynq 系列将 ARM CPU 与 Xilinx 7 系列 FPGA 结合在一起。它们通常出现在实验室设备、比特币挖矿设备以及任何需要在单一包装中集成 Linux 机器和 FPGA 的地方。该芯片从外部存储器中的签名镜像启动,例如 SPI 闪存芯片或 SD 卡。

Zynq 引导 ROM 支持签名和加密的固件镜像,这使其成为软件漏洞攻击的主要目标,但在控制权交给应用程序之前,ROM 的访问是被禁用的。这使得即使从解锁的开发工具包中,也很难读取 ROM。

Schretlen (2021b)描述了一种故障注入技术,用于转储引导 ROM。它要求将 PLL_DISABLE 引脚固定,并且需要用 SOT23 FET 替换一些去耦电容。当通过触发目标的复位信号时,时序过于不可预测,而 SD 卡自身的时序也太嘈杂,无法用作启动触发器。

解决方案是在从 SD 卡返回 Zynq 的最后一个字节之后触发。作者指出,SPI 闪存引导方法可能更具确定性,但所需的引脚在现有的开发板上并没有被引出。

当没有其他选项时,使用故障注入提取 ROM 是一种不错的方法,这也是首次提取该 ROM 时使用的方法。在获取 ROM 并进行逆向工程后,一个常见的目标是找到一种软件漏洞,允许在不使用故障注入的情况下提取 ROM。有关这种漏洞的更多信息,请参见章节 A.10。

E.17 STM32 体偏压注入

体偏压注入(BBI)攻击最早在 Maurine 等人(2012)中提出,作为一种通过局部提升微芯片背面电压来诱发故障的方法。这需要暴露芯片的背面,然后使用探针沿着背面探索任何特定攻击的最佳注入点。

尽管它比电压故障注入需要更多的设备和准备,但它有诱发局部故障的优势。这些故障仅限于芯片的某一区域,其他部分则能够正常工作。

O'Flynn (2020b)描述了针对 STM32-F415 的实际攻击,该芯片采用晶圆级芯片尺寸封装(WLCSP),自然暴露了芯片背面。回顾第十八章,WLCSP 通过将 BGA 焊球直接放置在芯片上,并将其焊接到电路板上,而无需任何塑料封装。这大大缩短了准备时间,因为不需要化学或机械地去除设备封装。

他使用了一种名为 ChipJabber BBI 的定制探头,位于 ChipWhisperer 的末端。每当 CW 故障触发时,两个电容器产生的低电压脉冲通过变压器发送到探头,进而将高电压脉冲输入到芯片背面的探头。电源由具有限流能力的实验台电源提供。请参见图 E.13。

O’Flynn 使用了一个三轴电动舞台和一个弹簧加载探头,扫描了 WLCSP 封装表面上的 256 个独特点。在这些封装中,表面层朝下,接触电路板,而背面则暴露在电路板之外供探头接触。某些封装的背面有一层薄而不透明的涂层,但可以用刀刮掉这层涂层。

图片

图 E.13: ChipJabber BBI 原理图

图片

图 E.14: 来自 Balda (2021) 的 STM32F103 偏置点

该变压器是专门绕制的,使用商业磁性铁芯,初级绕组为 26 AWG 磁性线绕 6 圈,次级绕组为 30 AWS 线绕 60 圈。绕组数较少时,电感较低,这对于快速反应时间是必要的。更多的绕组会减慢斜率并延长脉冲持续时间。

在故障方面,他更感兴趣的是为研究体偏技术提供一个方便的目标,而不是破解任何特定设备的读取保护。他的示例包括用于表征的嵌套循环,经典的 RSA-CRT 故障攻击以及对硬件 AES 加速器故障的初步表征。

由于 O’Flynn 的优秀论文已将 STM32 设置为目标,但止步于未进行内存提取利用,因此提供了第二篇论文的机会。Balda(2021)提供了这一论文,复现了针对 STM32F103 微控制器的工作,旨在提取锁定的固件。

他的 STM32F103 是一个线焊 BGA 封装,其中芯片的正面朝上,背面朝下接触电路板。这比 WLCSP 封装不太方便,但幸运的是,BGA 封装的中心引脚并不需要用于引导程序。Balda 慢慢磨去 PCB、电焊墩和 BGA 封装的底部,以暴露芯片。与芯片接触的铜垫在磨掉部分后,用手术刀将其从芯片上剥离。

这款芯片只有一个 RDP 级别,正如我们在第十一章中看到的,Balda 选择通过引导程序攻击它,而不是通过 JTAG。每次读请求发送到引导程序时为0x11 0xEE,BBI 故障注入有机会绕过设备的 RDP 检查并允许读取继续进行。

Balda 指出,成功的 RDP 绕过故障注入发生在引导程序读命令的最后上升沿后 8.95 微秒。每次读取内存时都必须执行此故障,但 60% 的成功率保证了操作的快速进行。

绘制这些故障的成功位置生成了图 E.14,显示在这些电压下,有效的故障几乎都发生在闪存附近。没有任何故障针对 CPU,Balda 假设这是因为 ROM 启动加载器从闪存的 FLASH_OBR 寄存器读取,该寄存器保存了 RDP 状态的单个位。

在命令最后一个上升沿之后 3.5 µs 的故障产生了不同且不希望出现的效果,导致所有闪存被大规模擦除,并摧毁了可能被恢复的信息。像这样的效果说明了为什么必须小心校准故障,而不是采用“漫天撒网”的策略,并将设备无人看管地放在柜子里运行。

E.18 PCF7941 擦除

NXP 有一系列实现为 RISC 微控制器的无线安全应答器。其中之一 PCF7941 已成功被干扰,用于编程替换车钥匙。

在旧金山的一家地下酒吧里,我听说这需要用酒精和干冰将芯片冷却几天,直到 FPGA 能够利用 2Link 调试协议进行解锁。听起来这个攻击使用了单次故障来一次性解锁整个芯片,但我不完全确定描述中的细节。

一些商业工具,如在章节 E.14 中提到的 VVDI Prog,支持 PCF7941\。它们通过有线连接干扰芯片,擦除它以便重新配对。这个故障仅用于允许擦除被锁定的芯片。这些工具似乎不会提取固件,因为它们的客户更关注为新车匹配钥匙。

图片

图 E.15:NXP PCF7941

E.19 没有欠压的 EFM32WG

EFM32WG 是一款来自 Silicon Labs 的小型 ARM Cortex-M 芯片,主要用于智能电表和工业自动化领域。其使用寿命保证至 2026 年。尽管 CPU 本身可能容易受到故障干扰,但该芯片配备了有效的欠压检测(BOD)电路,可以在启动加载器出现故障时重置芯片,从而有效防止攻击。

结果(2021a)描述了使用电磁故障注入(EMFI)干扰芯片的 CPU 区域,允许读取受保护的固件而不引发欠压。这是因为常规的电压故障会在引入任何故障之前可靠地触发四个欠压检测器(BOD)之一,因此需要使用 EMFI 提供的局部故障注入。

EMFI 系统是一种名为 Der Injektor 的定制系统。尽管我在撰写时该设计尚未公开,但可能在你阅读时已经发布。

Transistor(2023)成功地在博世智能家居系统上复现了这些结果。虽然 Limited Results 构建了定制的 EMFI 工具,但 Vegan Transistor 更喜欢修改一款 Langer BS 06DB-s 脉冲发生器,该脉冲发生器原本用于电快速瞬变(EFT)脉冲测试。

为了确定故障注入的正确时机,分别在锁定状态和解锁状态下追踪电源。通过在去耦电容器附近使用磁场探头进行此操作,并对其进行放大,以补偿芯片的低功耗。故障目标窗口在重置后 150 微秒开始,持续 47 微秒。紧接着,第一条指令开始执行。

故障过强会触发重置,通过稍微回溯直到重置停止,可以识别正确的电源电平。最终,JTAG 解锁,标准的 Segger J-Flash 读取出 128KB 的固件。

E.20 MPC55 通过 EMFI

O'Flynn(2020a)描述了针对 NXP MPC5676R 和 MPC5566 芯片的引导辅助模块(BAM)进行的电磁攻击,这些 PowerPC 设备在汽车 ECU 中非常常见。

从电气角度看,汽车级芯片唯一特殊的地方是它能够在更高的温度下运行。然而从安全角度来看,有一个完整的行业被称为芯片调优,它通过破解这些芯片来提升发动机性能。

值得注意的是,O'Flynn 并没有逆向工程 BAM ROM,因为实施他的攻击并不需要这样做。电源轨闪烁(Power rail glitching)也可能有效,但 EMFI 攻击允许在不将芯片从 2019 年雪佛兰 Silverado 的 ECU 板上移除的情况下进行攻击。无需拆卸去耦电容器或焊接晶体管来进行闪烁攻击。

类似的芯片如 STMicro 的 SPC57xx 和 SPC58xx 也有销售。这些芯片会在将代码缓冲到 SRAM 之后进行权限检查。这显著减慢了故障时序搜索,因为每次故障注入尝试都必须重复完整的传输。O'Flynn 尚未报告成功破解它们。

第三十一章:F 更多测试模式

F.1 8051 外部存储器

McCormac (1996) 和其他九十年代的资料描述了一个用于转储英特尔 8051 的漏洞。这个芯片有一个 !EA 引脚,它将外部存储器映射到启动区域。

该引脚通过仅在复位时采样而未锁存;你可以在软件运行时来回切换它!通过启动到外部 EEPROM,芯片的内存可以被转储,跳转从启动区域到 EEPROM 区域,然后重新启用 ROM 以将其作为数据读取。

一些 8051 衍生品,如 Signetics SCN8051H,仍然存在漏洞。其他芯片则在复位时锁存 !EA 引脚,以防止这种攻击。

Blair (2020) 是一个独立的 8051 芯片转储工具,适用于这个没有锁存引脚的芯片,包括一个 PCB 设计和一个 EEPROM 镜像来执行攻击。他的漏洞利用程序在目标 8051 内部运行,因此 PCB 不需要额外的微控制器。

F.2 TMS320C15,BSMT2000 !MP 引脚

像许多八十年代的芯片一样,TMS320 系列可以作为微控制器从内部 ROM 执行代码,或作为微处理器从外部存储器执行代码。Surply (2015) 主要关注的是 Sega Whitestar 弹球机和可编程阵列逻辑(PAL)的逆向工程,但它包含了对 !MP 引脚的巧妙利用,该引脚在这两种模式之间切换。这是由 FPGA 协调的,提供一个小型存储器,填充了 shellcode,同时在微处理器和微控制器模式之间切换受害者芯片。

Image

图 F.1:来自 Surply (2015) 的 TMS320C15 转储波形

Image

图 F.2:来自 Surply (2015) 的外部 Shellcode

Image

图 F.3:BSMT2000 / TMS320C15

!MP 引脚在复位时没有被锁存,因此你可以在指令执行过程中自由改变它,从而使指令从外部存储器中获取,同时第一个数据参数从内部存储器中获取。

一旦你知道 !MP 引脚没有被锁存,就可以通过切换该引脚并让 FPGA 模拟外部存储器来利用这一点。切换会导致芯片停止执行内部 ROM,并切换到执行 FPGA 的存储器。该引脚可以保持低电平以从外部存储器获取大部分指令,只有在短暂地从内部 ROM 获取指令时才会跳高。

他在 图 F.2 中的 shellcode 相当简单。初始化变量后,一个在地址 5 的无限 while() 循环不断将累加器的值和累加器地址的程序内存值转储到前两个 I/O 端口。代码中没有切换内部和外部存储器的逻辑;这一逻辑由 FPGA 处理,它将这些存储器提供给 TMS320。

Surply 在图 Figure F.1 中的时序图显示,!MP 引脚应该在从地址 5 读取TBLR 0指令后跳高。该引脚在从地址 6 读取下一个指令之前掉低。他指出,这个时序非常紧凑,任何违反它的行为都会导致漏洞失败。

F.3 6500/1 十伏

在 Commodore 收购 MOS Technology 以获得其 6502 技术后不久,他们发布了 6500/1 芯片,这是 6502 的掩模编程变种。6500/1 包含 2KB 的 ROM、64 字节的 RAM 和一些便捷的外设。它还具有测试模式,Brain(2014)中提供了该模式的利用方式。

查阅数据手册,Commodore(1986)将测试模式描述如下:

特殊测试逻辑提供了一种彻底测试 6500/1 的方法。将+10V 信号施加到!RES 线,可以将 6500/1 置于测试模式。在此模式下,所有内存读取操作都从 PC 端口进行。外部测试设备可以利用此功能测试内部 CPU 逻辑和 I/O。程序可以加载到 RAM 中,允许指令 ROM 的内容转储到任何端口,以便进行外部验证。

Brain 的源代码包含了两个用于转储 ROM 的漏洞。他的第一个方法基于 Gerrit Heitsch 和 Greg King 的建议,从 ROM 中直接提取数据,而不强制执行 shellcode。他观察指令读取,直到知道时钟的哪个阶段是操作码读取,然后指示 CPU 将内存位置加载到累加器寄存器中。最后,在 ROM 加载发生的周期中退出测试模式,以便从真实的 ROM 而不是端口PC读取。

他的第二个漏洞更接近数据手册的意图,将这个 shellcode 加载到0x0000的 SRAM 中,然后在非测试模式下执行它,将 ROM 的内容转储到PA0x80),同时在0x82处对PC进行触发,指示数据已准备好。

图片

图 F.4: 来自 Brain(2014)的 6500/1 转储器

图片

图 F.5: Commodore 6500/1

图片

图 F.6: 6500/1 ROM 位

图片

在这两种情况下,AVR 读取每个采样的字节,并通过串行端口将其转发到等待的桌面进行接收。这成功地提取了 Commodore 1520 绘图仪的固件和字体。

除了测试模式外,该芯片的 ROM 也很容易被拍摄。图 Figure F.6 中的示例位是通过使用 HNO[3]进行解封装并使用稀释的 HF 进行去层后看到的。

F.4 TMP90 外部内存

Galiano (2023) 是一个完全功能的漏洞利用,适用于来自东芝的 TLCS-90 系列 Z80 微控制器。示例包括 TMP90C840AN 和 TMP90CM40AN,以及来自相关的 TLCS-900 系列的 TMP91C640N 等芯片。该漏洞利用依赖于不可屏蔽中断(NMI)引脚,因此并不适用于整个系列;TMP90C844AN、TMP91-C642AN 和 TMP90CH44N 并不易受攻击。

Galiano 从 EA 引脚开始,EA 引脚控制芯片是从内部 ROM 启动还是从外部内存启动。然而,启动外部内存并转储 ROM 并不像看起来那么简单。EA 引脚只在复位时采样,它在选择从外部内存启动的同时禁用内部 ROM。

他的漏洞利用从外部 EEPROM 启动。然后,他利用 Z80 芯片在设置调用堆栈时的一个技巧,再次从这个 EEPROM 执行,同时内部 ROM 仍然启用并作为默认启动目标。

像 TLCS-90 系列的 Z80 芯片在复位时不会硬件重置堆栈指针。相反,通常第一条指令会设置堆栈指针。通过在该指令执行之前触发 NMI,Galiano 在堆栈指针有效之前将执行重定向到 NMI 中断处理程序!

当堆栈指针先前设置为 EEPROM 而不是 SRAM 时,目标芯片将把 AFPC 寄存器保存到位置不佳的堆栈中。AF 的值无关紧要,而此时 PC 可能为 0x9000。这两个值都不会写入 EEPROM,因为 EEPROM 不接受随机写入,所以在从中断处理程序返回时,程序计数器将强制恢复为只读堆栈中的值。

然后,代码可以初始化堆栈指针为 SRAM 中的一个地址,并继续自由读取所有内部 ROM 或 PROM,将其转储到串口,或复制到新的内存芯片中。

F.5 Mostek 3870 (Fairchild F8)

Boris Diplomat、Chess Traveler 以及其他一些 70 年代末期的国际象棋计算机使用了 Fairchild F8 架构的变种,称为 Mostek MK3870。Riddle (2013) 和 Rock (2013) 描述了使用该芯片测试模式的电气转储。

图片

图 F.7: Mostek MK3870

第 16 页的 Mostek (1978) 描述了 TEST 引脚的行为,该引脚根据电压激活不同的测试模式:

在正常操作中,TEST 引脚未连接或连接到 GND。当 TEST 设置为 TTL 电平(2.0V 到 2.6V)时,端口 4 变为内部数据总线的输出,而端口 5 成为内部数据总线的有线 OR 输入。端口 4 引脚上的数据是逻辑上的真,而强制输入到端口 5 的数据必须是逻辑上的假。

当 TEST 引脚置于高电平(6.0V 到 7.0V)时,端口按上述方式工作,另外 2K × 8 程序 ROM 被禁止驱动数据总线。在此模式下,操作数和指令可以通过端口 5 强制外部输入,而不是从程序 ROM 中访问。当 TEST 引脚处于 TTL 状态或高状态时,STROBE 停止其正常功能,变成机器周期时钟(与 F8 多芯片系统的写入时钟相同,但相位反转)。

简单来说,TEST 引脚可以将芯片置于三种可能的状态:1)当 TEST 引脚悬空时为正常执行,2)当 TEST 引脚为 3.5V(TTL 电压)时为启用 ROM,3)当 TEST 引脚为 7V(高电压)时为禁用 ROM。后两种模式均用于测试,区别在于是否允许内部 ROM 驱动数据总线。

为了转储 ROM,Riddle 首先将引脚移至高电压,禁用 ROM,从而可以注入加载指令。当指令执行时,他将引脚降至 TTL 电压,重新启用 ROM,使加载指令能接收到数据。

尽管 Riddle 的原始利用方案使用了 PIC 18F4620 以兼容电压,但 Rock 更倾向于使用 Raspberry Pi Pico 和电平转换器。

从 PIC BASIC Pro 直接移植 Riddle 的利用方案并不可行,因此进行了重大的结构性修改,以更通用地注入代码并回读结果。通过这一点以及少量的错误修正,成功地从 HP82143 打印机中转储了固件而没有损坏。

F.6 MC6801 测试模式

MC6801 微控制器可以从内部或外部 ROM 运行。Lind(2019)是一个开源项目,用于从摩托罗拉 MC6801 电气转储 ROM。

摩托罗拉(1984)描述了测试模式零,其内存映射如图 F.8 所示。从第 2.3 节来看,模式选择有点棘手,但通过复位时的引脚来处理:

MC6801 的工作模式由 RESET 上升沿时引脚 8、9 和 10 的电平控制。然而,这三个引脚也作为端口 2 的最低三位有效位。这些工作模式在 RESET 上升沿被锁存到 MCU 程序控制寄存器中,此后可以移除电平,且引脚可用于其他目的。工作模式可以从端口 2 数据寄存器读取,其中 PCO(引脚 8)、PC1(引脚 9)和 PC2(引脚 10)作为数据位 D5 到 D7 分别出现。

通过选择测试模式 0,Lind 的利用方案强制重置向量从外部 EEPROM 读取,而不是从内部 ROM 读取。此时,代码从外部内存执行,并能够自由读取内部内存。

Image

图 F.8:来自摩托罗拉(1984)的测试模式内存映射

Lind 的 shellcode 是 Daniel Tufvesson 的 MC3 监控程序的一个分支,存储在普通的 EEPROM 中,并使用 GAL16V8 可编程逻辑器件(PLD)来管理复位序列和内存总线。在受害者芯片启动监控程序后,可以发送标准监控命令,通过芯片的串行端口转储内部 ROM 的内容。

F.7 NEC uCOM4 测试模式

NEC 的 uCOM4 系列包括具有掩模 ROM 的 4 位微控制器,例如 D552 和 D553。Kevin Horton 和 Sean Riddle 将这些微控制器作为从古董跳棋和国际象棋游戏中恢复 ROM 的一种方法进行研究。

Riddle 的提取技术涉及掩模 ROM 摄影,这种方法非常便携,但解码可能会很费力。他的解码器如图 F.9 所示,揭示了每 128 行中有 16 个页面,每一对页面的顺序相反。Riddle(2023)展示了去层后的 ROM。

Horton(2023)提出的非破坏性方法是电气方法,而不是摄影方法。芯片有一个测试引脚,能够使其停止 CPU 并将位数据转储到 GPIO 引脚,但它仅在 256 字节的内存页面内执行此操作。它在一个循环中执行,所以你会得到所有字节,但你不一定知道它们是如何对齐的。

要电气提取其他页面,您必须单步执行 CPU,直到它跳转到另一个内存页面,然后使用测试引脚转储该页面。在该页面内,它将从程序计数器的值开始转储,因此页面的字节将有一些偏移,需要进行修正。通过识别已知页面中的跳转点并安排跳转,可以转储任何包含可达代码的页面。

Image

图 F.9:Sean Riddle 的 Fabulous Fred 解码器

Image

图 F.10:Fabulous Fred uCOM4 ROM

Image

图 F.11:EMZ1001E 扩散 ROM

F.8 AMI S2000 和 Iskra EMZ1001

南斯拉夫唯一的微控制器——Iskra EMZ1001,是 AMI S2000 系列的一个变种,甚至在芯片上也有 AMI 的标志。与苏联克隆芯片不同,这款似乎是由 AMI 授权并作为合作项目开发的。Zoltan Pekic 在 VHDL 中实现了一个 EMZ1001 的克隆,见 Pekic(2022),并且他慷慨地指引我查阅文档中的测试模式。

这个技巧,见于 AMI(1979)的第 4.9 页,涉及 ROMS 和 SYNC 引脚的交互。解释很简短,如果你没有认真查看以便克隆芯片,容易忽略。ROMS 引脚的描述如下:

ROM 源控制。连接到逻辑 1 或 0,以指示仅内部 ROM,或内部 ROM 加外部 ROM。连接到 SYNC 以通过外部程序覆盖 Bank 0,连接到反转的 SYNC 以验证内部 ROM 内容。

通过反转 SYNC 信号到 ROMS,我们可以将芯片强制进入验证模式。在程序计数器向前计数时,指令解码器将接收虚拟指令,而数据引脚输出内部 ROM 内容。

如果你不耐烦,ROM 位也可见。图 F.11 显示了经过 HF 去层后的 EMZ1001E 微控制器的位。

F.9 TMS1000 测试模式

很难确定给定的专利是否匹配给定的芯片,尤其是在多个专利可能指的是同一芯片的情况下。Caudel 和 Raymond (1974) 是德州仪器 TMS1000 芯片的专利,描述了许多内部信号和用于转储内部 ROM 内容的测试模式。该测试模式未出现在数据手册、程序员参考手册或其他官方文档中。

另一个专利,Boone 和 Cochran (1977),常被引用为 TMS1000 专利。两款芯片都具有 28 个引脚。两款芯片都用于 ROM 编程计算器。TMS1000 显然具有八位的 ROM 宽度。然而,Boone 和 Cochran 的芯片具有像 TMS0801 一样的 11 位指令。有关如何转储该芯片的 ROM 的优秀教程,请参见 Ilmer (2024),其中包括关于确定 ROM 位顺序的详细说明。

Caudel 和 Raymond 的专利中还有一张黑白芯片照片,与 TMS1000 非常相似,并且有一组匹配的操作码。他们专利的第 28 列描述了四个测试模式操作。

操作 1:ROM 字地址通过键盘引脚 K1 在控制键盘引脚 KC 的作用下串行加载到程序计数器中。将 KC 设置为 Vss 会导致在ϕ1 时采样该位,此时程序计数器不被使用。(芯片的时钟被划分为五个信号,其中ϕ1 是第一个。有关详细信息,请参见专利中的图 24。)

现在,如果你正在跟随专利和数据手册,你可能会注意到数据手册中没有 KC 引脚。这可以通过专利和数据手册之间的文档更改来解释。专利中将 KC 与四个键盘输入引脚一起归类为芯片照片中信号 75,如图 F.12 所示,而数据手册则将 INIT 引脚放置在该位置。

Image

图 F.12: Caudel 和 Raymond (1974) 的原型

Image

图 F.13: TMS1000

Image

图 F.14: TMS1000 引脚分配

输入和输出引脚也有不同的标签,因此在进行操作时可能会有一些混淆。

操作 2:ROM 页地址在 K1、K2、K4 和 K8 键盘引脚上并行加载。如果 KC 引脚在时钟相位ϕ2 时为 -Vdd,则四个位将被采样。请注意,这个页地址的并行加载发生在与字地址不同的时钟相位;专利在此建议了一种加速方法,即快速迭代页地址,同时很少调整字地址。

操作 3:通过内部 !BRNCAL 信号,可以将所选 ROM 地址的八位字加载到程序计数器中,该信号是 KC 和 K2 引脚的组合产生的。

操作 4: 从第三个操作中获取的结果可以通过输出引脚串行读取,并由 KC 引脚控制。幸运的是,这种串行传输可以在加载新地址的第一操作之前,且与其同步进行。

我的描述遗憾地缺少一些细节,我怀疑在使用该模式转储芯片之前,我永远也不会完全理解这个测试模式。如果你在我之前实现了它,请发封邮件给我,并附上你的论文副本以及任何我需要在此附录条目中纠正的勘误表。

F.10 Z8 测试 ROM

我无法引用任何现代使用这种测试模式的实例,但许多 Zilog Z8 芯片,如 Z8601 和 Z8611,除了主程序 ROM 之外,还包含一个测试 ROM。这个功能在 Zilog (1982) 中有解释,其中测试 ROM 的目的是测试那些无法通过执行主要测试的外部 EEPROM 代码直接操作的少数功能。

第一个线索来自于芯片的图像,其中 ROM 的大小比预期的要大。例如,Z0860008PSC 的内部 ROM 包含 256 列和 66 行,而不是预期的 64 行。这比数据表中广告的两千字节多了 64 字节。

从 Zilog (1982) 中,我们可以发现这些字节包含类似于 图 F.16 中的测试 ROM。测试 ROM 在 !RST 引脚被拉高 2.5 伏高于 VCC 并保持至少八个时钟周期后,会替代应用程序 ROM,然后可以放松到正常电压。对于 5 伏芯片来说,就是 7.5 伏。

测试 ROM 太小,无法进行大量测试,因此它首先通过 IO 端口映射外部存储器,并调用该外部存储器。接着,它跳转到外部存储器中的 0x0812(或 0x1012)位置,在那里 EEPROM 示例禁用中断并运行测试循环,通常会回调测试 ROM。回调似乎用于测试用于外部存储器访问的 I/O 端口;它们并不像 PC BIOS 调用那样方便。

图片

图 F.15: Zilog Z8611

图片

图 F.16: Z8601 测试 ROM

图片

图 F.17: 进入 Z8601 测试 EEPROM

在测试模式下运行时,lde 指令可以从测试 ROM 获取字节,而 ldc 指令则从应用程序 ROM 获取字。这样,一个简单的循环应该足以转储 ROM,而无需调用回测试 ROM。

这些芯片的 ROM 变种也可以通过照片方式转储。它们使用扩散 ROM,其位在用 HF 去层化后变得可见。

第三十二章:G 更多 ROM 摄影

G.1 TMS320M10, C15, C25, C5x

Caps0ff (2020a) 描述了逆向工程和摄影 TMS320M10 芯片的过程,这些芯片用于 80 年代的 Taoplan 街机游戏,如《飞翔的鲨鱼》和《极限虎》。相同的技术也适用于 M10 的早期继任者,如 TMS320C25。

Caps0ff 还提到之前对 TMS320C15 的研究,它使用了接触式 ROM,而不是扩散式 ROM。该芯片中的位使用了不同的排序方案,而在流行的 BSMT2000 音频芯片中,这些位已通过摄影提取出来,BSMT2000 是 C15 的预编程变种。^(1)

TMS320 的 ROM ID 通常位于型号编号附近,例如他们示例中的 D70015。你可能会问:“哎,为什么我要在这么多年后关心他们的型号编号,毕竟所有记录可能早已丢失?” 好吧,Caps0ff 分享了一个非常巧妙的技巧:在一个具有独特掩模的掩模编程 ROM 中,例如高产量的 TMS320 芯片,ROM 序列号与 ROM 位在同一个掩模上。因此,如果你剥离层以明确序列号,你会明确 ROM 位。它们处于同一层,深度完全相同。

在 M10 中,这就是技巧所在。去除几层以明确序列号时,ROM 位直接显现出来,而之前从表面几乎不可见。

图片

图 G.1:来自 TMS320C15 的 BSMT2000 ROM

Caps0ff (2020b) 描述了逆向工程 TMS320C50 和 TMS320C53 ROM 的过程。他们的真正目标是来自街机的 C53。通过首先从 C50 开发套件中使用调试器提取 ROM 镜像,然后将该文件与 ROM 位的照片进行比较,他们能够知道 C53 中 ROM 位的排序,只剩下银行顺序需要猜测。(C53 有四个银行,而 C50 只有一个。)这个 ROM 格式现在是 Zorrom 支持的众多格式之一。

一些 TMS320 芯片也可以通过滥用其微处理器模式来执行外部存储器,从而提取数据。有关此技巧的详细信息,请参见章节 F.2。

G.2 CH340 未知架构

Cornateanu (2021) 是关于芯片去封装和去层摄影以及 ROM 恢复的通用教程,CH340 USB/串口控制器是其示例目标。顶层金属层隐藏了位,使它们在表面上不可见。

Cornateanu 描述了使用 HF 去层芯片的过程,这去除了顶层金属层以暴露 ROM 位。从他的照片来看,这是一个扩散式 ROM,但骰子非常小,我在自己实验室里复现他的工作时遇到了相当大的困难。

位提取是使用 Rompar 执行的,但由于 CPU 架构(当时及现在)未知,位是通过 Bitviewer 解码为字节,而不是 Zorrom。位顺序是通过查看地址线解码器电路来确定的,然后通过识别 USB 描述符表和字符串来确认。

提取 ROM 后,他知道了内存内容,但仍不清楚 CPU 架构是什么,它是围绕 14 位字构建的奇怪架构。为这种架构编写 IDA Pro 插件仍然在进行中。

G.3 英特尔 8271 新 ISA

Evans(2020 年)描述了英特尔 8271 磁盘控制器掩膜 ROM 的影像转储,该 ROM 包含 864 字节。这个芯片也作为 NEC D765 销售。

这个顺序成功地猜测为从左到右然后从上到下,先 MSBit 后 LSBit,字节由每个 8 位组的一个位构成。位被反转。这使得前几个字节为 fc 06 02 f7。这恰好是正确的,但更难的部分是搞清楚指令集。

逆向工程指令集需要一些关于起始位置的线索。Ken Shirriff 的百科全书式知识提供了帮助。他发现 Louie、Wipfli 和 Ebright(1977 年)的会议演讲介绍了该芯片的设计,包括指令计数和芯片照片。Ken 还发现 Louie 提交了一项专利 US4152761A,描述了该芯片的设计。

凭借这些资源和大量对指令 PLA 位的研究,Evans 成功地逆向工程了大部分指令集,并解析了足够多的 ROM,从而提出了一种写入原始软盘轨道的方法。这使得克隆 BBC Micro 软盘成为可能,尽管为时已晚,无法在盗版中盈利。

Image

图 G.2:英特尔 8271 ROM

G.4 任天堂 64 CIC

与 第二十五章 中描述的任天堂 NES 的 CIC 芯片类似,任天堂 64 使用 SM5 系列的 4 位 Sharp 微控制器来实施授权,因此第三方不能制作自己的游戏。与原始 NES 不同,N64 的 CIC 成功地阻止了在该主机的整个商业生命周期内出现未经授权的卡带。

然而,这并不意味着该方案永远有效。发布十八年后,N64 的 CIC 芯片被两个团队和两种方法独立破解。

Kammerstetter 等人(2014 年)描述了一种逆向工程 CIC 芯片测试模式的技术,允许将一种调试器连接到芯片上,从而能够直接从 ROM 中读取程序。

作为平行工作,Ryan、H 和 McMaster(2015 年)描述了通过 Dash 蚀刻技术获取的掩膜 ROM 转储,在这种方法中,连接点被染色,以表明它们在强光下被 HNO[3]、HF 和 HAc 酸混合物掺杂了几秒钟。由于 Dash 蚀刻的成功率令人沮丧地低,他们购买了大量便宜的运动游戏卡带,并批量去壳提取这些卡带中的 CIC 芯片。

第三十三章:H  未排序攻击

H.1 PIC16C84 PicBuster

McCormac(1996)的第三章描述了早期电视盗版时代的一些固件提取漏洞。特别有趣的是针对 PIC16C84 的技巧,这是第一款包含电擦除 EEPROM 存储器的 PIC,而不是 OTP ROM 或 UV 擦除 EPROM。就像我们在第十九章中看到的 PIC 一样,保护保险丝使用与 EEPROM 位相同的浮动门晶体管。

这个技巧涉及到 VDD 电源电压与 !MCLR 引脚编程电压之间的差异。在正常操作中,VDD 应该小于 7.5V,!MCLR 应该小于 14V,相对于 VSS 地面电压。这种技术对早期芯片无效,因为它们没有电擦除功能。

为了利用 PIC16C84,芯片在错误的电压下进行电擦除。VDD 引脚保持在 13.5V,仅比 VPP 低 0.5V。然后将 VDD 电压降至标准 5V,并关闭 10 到 20 秒,再重新开机,这样可以读取数据。

H.2 PIC 校验和

PIC 微控制器实现了一种校验和,可以泄漏来自锁定芯片的信息,在某些情况下,通过执行第二次编程,你可以清除—但不能设置—位。Kaljević(1997)记录了校验和算法,并介绍了一种将该校验和系数归零的技术,以揭示源程序中的特定位。

在像 PIC16 这样的 14 位模型上,Kaljević 从校验和算法开始,image,其中 a 是 14 位指令字的高 7 位,b 是低 7 位。image 是 XNOR 运算符,∽ 表示反转,⊕ 表示 XOR 运算符。s 可以通过正常的 ICSP 协议自由读取,关键是揭示 ab 中的未知位。

知道 s 后,他指出,通过将字写入 0b11-111110000000 来清零 b,我们将得到 image,即 a 的反转。因此,s = ã ⊕ b = s[1] ⊕ b

然后我们可以声明 b = (ss[1]) & 0x7f,并且 image & 0x7f。从 ss[1] 重构的完整字可以轻松计算出适用于 PIC16C61、62、64、65、71、73、74 和 84 等 14 位 PIC 的字。

image

对于 PIC12 系列中的 12 位并行编程芯片,校验和算法有所不同。在这里,s = abc,其中 a 是指令字的高四位,b 是中间四位,c 是低四位。

与 14 位芯片中的一次写入不同,这里执行了两次写入。在第一次写入 0x0ff0 以清零 c 后,我们看到 s[1] = ab。接着,我们可以进行第二次写入 0x0f00 来清零 b,留下 s[2] = a。将所有内容串联起来,对于具有 ss[1] 和 s[2] 的 12 位芯片,我们的原始指令字将被揭示,其中 a = s[2],b = s[2] ⊕ s[1],c = s[1] ⊕ s

w = (s[2] & 0xf00) + ((s[2] ⊕ s[1]) & 0xf0) + ((s[1] ⊕ s) & 0xf)

至于如何执行写入,这篇论文变得有些难以理解。在 PIC16C71 和 61 型号中,前 64 个内存单元可以直接被覆盖。b被清零,恢复算法可以无歧义地恢复这些字,但其他内存则不能这么容易地写入。

为了编程一个已经被锁定的芯片以清除位,他建议先给芯片加电压,如果失败,再进行过热处理,如果还不够,再暴露在有限的紫外线下。这个电压技巧——可能与 H.1 章中的类似——是将芯片的电压设置在 6 到 9 伏之间,同时严格限制电流为 100mA。如果这仍然无效,他建议保持温度在 110 ^°C,并小心不要超过 140 ^°C。

如果这还不够,他提出通过暴露芯片并调整紫外线光的功率,使得擦除一个 PIC 芯片需要十分钟。然后,在 110 ^°C 的温度下,进行每次 30 秒的紫外线照射,直到保护位被设置,允许写入。接着,0x3f80被写入每个内存单元,芯片逐渐冷却到−20 ^°C。此时,保护位将恢复为零。写入将不再被允许,但来自写入的清除位也会被置为零。s[1] 可以从锁定的芯片中读取出来。

还有一个进一步的技巧仅在 x86 汇编代码中描述,用来写入0b11111111000000,这会将b设置为0x400x00。这给解码留下了一个难题,并提供了一些有用的注释,说明哪两个可能的指令字是正确的猜测。

H.3 ESP32 TOCTOU 用于 XIP

Espressif 的 ESP32 系列支持执行就地(XiP)模式,在该模式下,指令直接从 SPI 闪存中获取,而不是先复制到内部 SRAM 中。这使得应用程序可以使用更多的 RAM,代价是执行速度变慢。

在执行之前,代码会被验证并检查签名,但 Magesh(2023)描述了一种时检查到时使用(TOCTOU)攻击,通过在运行时交换两个 SPI 闪存芯片来攻击签名验证。这使得签名代码能够在未签名代码执行之前成功地进行测量。

Magesh 指出,当启用闪存加密(AES XTS)时,这个技巧不起作用,但他预计攻击者可能通过随机化一个页面,直到找到所需行为,来利用加密图像,同时保持其他页面不变。

H.4 DS5002 选择密文

来自达拉斯半导体的 DS5002 是早期并富有创意的代码读取保护尝试。代码保存在外部存储器中,并被加密,密钥则保存在内部的电池备份 SRAM 中。这对街机游戏修复造成了尴尬的局面,因为现有设备中的电池最终会耗尽。如果没有漏洞,运行游戏所需的代码也会随着电池的死去而消失。

这款芯片的指令集是 8051。加密是逐字节进行的,与其他字节无关,但与该地址是唯一对应的。无论是操作码还是参数,转换方式都是相同的。

除了加密,DS5002 在内存总线可能处于空闲状态时还会执行虚拟读取。这些地址读取的值没有任何实际用途,只是为了迷惑我们。

DS5002 也有封装成带电池备份的密封环氧模块版本。图 H.1 和 H.2 展示了该模块的表面显微镜和 X 光图像。

Kuhn(1996)和 Kuhn(1998)提出了一种针对该芯片的密码学攻击,首先备份外部 SRAM 的副本,然后将猜测的值输入到 CPU 中,观察地址的变化。

例如,你可能猜测某条指令是分支指令。由于地址是加密的,你不能仅凭下一个获取的地址来确定你的猜测是否正确。但如果你改变一个参数字节,几乎每个值都会将地址分支到不同的方向。

攻击的关键在于获取那一点信息,并用它来分裂许多具有已知内容的选定密文字节,从而允许我们执行任意代码。

你还应该了解,每个字节是单独加密的,并且它们不会影响后续字节。我们并不完全知道每个字节将如何被加密,但对于每个特定地址,我们可以构建一个字节表格。该表格是一个唯一的加密字节与明文字节的映射,并且当内存中前一个字节发生变化时,表格不会改变。正如你很快会看到的,我们并不太关心每个字节所在的地址。相反,我们关心的是强制这些字节变成已知值,并构建查找表,从而让我们选择适合特定明文的密文。

Wilhelmsen 和 Kirkegaard(2017)提出了一种更现代的同类攻击实现,由于其写作风格较为通俗易懂,因此更容易理解。他们描述了许多复杂的情况,且数学内容远少于前者。

Image

图 H.1:Dallas DS5002

Image

图 H.2:Dallas DS5002 模块的 X 光图像

许多 8051 指令在获取后需要几个时钟周期才能执行。DS5002 在这段时间内会获取与之无关的指令,以迷惑外部观察者,这使得我之前的描述显得有些过于简化。

此外,中断表保存在内部 SRAM 中,因此攻击者无法知道中断何时被触发。这在重置时尤为重要。

确定第一次真实指令何时被提取是必要的,因为首次观察到的访问可能是虚拟读取。他们通过尝试该地址上的所有 256 个值来实现这一点,如果这些值没有改变随后的内存访问,他们就知道该字节是虚拟的,可以自由忽略。这个过程会重复进行,直到他们识别出第一次真实的指令。

在确定了第一个指令字节的位置之后,他们接下来需要制作一些字节来适应那里。由于 DS5002 在重置时将 Port 3 设置为FF,他们可以通过暴力破解05 b0inc p3)作为前两个指令字节,将 Port 3 反转回00。我指的是暴力破解它;只有 65,536 种组合。

在这一点上,他们已经得到了前两个字节的一个密文/明文映射,但还没有其他的映射,因此不能随意更改它们。为了得到第三个字节的映射,他们暴力破解第一个字节,直到得到75,即mov iram addr, #data的操作码,此时他们可以执行75 b0 xx将所有 256 个明文值写入 Port 3。现在,第三个字节完全破解,尽管对于第一个字节只映射了两个值,对于第二个字节仅映射了一个值。

然后,他们调整第一个字节,直到它变成类似nop的指令,并调整第二个字节,直到它变成75。接着,他们可以像第三个字节那样扫描第四个字节的每个值!重复这一过程,他们能够得到一些 shellcode 字节,并可以将其强制写入芯片,前面是两个不重要的nop字节。

最后,他们插入了一些小的 shellcode。这个代码给出了代码和数据内存之间的边界:

Image

这个用来转储代码:

Image

这个用来转储数据:

Image

这个攻击涉及大量的重置操作,但他们报告仅用了两分钟就通过暴力破解了第一组指令,并且只用了四分钟就转储了 32 千字节的固件。

H.5 SAMA5 CMAC, SPA, 密钥

Janushkevich(2020)描述了 Microchip(前身为 Atmel)SAMA5 系列安全微控制器中的三个漏洞。

该系列包含一个名为 SAM Boot Assistance(SAM-BA)的引导监控程序,允许上传经过认证和加密的小程序并执行。这些小程序通常用作驱动程序,在 RAM 加载模块中实现对新内存设备的支持,以保持引导加载程序的体积较小,同时依赖基于密码的消息认证码(CMAC)认证来保证安全性。

请注意:CMAC 身份验证通常被认为是公钥签名的快速替代方案。当一切顺利时,CMAC 提供的身份验证比公钥签名要快得多。与签名不同,CMAC 可能会出现很大问题,因为 CMAC 依赖于共享的秘密密钥,而任一方都可能泄漏该密钥。可以将其想象成一封信:如果我们使用公钥加密通信,我的签名将保证信件来自于只有我可以访问的密钥,并且只有我才可能将其泄漏给第三方。但如果我们使用 CMAC 来验证我们的信件,你我都可以访问身份验证密钥。我们中的任何一个都可能将该密钥泄漏给第三方。

一些芯片将 SAM-BA 包含在 ROM 中。其他芯片没有 ROM,而是将启动辅助监视器链接到闪存。一个 GPIO 引脚配置引导加载程序入口,SAM-BA 支持通过 UART 和 USB 与主机计算机通信。标准程序是,当配置引脚在复位时为低电平或应用程序的复位向量为 0xffffffff 时,启动加载程序将首先尝试通过 USB 枚举,然后回退到 UART 控制台。

SAM-BA 具有一个华丽的 GUI 客户端和 TCL 脚本库,但对于第一个漏洞,我们将坚持使用 UART 变体的文本协议。Microchip 文档中载入安全小程序的过程包括以下交易,其中 applet.cip 是一个加密并签名的小程序二进制文件,大小为 9,870 字节。

图片

在此过程中,SAPT 命令处理程序将小程序加载到 SRAM 的 0x220000 地址,检查 CMAC 身份验证,并在原地解密小程序。身份验证检查的结果被放置在一个全局变量中。如果 CMAC 错误,后续的 CACK 消息将包含错误代码,并且全局变量将指示身份验证失败。

小程序加载完成后,SMBX 命令用于加载邮件箱。mailbox.bin 既没有加密也没有签名,它被加载到应用程序镜像中的邮件箱区域,地址为 0x22-0004。一个匹配的命令,RMBX,将在执行后检索邮件箱,以支持双向通信。

图片

小程序加载完成后,EAPP 命令可以用来执行小程序并处理邮件箱消息。除了邮件箱之外,SFILRFIL 命令可用于向设备发送或接收文件。

图片

现在我们已经覆盖了教程的基础内容,让我们来看看第一个可利用的漏洞。Janushkevich 首先注意到,RMBX 命令允许即使在邮件箱尚未加载的情况下,也能检索到邮件箱。因为邮件箱和小程序重叠,这使得他可以从内存中读取小程序的部分内容。

然后,他首先尝试了一个已签名、已加密的小程序和一个未签名、未加密的小程序。RMBX返回了第一个小程序的明文部分,显示它在执行前被解密到内存中。未签名的小程序也有部分从邮箱中返回而没有损坏,这意味着当 CMAC 验证失败时,未验证的消息仍然保留在内存中,没有被解密过程混淆。

最后,他尝试了用EAPPSFILRFIL执行小程序。这三者——我不是开玩笑——都无故执行了未加密、未签名的小程序。似乎SAPT命令记录了身份验证失败,但执行小程序的命令并未检查这个变量。这被记录为 CVE-2020-12787。

作为第二次攻击,他将一个 ChipWhisperer 连接到修改过的 SAMA5D2-XULT 开发板上,查看当该芯片执行 CMAC 身份验证时的功耗。通过识别一个时刻,当功耗跟随提供的 CMAC 词与计算出的词之间的进位减法发生剧烈变化时,他能够泄漏消息的正确 CMAC 的位,从最重要的位开始,一直到最不重要的位。在 1,300 次功率测量或二十分钟内,这使他能够伪造一个 CMAC 身份验证,用于引导镜像的启动、加载 SAM-BA 小程序或安装密钥。这被记录为 CVE-2020-12788。

他对这一系列的第三次攻击既简单又残酷:该引导加载程序使用的 CMAC 密钥是硬编码的,并且可以通过利用我们已经讨论过的漏洞,由小程序进行转储。这些密钥通过解密已发布的小程序进行验证,从而实现逆向工程,也许有朝一日可以被利用。这被记录为 CVE-2020-12789。

第三十四章:I 其他芯片

I.1 PAL 真值表

可编程阵列逻辑(PAL)和通用阵列逻辑(GAL)设备是早期的可编程逻辑技术,早于 CPLD 和 FPGA 设备。这些设备的编程方法通常是特定于芯片品牌的,而引脚排列和功能在不同厂商之间是兼容的。如今,它们大多用于复古计算模拟和修复项目。

DuPAL 是一个用于 PAL 逆向工程的开源工具套件,见 Battaglia (2020)。它包括一块带有 Atmega 芯片的硬件板,用于施加输入并采样 PAL 芯片的输出,以及可以导出观察结果或测试潜在芯片配置的 Java GUI 工具。

DuPAL 并不直接读取芯片中的原始内存,因此它仅限于从输入和输出端外部观察到的状态。当输出值作为输入反馈时,有时会因同步逻辑的延迟而导致混淆。

Surply(2015)描述了使用 Arduino Uno 从弹球机的 PAL16L8 芯片中提取真值表的过程。由于真值表过大,无法通过 Karnaugh 映射进行简化,但 Surply 成功使用 Niels Serup 的 Python Electruth 库中的 Quine-McCluskey 方法,在几小时内简化了 PAL 的真值表,揭示了机器多个 I/O 端口的地址空间。

Image

图 I.1:MMI PAL16R6B

也可以通过视觉方式提取这些芯片的内容。PAL 通过电迁移保险丝标记真值表位。它们的工作原理是通过非常细的金属线路传导过多电流,导致金属沿着电流的路径流动,从而断开线路。

I.2 Mifare Classic 门恢复

Nohl 等人(2008)描述了成功逆向工程当时 NXP 的 Mifare Classic RFID 标签所使用的加密算法。该芯片如图 I.2 所示,大小不到一毫米的正方形,有 1K 和 4K 版本。

Nohl 要求提供表面和去层化后的照片进行这次恢复,然后使用边缘检测和模式匹配来识别芯片的标准单元库。尽管芯片上有成千上万的门,但只有大约七十个独特的逻辑单元。该门集合已由 SRL(2012b)发布。

在六层芯片中,较上层的隐藏了单元识别。这些通过机械抛光而非化学刻蚀去除。图像随后使用 Hugin 拼接,由于 Degate 尚未编写,使用自定义的 Matlab 脚本执行标准单元识别。

在 Mifare Classic 被逆向工程后,Plötz 和 Nohl(2011)接着详细描述了 Legic Prime RFID 标签的逆向工程过程。作者们发布了他们的自定义 Matlab 脚本用于 Degate,并将他们的芯片集作为 SRL(2012a)发布。

Image

图 I.2:Mifare Classic


  1. 1 ↩︎

posted @ 2025-12-01 09:42  绝不原创的飞龙  阅读(0)  评论(0)    收藏  举报