【译】 Stealing the funds of all HTC EXODUS 1 users (HTC 区块链钱包安全漏洞分析)

HTC EXODUS 1手机带有集成的硬件钱包。该钱包允许通过拆分并将其发送给“受信任的联系人” 来备份其主种子。通常需要三个受信任的联系人来重建整个种子。我们表明,任何受信任的联系人或破坏了该受信任联系人的电话的攻击者都可以收回全部种子并窃取EXODUS 1所有者的所有资金。如果在2019年4月之前使用社交密钥恢复功能,我们强烈建议HTC EXODUS 1用户将其资金转移到另一种子。

介绍

在2018年,HTC推出了其首款面向区块链的智能手机EXODUS 1。与其他智能手机相比,它具有硬件钱包功能,可将主种子存储在安全的区域中。这样可以确保即使具有root特权的攻击者也无法访问主种子-它在安全区域内被加密。

图1:HTC EXODUS 1设备

 

我们对此款(硬件)钱包特别感兴趣,因为它提供了一个不错的功能:Social Key Recovery。在此博客文章中,我们将重点介绍EXODUS 1的特定功能。

它包含一个原始机制,可以强制执行种子的备份。种子被分成五份,每份被发送到受信任的联系人。如果用户丢失了手机,他们将可以通过请求其五个受信任联系人中的三个来传播种子来重建种子。份额数(5)和阈值(3)是固定的。

我们将首先提供有关实施社交密钥恢复的更多详细信息。然后,我们将介绍两种攻击方法:

  • 第一个演示了如何将阈值从三个可信联系人降低到两个。
  • 第二个示例演示了如何将阈值从三个受信任的联系人降低到一个,这意味着您的任何一个受信任的联系人都可以检索主种子并访问您的资金。

社交密钥恢复

主种子备份是硬件钱包用户的常见问题。仅从该种子生成每个用户机密。必须备份该种子,以确保您的钱包丢失并不意味着您的秘密丢失:可以从备份的种子将其恢复到新的钱包中。

如何备份种子?大多数硬件钱包都会提出一份纸质回收表(图3),用户必须在纸上写下其BIP39助记符(助记符是将您的种子表示为人类可读的单词的一种方式)。但是,要保证此纸张的安全性并非易事,因此为此设计了一些专用设备(图2)。例如,可以使用加密钢来防止助记符种子恶化。

图2:Cryptosteel-备份种子的设备

 

另一种解决方案是拥有一个备份硬件钱包,并用相同的种子初始化。但是,没有完美的解决方案可以解决所有问题。

图3:总帐回收表

 

图4:实践中的恢复表存储

 

HTC EXODUS 1带有自己的备份机制:社交密钥恢复。用户的种子被分成共享,并发送给受信任的联系人。1或2 的知识不会带来有关种子的任何信息。3 的唯一知识可以重建完整的种子。在该方案中,主种子永远不会在单个位置完全备份。

HTC硬件钱包采用名为Zion的Android应用程序的形式,以及存储种子并执行敏感操作的trustlet(在智能手机安全OS中执行的安全应用程序)的形式(图5)。秘密共享也在trustlet中计算:在下面,研究的机制在安全OS中实现

图5:Zion-体系结构概述

 

Shamir’s Secret Sharing

 

此问题可以通过以下方法解决:

  • 安全地存储多项式系数,以便以后可以恢复它们以生成其他份额,
  • 或仅在拆分前保持PRNG状态。

HTC使用的SSS实现受一个开源项目的启发,可在此处获得。此开源实现一次生成所有共享。一个人不能要求一个份额。为了允许随意添加受信任的联系人,HTC修改了实现,但牺牲了安全性。

HTC选择保留PRNG种子。但是该实现还使用了DRBG:这可确保输出是可预测的,并且生成的系数将始终相同。DRBG使用的种子(即PRNG状态)存储在加密分区内,仅可用于安全OS

随机数生成器:

原始实现的RNG(不确定的)已由以下功能代替:

#define RANDOM_POOL_SIZE 128

static uint8_t random_pool[RANDOM_POOL_SIZE];

size_t sss_rand(uint8_t *data, size_t len) {
  if (len == 0) {
    return 0;
  }

  while (len > RANDOM_POOL_SIZE) {
    memcpy(data, random_pool, RANDOM_POOL_SIZE);
    data += RANDOM_POOL_SIZE;
    len -= RANDOM_POOL_SIZE;
  }
  memcpy(data, random_pool, len);
  return len;
}

PRNG仅返回随机缓冲区的内容。此128字节的缓冲区由函数手动更新sss_update_secure_random_buffer

void sss_update_secure_random_buffer(const uint8_t *entropy, size_t size) {
  SHA256_CTX ctx;
  uint8_t digest[SHA256_DIGEST_LENGTH];
  uint8_t *p = random_pool;

  sha256_Init(&ctx);
  sha256_Update(&ctx, entropy, size);
  sha256_Final(digest, &ctx);

  for (int i = 0; i < 4; i++) {
    memcpy(p, digest, SHA256_DIGEST_LENGTH);

    sha256_Init(&ctx);
    sha256_Update(&ctx, p, SHA256_DIGEST_LENGTH);
    sha256_Final(digest, &ctx);
    p += SHA256_DIGEST_LENGTH;
  }
}

我们可以看到,作为输入参数传递给此函数的熵完全确定了PRNG的内部状态,因此也确定了PRNG的输出。正如我们之前所解释的,此行为是HTC想要的。该熵来自智能手机TRNG,其值由返回qsee_prng_getdata。使用128位熵并将其存储在加密分区中。

知道PRNG的输出足以完全确定整个随机输出序列。例如,如果我们知道返回的前32个字节,那么我们知道接下来的字节将对应于这些字节的SHA-256,然后对应于该值的SHA-256,依此类推……此外,PRNG的周期非常短128个字节。

但是,PRNG缺乏鲁棒性对我们的攻击无济于事:将使用PRNG的状态在两次调用之间固定的事实。如果两次之间的未调用,sss_rand则两次调用将始终返回相同的值sss_update_secure_random_buffer

HTC Social Key Recovery Shares计算

共享秘密是钱包的种子,用于导出每种加密货币的所有密钥。但是该实现增加了一个加密层来保护秘密。选择的密码是基于Salsa20和Poly1305(与TweetNaCl相同)的经过身份验证的流密码。

Fig. 6: The bitslice function

 

/*
 * Create `n` shares with theshold `k` and write them to `out`
 */
void sss_create_shares(sss_Share *out, const unsigned char *data,
                       uint8_t n, uint8_t k)
{
  unsigned char key[32];
  unsigned char m[crypto_secretbox_ZEROBYTES + sss_MLEN] = { 0 };
  unsigned long long mlen = sizeof(m); /* length includes zero-bytes */
  unsigned char c[mlen];
  int tmp;
  sss_Keyshare keyshares[n];
  size_t idx;

  /* Generate a random encryption key */
  sss_rand(key, sizeof(key));
...

  /* Generate KeyShares */
  sss_create_keyshares(keyshares, key, n, k);
...

Here is the sss_create_keyshares code :

/*
 * Create `k` key shares of the key given in `key`. The caller has to ensure
 * that the array `out` has enough space to hold at least `n` sss_Keyshare
 * structs.
 */
void
sss_create_keyshares(sss_Keyshare *out,
                     const uint8_t key[32],
                     uint8_t n,
                     uint8_t k)
{
...

  uint8_t share_idx, coeff_idx, unbitsliced_x;
  uint32_t poly0[8], poly[k-1][8], x[8], y[8], xpow[8], tmp[8];

  /* Put the secret in the bottom part of the polynomial */
  bitslice(poly0, key);

  /* Generate the other terms of the polynomial */
  sss_rand((void *)poly, sizeof(poly));
...

sage: load("rebuild_secret.py")
0102030405060708090a0b0c0d0e0f100102030405060708090a0b0c0d0e0f100a2b7065ad61a3ca403d62f61b21fabbab4de9811b3d2ce55c847488f231bf4e
Recovered 1 secret in 0.111693s

我们刚刚展示了如何降低到22重建机密所需的份额数(而不是33)。除了五分之三的阈值不再有效这一事实外,我们仍然认为安全威胁是不可忽略的。理想情况下,用户应将种子分成5个5彼此不认识的可信任联系人。实际上,使用55 受信任的联系人,即使其中一些彼此认识。

借助这种攻击,恶意联系人只需说服(或折衷)其他信任的联系人即可完全收回种子并访问资金。

打破机制

受先前攻击影响的固件如下(使用了欧洲ID):

  • 固件1.47.2401.2,它似乎是初始固件;
  • 固件1.53.2401.2,于2019-12-18。

2019年2月19日,发布了第三个固件。

通过研究这一点,我们非常惊讶地注意到sss_update_secure_random_bufferPRNG初始化函数从未被调用过。PRNG始终返回相同的值:其熵缓冲区,以固定值初始化(可能通过测试向量验证)。我们认为,trustlet已使用测试选项进行编译,而该选项应永远不会在生产中使用。结果,用于加密种子的密钥是固定的。由于此密钥已发送给每个联系人,因此任何人都可以解密种子并访问资金。

<span style="color:#514134"><span style="color:#2e2925"><code>secret_key <span style="color:#000000"><strong>=</strong></span> b<span style="color:#dd1144">"</span><span style="color:#dd1144">\x0e\x74\xcd\x69</span><span style="color:#dd1144">..."</span>
box <span style="color:#000000"><strong>=</strong></span> nacl<span style="color:#000000"><strong>.</strong></span>secret<span style="color:#000000"><strong>.</strong></span>SecretBox(secret_key)
nonce <span style="color:#000000"><strong>=</strong></span> b<span style="color:#dd1144">"</span><span style="color:#dd1144">\x00</span><span style="color:#dd1144">"</span> <span style="color:#000000"><strong>*</strong></span> <span style="color:#009999">24</span>
encrypted_seed <span style="color:#000000"><strong>=</strong></span> share1[<span style="color:#009999">1</span> <span style="color:#000000"><strong>+</strong></span> <span style="color:#009999">32</span>:]
seed <span style="color:#000000"><strong>=</strong></span> box<span style="color:#000000"><strong>.</strong></span>decrypt(nonce <span style="color:#000000"><strong>+</strong></span> encrypted_seed)[:<span style="color:#009999">16</span>]
<span style="color:#000000"><strong>print</strong></span>(mnemonic<span style="color:#000000"><strong>.</strong></span>Mnemonic(<span style="color:#dd1144">'english'</span>)<span style="color:#000000"><strong>.</strong></span>to_mnemonic(seed))
</code></span></span>

结论

负责任的披露

我们于2019.02.15向HTC Exodus披露了所有上述缺陷。

两个月后,还披露了其他漏洞(触摸屏驱动程序内部,受信任的UI内部以及ETH / BTC事务解析中的内存损坏)。HTC安全团队已经找到并修复了它们。

2019年3月5日,HTC Exodus团队在巴黎并借此机会访问了我们。他们甚至有机会进入Donjon。

HTC 在2019.03.25发行了新固件(1.62.2401.7)解决了所有这些问题。SSS修补程序包括使用可靠的PRNG,并将每个生成的共享保存在安全存储中。每当添加新的受信任联系人时,都会使用这些共享。

HTC Exodus于2019.04.05开始为Zion Hardware Wallet设立赏金计划。

在这些讨论之后,HTC向我们表明,此漏洞的披露触发了他们自己赏金计划的创建。当我们在建立赏金计划之前报告了这些错误时,我们没有得到任何赏金,但是当他们访问我们时,我们得到了出埃及记衬衫和贴纸。:) 非常感谢!

带走

我们研究了HTC Exodus 1手机的硬件钱包,并发现了社交密钥恢复机制上的两个关键漏洞。在攻击者能够在任何Zion信任联系人的Android手机上执行代码(Android漏洞,常规Android应用)的情况下,他可能会窃取相应EXODUS 1所有者的资金。或者,受信任的联系人可以直接访问种子。这些漏洞已得到正确修补。

不过,我们强烈鼓励所有使用社交密钥恢复来更改种子(并转移其资金)的EXODUS 1用户。确实,他们的种子可能早些受到破坏,或者仍然可以通过不会更新Zion的受信任联系人而受到破坏。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值