HN 日本語サマリー

← 一覧へ戻る
プログラミング

電動スクーターの逆アセンブルとファームウェアのRustによる書き換え

Reverse engineering my e-scooter and rewriting the firmware in Rust (bensimms.moe)

15 pointsby vinhnx4 コメント

要約

この記事は、筆者が自身の電動スクーター「Egret GT」のハードウェアとファームウェアをリバースエンジニアリングした過程を詳述しています。Bluetooth通信の解析から始まり、スクーターがファームウェアアップデートや非表示のテレメトリデータを送信していることを発見しました。さらに、USB-CポートがCANバスとして使用されていることを突き止め、ESP32-C6とCANバスモジュールを用いてカスタムファームウェアを開発し、ディスプレイユニットの機能を書き換えることに成功した技術的な挑戦について語られています。

全文翻訳

私のe-scooterをリバースエンジニアリングし、ファームウェアをRustで書き換える 私はEgret GT E-Scooterのハードウェアとファームウェアをリバースエンジニアリングしました。どのようにアクセスし、コンポーネント間の通信を解析し、ファームウェアをリバースエンジニアリングしたかを説明します。ディスプレイユニット用のカスタムファームウェアの作成についても触れます。 はじめに 昨年、Egret GTを購入しました。このe-scooterは航続距離100kmを謳っており、非常に大きなタイヤが快適な乗り心地を実現しています。ハイエンドe-scooterであることを示すために、速度、走行モード、バッテリー残量、航続距離を表示する320x480のLCDディスプレイが搭載されています。 私は所有するあらゆるものを分解して改造してしまう性癖があるので、ついにこのスクーターがどのように機能しているのかを解明することにしました。正確な理由は覚えていませんが、おそらくキーパッドの「下」ボタンを押しながらスクーターの電源を入れると、ファームウェアアップデートモードに入るという事実が原因だったのかもしれません。このメニューから終了ボタンをクリックすると通常の「走行」モードに入り、PINコードを入力せずにスクーターを使用できるようになります。私は常にしっかりしたロックでスクーターを固定していますが、それでも少し気にかかる点でした。 最初に手掛けたのは、スクーターをリモートでロック解除したり、いくつかの設定を変更したり、バッテリー残量を確認したりできるモバイルアプリでした。そのプロセスで皆さんを退屈させるつもりはありませんが、アプリのBluetoothハンドラーをざっと見た結果、以下のことがわかりました。 スクーターはBluetooth経由でファームウェアアップデートを実行でき、ファームウェアアップデートはいくつかの異なる場所に適用されるようです(ディスプレイ、コントローラー、ボタンパネル)。 アプリやスクーター上には表示されないいくつかのメトリクスがBluetooth経由で送信されています。例えば、各走行モードでの経過時間、デバイス温度、モーター電流、バッテリー電圧、バッテリー充電履歴などです。総走行時間、オドメーター、充電履歴などの詳細は、メーカーに送信され、スクーターのIDに紐付けられて保存されています。この動作はアプリでは明確に言及されていません :))))))) スクーターは、アプリが接続して設定するまで、自身の車両識別番号(VIN)を知りません。Bluetoothデバッグアプリを使って自分でVINを設定すると、Egretアプリはスクーターが別のモデルであるかのように偽装できます。私はスクーターの45km/hモデルのVINを偽装して、速度制限がそのような単純なチェックで実装されているか試しましたが、うまくいきませんでした。 やがてBluetoothインターフェースでの操作に飽きたので、ディスプレイのUSB-Cポートに目を向けました。メーカーは、これは携帯電話の充電専用だと述べており、いくつかのテストを行った結果、データピンが接続されていても、ディスプレイユニットはUSBホストとしてもデバイスとしても機能しないことを確認しました。しかし、私はもっと知っているはずだと思い、USB-Cブレークアウトボードを注文しました。それが届くと、それを接続し、各ピンをオシロスコープでプローブしました。驚いたことに、USB-Cピンのうち2本がCANバスとして使用されていました(これはひどく規格違反の匂いがします)。 CANバスの監視 図1:スクーターのCANバスに接続されたオシロスコープで、メッセージをデコードしている様子。 このCANトラフィックを監視するために、ESP32-C6、SN65HVD230、MCP2515^0を組み合わせて、おぞましいもの(図2に示されている)をでっち上げました。 図2:デバイス 私はCANペリフェラルを初期化し、すべてのCANメッセージをログに記録する簡単なプログラムを作成しました。次に、CANロガーをスクーターに接続し、起動中のメッセージを記録しました。 ``` CAN_FRAME:1024,false,[0, 40, 87, 4, 0, 0, 0, 0] CAN_FRAME:1025,false,[74, bd, 0, 0, 16, c, 0, 0] CAN_FRAME:1028,false,[20, 4e, 0, 0, 1, 0, b9, b] CAN_FRAME:1024,false,[0, 40, 87, 4, 0, 0, 0, 0] CAN_FRAME:1025,false,[74, bd, 0, 0, 16, c, 0, 0] CAN_FRAME:1028,false,[20, 4e, 0, 0, 1, 0, b9, b] CAN_FRAME:513,false,[0, 0, 0, 0, 0] CAN_FRAME:515,false,[0, 0, 0, 0, 0, 0, 21, 0] CAN_FRAME:528,false,[0, 0, 0, 0, 0, 0, 0, 0] CAN_FRAME:1024,false,[0, 40, 87, 4, 0, 0, 0, 0] CAN_FRAME:1025,false,[73, bd, 0, 0, 3, c, 0, 0] CAN_FRAME:1028,false,[20, 4e, 0, 0, 1, 0, b9, b] CAN_FRAME:513,false,[0, 0, 0, 0, 0] CAN_FRAME:515,false,[0, 0, 0, 0, 0, 0, 21, 0] CAN_FRAME:528,false,[0, 0, 0, 0, 0, 0, 0, 0] CAN_FRAME:1024,false,[0, 40, 87, 4, 0, 0, 0, 0] CAN_FRAME:1025,false,[73, bd, 0, 0, 3, c, 0, 0] CAN_FRAME:1028,false,[20, 4e, 0, 0, 1, 0, b9, b] CAN_FRAME:513,false,[0, 0, 0, 0, 0] CAN_FRAME:515,false,[0, 0, 0, 0, 0, 0, 21, 0] CAN_FRAME:528,false,[0, 0, 0, 0, 0, 0, 0, 0] CAN_FRAME:1024,false,[0, 40, 87, 4, 0, 0, 0, 0] CAN_FRAME:1025,false,[72, bd, 0, 0, ef, b, 0, 0] CAN_FRAME:1028,false,[20, 4e, 0, 0, 1, 0, b9, b] CAN_FRAME:513,false,[0, 0, 0, 0, 0] CAN_FRAME:515,false,[0, 0, 0, 0, 0, 0, 21, 0] CAN_FRAME:528,false,[0, 0, 0, 0, 0, 0, 0, 0] CAN_FRAME:1024,false,[0, 40, 87, 4, 0, 0, 0, 0] CAN_FRAME:1025,false,[72, bd, 0, 0, ef, b, 0, 0] CAN_FRAME:1028,false,[20, 4e, 0, 0, 1, 0, b9, b] CAN_FRAME:513,false,[0, 0, 0, 0, 0] CAN_FRAME:515,false,[0, 0, 0, 0, 0, 0, 21, 0] CAN_FRAME:528,false,[0, 0, 0, 0, 0, 0, 0, 0] CAN_FRAME:1024,false,[0, 40, 87, 4, 0, 0, 0, 0] CAN_FRAME:1025,false,[74, bd, 0, 0, e9, b, 0, 0] CAN_FRAME:1028,false,[20, 4e, 0, 0, 1, 0, b9, b] CAN_FRAME:513,false,[0, 0, 0, 0, 0] CAN_FRAME:515,false,[0, 0, 0, 0, 0, 0, 21, 0] CAN_FRAME:528,false,[0, 0, 0, 0, 0, 0, 0, 0] CAN_FRAME:1024,false,[0, 40, 87, 4, 0, 0, 0, 0] CAN_FRAME:1025,false,[74, bd, 0, 0, e9, b, 0, 0] CAN_FRAME:1028,false,[20, 4e, 0, 0, 1, 0, b9, b] CAN_FRAME:774,false,[55, 0, 0, 0, 2, 0, 0, 0] CAN_FRAME:513,false,[0, 0, 0, 0, 0] CAN_FRAME:515,false,[0, 0, 0, 0, 0, 0, 21, 0] CAN_FRAME:528,false,[0, 0, 0, 0, 0, 0, 0, 0] CAN_FRAME:768,false,[0, 5a, 64, 5a, 64, 0, 0, 0] CAN_FRAME:494,false,[60, 0, 0, 0, 0, 0, 0, 0] CAN_FRAME:495,false,[4c, 44, 2e, 43, 52, 2e, 53, 38] CAN_FRAME:495,false,[30, 37, 2e, 43, 2e, 32, 2e, 31] CAN_FRAME:495,false,[45, 47, 2e, 32, 2e, 32, 2e, 31] CAN_FRAME:495,false,[31, 0, 0, 0, 0, 0, 0, 0] CAN_FRAME:495,false,[0, 0, 0, 0, 0, 0, 0, 0] CAN_FRAME:495,false,[0, 0, 0, 0, 0, 0, 0, 0] CAN_FRAME:495,false,[0, 0, 0, 0, 0, 0, 0, 0] CAN_FRAME:495,false,[0, 0, 0, 0, 0, 0, 0, 0] CAN_FRAME:495,false,[0, 0, 0, 0, 0, 0, 0, 0] CAN_FRAME:768,false,[0, 5a, 64, 5a, 64, 0, 0, 0] CAN_FRAME:495,false,[0, 0, 0, 0, 0, 0, 0, 0] CAN_FRAME:495,false,[0, 0, 0, 0, 0, 0, 0, 0] CAN_FRAME:495,false,[0, 0, 0, 0, 0, 0, 0, 0] CAN_FRAME:495,false,[0, 0, 0, 0, 0, 0, 0, 0] CAN_FRAME:1856,true,[4b, 0] CAN_FRAME:1024,false,[0, 40, 87, 4, 0, 0, 0, 0] CAN_FRAME:1025,false,[74, bd, 0, 0, e4, b, 0, 0] CAN_FRAME:1857,true,[4b, 0, 0, 0, 69, 99, 52, 42] CAN_FRAME:1028,false,[20, 4e, 0, 0, 1, 0, b9, b] CAN_FRAME:1860,true,[1] CAN_FRAME:1861,true,[1, 15, 57, 20, 50, 59, 54, 34] CAN_FRAME:774,false,[54, 0, 0, 0, 2, 0, 0, 0] CAN_FRAME:513,false,[0, 0, 0, 0, 2] CAN_FRAME:515,false,[0, 0, 0, 0, 0, 0, ff, 1f] CAN_FRAME:528,false,[0, 0, 0, 0, 0, 0, 0, 0] CAN_FRAME:768,false,[0, 5a, 64, 5a, 64, 0, 0, 0] ```