3 ポイント 投稿者 GN⁺ 2025-01-06 | 1件のコメント | WhatsAppで共有
  • 古い Yamaha PSR-E433 シンセサイザーの SWL01U チップをリバースエンジニアリングし、USB-MIDI SysEx メッセージだけで RAM にコードを書き込み、LCD に Bad Apple の映像を表示
  • JTAG IDCODE 0x3f0f0f0f と OpenOCD/GDB による実験で、チップが ARM7TDMI コアのように動作することを確認し、内蔵 64KiB ROM と外部 16MiB フラッシュファームウェアをダンプ
  • ファームウェア内には MIDI SysEx 上で動作する 隠しシェルがあり、login とパスワード #0000 の後にメモリ読み書きコマンドを利用できた
  • 任意メモリ書き込みコマンドで RAM に ARM コードを注入した後、スタックの戻りアドレスを上書きし、JTAG や UART なしで MIDI ファイルの再生だけでコード実行が可能に
  • LCD 出力は CGRAM 制御、タスクテーブルのコピー、ディスプレイタスクの無効化、シェルコールバックの差し替えを経て、フレームあたりの転送量を 6732 バイトから 92 バイトに削減して改善

Yamaha PSR-E433 の内部調査

  • 対象機器は長年使っていた Yamaha PSR-E433 シンセサイザーで、メインボードには DMLCD の表記とともに、2 個のフラッシュチップ、RAM チップ、YAMAHA SWL01U チップがあった
  • SWL01U に関する公開情報はほとんどなく、オンラインで見つけた記事のひとつだけが SuperH CPU コアベースかもしれないと述べていた
  • 類似モデルである E443 のサービスマニュアルには SWL01U のピン配置があり、TESTNPROTN、2 つの双方向 UART、JTAG テストポイントが示されていた
  • 初期のアプローチは 4 つだった
    • TESTNPROTN ピンを操作して起動モードの変化を確認
    • UART Tx ピンにはんだ付けして出力を確認
    • JTAG でチップ識別コードを読み取る
    • フラッシュチップを取り外してファームウェアをダンプ
  • TESTN を有効にするとシンセサイザーは起動せず、PROTN は動作に変化を与えなかった
  • 未使用に見える UART Tx ピンへ直接はんだ付けしたが、TESTN/PROTN の 4 通りの組み合わせすべてで出力はなかった

JTAG で明らかになった ARM7TDMI の動作

  • JTAG はベンダーごとの実装に応じて詳細な回路説明が必要だが、ほぼすべてのデバイスが対応する IDCODE の読み取りを OpenOCD でまず試した
  • OpenOCD は IDCODE を 0x3f0f0f0f と報告し、この値は STMicroelectronics STR7xxx や Atmel SAM7xxx 系列のような ARM7 ベースのマイクロコントローラーと関連し得るように見えた
  • SWL01U を OpenOCD の arm7tdmi ターゲットとして指定すると正常に接続され、ハードウェアに 2 個の breakpoint/watchpoint ユニットがあると表示された
  • GDB で実行を停止・再開すると、ボード電流が予測可能に変化した
    • 実行中は約 115mA
    • 一時停止中は約 98mA
  • この電流変化は、実際に ARM7TDMI コアを停止・再開しているという強いシグナルになった

ROM とフラッシュファームウェアのダンプ

  • ARM7TDMI の文書によると reset vector はアドレス 0 にあり、GDB でアドレス 0 を読むと ldr pc, [pc, #24] 形式のジャンプ命令が見えた
  • アドレス 0 から 16MiB をダンプして Cutter で開いたが、文字列が 64KiB ごとに繰り返されていた
    • 例: SWL01U Internal0x0000bfd00x0001bfd00x0002bfd0 などに繰り返し出現
  • 繰り返しパターンと文字列から、このダンプは外部フラッシュではなくチップ内蔵メモリだと判断され、SWL01U には 64KiB ROM があると整理された
  • reset vector のジャンプ先は 0x02000000 で、このアドレスから再び 16MiB をダンプすると繰り返しはなかった
  • 外部フラッシュのダンプには、シンセサイザー使用中に見られる文字列が含まれていた
    • GrandPno
    • Tr1 will be OverWritten!
    • BogiWogi
  • 確認されたメモリ配置は次のとおり
    • 内蔵 ROM: 0x0000000064KiB
    • 外部フラッシュ: 0x0200000016MiB
    • 起動時、ROM はただちに外部フラッシュへ制御を渡す

Ghidra で見つかった隠しシェル

  • Cutter だけでは解析が不十分だったため Ghidra に切り替え、文字列と xref をたどりながらファームウェア構造を把握した
  • help?infover などの文字列が近いアドレスに集まっており、各文字列はコマンド名と関数ポインタのペアに見える配列へつながっていた
  • コマンド処理関数はステートマシン形式で、login 文字列と Passwd Error 文字列から、ログイン手順を持つシェルであることを確認した
  • シェル入力処理は 256 バイトのリングバッファを巡回しながら文字単位で処理し、\r 文字に出会うとコマンドを実行する構造だった
  • ログインの流れは次のとおり
    • login 入力時に passwd? を出力
    • パスワード #0000 入力時に login OK
    • 以後、コマンド実行が可能
  • 確認されたシェルコマンドには次が含まれる
    • logouthelp?infover
    • stackperf-onperf-offperf-disp
    • ddpd xxxxxd/s xxxxx
    • m ADDRESS DATAm/b ADDRESS DATAm/w ADDRESS DATAm/l ADDRESS DATA
  • info コマンドは次の情報を返した
    • DevelopName PSR-E433
    • DevelopNumber #3341
    • Main DevelopNumber #3341
    • Make data & time MAY 16 2012 19:00:57
    • J/E Select English

USB-MIDI SysEx 上で動作するシェル

  • シェルの出力関数は各バイトを上位/下位 4 ビットニブルに分けて別々のバイトに格納し、前後に固定ヘッダーと footer を付けていた
  • > プロンプトに対応するパケット構造は次のとおり
    • ヘッダー: F0 43 73 01 52 19 00 00
    • payload: 03 0E 02 00
    • footer: F7
  • MIDI SysEx メッセージは 0xF0 で始まり、メーカー ID と payload を経て 0xF7 で終わり、payload には MSB が 0 のバイトだけを入れられる
  • ヘッダーの 0x43 は Yamaha のメーカー ID で、シェルのパケット構造は Yamaha SysEx メッセージ形式と一致していた
  • シンセサイザーの USB ディスクリプタには MIDI インターフェースだけがあり、別のシリアルポートはなかった
  • Python スクリプトでターミナルとシェルプロトコルの間を変換すると、USB-MIDI 経由でシェルと対話できた

MIDI Shellcode によるコード実行

  • シェルの m/l AAAAAAAA DDDDDDDD\r コマンドは 32 ビットメモリ書き込みを行い、アドレスとデータを 16 進 ASCII で渡す
  • 4 バイトの payload を書く過程でも、実際の転送量は大きく膨らんだ
    • コマンドバイトはそれぞれ 2 個の 4 ビットニブルバイトに変換される
    • SysEx メッセージに 9 バイトが追加される
    • 3 バイトごとに 4 バイトの USB-MIDI パケットで包まれる
    • 4 バイト書き込みのためにシンセサイザーへ 72 バイトを送る必要がある
    • エコーとプロンプトまで含めると合計 396 バイトが行き来する
  • 未使用に見える RAM 領域を探して ARM アセンブリコードを配置し、スタックの戻りアドレスを上書きしてそのコードを実行した
  • 最初の payload はファームウェア内部の文字列出力関数を呼び出し、LCD の 8 文字テキスト領域に HeloWrld を表示した
  • この方式は JTAG や UART なしでも動作し、メッセージを MIDI ファイルに入れて再生するだけで実行できた
  • PSR-E433 ファームウェア 1.02 用の MIDI ファイルも提供されたが、別の Yamaha デバイスや異なるファームウェアバージョンの PSR-E433 で再生すると予測不能な動作をする可能性があると警告している

Bad Apple を LCD に表示する

  • Yamaha PSR-E433 の LCD コントローラーは ML9040A で、基本的にはドットマトリクスのテキスト文字を処理する構造だった
  • LCD にはドットマトリクス領域だけでなく、音符表記、7 セグメント領域、コード表記、下部のキーボード表示領域もあった
  • ML9040A には 3 種類のメモリがあった
    • DDRAM: 表示する文字データをホストが記録
    • CGROM: 文字コードをグラフィックパターンに変換
    • CGRAM: ホストが最大 8 個のユーザー定義文字を定義
  • ファームウェアは CGRAM を操作してドットマトリクス下の非テキスト表示要素を制御しており、この経路を使ってユーザー定義グラフィックを表示できた
  • 任意データを LCD コントローラーへ送るファームウェア関数を見つけ、CGRAM にチェックパターンを載せたが、ファームウェアが CGRAM を継続的に更新するためすぐ上書きされた

RAM 操作でディスプレイ更新を制御

  • フラッシュを直接上書きするとデバイスを文鎮化する危険があるため、すべての実験は電源再投入で元に戻せるよう RAM 操作に限定した
  • ファームウェアには原始的な RTOS のように見える構造があり、フラッシュには 64 個のタスクのコールバック関数、スタック、属性を定義するグローバルテーブルがあった
  • 起動時にフラッシュファームウェアが ROM にタスクテーブルの位置を知らせ、ROM はその位置を内蔵 SRAM のグローバル変数に保存した
  • タスクテーブルを RAM にコピーした後、ROM が新しいテーブルを使うように変更すれば、フラッシュを修正せずにタスクコールバックを差し替えられた
  • ディスプレイ更新タスクのコールバックをデフォルトの idle コールバックに変更し、ファームウェアが CGRAM を継続的に上書きできないようにした

転送効率と画面崩れの改善

  • Bad Apple の最初の実装は動作したが、転送効率が低いためフレームレートが非常に低く、画面アーティファクトがあった
  • フレームごとに CGRAM 64 バイトと 32 ビット戻りアドレスの上書きだけを送っても、実際の転送量は 70 バイトの payload あたり 6732 バイトだった
  • 低効率の主な原因は 2 つだった
    • データをシェルコマンド形式で包む必要がある
    • シンセサイザーがコマンドを文字ごとに大きな SysEx パケットでエコーする
  • シェルタスクのコールバックを自作コールバックに差し替えて raw データを受け取り、応答しないようにすると、コマンドのラッピングとエコーのオーバーヘッドを取り除けた
  • 追加のパッキング最適化後、フレームあたりの転送量は 6732 バイトから 92 バイトに減り、73 分の 1 になった
  • 残ったアーティファクトは、LCD 通信とパネルボタン/LED スキャンが同じ 8 本の GPIO ラインを共有しているために発生した
  • 最終実装では LCD に直接書き込まず、LCD/パネル多重化タスクがパネルスキャンを終えた後に目的のデータを送るよう要求することで、画面崩れを回避した

最終的な動作手順と残る解析対象

  • MIDI で LCD 映像を表示する最終手順は次のとおり
    • シェルにログイン
    • シェルのメモリ書き込みコマンドで実行コードを RAM に記録
    • スタックの戻りアドレスを上書きして RAM コードを実行
    • タスクテーブルを RAM にコピー
    • 新しいタスクテーブル同士が相互に参照するよう修正
    • ROM が新しいタスクテーブルを使うよう変更
    • ディスプレイタスクコールバックをデフォルトの idle コールバックに差し替え
    • シェルタスクコールバックを独自コールバックに差し替え
    • 独自コールバックで MIDI データをアンパックし、LCD/パネル多重化タスクへ渡す
    • MIDI で映像フレームを供給
  • SWL01U の MMIO 領域の理解はまだ限定的であり、メイン ARM コアとは別の DSP も残る解析対象である
  • 関連資料

1件のコメント

 
GN⁺ 2025-01-06
Hacker News のコメント
  • SuperH は Sega 32X、Sega Saturn、Sega Dreamcast にも搭載されており、HP Jornada のような初期の Pocket PC の一部にも使われていた
    ただし、Pocket PC の大半は ARM ベースだった

    • 産業用途でも広く使われていて、Mitsubishi は Lancer Evolution を含む一部車両の ECU にこのチップを使っていた
  • 前提がばかげているほどなのに、実際にやり遂げているのが驚き
    「もうひとつのパッキング最適化」に触れていたが、フレームをどう送信しているのか気になる
    ドットマトリクスが 7x5 文字 8 個なら合計 280 ビット、つまりフレームあたり 7 ビットのグループが 40 個だが、送信にはその倍の容量を使っているように見える
    制御データのせいで無駄になっているのか、それとも送信方式が少し非最適なのか気になる

    • ドットマトリクスは実際には 5x8 文字 8 個なので合計 320 ビットで、この 320 ビットをシェルプロトコルで使える 1 バイトあたり 4 ビットにパックしている
      ここにパケットヘッダーとフッターの 9 バイトが追加される
      記事には 92 と書いていたようだが、計算を間違えたらしい
      7 ビット全体を使う方法を見つけるのが難しすぎたので、元の方式と比べれば最適解よりごくわずかに悪い程度の解法を選んだ
      正確なアルゴリズムが気になるなら、まだ整理されていないコードだがこれらのファイルを見るとよい: https://github.com/portasynthinca3/swl01u/blob/master/fun/bi..., https://github.com/portasynthinca3/swl01u/blob/master/fun/ba...
  • 「リバースエンジニアリングはあまり経験がない」というのがこのレベルなら、残りの人たちはいったいどのあたりにいるのかわからない

    • 多くを知っているが、自分が何も知らないことを自覚しているのは経験値 レベル4 くらいだと思う
      レベル1は新しく熱意はあるが、まだ知らないことをわかっている状態、レベル2は「私は神だ」、レベル3は「私はバカだ」の段階
    • 特に優れた 非専門エンジニア がこういうことをよく言う
  • 「世界初の MIDI シェルコード」と言っているが、主要プラットフォームの大半では MIDI シェルコード は 20 年以上前から存在していた: https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=midi

    • バッファオーバーフローは多いが、それらの脆弱性について実際に シェルコード を書いた人がいたかどうかは別問題
  • 当然 SysEx だった
    標準 MIDI における SysEx は、Python におけるインラインアセンブラのような存在
    ほぼすべての MIDI 機器の中には、文書化されていない独自のものが潜んでいる

    • 曲を 1 つ演奏して リモートコード実行 を起こせたらいいのに
      MIDI キーボードを挿して Am6,9/G# を弾いたら root 権限のターミナルウィンドウが開くなら、本当にかっこよさそう
    • Google がここ数年作っている Chrome の MIDI サポートに、どんな SysEx ハック を詰め込むのか楽しみ
    • こういう世界があるとはまったく知らなかった
      最近 MIDI ファジングをするには何が必要か調べていて、そのために .mid ファイルを生成する資料は見つけたが、求めていたものとは少し違っていた
      代わりにこちらを探索してみる価値がありそう
    • SysEx は素晴らしい
      最近のシンセサイザーがだんだん使わなくなっているようでかなり残念、特に Roland
      それでも Behringer はまだかなりよく対応している
      たとえば Deepmind は MIDI CC の範囲もすでに良いが、SysEx でほぼ 100% プログラム可能
  • 驚くべき研究
    実際の DNA/RNA 分子 にシェルコードを合成して、DNA シーケンシング装置でリモートコード実行を示した 2017 年の研究を少し思い出した: https://www.usenix.org/conference/usenixsecurity17/technical...
    「次は OSC か」と言おうとしたが、まだ MIDI が支配的なようだ

  • 全文を読むことを勧めるが、要点となる文はこうだと思う
    「この[キーボードメーカー]の狂った人たちは、USB 上の MIDI SysEx メッセージ上で動くシェルを作っていた」
    「最も興味深いコマンドは 任意メモリ読み書き コマンドだ。望むなら MIDI でシンセサイザーのメモリを覗いたり突いたりできる」
    「望むなら、これらのメッセージを MIDI ファイルに書き込んで、ほかの MIDI ファイルと同じようにシンセサイザーで再生できる。おや、いい考えが浮かんできたぞ…」
    「ファームウェアを掘り続けた数多くの眠れない夜の末に、任意データを LCD コントローラーへ送る関数を発見した」

    • ここで本当の問いは、キーボード上で実行中のコードを変更して、同じモデルの別のキーボードがこの MIDI データを受け取ったときに感染を試みるようにできるかどうか
      ある意味では IoT の悪夢 を少し垣間見るようなもの
      ほとんどどんな機器にもバックドアがあり得るし、場合によっては #0000 のような間抜けなバックドアでさえあり得る
    • これを MIDI ファイルのように再生したら、出てくるものは ダブステップ になりそう
    • MIDI でシンセサイザーのメモリを読み書きできると言うと簡単そうに聞こえるが、SysEx には配送保証も接続やセッションの概念もないので、もどかしいことがある
      パケットロス が起きるのは完全に正常
  • Bad Apple のコマンドの合間に MIDI 音楽 を挟み込んで、音声も自前で再生させられるのか気になる

  • リポジトリの README には イメージダンプ が入っていると書かれているが、実際にはない
    これで正しい状態なのか気になる

    • ミスだった
      最後の瞬間にアセンブル済みのコード片を除外しようとして *.bin.gitignore に追加したところ、ダンプも一緒に除外されたらしい
      数時間以内にアップロードする予定
    • そうだと思う
      そのダンプは Yamaha の著作権 で保護されている可能性があるので、むしろ良い判断かもしれない
  • もし HN 読者の中に Armenia にいる人がいれば、Porta が 1 月 10 日に Hacker Embassy でこのテーマについて発表する
    ぜひ来てほしい: https://t.me/hackerembassy/17