車のナビゲーションシステムを改造して時刻を変更する方法
Jailbreaking my car's navigation system just so I can change the time (blog.nns.ee)
要約
筆者は、時刻変更機能がナビゲーションSDカードの挿入を必須とするルノー車のナビゲーションシステムを改造した。SDカードなしでは設定メニューにアクセスできないことが判明し、ファームウェアの解析やエミュレータの構築を経て、カーネルのcore_patternを悪用する手法で任意のコード実行権限を獲得し、最終的に時刻設定を変更することに成功した。
全文翻訳
車のナビゲーションシステムを改造して時刻を変更する方法
私が運転している車は2014年製のルノー・ラティテュードです。決して派手な車ではなく、年季も入ってきていますが、A地点からB地点への移動は問題なくこなせますし、その他の機能もそこそこ使えるので、不満を感じたことはありませんでした。
車を購入したとき、前のオーナーに「ナビゲーションマップSDカード」が必要かどうか尋ねられました。
その方は、時刻を変更するためだけにこのSDカードが必要だと言っていましたが、私はそれをあまり信じられませんでした。彼が嘘をついているという意味ではなく、単に正しい設定メニューに入る方法が分からなかっただけかもしれません。なぜなら、オンラインで似たようなヘッドユニットの動画を見ると、時計設定オプションがメニューに表示されていたからです。
SDカードの地図データは明らかに古く、正常に機能しなかったため、必要ないだろうと判断し、前のオーナーはそれを捨てました。
特に困ることはありませんでした。ダッシュボードのディスプレイには「マップが見つかりません!」という大きなメッセージが表示されていましたが、それほど気にはなりませんでした。
[画像: エストニア語で「マップが見つかりません!」と表示されているヘッドユニットのディスプレイ画像]
他の多くのヨーロッパ諸国と同様に、エストニアでも夏時間と冬時間があり、最近UTC+3からUTC+2への切り替えが行われました。そのため、ダッシュボードに表示される時刻が1時間ずれてしまいました。設定メニューを調べ、簡単に「時刻設定」オプションを見つけられることを期待しました。
しかし、それは叶いませんでした。
「時刻設定」オプションがあるはずのメニューには、そのオプションが単純に存在しなかったのです。オンライン動画のメニューと比較しましたが、オプションは完全に欠落していました。ダッシュボードには2つの「設定」ボタンがありましたが、1つは機能し、Bluetoothや電話帳の設定のみを変更できましたが、時刻や日付の設定はありませんでした。2つ目の設定ボタンは全く機能せず、押すたびに「マップが検出されません!」というポップアップメッセージが表示されるだけでした。
私は、時刻を変更するオプションがないとは信じがたく、「明らかに何か見落としているはずだ」と思い込み、正しいメニューにたどり着くために様々なメニューやボタンの組み合わせを試したり、この特定のユニットに関する情報をウェブで検索したりして、1時間近く費やしました。
とにかくリバースエンジニアリングだ
まだ諦めきれず、ファームウェアを取得して解析し、正しい設定メニューを開く方法の手がかりを見つけようとしました。
幸いなことに、この旅に出たのは私が初めてではありませんでした。すでにこの特定のユニットでリバースエンジニアリングを行った人がいました(https://github.com/andronedev/carminat-tomtom-live-re/blob/main/README.md)。フランス語で、おそらくLLMで支援されたものでしたが、アップデートファイルを実際に取得するための正しいURLを示してくれました。
展開すると、アップデートファイルにはttsystemというファイルが含まれており、これはデバイスのルートファイルシステムを含むext3ファイルシステムであることが判明しました。これはまさに私が探していたものでした。このユニット自体(TomTom Carminat LIVE、コードネームbeaumont)は、Samsung S5P6440 SoCを中心に構築されており、Linux 2.6.28.10をベースにしたTomTom独自のOSで動作しています。
しばらくリバースエンジニアリングを行った結果、前のオーナーの言っていたことが完全に正しかったという結論に至りました。ナビゲーションSDカードが挿入されていない限り、正しい設定メニューに入る方法は全くありませんでした。実際、設定アプレットを指し示すメニュー定義はSDカード自体から読み込まれており、適切なメニュー定義を含むカードが挿入されていないと、ユニットは設定アプレットにどのように入るべきかさえ認識していませんでした。
TomTom(またはルノー)のこの決定は、今でも私には不可解です。
さらに悪かったのは、これが正規のSDカードでなければならないことでした。システムにはDRMチェックがあり、正しいCID(カードに焼き付けられた一意のID)と、その特定のCIDに基づいた署名を持つファイルがないSDカードは拒否されました。そのため、ファイルを空のSDカードにコピーすることさえできませんでした。おそらく、地図の海賊版を防ぐためのDRMだったのでしょう。
私は地図を求めていません。ナビゲーションにはスマートフォンを使っています。ただ、あの時計の時刻を変更したいだけなのです。
オンラインで調べたところ、必要なSDカードを販売しているサードパーティが見つかりましたが、エストニアへの送料を含めると、約60ユーロかかることが分かりました。それはどうしても払いたくない金額でした。
よし。自分でroot化してやってみよう。
簡単な勝利?
これらのユニットをジェイルブレイクした人は見つかりませんでした。画像リンクが期限切れになっている古いフォーラムのスレッドで、ユニット自体に関する一般的な情報がいくつか見つかりましたが、root化は難しいというのが一般的な意見でした。
2011年製のLinux 2.6システムがハッキングするのが非常に難しいとは予想していなかったので、私は単純に「簡単な勝利」を探しに行きました。
最初のアイデアは、ext3アップデートイメージ自体を変更することでした。ファイルシステムをマウントし、initスクリプトを目的の動作をするように変更し、それを空のSDカードにコピーしてユニットにカスタムイメージをフラッシュさせるのが簡単だと思いました。
システムは、標準の、変更されていないttsystemイメージのインストール確認プロンプトを問題なく表示しましたが、変更したイメージにアップデートすることは一度もできませんでした。なぜ拒否されるのか全く分かりませんでした。アップデートファイル自体のアップデーターロジックをチェックしましたが、私のイメージが拒否される理由を見つけることはできませんでした。
この時点で、QEMUとカスタムカーネル(同じ2.6バージョンベース)、いくつかのパッチ、そしてシステムが実際に起動するための非常にシンプルなCANエミュレータを使用してエミュレータをセットアップしました。ただし、起動は不本意で、3秒ごとに再起動しようとしたため、再起動システムコールを無視するようにパッチを当てました。
これを使って、変更したアップデートイメージのインストールをテストしました。その後、デバイスがアップデートを実行するために起動するrecoveryパーティションを実際には持っていないことに気づき、何か検証レイヤーがあるに違いないと結論付けました。
ttsystemイメージをさらに詳しく調べたところ、高エントロピーのデータがいくつか続き、ゼロブロック、そして40バイトの高エントロピーデータが付加されていることが確認できました。これは何らかの署名(40バイトはSHA-1付きDSAかもしれない)だと推測しましたが、実際の検証を行うバイナリにアクセスできないため、どのように攻撃できるか、あるいは攻撃できるのかさえ理解できませんでした。
振り出しに戻る
エミュレータの構築に労力をかけるのをためらっていましたが、様々な攻撃をテストし、システムがどのように動作するかを把握するために非常に役立つことが判明しました。
例えば、ほとんどのパーティションが読み取り専用(read-only)、noexec、またはその両方でマウントされていることを発見しました。ライブシステムからのマウント情報です。
rootfs on / type rootfs (rw)
none on /sys type sysfs (rw,nosuid,noexec)
none on /proc type proc (rw,nosuid,noexec)
/dev/mmcblk0p5 on /content type ext3 (rw,nosuid,nodev,noexec,noatime,nodiratime,errors=continue,data=ordered)
/dev/loop0 on / type ext2 (ro,noatime,nodiratime,errors=continue)
none on /dev type tmpfs (rw,nosuid,noexec,size=1024k)
none on /tmp type tmpfs (rw,nosuid,noexec,size=2048k)
none on /var type tmpfs (rw,nosuid,noexec,size=5120k)
none on /dev/pts type devpts (rw,nosuid,noexec,gid=5,mode=620)
none on /var/log type tmpfs (rw,nosuid,noexec,size=1024k)
/dev/mmcblk0p3 on /mnt/flash type ext3 (rw,sync,nosuid,nodev,noexec,errors=continue,data=ordered)
/dev/mmcblk1p1 on /media/sdcard type vfat (rw,sync,nosuid,nodev,noexec,noatime,nodiratime,fmask=0022,dmask=0022,codepage=cp437,iocharset=iso8859-1)
/dev/loop1 on /media/loopback type ext3 (rw,nosuid,nodev,noexec,noatime,nodiratime,errors=continue,commit=3600,data=journal)
none on /mnt/movi type unionfs (rw,nosuid,nodev,noexec,noatime,nodiratime,dirs=/media/loopback=rw:/media/sdcard=ro)
注目すべきは、SDカードのベースとなるFAT32ファイルシステムが/media/sdcardにマウントされ、システムがその上に/mnt/moviでunionfsをマウントしていることです。このunionfsは、外部SDカード上のファイルとして存在するext3ファイルシステムへの書き込み可能なレイヤーを持っていました。このセットアップの意図は完全には分かりませんが(data=journalを使えるようにするため、停電からの復旧がより良い?)、いずれにせよこれらのマウントはすべてnoexecでした。
2011年製のシステムにしては、驚くほど強力なセキュリティのように思えました。
エミュレータが役立ったもう一つの点は、システムが外部SDカードから読み込もうとするすべてのファイルを特定できたことです。その中の1つにmaster.scriptというファイルがありました。これは特定のUIナビゲーションアクションを自動化するためのカスタム言語のスクリプトです。これは起動ごとにロードされ、時刻設定に必要な正しいメニューを開く方法を見つけられるのではないかと期待しました。
[画像: master.scriptを実行するように促すヘッドユニットのディスプレイ画像]
私はこのスクリプトを解析するバイナリttnをリバースエンジニアリングし、その機能を文書化しましたが、残念ながら必要なメニューを開く方法は見つかりませんでした。
さらなる空回り
master.scriptに労力を注ぐべきだとまだ考えていました。なぜなら、潜在的な攻撃ベクトルとなりうる多くの機能を持っていたからです。
いつものように、コマンドインジェクションを実行できるsystemやpopenの呼び出しを探しました。残念ながら、ユーザー入力で構築されたものは見つかりませんでした。
パーサーをリバースエンジニアリング中にSHEX/SHELLEXECUTEコマンドに出くわし、これは大当たりだと思ったのです。これはlaunchserverに任意の引数を渡し、execveで提供されたコマンドラインを実行しました。
これには2つの障害がありました。1つ目は、カスタム実行可能ファイルを直接実行する方法がまだなかったことです。SDカードはnoexecでマウントされており、そこにある実行可能ファイルやスクリプトは実行できません。しかし、これはスクリプトを直接実行するのではなく、例えば/bin/sh /media/sdcard/payload.shのように実行することで回避できました。これはルートファイルシステム上の/bin/shを経由するため、noexecではありませんでした。
しかし、2つ目のハードルの方が大きかったです。launchserverは何でも喜んで起動しますが、SHEXは空白で区切られた単一のトークンしか受け付けないことが判明しました。これは、どのバイナリを実行するかは制御できても、引数を渡すことはできないことを意味します。前の障害と組み合わせると、ルートファイルシステムに既に存在するスクリプトやバイナリしか実行できないため、これも行き止まりのように思えました。ほとんどを調べましたが、役に立つものは見つかりませんでした。
敗北感から、ファイル書き込みを調べ始めました。もし、本来許可されていない場所にファイルを書き込めたら、何らかのてこ入れができるかもしれません。
案の定、LOGコマンドを発見しました。これはファイルを切り捨て(#define LOGFILE log.txtで定義)、指定したファイルに1行だけ書き込むことができました。これはスペースや通常のASCII文字を許可しましたが、バイト0x20から0x7Eのみでした。つまり、バイナリデータを書き込むことは不可能でした。ttnはrootとして実行されるため、書き込みもroot権限で行われます。
また、古典的なパストラバーサル脆弱性もありました。LOGFILEを../で定義すると、意図したログファイルディレクトリの外に書き込むことができ、理論的には印刷可能なASCII文字をシステム上のどこにでも書き込める可能性がありました。
しかし、マウントポイントの設定で再び壁にぶつかりました。ルートファイルシステムは読み取り専用でマウントされていたため、上書きすることはできませんでした。他のすべてはnoexecでマウントされており、決して実行されることはありませんでした。また、上書きできる有用な設定ファイルがないか調べましたが、ほとんどのファイルはカスタムバイナリ形式で、LOGで書き込めないか、そうでなければ役に立たないものでした。これも行き止まりのように見えました。
その他、master.scriptは画像(GIF/BMP)を画面に表示することを許可していました。ユニコーンを使ってファジング環境をセットアップし、画像デコーダーのバグでメモリ破損を引き起こせるか試しましたが、OOBリードは多数見つかったものの、有用な書き込みは見つかりませんでした。
カーネルエクスプロイトのトリックを借りる
しばらくの間、リサーチ、リバースエンジニアリング、そして様々なコード実行戦略を考案していましたが、どれもうまくいかず、一歩引いて全体像を見て、すでに持っているパズルのピースを組み合わせられないかと考えました。
とりわけ、以下のことが可能だと知っていました。
- 任意のバイナリを実行できるが、引数はなく、ルートファイルシステムに既に存在するバイナリに限られる。
- 任意のパスにASCII文字を書き込める。
ここでひらめきがあり、カーネルエクスプロイトがよく使うトリックを思い出しました。ユーザーランドにスクリプトを実行させるのではなく、カーネルにスクリプトを実行させたらどうだろうか?
Linuxカーネルエクスプロイトでは、任意のメモリを書き込める場合、core_pattern文字列を上書きすることがよくあります。通常、これはプロセスがクラッシュしたときにコアダンプがどのように保存されるかを決定しますが、コアダンプをプロセスにパイプする(|/bin/my-coredump-handler)という特別な構文も受け入れます。クラッシュが発生すると、カーネルはcore_patternで指定されたプロセスを起動し、そのデータがパイプされます。
core_patternは起動されるプロセスのコマンドライン引数もサポートしています。ユニットのカーネルは2.6.28でしたが、この機能のサポートは2.6.24に登場していたため、ここで運が良かったです。
さらに重要なのは、ユーザーランドから/proc/sys/kernel/core_patternにパターンを書き込むことでcore_patternを設定できることです。
LOGFILEを../../proc/sys/kernel/core_patternに簡単に設定し、文字列|/bin/sh /media/sdcard/payload.shをLOGすることで、ルートファイルシステムの/bin/shを実行し、root権限でスクリプトを実行させることができます。
任意のバイナリを実行できることも、引数なしでも役立つことが判明しました。なぜなら、コアダンプをトリガーするためにプログラムをクラッシュさせる必要があったからです。スクリプトパーサーやメインUI自体をDoS脆弱性でクラッシュさせても、システム全体がすぐに再起動してスクリプトが終了してしまうため、うまくいきません。
エミュレータセットアップを使用して、システム上のほぼすべてのバイナリを実行し、/bin/dbus-monitorがシステムバスに接続できない場合(ユニットでは実行されていなかった)にSIGABRTを発生させ、コアダンプをトリガーすることを発見しました。これは完璧でした。
組み立てる
最終的なmaster.scriptは非常にシンプルになりました。
#define LOGFILE ../../proc/sys/kernel/core_pattern
LOG |/bin/sh /media/sdcard/payload.sh
SHEX /bin/dbus-monitor
最初に、payload.shスクリプトでrebootを呼び出すことで成功を確認しました。SDカードを挿入してユニットを起動し、「master.scriptを実行しますか?」というプロンプトで「はい」を押すと、ユニットが再起動し、コード実行ができたことを確認できたときは非常に嬉しかったです。
最初に行ったことは、ddを使用して内部eMMCの完全なバックアップを作成することでした。より完全なシステムダンプがあれば、興味のある日付と時刻の設定がカスタムバイナリ形式の/mnt/flash/settings.datにあることをすぐに突き止めました。さらにリバースエンジニアリングを行った結果、このユニットは実際にはタイムゾーンという概念をほとんど持っていないことが判明しました。GPSから時刻を取得し、ローカルオフセットを分単位の数値として保存します。私の場合は180に設定されていました。
それを120に変更しました。
こうして、ついに車のタイムゾーンを修正することができました。