前言
在这篇文章中,我主要讨论我在Ledger硬件钱包中发现的一个漏洞。由于Ledger使用了自定义的体系结构对其安全元素进行了相应限制,所以才导致该漏洞的存在。
攻击者可以利用这一漏洞,在用户接收比特币之前对设备进行破坏,或者以物理等方式远程从设备中获取私钥。
在设置种子之前进行物理访问
该方式也被称为“供应链攻击”,是本文主要讨论的内容。该攻击方式不需要目标计算机上具有任何恶意软件,也不需要目标用户点击确认任何事务。尽管有一些其他的限制条件,但我已经成功在一台真正的Ledger Nano上演示了此种攻击方法。此外,我在几个月前已经将相关源代码发送给Ledger,以便他们能够重现这一问题。
https://saleemrashid.com/assets/ledger-exploit-73ac411c441ba7fdea0d567237ca7f7b1e0e91fa8a2b2230eae5fc1dc90a3611.mp4
从上面的视频可以看出,对生成的恢复种子(Recovery Seed)执行供应链攻击以修改的方式并不困难。因为所有的私钥都是由恢复种子生成的,所以攻击者可以借此窃取到任何设备上的资金。
安装后的物理访问
这通常被称为“邪恶女佣攻击(Evil Maid Attack)”。通过这种方式,只要该设备在攻击后被使用过一次,攻击者便可以提取PIN码、恢复种子,以及使用任何BIP-39口令。
和上一种方案一样,不需要预先在计算机上安装恶意软件,同样也不需要用户进行任何确认。只需要攻击者安装一个自定义的MCU固件,就能在用户下一次使用的时候,在用户不知情的情况下取得私钥。
恶意软件(包含社会工程学)
这种攻击要求用户更新受感染计算机上的MCU固件。具体可以通过显示错误消息来实现,该消息要求用户通过按住左侧按钮来重新连接设备(以进入MCU引导程序)。随后,恶意软件就可以使用恶意代码对MCU进行更新,允许恶意软件控制设备上的显示内容以及确认按钮。
如果在官方的固件更新发布时利用这种攻击方式,就会变得非常有迷惑性,在撰写本文的两周前就发生过这样的攻击。
PoC
如果你想错过自己开发漏洞利用方式的乐趣,可以在Github上找到我的PoC:https://github.com/saleemrashid/ledger-mcu-backdoor 。
大家可以根据里面的说明进行操作,将其安装在运行固件1.3.1或更低版本的Ledger Nano S上,即可重现上述视频中的攻击。然而,由于该PoC只用于教学目的,因此我故意将该攻击调整得不太可靠。
硬件钱包的背景介绍
像比特币这样的加密货币,会使用公钥密码来保护资金的安全。只有持有私钥的用户才可以合法使用所拥有的资金。
这样一来,就给用户带来了一个问题:用户应该如何保护他们的私钥?大家知道,人类在保护机密和设备安全的这一方面非常脆弱,甚至连安全专家都不是绝对可靠的。
为了解决这一问题,目前已经发明了一类名为“硬件钱包”的设备。顾名思义,这些硬件设备可以存储用户的私钥,以防范恶意软件。有很多此类设备都是通过USB的方式连接到PC,但并不会泄露PC的私钥,就像硬件安全模块(HSM)一样。
然而,获取私钥并不是攻击者窃取比特币的唯一方式。攻击者还可以对交易的接受者和金额进行更改。如果该操作是在暗中进行的,那么大多数用户都不会意识到这种攻击的发生,直到发现时已经无法再收回自己的资金。
因此,硬件钱包与简单的硬件安全模块有所不同,它需要具有以下功能:
1、用于可视化验证交易信息的可信显示;
2、设备上的按键,用于确认或拒绝交易。
硬件钱包还需要防范各类攻击,其中包括:
1、远程攻击(当攻击者可以通过计算机上的恶意软件获取私钥时);
2、供应链攻击(当攻击者可以在用户接收到信息前修改设备时);
3、未经授权的物理访问攻击(当攻击者可以获得物理访问权限时)。
进一步,我们可以将最后一类攻击再细分成两类:盗窃设备和“邪恶女仆”攻击。如果攻击者可以窃取到设备,那么他们就有更长的时间来执行攻击,并可能会有权限访问到昂贵的实验室设备。然而,用户可能会发现自己的设备丢失,从而立即更换新的私钥,这样一来这种攻击方式便随之失效。
一些安全特性(如使用未存储在设备上的密码验证短语)可以防止攻击者窃取资金,因为设备目前不完全包含恢复私钥所必需的信息。
对于另一种攻击方式,如果采取“邪恶女仆”攻击,攻击者便只有有限的时间来执行攻击。由于这些攻击可以在多种场景下实现,因此会更加危险:
1、顾名思义,一个“邪恶女仆”可能会在酒店房间清洁时非法窃取设备;
2、在通过机场安检的过程中,设备可能会被短时间持有;
3、如果将设备委托给亲属或律师,可能会产生风险。
在本文的相关披露中,我们主要关注供应链攻击的情况,也就是说,当用户从经销商或第三方购买硬件钱包后,是否可以信任这个硬件钱包。但是,正如我在本文开头所简要解释的那样,这里描述的方法可以应用于其他两种攻击方式。
攻破结构
2014年9月,Ledger发布了HW.1。这个钱包基于ST23YT66,这是一款支持USB的智能卡。但不幸的是,该设备没有可信显示屏及按钮,这样就使得钱包在使用过程中非常危险。
2016年7月,Ledger推出名为Nano S的新设备,该设备基于ST31H320安全元件,同样采用USB连接方式,但包含了确认按钮和可信显示屏。
2017年11月,我开始认真研究Nano S的安全性。
尽管我目前还没有精力来研究最新的Ledger Blue,但其功能与Nano S相同。在撰写本文时,还没有发布任何固件更新来修复Ledger Blue中的漏洞。
双芯片结构
尽管ST31H320( http://www.st.com/en/secure-mcus/st31h320.html )没有可用的公共数据表,但我们迅速地查看了数据摘要( http://www.st.com/resource/en/data_brief/st31h320.pdf ),该安全元件不支持显示器,甚至它居然不支持USB。它所支持的唯一接口是较低吞吐量的UART。
为解决这一问题,Ledger开发了一种新的架构。Nano S增加了第二个非安全的微控制器(STM32F042K6, http://www.st.com/en/microcontrollers/stm32f042k6.html ),由它来充当安全元件的代理。该处理器负责显示器、按钮和USB接口的驱动,并连接存储实际私钥的安全元件。
从现在开始,我们将ST31安全元件成为SE,将STM32微控制器称为MCU。架构图如下所示:
SE只能与MCU直接通信,但MCU却可以代表SE与外部设备进行通信。
安全元件的一个重要特性就是,我们可以执行加密证明,以确定它在运行真正的Ledger固件。这实际上是Ledger的一个亮点,Ledger认为这个安全功能非常强大,以至于Ledger钱包不需要防包装篡改( https://www.ledgerwallet.com/genuine )(archive.is / archive.org)。
Ledger的CTO甚至告诉用户,从eBay上购买是完全安全的。
这样就产生了关键的问题,虽然我们可以信任SE上的软件,但是MCU并不是安全的芯片,我们可以证明它的固件可以被攻击者替换。
在这里存在的问题是:如果要保证Ledger的安全,那么信任链必须要保存着SE中,这就意味着SE必须验证MCU上的固件。
硬件篡改
虽然本文将着重介绍软件篡改,但还是希望大家注意,如果没有软件漏洞存在,我们还是可以通过硬件篡改的方式来对设备进行漏洞利用。
为了保证设备绝对的安全,我们必须对物理硬件进行彻底的验证,这一点非常重要。
由于包装和实际设备都不能被用户明显地看到,因此攻击者对设备的修改行为也是隐蔽的,这里就在要强调验证物理硬件的重要性。
当存在有未经授权访问的风险时,也应该对硬件进行验证,否则很容易受到“邪恶女仆”类型的攻击。
Ledger提供了针对该攻击方式的指导说明( https://support.ledgerwallet.com/hc/en-us/articles/115005321449-How-to-verify-the-security-integrity-of-my-Nano-S- ),但我们注意到在说明中存在着两个问题:
1、这些图片的质量各不相同,Ledger需要提供能清晰展现每个组件的高分辨率图片;
2、并没有展现设备的反面细节。
验证设备反面的细节是非常重要的,特别是因为这是MCU的JTAG头(调试接口)所在的地方。
除了这两个问题之外,我还对于其中一个具有额外闪存(但引脚数相同)的MCU重新标记为STM32F042K6有所质疑。
尽管这一部分的内容非常重要,但我本文中所描述的攻击不需要硬件篡改。
验证MCU固件
假设用户已经仔细检查了固件,而且确信其没有被修改。但如果攻击者只是更改了MCU的固件,那么会发生什么呢?
Ledger考虑到这种攻击方式,并且为了防范此类情况,设定SE去验证MCU固件。
但事实证明,在非安全的处理器上验证固件并不是一件简单的事情。SE只是一个简单的智能卡,其与MCU通信的唯一方法就是通过一个低吞吐量的UART。那既然不能直接访问MCU上的RAM或闪存,SE如何验证其固件呢?
Ledger的方案是,SE要求MCU传输其闪存的全部内容,如下所示。
起初来看,这样的机制似乎有问题。从本质上,我们是在要求一个(有可能已经被攻陷的)MCU来证明它正在运行官方的Ledger固件。然而,如果单片机受到危害,那么是否有机制可以防范它发送不同的代码呢?这是Ledger试图解决的一大难题。
Ledger采用的理论基于这样的一个事实:MCU具有相对有限的闪存容量。如果要运行恶意固件,那么攻击者还需要存储官方Ledger固件,以便能够满足SE的要求。因此,Ledger 希望通过限制闪存量的方式来将这种利用方法变得困难。
具体而言,Ledger选择验证整个闪存,并用随机数据填充空白区域,这样就难以使得存储有恶意代码的MCU通过验证。
这是一个非常不错的想法,而且有可能会做到,但我完全不同意这个解决方案。
攻击模式
尽管有一些明显的方法,可以针对这样的设计发起攻击,例如通过USB的方式从连接的PC中传输恶意代码。但是,如果我们能尝试一种自包含的攻击(例如:供应链攻击),这样会更有趣。
我选择的方法是“压缩”代码。考虑到执行时间、内存使用量和代码大小之间的权衡,我们不可能使用诸如DEFLATE或LZMA之类的压缩算法。如果使用这些算法,用户就会发现他们的钱包在20秒后才能启动。
尽管压缩整个闪存空间的方案是可以实现的,但我并不想更换保存在闪存之中的MCU引导程序。其原因在于,有两种方法可以在设备上安装新固件:
1、使用JTAG,嵌入式固件开发人员所使用的调试接口可以上传新的固件;
2、使用引导加载程序(Bootloader),这也是Ledger用户用于安装固件更新的方法,大家可以在GitHub上找到由Ledger提供的Python工具( https://github.com/LedgerHQ/blue-loader-python/blob/9914b3746a3d784a0d7ca118096ddd383cb66141/ledgerblue/loadMCU.py )。
第二种方式也是我在使用的方法。但如果我在刷入新引导程序过程中出现错误,那么该方法将会失败,只能通过使用JTAG接口来恢复。
因此,更换引导加载程序(Bootloader)并不是一个合适的选项,我们必须排除掉不能用的压缩方案。
然而,还存在着另外一种方法。在编译C程序时,工具链(编译程序的软件套件)会执行许多内容,从而使全部任务都能成功完成。例如,许多处理器并没有用于划分较大数字的指令,编译器会通过插入除法操作的软件来解决这一问题。另一个例子是,在为函数中定义的变量声明初始值时,当函数被调用,编译器会在开始时插入额外的代码,用于将数据复制到堆栈中。
编译器插入执行这些任务的额外函数,被称为“编译器内在函数”。由于MCU同时具有引导加载程序和固件,并且它们是完全独立的程序,所以这些内在函数将在闪存中出现两次,每个程序中只出现一次。
这样一来,我们就可以插入恶意例程,用来代替编译器intrinisc例程的一个冗余副本(特别是固件中的副本)。这样一来,我们在引导加载程序中就保留了完整的代码副本。
由于引导加载程序中的原有内容与固件中的内容完全相同,因此当SE向MCU询问其闪存内容时,我们可以通过删除恶意代码并将其从引导加载程序发送给代码,从而“拼凑”出正确的映像。当固件需要使用原有内容时,我们可以跳转到引导加载程序中的原有内容。
如果你是自行实验,在根据源代码构建引导程序和固件后,可以使用此命令来查找想要的符号。
nm -S --size-sort -t d bin/token | grep -i " t "
通过该命令,我发现了一些引导加载程序和固件中都一致的符号。毫无疑问,这些都是编译器内在函数。
134228637 00000124 T memcpy
134228761 00000140 T memset
134228357 00000266 T __udivsi3
为了实际使用我们隐藏的恶意代码,我们将不得不对其他功能进行挂钩。我们通过在想要定位的函数中插入到有效载荷的分支来实现。 这就需要将发送闪存内容的函数挂钩到SE,以便发送引导加载程序中的函数,而不是我们的恶意代码。
我也会对绘制到屏幕上的函数进行挂钩,从而就可以进行各种有趣和令人兴奋的操作。其中就包括,改变显示的比特币地址以及修改键盘记录密码的内容,我会在稍后进行解释。
用这两个钩子和__udivsi3作为我们的攻击向量,这就是我们所说的漏洞的攻击方式。
通过该方式,我们可以释放258字节的有效载荷。所以,即使我们是想将memcpy和memset放入,也需要对大小进行优化。
漏洞利用
我们的有效载荷共需要两部分:
1、修改发送给SE的闪存内容代码,以欺骗验证过程;
2、诸如键盘记录器、密钥生成后门等攻击。
既然我们的漏洞利用方式中无法对SE进行攻击,那么我们如何添加后门呢?
Ledger的SE固件拥有用户界面应用程序,它负责仪表盘显示和设置功能。同时,它也会用于生成恢复种子的初始过程。
如果我们可以修改用户界面,就可以修改在最开始生成的恢复种子。这一过程很简单,原因在于用户界面是开源的,并且Ledger允许用户安装修改后的UX应用程序。
在正常情况下,设备会显示“用户界面不是官方的“警告,任何一位仔细的用户在看到这个提示后都会敲响警钟。
但回顾之前的内容·,我们可以控制显示屏所显示的内容,从而就可以简单地隐藏掉非官方UX的警告。
在这个演示中,我们不会进行攻击者真正会执行的行为,例如产生一个看起来随机但实际上完全可预测的恢复种子,我们将会进行一些更为直观的操作。
diff --git a/src/bolos_ux_onboarding_3_new.c b/src/bolos_ux_onboarding_3_new.c
index ce1849c..b950ae7 100644
--- a/src/bolos_ux_onboarding_3_new.c
+++ b/src/bolos_ux_onboarding_3_new.c
@@ -395,7 +395,7 @@ void screen_onboarding_3_new_init(void) {
#else
G_bolos_ux_context.onboarding_kind = BOLOS_UX_ONBOARDING_NEW_24;
- cx_rng((unsigned char *)G_bolos_ux_context.string_buffer, 32);
+ os_memset(G_bolos_ux_context.string_buffer, 0, 32);
G_bolos_ux_context.words_buffer_length = bolos_ux_mnemonic_from_data(
(unsigned char *)G_bolos_ux_context.string_buffer, 32,
(unsigned char *)G_bolos_ux_context.words_buffer,
如果你熟悉C语言,就会注意到我正在用一个函数调用,来将随机数生成器的系统调用替换为一个函数调用,并将所有的熵设置为零。正如大家在文章中视频所看到的那样,它会生成一个恢复种子,其中前23个字不被使用(最后一个字有所不同,作为校验和)。
由于私钥来源于恢复种子,因此如果我们控制恢复种子,就可以控制设备生成的所有比特币地址。
如果我们结合上述内容,就会创造出这种非常有效的攻击方式。
当然,由于SE认为MCU在运行的是正版固件,所以可以通过认证。正如我前面提到的那样,我们不需要进行硬件篡改,就可以破坏Ledger的完整性。
由于攻击者可以控制可信任显示屏和硬件上的按钮,因此要检测到这一漏洞是非常困难的。
漏洞修复
Ledger采取了多种缓解措施,试图阻止攻击者利用此漏洞。
首先,MCU固件已经过优化和重新安排。具体而言,固件调用引导加载程序中的函数,而不是复制函数。虽然这可以防止特定的攻击模式,但我们知道,还存在其他的攻击方法,可能是我们所不知的。
其次,在SE要求MCU发送闪存中的内容时,SE会将MCU进行计时。这是为了防止使用压缩算法而设计的。它也可以防止计算机通过USB方式向固件中添加代码。由于代码可以保存在RAM中,因此我不太确定后者的详细完成方式。
但是,需要注意的是,SE运行频率达28MHz,而MCU运行频率高达80MHz。这就引发了一个问题,就是一个较慢的芯片是否能对一个更快的芯片进行准确计时,以防止它执行额外的内容,特别是在慢速UART通信的前提下。
Ledger拒绝提供更详细的资料,因此我没有机会验证这些缓解措施是如何修复漏洞的。
然而,是否真的有可能使用时序和“难以压缩”固件的组合来实现该模型中的安全性呢?
使用这个模型来构建安全系统是一个非常令人兴奋的建议,我认为Ledger需要推动这一趋势的发展。
与Ledger的沟通
在计划披露此漏洞之前,我曾与Ledger的首席执行官进行过沟通,大家可以在archive.is和archive.org上面找到其主要评论的归档副本,以防止因为一些原因而消失。
由于Ledger的一些评论是主观的,而还有一些更具有事实性,因此下面我将讨论一些这些评论。
我想要解决的第一个问题就是,在文档中,这些漏洞可能需要一系列令人难以置信的前提条件。
根据Saleem报告的漏洞,我们需要在设置种子之前先物理访问设备,安装MCU固件的自定义版本,在目标计算机上安装恶意软件并让用户点击一个特定的按钮。
我对这种方法的来源感到困惑。在后来的沟通过程中,我被告知当他们在Reddit上发表这些内容时,首席执行官根本不知情这些漏洞的存在。
正如我在文章开头所说的那样,有三种方法可以利用这一漏洞,其中有一种方案所需的条件较为简单。
我之前提到的恶意软件攻击向量,就能很好地引出我与Larchevêque评论相关的一个问题。
当出现严重的安全问题时,可以选择下面两种缓解/修复方式之一。
完全隐藏安全修复程序
这是一种避免引起黑帽子关注的有效方法,如果我们的产品是闭源的,那么就可以实现。
但这也有缺点,大多数用户考虑到时间、性能等问题,会避免进行更新。
提醒用户关键安全问题并强制更新
这通常用于开源产品,或者是供应商怀疑野外已经有安全漏洞的利用时。
然而,其缺点在于,这样的提醒会提示黑帽黑客。因此,用户必须立即更新,以获得先发制人的优势。
Ledger采用了一种有缺陷的方案,他们采用了两个方法中最糟糕的一个。由于会将重点放在固件更新中的安全修复程序,而不是提醒用户进行攻击,就会失去先发制人的优势。
这样一来,黑帽黑客就有足够的时间来确定如何利用风险,从而使所有用户都面临恶意软件的攻击威胁。
我的担忧被证明是正确的,因为我已经在固件更新中看到了相应的说明。
时间节点
2017年11月11日:向Ledger CTO Nicolas Bacca正式报告漏洞;
2017年11月14日:采用改进的MCU固件和用户界面,来演示实际的供应链攻击。将代码发送给Bacca。
2017年12月30日:通过将固件降级为不支持的版本来更新Ledger Nano S。
2018年3月6日:Ledger发布Ledger Nano的固件更新。
2018年3月20日:发布Write-up和PoC代码。
Ledger Blue在本文撰写之前尚未发布固件更新。
致谢
感谢Josh Harvey为我提供Ledger Nano S设备,因此我可以将理论上的攻击方式实际实践一遍。同时感谢Matthew Green、Kenn White和Josh Harvey在我撰写本文的过程中提供的宝贵帮助。