那是一个温暖的夏末午后,一位长期客户打来电话。他们需要有人帮助他们摆脱困境——而且要快。这位客户制造的嵌入式设备遍布世界各地的办公室。他们最新的创造拥有所有正确的安全功能,这是在他们的硬件约束条件下所能达到的最佳水平。这些设备由在微控制器上运行的固件驱动,该固件提供强大的无线通信;我知道这是我擅长的工作。
客户的工程师试图将正确的安全功能构建到他们的固件中:一个通常无法更新的引导加载程序,其中包含静态信任根,以及加密签名的二进制固件更新。他们甚至加强了他们的引导加载程序,以防御直接攻击固件更新协议的任何人——针对像我这样的人的对策。客户为设计的安全性感到自豪;毕竟,当时大多数消费类产品才刚刚弄清楚固件读取保护。
一个不幸的失误引发了当前的危机:客户不小心删除了签署新固件更新所需的私钥。他们有一些令人兴奋的新功能要发布,以及通常的一系列可靠性改进。他们的客户越来越不耐烦,但当被问及发布日期时,我的客户不得不拖延。他们怎么能提出一个有意义的日期?他们已经失去了签署新固件版本的能力。
考虑到这些设备在现场的庞大数量,以及每台设备的成本,更换计划是绝对的最后手段。经济损失将是巨大的,而且组织这样一个计划将是令人难以置信的艰巨。在召回成为必要之前,逆向工程尝试是最后的“背水一战”。
任务似乎很简单:找到一种方法将新的静态信任根修补到引导加载程序中(一个哲学问题:它真的是静态的吗?),从而使客户能够使用新密钥签署固件更新。由于引导加载程序已经过加强(我的客户声称),直接攻击固件更新程序是不可能的。
有问题的设备有多个串行端口,一旦客户的应用程序启动,这些端口会公开各种复杂的协议。设计中的主微控制器包括一个现成的 Arm Cortex-M3 内核,带有内置闪存和板载 RAM。该微控制器没有硬件信任根,因此引导加载程序提供了所有固件安全功能。值得注意的是,没有任何措施可以确保引导加载程序不被修改。
客户没有设备上发布的固件的确切源代码,因为整个版本在失误期间丢失了。更糟糕的是,我无法轻易使用生产设备进行测试(可以做到,但这会给参与带来很多时间——客户无法承受的时间),并且客户竭尽全力保护设备免受固件读取,甚至禁用了 JTAG(联合测试行动组)调试端口。更具挑战性的是,发布固件映像不会发出调试日志消息。然而,一个事实对我有帮助:固件是基于开源组件构建的。引导加载程序和实时操作系统都被许多此类项目使用。
由于时间至关重要,并且对生产二进制文件进行静态分析将花费大量时间,因此我需要找到一条快速的代码执行路径。我只有已编译的发布版本的固件和客户想要安装在现场部署设备上的新版本的源代码。新代码是一个起点,可以从中寻找通信协议处理代码中的缺陷;客户声称此代码没有发生重大变化。我已经习惯了在条件更差的情况下工作。
一个 UART(通用异步接收器/发送器),或串行端口,公开了一个成帧命令协议。处理逻辑如下:
• 当字节到达时,中断处理程序从 FIFO(先进先出)硬件队列中读取到缓冲区,直到收到 FRAME END 字节。分配了八个这些缓冲区的数组作为一个连续的数组。没有检查以确保输入消息不超过其中一个缓冲区的长度。
• 接收到 FRAME END 字节后,中断处理程序将指向已填充缓冲区的指针以及有效负载的长度(作为无符号 32 位整数)推送到协议处理程序线程的工作队列中。这向线程发出信号,表明数据已准备好处理。
• 协议处理程序线程从其队列中弹出新的工作项,并将接收到的字节复制到 1,024 字节的工作缓冲区中。复制的字节数也没有针对工作缓冲区大小进行检查。
这里有一些有趣的缺陷。首先,中断处理程序代码没有逻辑来检查字节是否被写入到其活动缓冲区的末尾之外。其次,协议处理程序线程盲目地接受来自输入缓冲区的有效负载长度,复制它被告知的任何内容,而不进行检查。此错误可用于将恶意消息复制到工作缓冲区旁边的其他数据之上。
下一步是将发布的固件映像加载到微控制器供应商销售的标准开发套件上。我需要找到一种加载我自己的代码的方法。一些快速实验表明,发送过长的消息(10 KB 的值 0x4f)会导致设备卡死。成功了!但为什么会崩溃?
手头有一些版本的源代码,并且在开发套件上运行着另一个类似版本的固件,这意味着我可以开始调试了。快速检查显示设备在失败时抛出了总线错误。当尝试从无效地址读取、写入或尝试执行指令时,Cortex-M CPU 上会发生这种情况。设备的状态寄存器指示在 0x4f4f4f4f
处发生了无效指令获取。这对我是个好消息,因为我现在可以控制程序计数器了。
Cortex-M 系列微控制器旨在易于使用普通的 C 代码进行目标定位,只需要最少的汇编语言粘合代码。ISR(中断服务例程)是普通的 C 函数,由硬件直接调用。在处理中断请求时,内置于 CPU 内核中的逻辑会在正在运行的进程的堆栈上准备一个堆栈帧。该帧存储中断进程的寄存器,以及有关 CPU 状态和中断时指令指针值的信息。然后,CPU 切换到单独的中断模式堆栈指针,并调用 ISR。如果您可以覆盖保存的进程上下文的内容——尤其是保存在中断入口处的指令指针——那么您可以告诉 CPU 稍后返回到一些不同的代码。
现在,我所需要做的就是用我想运行的某些代码的地址替换我的充满 0x4f
字节的缓冲区。下次进程唤醒时,CPU 将直接跳转到它从此保存的上下文中读取的地址。
控制代码执行是一个问题,但我还需要存储重写信任根的代码。许多微控制器提供高达 1 MB 的闪存,如果您是嵌入式系统开发人员,这是一个奢侈的存储量。RAM 是一种更宝贵的商品——在许多情况下只有几KB。此设备上的所有代码都可以直接从闪存运行,为 CPU 状态和数据结构留下 RAM。然而,这意味着在执行固件更新时,没有足够的内存来容纳完整的程序映像。
闪存在此设备上分为两个分区:引导加载程序内存和应用程序映像内存。图 1 描述了闪存的使用。(请注意,固件更新程序内置于引导加载程序中,无法更新自身。)图 2 显示了来自合法应用程序映像更新的应用程序映像结构。图 3 显示了应用程序映像的安全功能,以及一些添加的代码和数据。
内置于引导加载程序中的更新程序会擦除整个应用程序映像内存,并在其位置写入新的映像。一旦完整的映像准备好在闪存中,引导加载程序将检查应用程序映像的签名,包括标头的内容,验证其真实性。如果签名检查失败,引导加载程序将立即擦除刚刚下载的数据。修改固件映像是不可能的,因为签名检查会失败。我还能做什么?
更新程序很简单。只要您不断向其提供数据,它就会将传入的数据写入闪存。
查看客户使用的开源引导加载程序的代码,发现了一个可能有用的错误:签名检查仅在标头中指定的代码区域执行。只要原始标头、代码和签名未修改,引导加载程序就会启动映像。快速测试证明情况确实如此。附加了额外数据的映像成功启动,但额外数据被忽略了。由于此设备上的所有闪存都是可执行的,因此我可以简单地跳转到附加到有效更新映像的额外代码。
所有那些引导加载程序加固都白费了...
最后一步是编写一个有效负载,该有效负载将“增强”引导加载程序,以使用来自客户的新公钥验证应用程序映像签名。我的有效负载很简单:从闪存中擦除原始公钥,并在其位置写入新密钥。在随后的重新启动中,引导加载程序将接受使用新密钥签名的新固件映像——客户现在将该密钥保存在几个安全的地方。
我的客户的“背水一战”努力得到了回报。他们很快发布了带有新功能和修复程序的固件版本。他们的客户并没有意识到原始签名密钥已经丢失,并且新的固件映像是通过利用我在对设备进行逆向工程时发现的错误来安装的。新的固件版本还包括这些错误的修复程序。
如果我告诉你这份工作并非独一无二,你会相信我吗?我至少为三个不同的客户遇到过非常相似的情况,他们都处于同样的困境中。在交付我的“修复”后,我总是会跟进,建议我的客户如何存储和管理固件签名密钥。由于这些是他们设备的密钥,因此出于设备生命周期和安全原因,它们都应该受到尊重。
如今,许多商品微控制器都提供静态信任根,内置于硅芯片的引导 ROM 中。在这种情况下,进行此类破解将更加困难,因此保护密钥变得更加重要。此外,如果客户的设计使用了 Cortex-M 系列提供的内存保护功能,那么这项工作将会更具挑战性。
幸运的是,虽然我帮助许多客户解决了这个问题,但没有一个客户要求进行此类工作超过一次。吸取教训了吗?
菲尔·瓦雄 是彭博 CTO 办公室的安全架构和工程经理,他和他的团队在那里从事与身份、身份验证以及数据科学在运营安全挑战中的应用相关的项目。在加入彭博公司之前,他与人共同创立了一家专注于高速数据包捕获和分析的初创公司。他还开发了高频交易系统,为身份和安全基础设施设备设计和实施了固件,构建了合成孔径雷达数据处理工具,并从事运营商路由器的数据平面流量工程。他的主要兴趣是开发与业务问题相关的威胁模型,设计安全的嵌入式系统,以及努力改善日益互联的世界中个人的隐私保护。
版权 © 2021 归所有者/作者所有。出版权已许可给 。
最初发表于 Queue 第 19 卷,第 6 期—
在 数字图书馆 中评论本文
彼得·阿尔瓦罗,塞维琳·蒂蒙 - 从故障测试中抽象出天才
本文呼吁分布式系统研究界改进容错测试的最新技术水平。普通用户需要自动化选择定制故障注入的工具。我们推测,超级用户选择实验的过程可以在软件中有效地建模。本文描述了一个验证此推测的原型,介绍了来自实验室和现场的早期结果,并确定了可以使这一愿景成为现实的新的研究方向。
帕特·赫兰德,西蒙·韦弗,埃德·哈里斯 - 大到不能倒
Web 规模的基础设施意味着大量服务器协同工作,通常是成千上万的服务器都在朝着相同的目标努力。如何管理这些环境的复杂性?如何引入共性和简单性?
史蒂夫·切辛 - 为了乐趣和利润注入错误
生活中一个不幸的事实是,任何带有活动部件的东西最终都会磨损和发生故障,电子电路也不例外。当然,在这种情况下,活动部件是电子。除了电迁移的磨损机制(移动的电子逐渐将金属原子推出位置,导致导线变细,从而增加其电阻并最终产生开路)和树枝状生长(相邻导线之间的电压差导致位错的金属原子彼此迁移,就像磁铁会相互吸引一样,最终导致短路)之外,电子电路也很容易受到背景辐射的影响。
迈克尔·W·夏皮罗 - 现代操作系统中的自愈
每天驾驶连接旧金山和门洛帕克的 101 公路,广告牌上微笑的面孔让我安心,2004 年的计算机世界一切都很好。他们告诉我,网络和服务器可以自我防御、自我诊断、自我修复,甚至有足够的计算能力从所有这些内省中剩余下来,以执行其所有者分配的任务。