iPhone/iPod touch の jailbreak (TIFF exploit) 解説

ちょっと前、iPhone/iPod touch のTIFF脆弱性を突くための画像を生成するソースコードが公開された。
どんなもんだろうとちょっと見てみたら意外にシンプルでびっくり。

http://www.toc2rta.com/?q=node/30
http://www.toc2rta.com/files/itiff_exploit.cpp

興味が湧いたので周辺の情報も含め少し調べてみたら、ハッカーたちの苦労も知ることができてとっても面白かった。
せっかくだったのでまとめてみたいと思う。

かなり長くて、技術的な話になってしまった。 退屈かもしれないけどよかったらお付き合いください。

iPhone/iPod touch の jailbreak とは

まず最初に iPhone/iPod touch の jailbreak の一般的な話。 iPhone/iPod touch はあの小さなデバイスの中にスケールダウンした Mac OS X、正しくは Mac じゃないので “OS X” がインストールされ動作している。 多機能な OS X だが、Apple はセキュリティを重視し今のところユーザが好きなアプリケーションをインストールすることができないようにしている。(2008年 2月に公式なSDK (開発ツール) が公開されることが先日発表になった)

そんな Apple の対応が気に入らない一部のユーザが、iPhone/iPod touch を勝手に調べまくり、とうとう好きなアプリケーションインストールする方法を発見。 Apple に自由を奪われた iPhone/iPod touch を解放するという意味から、この方法は jailbreak (脱獄) と呼ばれるようになった。

当初の jailbreak は iPhone のリストア用のファームウェアの暗号を破り、ファイルを書き換えたあとにデバイスにリストアするという方法が使われていたが、iPod touch ではファームウェアの暗号が変わったため、少なくとも再び暗号を破るまでの間は jailbreak は難しいとされていた。

iPhone/iPod touch の TIFF 脆弱性

そんな中、iPod touch や iPhone が使用している画像表示用のライブラリ libtiff のバージョンが古く、バッファオーバーフローのバグが残っていることが発見された。 このバッファオーバーフローの脆弱性はPSP (プレステポータブル) をハックするときにも使われていて、iPhone/iPod touch でも同じようにできるんじゃないかと言われるようになった。

そこでいろいろ調べてできあがったのが今回の jailbreak の方法。
(jailbreak の手順に関してはここでは触れないので Google さんにでも聞いてみて)

まずはTIFFフォーマットの概要と、脆弱性の説明を。

TIFF フォーマットはファイルヘッダのあとに画像の幅や高さなどの情報を格納したIFD (Image File Directory) と呼ばれるエントリが並び、そこで画像の幅や高さといった情報が格納される。IFDのそれぞれのエントリは12バイト固定で、こんな構造になっている。

0 +----------------------------+
  |      IFD タグ(2 byte)       |
2 +----------------------------+
  |  フィールドタイプ (2 byte)      |
4 +----------------------------+
  |  値 or データサイズ (4 byte)  |
8 +----------------------------+
  |     オフセット値 (4 byte)     |
11+----------------------------+

8バイトから11バイト目のオフセット値は、4バイトで収まらないようなデータを処理するときに使われ、データのファイル先頭からのアドレス(オフセット)を示している。(このとき、4-7バイト目にデータサイズが格納されている。)

データが直接IFDエントリ内に格納されているか、オフセット先に格納されているかはIFDタグで示される情報の種類によって決まる。

短いけど jailbreak に関して必要な情報はこれぐらいかな。

もう少し詳しく知りたい人はコチラのページ (IFD が全部 IDF と書かれてるけど…) や Adobe のサイトにある仕様書のPDF でも見て欲しい。

脆弱性情報のページ によると、原因は libtiff 内の tif_dirread.c というソースコードの中の TIFFFetchShortPair にあるとのこと。

tif_dirread.c のコードはこんな感じ。 dirread なんて書くとファイルシステムアクセスのように思えるが、このdir は IFD (Image File Directory) のdir (directory) のこと。 ソースコード中では dir 構造体の中に上に書いた IFD の情報が dir->tdir_tag, dir->tdir_type, dir->tdir_count, dir->tdir_offset として入っている。

static int
TIFFFetchShortPair(TIFF* tif, TIFFDirEntry* dir)
{
  switch (dir->tdir_type) {
    case TIFF_BYTE:
    case TIFF_SBYTE:
    {
      uint8 v[4];
      return TIFFFetchByteArray(tif, dir, v)
      && TIFFSetField(tif, dir->tdir_tag, v[0], v[1]);
    }
    case TIFF_SHORT:
    case TIFF_SSHORT:
    {
      uint16 v[2];
      return TIFFFetchShortArray(tif, dir, v)
         && TIFFSetField(tif, dir->tdir_tag, v[0], v[1]);
    }
    default:
      return 0;
    }
}

この TIFFFetchShortPair のコードだけでは悪さはわからないのだが、case の中で定義されている変数 v が問題。

変数 v は4バイト分の配列 (1バイト*4 か 2バイト*2) として宣言されているが、TIFFFetchShortPair 中でさらに呼びだしている TIFFFetchByteArray/TIFFFetchShortArray は dir->tdir_count が2バイトか4バイト以上であれば、そのサイズ分dir->tdir_offset位置にあるTIFF画像の内容を v にコピーするようなロジックになっている。しかし、サイズのチェックがないのでサイズを書きかえてしまえば何バイトでもコピーできてしまう。

変数 v はオート変数なのでスタックに格納されている。 そのため、v に4バイト以上のデータをコピーした場合、スタックの中身を破壊してしまうことになる。

スタックにはオート変数だけでなく、関数の引数や戻り先なども格納されているので、スタックを書き換えられるということはこういったプログラムの制御もコントールできてしまう。 これがバッファーオーバフローによる脆弱性。

特にこのTIFF画像を使ってシステムに潜入する方法やコードは TIFF exploit (ティフ エクスプロイット, TIFFを使った抜け道) と呼ばれている。

もう少しプログラムを追ってみると TIFFFetchShortPair 関数が呼ばれるのは tif_dirread.c 内のこの部分

     case TIFFTAG_DOTRANGE:
         (void) TIFFFetchShortPair(tif, dp);

DOTRANGE という IFDタグを処理するとき。

ということで、TIFF ファイルの DOTRANGE の IFD エントリのデータサイズ 4-7 バイト目に細工をして、コードを送りこめば TIFF exploit が完成する。

TIFF exploit のためのコードを考える

じゃあ、どんなデータ(コード)をここに入れればいいのか。

一番簡単なのはスタックに実行させたいプログラムを流しこんでおき、RET命令などで戻るアドレスをそのアドレスにおきかえてしまうという方法。

しかし、iPhone/iPod touch が採用している ARM プロセッサは脆弱性対策の一環としてスタック領域からのプログラム実行を許していない(= PC (プログラムカウンタ)にスタックエリアのアドレスを指定できない) のでこの方法が使えない。

そこで、この exploit ではレジスタの値を調整し、すでにある関数や命令のアドレスにジャンプして実行してもらうという方法を使った。 これは解読されている古いバージョンの iPhone/iPod touch のファームウェアを逆アセンブルすればある程度はわかる。 できることは制限されるけど、ある程度のことはこの方法で実現できそうだ。

飛びたいアドレスにジャンプする方法はスタックの状態を把握し、RETアドレスを調整するのが汎用的な方法として考えられるが、今回はダミーの TIFF イメージを iPhone/iPod touch に読みこませてみたら、勘違いしたプログラムが画像中のあるオフセットのデータをPC(プログラムカウンタ)に書きこむことがわかったため、そのオフセットにジャンプしたいアドレスを書くことにしたらしい。

そして、ラッキーなことにARMプロセッサはプログラムからPCをセットする命令が用意されていた。

Node ldmia_r4_r0(version == 0 ? 0x310b668c : 0x3125368c); // ldmia r4!, {r0, r1, r2, r3, r5, r6, r12, sp, lr, pc}
Node ldmia_sp_r4(0x3000adfc);                             // ldmia sp!, {r4, r7, pc}
Node ldmia_sp_r0(0x300df800);                             // ldmia sp!, {r0, r1, r2, r3, pc}

exploit プログラム内の ldmia という命令がソレで、sp(スタックポインタ)が指しているアドレスの内容を順にレジスタにセットしていくようになっている。

先ほど書いたARMプロセッサの脆弱性対策のため、この命令を直接スタックに書いて呼びだすことはできないので、同じ命令を呼びだしているコードを見つけ、そのアドレスを指定するようにする (0x310b668c などの数字がソレ)。 これを駆使すればある程度の関数呼びだしを exploit コードの中で指定することができる。

さて、iPhone/iPod touch のデータにアクセスするのは、普段 iTunes との間で行われる通信方法をエミュレートするような形で行われる。 この通信は iPhone/iPod touch 上で走っている afc サービスを介して行われるが、通常このサービスを介してアクセスできるのは音楽データなどの入った /var/root/Media という領域以降に限定されている。

ということは、 /var/root/Media を / ファイルシステムに見せかけるようにする以下のようなコードを TIFF の中に埋めこんでしまえばそのファイルを読み書きできるはず。

  1. これまでの/var/root/Media ディレクトリを /var/root/Oldmedia にリネーム
  2. / のシンボリックリンクとして /var/root/Media を作成
  3. / を read/write モードで再マウント (オレ的にはいらない気もするんだけど…)

実際にそれをやっているのが以下のコード。

build_tif(base, ldmia_r4_r0);            // set stack base and initial jump
stack.Add(Node(0, Node::PTR));           // r0 = "/var/root/Media"
stack.Add(Node(1, Node::PTR));           // r1 = "/var/root/Oldmedia"
stack.Add(Node(20, Node::BYTES));        // r2,r3,r5,r6,r12
stack.Add(Node(12, Node::STACK));        // sp    -> offset 12
stack.Add(ldmia_sp_r4);                  // lr = load r4,r7,pc from sp
stack.Add(rename);                       // pc = rename(r0, r1)
stack.Add(Node(12, Node::STACK));        // r4 = sp -> offset 12
stack.Add(Node(4, Node::BYTES));         // r7 = unused
stack.Add(ldmia_r4_r0);                  // pc = load r0...lr from r4
stack.Add(Node(2, Node::PTR));           // r0 = "/"
stack.Add(Node(0, Node::PTR));           // r1 = "/var/root/Media"
stack.Add(Node(20, Node::BYTES));        // r2,r3,r5,r6,r12
stack.Add(Node(12, Node::STACK));        // sp -> offset 12
stack.Add(ldmia_sp_r0);                  // lr = load from r0..pc from sp
stack.Add(symlink);                      // pc = symlink(r0, r1)
stack.Add(Node(3, Node::PTR));           // r0 = "hfs"
stack.Add(Node(2, Node::PTR));           // r1 = "/"
stack.Add(Node(0x00050000, Node::VAL));  // r2 = MNT_RELOAD | MNT_UPDATE
stack.Add(Node(8, Node::STACK));         // r3 = **data
stack.Add(mount);                        // pc = mount(r0, r1, r2, r3)
stack.Add(Node(4, Node::PTR));           // data = "/dev/disk0s1"

解説するとこんな感じ

  1. まず ldmia_r4_r0 を呼びだし、現在のSP(スタックポインタ)以降のデータがレジスタにコピーされる。
  2. 最後に PC (プログラムカウンタ) が rename 関数のアドレスに置きかわり、戻り先となるアドレスとして ldmia_sp_r4 が lr にセットされて rename 関数が実行
  3. rename 関数が終了すると、ldmia_sp_r4 が呼ばれスタックポインタのアドレス位置の調整を行い、ldmia_r4_r0 命令を呼ぶ
  4. 再びスタックの内容がレジスタにコピーされ PC が symlink に変わり symlink が実行。 戻りアドレスは ldmia_sp_r0 (今度はスタックポインタの調整はいらないっぽい。 このヘンは試行錯誤の結果だと思われる)
  5. symlink 終了後 ldmia_sp_r0 がスタックの内容をレジスタにコピーして mount 命令が実行される
  6. プログラムカウンタがおかしなところを指し Safari がクラッシュする。必要な処理は終わっているのでクラッシュしても影響はない。

これで、スタックをイジるだけでやりたいことができてしまう。 関数を呼びだしたあとにジャンプする lr レジスタと ldmia 命令を駆使して関数を次々ジャンプさせてるところはある意味芸術的。

コードの動きを見ると、TIFF 画像を見たあとディスク領域が 300MB ぐらいに減ってしまうのは

/var/root/Media がOSのシステム領域に置き換わっていたからだとわかる。 jailbreak は黒魔術じゃなかった。

この作業のあとは iPhone/iPod touch と通信する iPHUC(iPHoneUtilityClient) というツールを使ってルートパーティションを吸いだし、fstab を書換えてファイルシステムを読み書き可能な形にし、afc サーバを / からアクセスできるように変更したあとに、元に戻せば好きなアプリケーションをインストールしたり、好きなファイルを書きかえたりできる。

これが ファームウェア 1.1.1 の iPod touch で jailbreak するときにやっていることだった。

これを書いている途中に、 Safari で細工された TIFF 画像を見るだけで完了するという Jailbreak の方法が出たがおそらく、TIFFイメージの中にコンパイルされたコードを埋めこみ、RET先を調整し、memcpy といった関数を使ってヒープ領域に書きこみ、そこで処理を実行するという感じで実現されていると思われる。

いやー面白いなぁ。 けど、これを自分で見つけられるとはとても思えない。 ハッカーたちの執念恐るべし。

参考:










3 Comments

  1. 偶然、ググっててお邪魔しましたが、ハックの恩恵を受けている者として、ハッカー達に感動すら覚えました。プロジェクトX(古いか)1本出来そうですね。
    自分では出来ないと言いつつ、これだけの解説の出来るブログ主さんにも尊敬しつつ、書かれている事の1/3も理解出来ずにハッカーやブログ主など、世の中には本当に凄い人達が居るとうれしくなりました。
    これからも異国でのご活躍を応援致しております。(半分ジジィより)

  2. コメントありがとうございました。
    今回いろいろ調べて、ハッカーたちの努力を垣間見れたのはすごく面白く、かつ勉強になりました。
    試行錯誤での作業が多い(だろう)から、まとめるのが大変でしょうが、ハッカー達がいろいろイジって解析をしている様子ってちょっとしたドキュメンタリーになりそうですね。
    残念ながら次のファームウェアでは TIFF の脆弱性が塞がれるみたいですが、彼らならまた何かをやってくれるんじゃないかと思ってます。

  3. Jailbreak(脱獄)は違法か合法か?

    iPod touch, iPhoneを使う上でJailbreak(脱獄)をするかしないかはかなり大きな分かれ道だと思います。Jailbreak自体が何なのか分からない方は以下のあたりで確認してみて下さい。
    JailBreakとはそ…