1 ポイント 投稿者 GN⁺ 2025-05-25 | 2件のコメント | WhatsAppで共有
  • tachy0n は、iOS 13.0〜13.5 で動作したカーネル権限昇格 exploit で、2020年5月23日に unc0ver v5.0.0 に 0day として組み込まれ、当時の最新 iOS を直接狙っていた
  • 中核は lio_listioLightspeed 競合状態で、kalloc.16 オブジェクトを二重解放し、異なるオブジェクトが同じメモリを指す状態を作れた
  • 同系統のバグは以前、iOS 11 向けの Spice jailbreak/untether でも使われていたが、アプリサンドボックスと racoon 環境では権限・サンドボックス・スプレー手法の制約が大きく異なっていた
  • unc0ver 向け exploit は OSData の重なりを繰り返し確保し、IOBufferMemoryDescriptorIOAcceleratorFamily2 を用いて fake task と fake mach port を構成する
  • iOS 14 以降、Apple は allocator 分離、sequestering、PAC、オブジェクト単位のハードニングによって exploit 戦略そのものを遮断する方向へ移り、公開されている iOS カーネル exploit の知識は iOS 13 時代より大きく立ち遅れている

tachy0n の公開が異例だった理由

  • tachy0n は iOS 13.0 から 13.5 までに影響した古い exploit で、2020年5月23日に unc0ver v5.0.0 に含まれて公開された
  • 当時の基準では標準的なカーネルのローカル権限昇格 (kernel LPE) だったが、最新 iOS バージョンに影響する 0day jailbreak として公開された点が珍しかった
  • Apple は exploit 公開から約1週間後、このバグ専用の パッチ を配布した
  • このバグは iOS 13.5 では 0day だったが、それ以前には 1day としてすでに利用されたことがあった
  • Pwn20wnd はアプリサンドボックスから到達可能な 0day を探しており、出発点は既知の 1day に対する 回帰テスト だった
    • iOS 12 の SockPuppet は iOS 12.3 で修正されたが、iOS 12.4 で再登場した
    • この事例は、その種のバグに対する Apple の回帰テスト不足を示しており、Pwn20wnd はいくつかの既知 1day の回帰テストを実装してヒットを得た

Lightspeed: lio_listio 競合状態

  • tachy0n の中核バグは Synacktiv の Lightspeed バグで、CVE-2020-9859 と CVE-2018-4344 の可能性に関連している
  • 脆弱性は、非同期またはバッチ型ファイル I/O を実行する lio_listio syscall にある
  • カーネルは送信された I/O ジョブを追跡するために aio_lio_context 構造体を割り当てる
    • 構造体には io_waiterio_issuedio_completed フィールドがある
  • 実際のジョブは別スレッドで実行され、すべての I/O が完了すると do_aio_completion がそのコンテキストを解放する
  • 何もジョブがスケジュールされなかった場合は、lio_listio の現在スレッドがコンテキストを解放しなければならない
  • 問題は、この判定に 競合状態 があることだ
    • 別スレッドにジョブが送られたが、判定時点より前にすでに完了してコンテキストが解放される可能性がある
    • このとき lio_listio は dangling pointer になった lio_context を再び検査する

double free が exploit につながる流れ

  • exploit に必要な順序は次のとおり
    • lio_listiolio_context を割り当てる
    • ジョブが完了し、do_aio_completionlio_context を解放する
    • 解放されたメモリを攻撃者制御のオブジェクトで再割り当てし、lio_context->io_issued == 0 のように見せる
    • lio_listio がそれを見て攻撃者オブジェクトを再度解放する
    • 同じメモリを別のオブジェクトで再割り当てし、異なる2つの割り当てが同じメモリを指すようにする
  • 64ビットデバイスでは lio_context は最小の zone である kalloc.16 に入る
  • iOS 14 より前は、オブジェクト種別に関係なくサイズ単位で同じ allocation site を共有していた
    • C++ オブジェクト、ポインタ配列、ユーザー提供データバッファが同じサイズ bucket 内で互いのメモリを再利用できた
  • この double free は、中間の再割り当てがなければ容易に致命的状態へ進む一般的な double free とは違う動きをする
    • lio_context->io_issued は割り当て中には 0 にならない
    • 解放後、allocator は先頭 8 バイトを canary 値と freelist pointer、またはオブジェクトアドレス XOR 値で上書きする
    • したがって 2 回目の free は、中間に再割り当てがあり、かつ 4〜7 バイトが 0 の場合にのみ発生する
  • 実際の exploit はこの競合を何度も再試行でき、システム上の別オブジェクトが偶然必要なバイトを 0 にして double free を引き起こすケースは実運用ではまれだった

Spice における以前の活用

  • 同じバグは、iOS 11.x を対象にした Spice jailbreak/untether でも使われていた
  • Spice は Sparkey、littlelailo とともに Jake Blair チームが開発しており、当時の最新バージョンは iOS 13.x だった
  • 目標は、アプリ環境と racoon 環境の両方で mach port forgery を作ることだった
    • iOS 14 より前のカーネル exploit では、ユーザー提供値を mach port ポインタとして解釈させられれば、その後の段階が決定的に容易になった
  • Lightspeed で mach port forgery を作る基本的な流れは次のとおりだった
    • lio_context の最初の free を引き起こす
    • サイズ 1 または 2 の OOL mach ports descriptor を持つ mach message を spray する
    • 最初の entry を MACH_PORT_NULL にして kalloc.16 に入れ、io_issued が 0 に見えるようにする
    • 2 回目の free で OOL mach ports 配列を解放する
    • kalloc.16 に制御データを spray して mach ports 配列を fake pointer に置き換える

アプリサンドボックスと racoon の制約の違い

  • A7〜A9(X) では PAN がなく、mmapmlock だけで userland アドレスをカーネルポインタのように逆参照できた
  • A10 と A11 もサポートしようとしたが、アプリサンドボックスでは適切なカーネルアドレス leak と制御データ配置先が見つからず完成しなかった
  • 活用しようとしていた 1day には、Ian Beer のカーネルスタック情報 leak と backboardd サンドボックス脱出があった
    • 計画は共有メモリポインタを leak するか、カーネル __DATA セグメントにデータを配置する方式だった
    • 適切な target が見つからず、アプリ経路での A10/A11 サポートは実現しなかった
  • racoon 経路では条件が異なっていた
    • root で動作するが、一般アプリよりさらに厳しいサンドボックスを持っていた
    • IOSurface へのアクセスがなく、一般的な IOSurface::setValue 経由の OSUnserializeXML spray は使えなかった
    • その代わり RootDomainUserClient::secureSleepSystemOptions 内の OSUnserializeXML 呼び出しを利用し、一部オブジェクトを leak という形で spray できた
  • racoon はすべての sysctl の読み書きを許可するサンドボックスプロファイルを持ち、root 権限もあった
    • カーネル slide が分かれば、カーネル __DATA 内の sysctl global を既知アドレスのデータ格納先として使えた
    • Spice では vm.swapfileprefix が選ばれた
  • カーネル slide の取得には panicall の CVE-2018-4413 が使われた
    • sysctl_procargsx の情報 leak により、kernel_map の初期化されていないカーネルメモリをほぼ 1 ページ分 leak できた
    • これによりカーネルコードと heap pointer を得て、A7〜A11 を処理できた
    • アプリサンドボックスでは sysctl_procargsx がブロックされており、同じ方法は使えなかった

unc0ver 向け tachy0n exploit の構造

  • unc0ver の対象は A8〜A13 だったため、A10+ を無視したり userland 逆参照に依存したりはできなかった
  • exploit は失敗しうるメモリ corruption 段階を考慮し、二層構造 で設計された
    • 下位層は lio_listio を呼ぶ freerer スレッドと、IOSurface を通じて OSData を unserialize する racer スレッドを実行する
    • デフォルト値は freerer 4 個、racer 16 個で、調整可能
  • IOSurface 経由で unserialize されるデータは、複数の OSData entry を持つ OSDictionary だった
    • io_issued に対応する位置は 0 である必要があった
    • 0x414141410x69696969 のような magic value とキー値 k が overlap 検出に使われた
  • 競合後、すべての OSData 値を検査する
    • magic value が変わったオブジェクトは、システムの別オブジェクトに取られたと見なし、後で cleanup 対象として印を付ける
    • キーとバッファ内部の k 値が一致しなければ、別の OSData オブジェクトが同じ backing buffer を指す overlap が発生したと判断する
  • コード中の maybe_reyoinkoverlap 関数は、こうした重なり情報を構成して上位層へ渡す
  • 上位層は重なった OSData オブジェクトを使って fake mach port を構成する
    • 1 つの OSData を free する
    • OOL port descriptor を持つ mach message を spray する
    • 別の OSData を free する
    • fake task port ポインタを含む新しい OSData で再割り当てする

既知のカーネルアドレスに制御データを配置する

  • exploit は、OOL ports descriptor 配列として再割り当てされた内容を OSData として読み取り、mach port の raw kernel pointer を leak できた
  • 以降の段階では task port と IOSurfaceRoot service port のアドレス leak にこれを使うが、核心的な問題は、制御可能な buffer のカーネルアドレスを安定して得ることだった
  • XNU ソースから見つかった候補は IOMemoryDescriptor だった
    • _ranges フィールドは IOVirtualRange 配列で、単一の IOVirtualRangekalloc.16 にちょうど収まる
    • ただし通常の IOMemoryDescriptor は range が 1 つしかない場合、heap allocation の代わりに _singleRange を使う
  • IOBufferMemoryDescriptor だけは例外的に、range 1 個に対して IONew(IOAddressRange, 1) を呼び、heap allocation を行う
  • これを任意に割り当て、ユーザーアドレス空間にマップできる都合のよい場所が IOAcceleratorFamily2 の AGX インターフェースだった
    • IOGraphicsAccelerator2 で type 0 userclient を開くと IOAccelContext2 を得られる
    • ::clientMemoryForType() で 3 種類の memory descriptor を map できる
    • type 0 は 0x8000 bytes で、victim descriptor の識別に使われた
  • exploit は次のループを使った
    • IOAccelContext2 を開き、重なった OSData を 2 つ取得する
    • 片方の OSData を free する
    • 事前に開いておいた IOAccelSharedUserClient2IOConnectAddClient() で接続する
    • 残った OSData を読み、先頭 8 バイトが page-aligned な kernel pointer で、次の 8 バイトが 0x8000 かを確認する
    • 条件に合わなければ IOAccelContext2 を閉じて繰り返す

pageable memory、fake port、zone_require

  • memory descriptor をプロセスにマップし、カーネルアドレスを把握した後も、メモリが kIOMemoryPageable として作られるという問題が残っていた
  • fake mach port と fake task オブジェクトは preemption が無効な状況でアクセスされうるため、カーネル側でその page を fault-in しなければならなかった
  • IOAccelContext2::processSidebandBuffer を間接呼び出しする IOAccelContext2::submit_data_buffers external method 2 を 2 回呼び出して、これを処理した
    • 共有メモリの先頭から 0x10 バイト後ろにある構造体を読む
    • 最初の構造体は tok == 0x100 で、ページ全体を覆うように作られており、2 枚目のページまで進む
    • 2 枚目のページには、その後 fake object データを置ける
  • その後の段階は fake task、fake port、OOL descriptor switcheroo、任意読み取り primitive の構築へと続く
  • zone_require の回避も必要だった
    • 当時の zone_requirezone_map 外のページを許容し、ページ先頭 0x20 バイトを metadata のように解釈していた
    • 正しい zone index を入れれば、目的の zone を通過するためのパスのように使えた
    • このため task 用ページと mach port 用ページの、合計 2 ページが必要だった
  • この exploit は現在 GitHub で公開されている

公開後の分析とパッチ

  • 最新署名版向けの完全な 0day exploit 公開は、iOS jailbreak scene の注目を集めた
  • 当時 Project Zero にいた Brandon Azad は、exploit 公開後 4 時間以内に脆弱性を特定して Apple に通知した
  • exploit 公開 6 日後、Synacktiv は 新しい記事 で、iOS 12 の元の fix が memory leak を生み、その leak を修正しようとした試みが元のバグを復活させた可能性を扱った
  • exploit 公開 9 日後、Apple はパッチを配布した
  • その後 XNU には、このバグに対する 回帰テスト が追加された
  • 公開 54 日後、リバースエンジニアリング版の “tardy0n” が Odyssey jailbreak に組み込まれ、対象はやはり iOS 13.0〜13.5 だった

iOS 14 以降で変わった exploit 環境

  • iOS 14 は、Apple のカーネルセキュリティ戦略の変化を示している
  • iOS 14 より前は、初期 primitive が heap overflow、C++ オブジェクトの over-release、type confusion など何であっても、次の target はたいてい mach port だった
  • iOS 14 における最大の変化の 1 つが、allocator である kalloczalloc だった
    • zone map を複数の “kheap” 範囲に分割した
    • ユーザー制御データとカーネルオブジェクトが別々の heap に入るよう分離した
    • カーネルオブジェクトには sequestering を適用し、特定 zone に割り当てられた virtual address page が再起動まで他 zone に再利用されないようにした
    • physical memory は解放されても virtual memory range は他オブジェクトに再利用されず、カーネルオブジェクトの type confusion を事実上遮断した
  • guard page、起動ごとに変わる zone allocation 開始位置、その後の精緻化も加わり、cross-zone 攻撃の信頼性は大きく低下した
  • Apple は単に個別のバグを塞ぐのではなく、exploit 戦略を塞ぐ方式 へと変わった
    • exploit が kmsg struct を corruption target に使えば、その構造体は署名される
    • pipe buffer を安定したカーネル読み書きインターフェースに使えば、関連ポインタが PAC の対象になる
    • 無関係なオブジェクトを victim に使う手法が現れれば、そのオブジェクト型がハードニングされる
  • その結果、exploit 開発では初期の memory corruption 0day より exploit strategy のほうが価値を持つ状況が生まれた

公開知識の断絶

  • iOS 14 より前は、公開されている iOS セキュリティ研究の知識は非公開知識とほぼ同水準だったと評価されている
  • iOS 14 以降は、例外を除けば情報共有が事実上止まった状態になっている
  • iOS 19 beta まで数週間という時点でも、iOS 18 または iOS 17 に対する公開カーネル exploit は存在しないとまとめられている
  • Apple のセキュリティノートには実地で悪用された脆弱性が時折掲載されるが、公開情報は非公開研究に追いついていない
  • tachy0n が公開されてからまだ 5 年しか経っていないという事実は、iOS カーネル exploit 分野がどれほど急速に変化したかを示している

2件のコメント

 
ndrgrd 2025-05-26

Appleのハードウェアは素晴らしいですが、ソフトウェアはユーザーを縛り付けようという意図に満ちています。
自分で作ってビルドしたアプリを自分の端末でだけ動かそうとしても、100ドルのサブスクリプションが必要です。

中小規模のオープンソースアプリを使い、自分でビルドして利用する開発者なら、
Appleの端末で脆弱性を突いて脱獄しながらサイドロードするくらいなら、ただAndroidを使うほうが楽です。

 
GN⁺ 2025-05-25
Hacker News の意見
  • 1兆ドル企業に勝った方法が、Apple が特に苦手とする単純で退屈な作業、つまり回帰テストだったという点が印象的
    iOS 12 で jailbreak に使われた大きな脆弱性の一つである SockPuppet は、Project Zero の Ned Williamson が発見して Apple に報告し、iOS 12.3 でパッチが当てられ、その後 Project Zero のバグトラッカーで公開された
    ところが iOS 12.4 で、まるで一度もパッチされていなかったかのように再び現れた。おそらく Apple がそのバージョン向けに XNU を別ブランチへフォークした際、パッチを適用し損ねたのだと思われる
    これは、この種の脆弱性に対する回帰テストが存在しないことを強く示すシグナルで、既知の 1-day をいくつか自動化するだけで Pwn がすぐ命中させられた
    Linux、FreeBSD、OpenWRT、OpenSSH のようなプロジェクトの新バージョンに対して、過去の脆弱性を継続的に走らせる CI ファームを運用しているところがどれくらいあるのか気になる

    • 回帰テストは、修正したバグが再発していないか確認する標準的な QA 手順
      20年前の大学時代に Mozilla でボランティア QA をしていたが、レンダリング/レイアウトや JavaScript エンジンのバグを中心に、増え続ける回帰テスト群があった
      再現と修正確認のために最小テストケースを作っていたので、それをビルドパイプラインに入れるのも簡単だった
      バグは避けられないが、時間とお金をかけて直したバグが復活するのは最悪のシナリオ
      品質を重視する組織は確実に回帰テストへ投資するが、多くの組織は QA を尊重せず、そもそもやらないか、最安値の外注に丸投げする
      Apple が、歴史的に最も注目度の高いバグの種類である jailbreak に対する回帰テストを備えていなかったというのは本当に奇妙
      最近の Mozilla にはさまざまな批判があり得るが、2000年代初頭でも Tinderbox や Bugzilla のようなツールでかなり堅実な QA と CI/CD を運用していた
      DevOps が流行してこうしたやり方を一般化したとき、すでにみんなやっていたことではないのかと思っていたが、それは自分の勘違いだった
    • ここでいうプロジェクトに情報機関まで含めるなら、少なくとも G10 の情報機関とロシア、中国、北朝鮮、そして多数の民間グループはこうした作業をしていると見てよい
    • 根本的な問題は、多くの組織がセキュリティ業務を別の流れ、別のバグ分類として隔離していることにあるように見える
      セキュリティと機能開発の分離が生んだ、コンウェイの法則のような状況
      ビルド/リリース手順と成熟した回帰テスト群があったとしても、内部の組織構造上、こうしたセキュリティ問題はその中に入らない可能性が高い
    • 名前は思い出せないが、issue ごとにテストケースのディレクトリがある FOSS プロジェクトを見たことがある
      数千個は軽くあって、おそらく SQLite だったと思う
      見習うべきやり方
      修正をバックポートしないなら、テストもバックポートしない可能性が高そう
  • kheap separation、task port 緩和、SSV、SPTM といった言葉を見ていると、外国語で友人と普通に会話できていたのに、突然脳外科手術や核物理学の説明に移って理解が崖から落ちるような感覚だった
    溶鉱炉の改修についての会話を通訳しようとしたときも似たような感じだった
    jailbreak が今では昔ほど活発でないのは残念
    jailbreak した iPad で実用的なことをしたことはほとんどないが楽しかったし、今なら tethering アプリと UTM、それに JIT の回避策を入れたい
    SideStore も有望そうに見えたが、自分のアカウントは一時期有料の Apple Developer アカウントだったため、期限切れにならない App ID が10個残っていて、そのせいで新しいアカウントを作るか再びお金を払わない限り、UTM のようなアプリをインストールできない

    • 昔 iPhone 4 を jailbreak して使っていて、それが iPhone をメイン端末として使える事実上唯一の方法だった
      それを失ってから Android に戻ったが、その頃には Android も基本機能の面でかなり追いついていた
  • Apple は最近 jailbreak に100万ドルを支払うと聞いたが、それが自由市場価格の下限なのだろう

  • これが事実なら、Apple は驚くべき戦略を使ったことになる
    デバイスで root を得るあらゆる道を塞いでおけば、jailbreak 開発者たちが無料で見つけた脆弱性を Apple がパッチできる

    • ただし完全にそうというわけではない
      記事によると、非公開コミュニティは今も exploit を持っており、Apple はそれらをパッチしている
      公開コミュニティやこの開発者の側だけが、何らかの理由でもはやそうではないようだ
  • 記事全体で一番よかった文は「iOS 13.0 でそのバグを unpatch してくれた誰かにも感謝したい。それも非常に素晴らしい行動だった。」だった

  • 「5年後に自分たちがどこにいるのか想像もつかない」とあるが、私は想像できる
    iMessage は今もデバイス、アカウント、データの窃取を可能にしている

  • tethered なのか untethered なのか記事には書かれていなかった

    • tachy0n はローカル権限昇格(LPE)なので、その分類はあまり当てはまらない
      これが含まれて配布された jailbreak である unc0ver は、おそらく「semi-untethered」だったと記憶している