- Tozo T6イヤホンのペアリング・接続・切断時のシステムサウンドが大きすぎたため、ファームウェア内の音声ファイルのゲインを直接下げる方法で解決した
- チップセットはAiroha AB1562系と推定され、AirReps156Xアプリを通じて診断情報の取得と修正ファームウェアの書き込みが可能であることを確認した
- Tozoアプリのアップデート確認トラフィックをmitmproxyで傍受し、
/api/v1/getOtaVersionV3の応答からイヤホンのファームウェアバイナリへのリンクを確保した
- ファームウェアは左右イヤホン用のFotaPackage 2個とFileSystemImage 2個で構成されており、修正対象のファイルシステムイメージ内にはmp3ファイルが元のまま入っていた
mp3gainでmp3を再エンコードしたり長さを変えたりせず、-19.5dBだけ下げてからイメージ内部のバイトを置き換えて書き込み、機器は正常に動作して音はかなり小さくなった
大きすぎるシステムサウンドと初期仮説
- Tozo T6イヤホンはペアリング、接続、接続解除のたびに音を再生しており、この音がユーザーの好みの水準よりも大きすぎた
- イコライザーで全帯域を数dB下げる方法では解決せず、Tozoにメールで問い合わせても会社からは対応できないと返答された
- 目標は、機器上で動作するファームウェアを修正して、その音声ファイルの音量を下げることだった
- 最初は、いくつかの前提を置いて進めた
- オンラインで機器用ファームウェアバイナリを入手できる
- ファームウェアがELFのような理解しやすいバイナリ構造かもしれない
- 音声ファイルがファームウェア内に含まれていて、オフセットと長さが分かれば修正できる
- 音声がPCMのような単純な形式かもしれない
- 修正したファームウェアを機器またはチップセット用ツールで書き込める
- 実際には多くの仮定が外れており、リバースエンジニアリングそのものよりも、プロキシのような分析インフラの構築や回避経路の探索に多くの時間がかかった
機器とチップセットの特定
- 低価格な電子機器には通常、複数の主体と階層が絡んでいる
- 製品をブランドとして販売するベンダーは、この場合Tozoである
- ファームウェアを実行する中核ハードウェアとしてチップセットがある
- チップセットはARMやMIPSのような基盤技術から派生したISAを使うことがある
- 追加のコプロセッサやハードウェアインターフェース向け機能が統合される場合もある
- Tozo Androidアプリを逆アセンブルすると、Airoha SDKや特定のチップモデルへの参照、機器と通信する基本機能が見つかった
- RedditのAirPods複製品関連コミュニティ
/r/airrepsから進め方の情報を得て、AirReps156Xアプリも確認した
- AirReps156XアプリはAiroha SDKを使用しており、Airoha機器の診断情報を取得できた
- このアプリに機器を接続すると診断文字列
QW_1562U_SDK1.5.1が表示され、これを根拠に機器のチップセットをAiroha AB1562シリーズと判断した
- AirReps156Xアプリには新しいファームウェアを書き込む機能もあり、修正ファームウェアを機器に載せられるという重要な条件が満たされた
TozoアプリのトラフィックからファームウェアURLを探す
- Tozoアプリはイヤホンに接続すると、現在のファームウェアバージョンと最新版かどうかを表示する
- アプリがサーバーと通信して最新ファームウェア情報を確認していると見て、アップデート確認の過程から実際のファームウェアファイルURLを探すことにした
- デコンパイルしたコードを最後まで読む静的解析ではなく、ネットワークリクエストを直接見る動的解析を選んだ
- 無線NIC、
hostapd、mitmproxyで傍受プロキシを構成した
- Tozo APKは
apktoolとuber apk signerでパッチし、ユーザーCAストア内のmitmproxy TLS証明書を信頼するように変更した
- Androidアプリは通常、システムCAストアだけを見ることが多い
- APKをパッチすることでユーザーCAストアも使うようにし、再署名してAndroid上で実行できるようにした
- プロキシ構成には、AP設定、
iptablesで80/443トラフィックをmitmproxyポートへリダイレクトする作業、NAT設定が含まれた
- アプリがファームウェアバージョンの横に「current」と表示するとき、
/api/v1/getOtaVersionV3エンドポイントへリクエストを送っており、応答には必要なファームウェアbinリンクが含まれていた
ファームウェアファイル構造と分析
- 確保したファームウェアは合計4ファイルで構成されていた
- 左右イヤホンそれぞれの
FotaPackage
- 左右イヤホンそれぞれの
FileSystemImage
- 2つのファイルシステムイメージは同一で、固有のファイルは左右用FotaPackage 2個とファイルシステムイメージ1個の計3個だった
file、strings、hexdump、binwalkでフォーマットや内包ファイルを確認しようとした
- ファイルシステムイメージでは一部のファイル名文字列が見えたが、
binwalkでは期待していたmp3ファイルを見つけられなかった
- mp3には明確なmagic numberやfooterがないため、任意のバイナリ内でオフセットと長さを確定するのが難しかった
- 開始は
0xFFFFまたは0xFFFEになりうる
- どちらもファイル識別子として十分に固有ではない
- ファイルシステムイメージ構造が分かれば各ファイルの開始位置と終了位置が分かるはずだと考え、イメージフォーマットを理解する方向に進んだ
エントロピー分析とROFS
- エントロピー分析は、ファイルのどの部分が定数、ランダムノイズ、ASCIIテキストに近いか、またその切り替わり位置を可視化するのに有用だ
- ファイルシステムイメージは構造が見える形式だったが、FotaPackageファイルは圧縮または暗号化されているように見えた
- 左右のFotaPackageはヘッダの一部だけがまばらに異なり、本体はほぼ同一だが、末尾約7KBで完全に異なっていた
- この差が正確に何を意味するのかは確認できなかったが、不透明な変換が適用されており、大きな労力なしに意味のある情報を得るのは難しいと判断した
- ファイルシステムイメージはASCII文字列
ROFSで始まっていた
ROFSに関する公開文書や一致するフォーマット情報は見つからなかったが、後で見つけたAiroha SDKにはこのイメージを読むインターフェース実装が含まれていた
- 一時はFotaPackageを解読する方向も試したが、SDKがファームウェア送信前に変換を行っていないことを確認できたにとどまり、成果はなかった
mp3を再エンコードせずに下げる
- ファイルがmp3だと分かったことは、最初はリスク要因だった
- mp3エンコーダには多数のオプションがあり、未知のデコーダが特定の合法ファイルを処理できない可能性がある
- 接続直後に再生される音声が問題を起こすと、機器が再接続される前にクラッシュし、復旧不能になる恐れがある
- mp3を再エンコードするとファイル長が変わる可能性があり、その場合はファイルシステムイメージ内の長さ情報まで正確に修正する必要が生じるかもしれなかった
- 幸い、mp3は再エンコード、長さ変更、メタデータ変更なしにゲインを調整できた
- これはJPEGを再エンコードせずに回転できるのと同じで、内部データ構造の一部だけを変更する方法に近い
SDKから得た決定的な手がかり
- チップセット名で検索してAiroha SDKのコピーを見つけ、その中に機器から聞こえるものと同じ
.mp3ファイル群が含まれていた
- 簡単なPythonプログラム
bincontains.pyを書き、あるファイルが別のバイナリ内にそのまま含まれているかを確認した
- SDKのmp3ファイルがファイルシステムイメージに元のまま入っていることを検証した
- 圧縮されていなかった
- ブロックに分割されていなかった
- したがって、イメージ内でのオフセットと長さを計算できた
- ROFS関連のSDKコードを軽く見たところ、チェックサムの存在を強く示すシンボルは見当たらなかった
- この時点で、追加のリバースエンジニアリングなしに修正に必要な条件がそろった
- ファームウェアファイルと書き込み手段がある
- mp3ファイルのイメージ内での位置と長さが分かる
- mp3ゲインは長さを変えずに調整できる
- ファイル内部のバイト範囲だけを変えてもファイルシステムメタデータは壊れないと仮定する
ファイルシステムイメージの修正と書き込み
- BashスクリプトはSDKのmp3ファイルを走査しながら、ファイルシステムイメージ内に含まれるファイルを見つけた
- 含まれていたmp3を一時ファイルにコピーし、
mp3gainでゲインを下げた
- 使用した調整値は**-19.5dB**だった
- 修正後のmp3サイズが元と同じであることを確認してから、
ddでファイルシステムイメージの該当オフセットにバイトを書き戻した
- 最終的なファームウェアイメージは、バイナリdiffでも想定どおり数バイトだけが異なっていた
- 修正したファームウェアを機器へ書き込んだところ、機器は正常に動作し、システムサウンドは開始前よりかなり静かになった
結果と限界
- ファームウェア暗号化を解読したり、
ROFSファイルシステムフォーマット全体を理解したりする必要はなかった
- 実際には、多くのリバースエンジニアリング時間は最終的な解決に直接必要のない回り道に費やされた
- システムサウンドの音量調整が機器の標準機能であれば、このような改変は不要だったはずだ
- 音声を再生する機器であれば、機器から出るすべての音に適用される音量制御がUIレベルで提供されるほうが適切である
- このケースでは、ファームウェアイメージ内部のmp3のゲインだけを下げる方法で十分な回避策を作れた
1件のコメント
Hacker Newsのコメント
誰かが私のBluetooth睡眠マスクもこんなふうに直してくれたらいいのに
全体的にはかなり良いのだけど、バッテリーが少なくなったり電源が切れそうになると、それを最大音量で知らせてくる
睡眠マスクなのにそれはね
似たように欠陥のある目覚まし時計を使っていたことがあって、MSF無線時刻同期機能が入っていた
ところがMSF時刻信号と再同期するたびに、アラームと同じ音を2〜3秒鳴らし、しかも無効にもできなかった
いつも午前3時ごろというひどい時間に鳴るので、結局開けてMSFアンテナを切り取り、時計がいつも少しずれていると分かっていながらそのほうがよく眠れた
その間、通話相手がその数秒で何を言ったのか必死に追いつこうとするが、うまくいかない
この通知は、何もしないよりずっと悪い。警告がない場合の最悪は音が途切れて相手の話を聞き逃すことだが、このイヤークリップはまさにその効果を、より早いタイミングで意図的に作り出している
既存の音声の流れに耳障りでないビープ音パターンのような形で混ぜ込めない理由はない。1秒もかからず、防ごうとしている問題を自分で作り出すこともないはずだ
想像しがたいほどひどいBluetoothの挙動としては、ボイスチャットを使うと通常のコンピュータ音声がすべて消えることもある。ボイスチャットがマイク入力を使うと、Bluetoothデバイスは「ヘッドセット」モードに切り替わり、このモードはステレオをモノラルに変え、音声入力を提供している、または提供する可能性がある間に許される唯一の出力になる
音声入力を使わないアプリは、もはや存在しないBluetoothヘッドホンデバイスに向けて再生し続けようとするため、どれも音が出せなくなる
なぜデバイスモードが複数必要なのか分からない。家族と話すという副作用のために機能を失いたい理由がない。マイクが有効になる可能性があるというだけで、左右の耳に別々の音声信号を同時再生するのが、なぜそんなに難しいのか分からない。Bluetoothでない機器はこれを処理できるし、注目機能としてさえ扱われない。「マイクを使っている間もヘッドホンが無効にならない」が、なぜ特別であるべきなのか?
すごい! 最後までやり遂げた元の作者は立派だ
大音量イヤホンの話が出たついでに、私には逆の問題があるのかもしれない。トレッドミルでBoseのスポーツ用イヤホンを、快適で控えめな音量だと思うレベルで使っているのに、iPhoneは音量が高すぎて聴力を損ねているという警告を出してくる
電話のほうが正しいのだろうか? もしそうなら、耳の健康のために楽しさを少し犠牲にするつもりはある。でも、もっともらしい別の仮説もある。このイヤホンは、同じ音量設定でも私が使ったほかの製品より実際の物理的音量が明らかに低いので、Appleの雑なモデリングが、私のような人に誤警告を出している可能性がある
Appleが製品モデルと音量設定を実際の物理音量に対応づけるデータベースを作っているなら称賛したい。でも警告や機能説明には細部がまったくなく信用できず、Appleが大学の課題レベルのモデルを実製品に入れたというだけで、運動をつまらなくしたくはない
この警告を支えているデータサイエンスがしっかりしているのか知っている人はいる?
第一に、iOSでは私のBluetoothイヤホンの最小音量が大きすぎる。試したすべてのサードパーティ製ヘッドホンでそうだったし、オンラインでは10年も不満が続いている。EUが修正を求める法律まで通したが、ネタバレすると効果はなかった
どうかUIの最小音量はハードウェア音量の整数値1に対応していてほしい
第二に、サードパーティ製アプリは車のBluetoothメディア閲覧メニューに音楽やポッドキャストを表示できない。Androidではできる
だからAndroidでは車のジョグホイールでポッドキャストを聞き、Tidalをストリーミングできるが、iOSではできない
ほかにもBluetoothへの不満はある。なぜ私のApple Watchは車のステレオをブラックリストに入れるのか? iOSバージョンNとN-1のBluetoothは本当にバグが多い
ただただ明らかに馬鹿げた機能だ。root化さえできれば、どこかの設定ファイルにあるそのスイッチを切れるのだろうが、Samsung Galaxy J1 (2016)では一度も成功していない
私のBoseもBluetoothチップセットによって動作が違う。Linuxでは、まともに聞こえるようにするには音量を150%にしないといけない
とはいえ、そんなことをしていると信じる理由はない。ヘッドホンメーカーがBluetooth接続時にdB範囲を報告して、その種の機能を可能にしているなら面白いが、そんな話は聞いたことがない。3.5mmジャックと違って、Bluetoothならそういう機能を実現できる余地はある
ノイズキャンセリングを使えば、音量を20%未満に保ちながらでも、耳に無理をさせない快適なリスニングができる
私の場合、数年前に長時間の運動後に耳の痛みが出始めたので、Appleの警告はおそらく正しいと思う。ノイズキャンセリングを使うようになってからは完全になくなった
こういう作業は本当にいい。急にこのイヤホンのモデルがかなり興味深く感じられてきた
付け加えると、Bluetoothデバイスが出すシステム音は、製品ごとの差が最も強く出る要素の1つだと思う。中には本当にひどいものもある: https://youtu.be/J2wPsH64JEM
なのに、レビューや製品ページでその製品がどんな音を出すのか教えてくれるのを見たことがない。1日に何度も聞かされるうえに、オフにする方法もないのに
この音を変えられるようにするだけでも、かなり簡単な差別化要素になりそう
耳栓をして騒がしい作業場にいるときは完璧なんだけど、静かなオフィスで集中するために音楽を小さく流していると、不快だしびっくりする
どうしてシステム音が音量設定に従わないのか理解できない
スマホに接続されたヘッドセットが、スピーカーセットなのか、インイヤーモニターなのか、骨伝導なのかなどを分かってくれるといいのに
普通のスピーカーセットを使って、家のどこにいても聞こえるようにわざと大きめに流したいときに、「音量が大きすぎます」という通知は腹立たしい
骨伝導ヘッドホンはちゃんと聞くためにかなり大きな音量が必要なので、なおさら腹が立つ
これがファームウェア暗号化のないAiroha向けでよかった
興味があれば、ファームウェア形式用の010 Editorテンプレートもある
https://github.com/ramikg/airoha-firmware-parser
技術力は認めるけど、ファイルの再生音量を少し変えるような基本的なことをするのに、ここまでの努力が必要だという事実は残念
ツールを思い通りに動かすのに、こんな苦労が必要であるべきではないと思う
「理解できる」で済む話ではない。代金を払っているのだから、これは直すべき製品の問題だ
この記事を読んで中古のTozo T6を買ったけど、見たところかなり最近製造されたもののようだったのに、再現できなかった
公式のTozoアプリはヘッドセットを認識すらできず、AAC対応で識別されるAirohaチップセットを使っているかどうかも確認できなかった。自分のものはSBCしか対応していない
偽物を買ったか、筆者が買った後に内部変更があったのだと思う
一部の音声ファイルは、ネット上にある不完全なAiroha SDKに含まれているものと同じように聞こえるが、別の新しい音声ファイルも再生する
この結果を独立に検証したり、いじって遊んだりしたいなら、偽AirPodsのほうがよいルートかもしれない
大きくて不快なシステム音について、もっと多くの人が不満を言ってほしい
自分のSony WH-1000XM4にもまったく同じ問題があって、Sonyはファームウェアのペイロードを暗号化し、デバイス側で復号しているように見える
もう少しで分解して全部ダンプして探ってみるところだったけど、手が震えて壊してしまう可能性が高すぎる
ハック可能なノイズキャンセリングヘッドセットなら、かなりの額を払う気がある