Rabbit R1 を Apple Silicon Mac で脱獄をして Android を入れたりする話。

Running Android on Rabbit R1

Rabbit R1 とは

最近流行りの AI で人々の行動をアシストするデバイスらしい。

公式 YouTube の Keynote 動画 を見ると LLM (Large Language Model) をもじった Large Action Model などと喧伝しており、動画を見れば分かるが、買うまでもなくゴミであることに疑いの余地はない。もちろんあらゆるガジェットレビューワーがそのようなレビュー記事や動画を上げているが、問題はそこではなく、どうやら脱獄できるらしいということだ。

元々興味はなかったのでスルーしていたけど、脱獄できるなら絶対楽しいに決まっている。僕が買った時点で Mac でできるかどうかの情報が無かったが、ちょっと調べた感じ「まぁなんとでもなるやろ」って感触になったのでシュっと買った。

脱獄の日本語の情報と Mac でのやり方が全然ない

ので書きます。

Mac でやりたかったというのがモチベーションだけど、最終的には Linux でも Windows でも --serialport での場合のパフォーマンスが改善する改造に落ち着いた。

RabbitHoleEscapeR1/r1_escape を見て情報を集めたところ、どうやら mtkclient を動かしてやれば良いということが分かる。

Rabbit R1 には MediaTek Helio P35(MT6765) が搭載されている。これは 2019 年頃のミドル帯スマホに使われたような SoC で時代遅れと言って良いのだが、おかげで kamakiri を使って MediaTek の SoC 自体の脆弱性を突くことができる。完全な特権を奪取してあらゆる操作が可能であり、それには OEM ロック解除や ROM 焼きも含まれる。

ちなみに日本語で mtkclient について調べると

進研ゼミのこどもちゃれんじで利用されているチャレンジパッドを脱獄する情報で溢れている。我々が PSP に CFW を入れて育ったように、最近の子どもたちはチャレンジパッドに YouTube アプリを入れて育っているのであった。日本の未来は明るい。

mtkclient が Mac で不安定だったので パッチ をあてた

手元で試行錯誤した感じ、 libusb を使う方法は中々上手く行かなず解決が難しそうであったが --serialport オプションと --debugmode オプションを付けると確率的に途中まで進む現象が観測された。

ということはシリアル通信部分の実装が雑である可能性が高く、適切にバッファ管理さえしてやれば動くだろうという直感が働いたので、コードを追ってパッチをあてることにした。

Python でのシリアル通信のライブラリのドキュメントを読みつつ実際の挙動を試した感じ、

  • read timeout が 0.02 の指定は早すぎて普通にタイムアウトしまくる。
  • flush() は書き込みを始めるだけで、どうやらバッファが空になるまで待つ訳ではない。
    • ただし ドキュメント を見ると、書き込み完了まで待つように記述されているので、 pyserial が悪いか、 Mac の USB シリアルドライバが悪い可能性がある。
    • 書き込み完了ではなく、書き込み命令実行完了、の可能性がある。
  • flushInput()reset_input_buffer() に変更された。
    • 名前から、完了を待つような感じはしない。実際待たれない。
  • flushOutput()reset_output_buffer() に変更された。
    • 名前から、完了を待つような感じはしない。実際待たれない。
  • out_waiting が 0 になる前に write() を呼ぶと落ちる。

ということが分かったので、単純に read timeout を伸ばして、 write() の前後で out_waiting が 0 になるまで待つコードを挿入したところ安定するようになった。

def wait_for_ready(self):
    if self.device.out_waiting == 0:
        return
    self.device.flush()
    while self.device.out_waiting != 0:
        pass

この結果、至るところに挟まれていた念のためと思われる time.sleep() をコードから掃除できるようになり、バッファリングのパケットサイズ調整も含めると ROM 焼きの帯域を 0.07 MB/s から 12 ~ 13 MB/s に向上させることに成功した。

fastboot で ROM 焼きをすると USB 1.2 の理論値に近い 40 MB/s ほど出るのでもっと行けるはず…と思いちょっと格闘したが、 mtkclient での書き込みの高速化は自分ではこの辺が限界だった。

高速化した結果、初回 handshake 時のタイミングが合わなくなってしまったので逆に time.sleep() を挟む必要がでてきてしまい、そこには別途追加している。

r1.sh でシレっと SP Flash Tool が使われていたり、 mtkclient が 別の fork だったりする

脱獄のチュートリアル などで案内されている r1.sh だが、 main ブランチの r1.sh を見ると AgentFabulous/mtkclient に向いていたり、 Releases/20240605 に含まれている r1.sh では SP Flash Tool を使うように変更されていたりと、コードを良く読まないと気付かない色々があったが、まぁ気付けたので良し。こちらとしては 自前でパッチをあてた mtkclient を使えるので特に問題はない。

mtkclient のインストール

mtkclient の install に関しては README に記載があるのでそれで問題ないはず。git clone は dnpp73 のフォークに読み替えてもらう必要はある。 mac ブランチにチェックアウトする必要もある。macfuse は pip3 install のために必要だが、 SIP を無効にしてまで有効化する必要はない。capstone に関しては何も指定しないと x86 のバイナリが降ってくるので、自前でビルドするために先に個別に入れておく。また全体を通じて sudo は不要。pyenv と venv を使えば良い。Python 3.9 とあるが、 3.11 でも動作することは確認してる。

バックアップとリストア

自分の r1 でバックアップを取っておかないと、恐らく存在するであろう端末自体の UDID とか IMEI が一生失われることになる。

ただ userdata パーティションは不要。90 GB くらいあるし、そもそも端末に何も保存するデータはない。全部 Rabbit Hole というクラウド側から取ってこれるような設計になっている。

バックアップは 20 ~ 30 MB/s くらい出る。10 分かからずにダンプできる。

python3 ./mtk.py rl /path/to/backup --skip='userdata' --serialport

リストアは 12 ~ 13 MB/s 程度出るはず。それでも 15 分くらいかかる。

python3 ./mtk.py wl /path/to/backup --serialport

frp パーティションの末尾のビットを立てると fastboot flashing unlock ができるようになる。

どこからそんな情報が出てくるんだよ!って思ったけど、 frp パーティションを弄ると OEM Unlock ができるようになる。

ちなみに frp は Factory Reset Protection の略らしい。

ついでに AVB は Android Verified Boot の略らしい。 vb なんとかっていうパーティションはそれに使われているようだ。署名やメタデータの情報が格納されていてブートローダーが検証に使っているらしい。

ということでまず frp のダンプをして

python3 ./mtk.py r frp frp.bin

こんな感じのシェルスクリプトでビットを立てて

LAST_BYTE="$(xxd -p -l 1 -s -1 frp.bin)"
if [[ "${LAST_BYTE}" == '00' ]]; then
    printf '\x01' | dd of=frp.bin bs=1 seek=$(($(stat -c%s frp.bin) - 1)) conv=notrunc
fi

書き戻す

python3 ./mtk.py w frp frp.bin

ブートローダーに入るためにコマンドを送る必要がある (これもどこからそんな情報が出てくるんだよ!って思った) ので mtkbootcmd.py あたりを使って python3 ./mtkbootcmd.py FASTBOOT などとしてやればブートローダーが立ち上がり、以降は fastboot コマンドで操作できるようになる。

Android を焼き込む

焼き込む手順については r1.sh#L104-L109 の通り、上から順にやっていった。

流石に Android OS のビルドは分からんので ありもの を使った。vbmeta.img も同様。

カメラに問題が出たが rabbitude/dumps から以前のバージョンの boot.img と vendor.img を持ってきて焼き込んだところ正常に動作することまでは確認できた。

画面のサイズが小さいので

adb shell wm density 160

で調整すると良い。またカメラを回転させるには

adb root

adb shell 'echo 0 > /sys/devices/platform/step_motor_ms35774/orientation'
adb shell 'echo 90 > /sys/devices/platform/step_motor_ms35774/orientation'
adb shell 'echo 180 > /sys/devices/platform/step_motor_ms35774/orientation'

とやってやると物理的に回って楽しい。

Google Play Store を使いたければ ここ の通りにやれば良い。

満足したので結局入獄している

脱獄すること自体が楽しくて勉強になるので、一通り遊んだ後は元に戻した。Android を動かすには非力すぎて全然快適ではないし、そんなことは買う前から分かりきっていたので…。

Android のブートシーケンスの理解がちょっと深まったのが良かったが、まだまだ知らない世界がたくさんある。

今度時間があったら素の AOSP から Android 自体のビルドなどを調べてみてもいいかもしれない。

ちなみに海外の脱獄コミュニティでは

Discord に入って会話を眺めている限り、既に Rabbit R1 に Android を入れることには興味を失っているように思える。どちらかといえば rabbit OS のまま任意の apk を入れたり、rabbit OS のリバースエンジニアリングすることに興味の関心がありそうだ。

技術的に詳細な ブログ記事 を書いてる人がいたが、その中で研究の成果として Rabbit 社の GPL 違反に気付き、問い合わせたが無回答だったことについても触れられている。

R1 の端末内のデータを消す手段が脱獄するしかなかったという問題もあったようだ。現在ではファクトリーリセットの機能が足されたが、まぁこういうのはあまり苛めすぎない方がいいのではないかとも思う一方で、ハッカー達がちゃんと監視しないと最低限の品質と機能さえ満たされない製品がリリースされ、修正されないまま放置されるという問題もあるよなぁ、とも思う。