セキュリティ
コード数行。1,596 BTC消失
Lines of code. 1,596 BTC gone (onekey.so)
要約
COLDCARDハードウェアウォレットで、5年間 unnoticed だった4行のコードに起因する乱数生成の脆弱性が発見されました。この欠陥により、約7,300アドレスから1,596 BTC以上が盗難されました。記事では、欠陥の詳細、攻撃者がウォレットを特定した方法、影響を受けたユーザーが取るべき対策、そしてOneKeyが同様の事態を防ぐために実施しているチェックについて解説しています。
全文翻訳
コード数行。
1,596 BTC以上消失。
2つのファイル、4行のコード、5年間 unnoticed でした。
これはCOLDCARDの乱数生成失敗をステップバイステップで分解したものです。
欠陥は何だったのか、攻撃者はどのようにウォレットを見つけたのか、影響を受けたオーナーはどうすべきか、そしてOneKeyが同じことが静かに起こらないように実行している4つのチェックについてです。
OneKeyはどのように構築されているか
私はCOLDCARDを所有しています
デバイスをテストする
見過ごされた設定、測定済み
5年間
欠陥は完全に公開されたコードの中に unnoticed で存在していました
1,596 BTC以上
約7,300アドレスから盗難が確認されており、まだ増加中です
741億
最大の単一スイープ — 1,083 BTC、当時の約7,000万ドル
1兆分の91
Mk3で攻撃者が打ち勝たなければならなかったオッズ — 設計では78桁の数字に対する1回のチャンスが求められていました
OneKeyをお持ちですか?あなたは影響を受けていません。
あなたのキーは、メインプロセッサ上のソフトウェアではなく、セキュアエレメント内のハードウェアによって生成されます。
私たちのランダム性がどのように機能するか、そしてそれを自分でテストする方法を以下に示します。
何が問題だったのか
そこにあった、しかし何も機能しなかった安全チェック
専門用語なしの6つのステップ。
各ステップの横にある注釈は、正確な技術バージョンであり、誰でも作業を確認できます。
01
他のすべてはこの1つの数値に依存しています
ハードウェアウォレットはいくつかのジョブを実行します。
感染したコンピュータの画面を信頼するように求めるのではなく、実際に署名しているものを表示し、キーを分解に強いチップの中に保持します。
これらの防御策のすべては同じことを前提としています。
それは、キーが導出された数値が推測できなかったということです。
もし推測できたなら、攻撃者はそれらのいずれも突破する必要はありません。
24単語のフレーズが描かれるべきプールは、観測可能な宇宙のすべての原子の約1000個に1つの数値を保持しています。
ポイントは、あなたのフレーズが隠されていることではありません。
それは、プールが検索できないということです。
技術的詳細
12単語は128ビットの乱数を持ち、24単語は256ビットを持ち、それにSHA-256のチェックサムが加わります。
BIP-39は結果をBIP-32マスターキーに拡張します。
02
COLDCARDはそのプールからの引き出しを停止しました
2021年のファームウェア変更により、ウォレットは専用の乱数チップにその数値を要求するのを静かに停止し、代わりに通常のソフトウェアに計算させました。
並べて、これがインシデント全体です。
それをセキュリティが3分の2に削減されたと読むのは魅力的です。
そうではありません。
各ビットは作業を倍増させるため、88ビットを失っても仕事は3分の1に縮小されるわけではありません。
それは2⁸⁸の要因で縮小されます。
1兆は人にとっては依然として大きな数ですが、それは一度に1つずつ処理するために高性能マシンを借りている人にとっては大きな数ではありません。
技術的詳細
シード生成はckcc.rng_bytes()からngu.random.bytes()に移動しました。
Coinkiteは、Mk3で約40ビット、Mk4/Mk5/Qで約72ビットの残りの検索スペースを、128ビットのターゲットに対して推定しています。
Blockによる独立した分析は、同じオーダー(2⁴⁰·⁷未満、2⁷³·³未満)に達し、さらに少ない条件でさらに少なくなることを発見しています。
24単語が描かれるべきもの
115,792,089,237,316,195,423,570,985,008,687,907,853,269,984,665,640,564,039,457,584,007,913,129,639,936
COLDCARDが実際に描いたもの
1,099,511,627,776
03
なぜならソフトウェアはランダムではありえないからです
コイン投げを予測不可能にするものを考えてみてください。
投げの力、重力、エアコンからのそよ風、地面の微かな振動。
あなたが名前を付けられる数十の要因と、あなたが名前を付けられない数十の要因。
誰も軌道を計算できないので、誰も結果を呼び出すことができません。
ソフトウェアにはそれらの none がありません。
それは少数の開始値に対して固定された公式を実行し、固定された答えを生成します。
そしてここでは、開始値はチップのシリアル番号とデバイスが電源オンになっていた時間でした。
レシピと材料を知っている攻撃者は出力を推測しません。
彼らはそれを再計算します。
技術的詳細
ハードウェアRNGが無効になっているため、rng_get()はMicroPythonのYasmarang PRNGにリンクされ、非秘密状態(UID、SysTick、RTC)からシードされ、その後新しいエントロピーを収集しなかったため、候補はオフラインで列挙できます。
Coinkiteは、この問題だけでリスクにさらされる50回以上の公正でプライベートなロールと混合されたシードをリスクにさらさないと考えています。
50 × log₂6 ≈ 129ビット。
04
そしてそれを停止するために意図されたチェックは間違った質問をしました
これは座って考える価値のある部分です。
ビルドには、ハードウェアジェネレータなしでファームウェアが出荷されるのを防ぐために特別に書かれたガードがありました。
それは設定が存在するかどうかを尋ねました。
設定がオンになっているかどうかは尋ねませんでした。
設定は存在しましたが、オフになっていました。
そのため、ガードは静かに保たれ、ビルドはエラーを報告せず、ファームウェアは出荷されました。
技術的詳細
#ifndef は定義されているかどうかのみをテストします。
ボード設定はMICROPY_HW_ENABLE_RNGを0として定義しました(定義されており、無効になっています)。
そのため、#error はトリガーされず、リンカはrng_get()をソフトウェアフォールバックに解決しました。
欠陥、ソースからの引用。
2つのファイル、4行のコード、5年間。
libngu/random.c
#ifndef MICROPY_HW_ENABLE_RNG
#error "hardware RNG is required"
#endif
boards/COLDCARD/mpconfigboard.h
#define MICROPY_HW_ENABLE_RNG (0)
[advisory] 0として定義されているが、定義されている — #error は決して発火しなかった
ファームウェアはクリーンにビルドされ、rng_get()はソフトウェアPRNGにリンクされました
$ libngu/random.c
05
5年間、それは何も間違っているように見えませんでした
弱いリカバリフレーズは、強いフレーズと全く同じように見えます。
24の通常の単語、正しいチェックサム、正常に動作するデバイス。
画面に警告はなく、ビルドにエラーはなく、気づくべきことは何もありません。
ソースは常に公開されていました。
誰でもそれを読むことができました。
コードを読むことができることと、誰かがその部分を読んだことがあることは同じではありません。
技術的詳細
出力長、非ゼロ性、非繰り返し性はすべて弱いPRNGでパスするため、通常の煙テストでもこれをキャッチできませんでした。
06
そして誰かが計算をしました
彼らは単一のデバイスに触れることさえしませんでした。
彼らは自分のマシンで可能性のあるフレーズを生成し、それぞれが生成するビットコインアドレスを計算し、それらのアドレスを公開ブロックチェーンに対してチェックしました。
残高を保持していたすべてのアドレスが空になりました。
2022年から引き出しに入れられたままだったウォレットは、日常使用されているウォレットと同じくらい露出していました。
技術的詳細
候補はBIP-39/BIP-32を通じてオフラインで導出されます。
最終的な残高ルックアップのみがネットワークに触れるため、被害者側には事前のシグナルはありません。
推定値に関する1つの注意点:
それらは現在の仮定の下で攻撃者が直面する推測の量を記述しており、ストップウォッチではありません。
誰も特定のウォレットをクラックするのにどれくらい時間がかかるか教えてくれません。
このページは勝利のラップではありません。
Coinkiteはこれを公に開示しました。