# kok3shi (iOS 9.3.xのJailbreak戦略)

# 概要

ここでは、arm64デバイスのiOS 9.3.xをjailbreakするために使用した戦略等についてを振り返ります。
筆者である私は専門家ではないため、記事の一部に間違いがある可能性がありますが、何卒ご容赦ください。

WARNING

この記事の内容は全て教育、学習目的で提供されるものであり、これらを悪用することを禁じます。
本サイトでは、iOSを最新バージョンへ更新して修正パッチを適用する、もしくは新しいセキュリティ機構を備えたデバイスに買い換えることを強くお勧めします。

# iOS 9.3.3 jailbreak

iOS 9.3.3向けjailbreakは2016年7月にPangu 9として一般に提供され、その後、2016年8月4日にリリースされたiOS 9.3.4によって使用されたバグが修正されました。
iOS 9以降を搭載するarm64(Apple A7 - Apple A9X)デバイスにはカーネルに対するパッチを防止する機能(以下 KPP)が導入されており、iOS 8以前のようにカーネルにパッチを適用してAMFIやSandbox等のチェックを無効化することはできません。では、iOS 9.3.3のjailbreakではどのような手法が取られたのでしょうか。

# 保護

KPPはEL3上で動作しています。KPPは__TEXT__DATA.__constの整合性をチェックし、整合性が取れなければカーネルパニックを引き起こすようになっています。仮にKPPデバイス上で保護領域にパッチを適用した場合でもすぐにパニックが発生することはありません。しかし、どこかのタイミングでKPPによるチェックが必ず行われ、その直後にカーネルパニックに陥ります。また、KPPにはいつ実行されるのか予測可能という設計上の欠陥があります。

# 回避策

# DATA only jailbreak

PanguによるiOS 9.3.3のjailbreakは、KPPで保護されていないDATA領域のみにパッチを設定することで実際にこの制限を回避して、様々なパッチを適用させました。 933
iOS 9.3.3のKPPは__TEXT__DATA.__constを保護しています。AMFIやSandboxのMAC policiesは__constにあるため、ポインタを改竄してNULLに置き換えたりhookしたりすることはできません。しかし、__gotなどのDATA領域は保護されていませんでした。私はPangu9のjailbreakを解析することで、どのようなパッチを適用していたのかを調べてみました。

調べてみると、Pangu9は__gotにストアされているstub関数のポインタを改竄することで様々なチェックを回避していました。

# AMFI

# _PE_i_can_has_debugger

ret1 gadgatに置き換えることで、常にdebugが有効になります。

# _cs_enforcement

ret0 gadgetに置き換えることで、コード署名の施行が無効化されて任意のコード実行が可能になります。

# _vnode_isreg (amfi_execve_hook)

ここはshellcodeのポインタに置き換えられていました。
DATAのみのjailbreakといってもshellcodeは有効なようで、hookされた_vnode_isregはcred_label_update_execveによって呼び出された際に任意のcsflagsを設定するshellcodeを実行するように変更されていました。この手法によって、Pangu9ではMAC policyへのhookを使用せずにいくつかの重要な強制アクセス制御を操作しているようです。

# Sandbox

# _PE_i_can_has_debugger

ret1 gadgatに置き換えることで、常にdebugが有効になります。

# _memset (mac policlies)

ここもshellcodeのポインタに置き換えられていました。
MAC policy hooksはまず最初にmemset.stubを呼び出すため、_memsetをhookして制御を奪った後、shellcodeによってX0 register = 0を設定し、最後にそれらの関数のret gadgetにジャンプします。これらの関数は必ずreturn 0を返すようになり、チェックを回避可能になります。

# remount rootfs

jailbreakでは、read-onlyでmountされているroot filesystemをread-writeで再マウントして、実際にrootfsに書き込みを行えるようにしますが、これを行うためには__mac_mountとLwVM.kextのチェックにパッチを適用する必要があります。
Pangu9の__mac_mountへのパッチはKPPと競合することで達成されているように見えました。KPPのチェックは必ずしもすぐに行われるわけではないため、非常に短い時間でPatch/Restoreを行うことでKPPに検知されることなく一時的にパッチを適用できます。
一方、LwVM::_mapForIOパッチは、保護をcheckoutするパッチを適用するのではなく、パーティション構造内のrootパーティションの保護を解除することによって実際に適用されました。これは、パーティション構造へのポインタの配列を含むLwVMマネージャを見つけることによって行うことが可能なようです。

# iOS 9.3.4での緩和策

iOS 9.3.4のセキュリティレポートでは、Pangu9で使用されたIOMobileFrameBufferのバグ修正のみが掲載されていますが、実際にはKPPにも修正パッチが適用されていました。 iOS 9.3.4のKPPは、誤って(...?)保護されていなかった__gotなどをチェック対象としてPangu9のカーネルパッチ手法を修正しました。 934-1

しかし、KPPには先程述べたように設計上の欠陥があるため、実際にはKPPによるチェック自体を回避することができます。この回避策はyalu102によって、以下の手順で実証されました。

  • 偽のTTBR1_EL1を設定して偽のページを作成します。
  • CPACR_EL1の呼び出しをkppshへのトランポリンに置き換えます。
  • idle_sleep handler と deep_sleep handlerをhookしてTTBR1_EL1を偽のものに切り替えます。
  • CPACR_EL1の呼び出しが起こるとkppshが実行され、オリジナルのTTBR1_EL1に切り替わってからKPPがトリガーされるため、チェックは常に合格するようになります。
  • CPACR_EL1の呼び出し直後にTTBR1_EL1を偽のものに切り替え、パッチをやり直します。

私は、この手法を使用してkextのDATAセグメントを書き込み可能にすることで、iOS 9.3.4+でもPangu9と同様のパッチを適用できるようになるのではないかと考え、以下のようなパッチ戦略を立てました。

# iOS 9.3.4 jailbreak

以下のパッチ戦略は実際に私が使用した手法となります。

  • iOS 9.3.4以降を検知した場合、jailbreakパッチを行う前にまずKPP bypassをセットアップするようにします。
  • カーネルをdumpして実際のAMFI/Sandbox/LwVMのkextの位置を特定して、それぞれの__DATAセグメントのアドレスを取得します。
  • KPP bypassをセットアップした後、__DATAセグメントを書き込み可能ページにRemapして、この領域を再び書き込み可能にします。 934-2
  • 最後に、書き込み可能となった__DATA領域に対してPangu9のパッチを適用します。 934-3

また、LwVMと__mac_mountパッチに関しては、Pangu9のものとは異なるパッチを使用しました。

# LwVM

# _PE_i_can_has_kernel_configuration

partition->isWriteProtectedのチェックの前に存在する、PE_i_can_has_kernel_configuration.stub関数のチェックを置き換えます。 PE_i_can_has_kernel_configurationが実行されると、partition->isWriteProtectedのチェック合格後の領域にジャンプするようになるため、実際に書き込み保護のチェックを回避できます。 iOS 9.3.0-9.3.1ではPE_i_can_has_kernel_configurationの代わりにPE_i_can_has_debuggerがこれを行います。
また、この手法はiOS 9.2以前に対しては有効ではありません。

# rootvnode

rootfsのread-writeでのマウントは__mac_mountが制限していますが、rootvnode構造内のv_flagのVROOTをunsetすることでread-writeで再マウント可能になります。vfs_syscalls.c

最終的に、これらを適用することでarm64 iOS 9.3.4搭載のデバイスをjailbreakすることに成功しました。 934-ss

ただし、4kデバイス (Apple A7/A8)では、KPP bypass自体の信頼性に対する問題が報告されている点には注意が必要です。

# 最後に。(時の経過の産物)

iOS 9.3.4は2016年8月にリリースされたOSですが、私がこれらのチャレンジを行なったのは2021年から2022年にかけてです。この数年の間に、checkm8と呼ばれるiOSデバイスのSerureROMに存在するバグが公開されたため、AP側の制御に関してはROMに攻撃することで容易に達成可能になりました。
代表的なものが、このcheckm8 exploitを使用してjailbreakを実装したcheckra1nと呼ばれるツールです。checkra1nはSecureROMでexploitを実行した後、pongoOS と呼ばれる独自のbootloaderを起動します。pongoOS上ではカーネル等に様々なパッチを適用することが可能で、パッチを適用してからデバイスを起動させることができます。
これはKPP(や、後続の保護機構であるKTRR)の保護が生じる前の段階にあるため、保護が開始される前にパッチを適用することで、KPP/KTRRの制限の全てを回避可能になります。

checkra1nはiOS 12以降を対象としていますが、pongoOS自体はiOS 8でも動作することが確認されています。私は最終的にiPhone 6s iOS 9.3.4搭載のデバイスを使用して、pongoOS上からカーネルパッチを適用してデバイスをjailbreakすることに成功しました。

  • checkra1nをCLIで実行してpongoOSを起動します。
  • 自作モジュールをpongoOSが起動しているiPhone 6sに送信して読み込ませます。
  • モジュールはカーネルにパッチを適用しますが、保護機構はまだ働いていないため全て成功します。
  • パッチを適用したら起動します。KPPによる保護が開始されますが、既にパッチ済み状態のため、jailbreak状態で起動します。 934-pongoos

この手法でパッチする場合、KPPと競合することなくjailbreakを適用することが可能となるため、先程問題視した4kデバイスのiOS 9.3.4-9.3.5デバイスでも安定した環境を得られる可能性があります。

# 参考

# デモ

少々汚いコードにではありますが、参考になれば幸いです。