mirror of
https://github.com/Lakr233/vphone-cli.git
synced 2026-09-05 09:04:27 +00:00
kernel: Introduce EXP variant and hv_vmm_present OID rename (KernelEXPPatcher)
Introduces a new `exp` firmware variant on top of JB and ports the
`hv_vmm_present` sysctl rename to it.
Variant infrastructure
----------------------
- Adds `case exp` to `FirmwarePipeline.Variant`,
`VPhoneCLI.PatchFirmwareCLI.VariantOption`, and
`VPhoneVirtualMachine.Variant`. Every `switch variant` block in
`FirmwarePipeline.buildComponentList` includes the new case.
- Adds `make fw_patch_exp` + `make cfw_install_exp` Makefile targets
and an `EXP=1` flag for `make setup_machine`, mutually exclusive with
`JB=1` / `DEV=1` / `LESS=1`. `SPOOF_BUILD` env var is threaded
through for later use by the build-version step.
- Adds `--exp` to `scripts/setup_machine.sh` alongside `--jb` /
`--dev` / `--less`. The post-install JB-Finalize block also fires
for EXP (since EXP inherits the JB rootfs deployment).
- Adds `scripts/cfw_install_exp.sh` as the EXP install script
starting point: identical phase set to `cfw_install_jb.sh` (JB-1..JB-5)
with banner/header/footer updated for EXP. Subsequent commits in this
branch add the EXP-only experimental phases on top.
- Updates the variants table in `AGENTS.md` / `README.md` and the
three translated READMEs to include the new `Experimental (EXP)`
row, plus a paragraph describing what EXP adds on top of JB.
Kernel patch
------------
- New `KernelEXPPatcher` orchestrator in
`sources/FirmwarePatcher/Kernel/`, chained after `KernelPatcher` +
`KernelJBPatcher` for the `.exp` variant only. Inherits
`KernelJBPatcherBase` to reuse the JB symbol-table / ADRP-BL index /
branch-encoder infrastructure.
- New `KernelEXPPatchHvVmmRename` patch in
`sources/FirmwarePatcher/Kernel/EXPPatches/`. Part A flips byte 0 of
the NUL-delimited `\\0hv_vmm_present\\0` cstring (the sysctl OID's
`oid_name` value) — `'h'` -> `'X'` — so the kernel resolves
`sysctlbyname("kern.hv_vmm_present")` as ENOENT and
`sysctlbyname("kern.Xv_vmm_present")` to the OID's real int value.
Part B mangles byte 5 of every kernel-internal occurrence of
`kern.hv_vmm_present` so callers keep hitting the renamed OID; two
byte-aligned forms are covered (NUL-delimited cstring + sandbox-profile
name-token with trailing `\\x0f`).
- Patch IDs are `kernelcache_exp.hv_vmm_oid_rename` and
`kernelcache_exp.hv_vmm_internal_caller_mangle`. Idempotent.
- `KernelJBPatcher` is unchanged at the call-site level (no
`patchHvVmmRename` call); its docstring is updated to point at
`KernelEXPPatcher` for the EXP-only patch.
JB and other variants are NOT affected: `cfw_install_jb.sh` and
`cfw_install_dev.sh` are untouched in this commit.
This commit is contained in:
@@ -28,14 +28,17 @@ For any changes applying new patches, also update research/0_binary_patch_compar
|
||||
|
||||
## Firmware Variants
|
||||
|
||||
| Variant | Boot Chain | CFW | Make Targets |
|
||||
| --------------- | :---------: | :-------: | ---------------------------------- |
|
||||
| **Regular** | 51 patches | 10 phases | `fw_patch` + `cfw_install` |
|
||||
| **Development** | 65 patches | 12 phases | `fw_patch_dev` + `cfw_install_dev` |
|
||||
| **Jailbreak** | 126 patches | 14 phases | `fw_patch_jb` + `cfw_install_jb` |
|
||||
| Variant | Boot Chain | CFW | Make Targets |
|
||||
| ---------------- | :------------: | :-------: | ---------------------------------- |
|
||||
| **Regular** | 51 patches | 10 phases | `fw_patch` + `cfw_install` |
|
||||
| **Development** | 65 patches | 12 phases | `fw_patch_dev` + `cfw_install_dev` |
|
||||
| **Jailbreak** | 126 patches | 14 phases | `fw_patch_jb` + `cfw_install_jb` |
|
||||
| **Experimental** | 140 patches | 18 phases | `fw_patch_exp` + `cfw_install_exp` |
|
||||
|
||||
> JB finalization (symlinks, Sileo, apt, TrollStore) runs automatically on first boot via `/cores/vphone_jb_setup.sh` LaunchDaemon. Monitor progress: `/var/log/vphone_jb_setup.log`.
|
||||
|
||||
> EXP is a JB superset that patches the kernel and DSC to make some Apple services think the device is not a VM, while keeping VM-specific services (graphics passthrough, compute/accel fast paths) working correctly. Other variants are deliberately NOT affected by these changes.
|
||||
|
||||
See `research/` for detailed firmware pipeline, component origins, patch breakdowns, and boot flow documentation.
|
||||
|
||||
## Architecture
|
||||
|
||||
@@ -46,12 +46,18 @@ help:
|
||||
@echo " make setup_machine Full setup through First Boot"
|
||||
@echo " Options: JB=1 Jailbreak firmware/CFW path"
|
||||
@echo " DEV=1 Dev firmware/CFW path (dev TXM + cfw_install_dev)"
|
||||
@echo " EXP=1 Experimental firmware/CFW path (JB + EXP-only patches:"
|
||||
@echo " kernel hv_vmm rename, DSC byte-5 mangle, watchdogd surgical,"
|
||||
@echo " DT identity properties, post-restore DT rewrite, opt-in build spoof)"
|
||||
@echo " LESS=1 Build, keeping iOS security mitigations enabled."
|
||||
@echo " SKIP_PROJECT_SETUP=1 Skip setup_tools/build"
|
||||
@echo " NONE_INTERACTIVE=1 Auto-continue prompts + boot analysis"
|
||||
@echo " SUDO_PASSWORD=... Preload sudo credential for setup flow"
|
||||
@echo " NO_BINPACK=1 Excludes the SSH, VNC, ... binaries from being installed (patchless-only, currently)"
|
||||
@echo " NO_VPHONED=1 Excludes vphoned from being installed (patchless-only, currently)"
|
||||
@echo " SPOOF_BUILD=<id> (EXP only) Rewrite ProductBuildVersion in SystemVersion.plist to <id>"
|
||||
@echo " e.g. SPOOF_BUILD=23F77 makes Settings -> About show that build."
|
||||
@echo " Omitted/empty -> EXP-JB-7 skipped, build version stays at the IPSW value."
|
||||
@echo ""
|
||||
@echo "Setup (one-time):"
|
||||
@echo " make setup_tools Install all tools (brew, trustcache, insert_dylib, venv+pymobiledevice3)"
|
||||
@@ -96,6 +102,7 @@ help:
|
||||
@echo " NO_VPHONED=1 Excludes vphoned from being installed"
|
||||
@echo " make fw_patch_dev Patch boot chain with Swift pipeline (dev mode TXM patches)"
|
||||
@echo " make fw_patch_jb Patch boot chain with Swift pipeline (dev + JB extensions)"
|
||||
@echo " make fw_patch_exp Patch boot chain with Swift pipeline (JB + EXP experimental)"
|
||||
@echo ""
|
||||
@echo "Restore:"
|
||||
@echo " make restore_get_shsh Dump SHSH response from Apple"
|
||||
@@ -110,6 +117,7 @@ help:
|
||||
@echo " make cfw_install Install CFW mods via SSH"
|
||||
@echo " make cfw_install_dev Install CFW mods via SSH (dev mode)"
|
||||
@echo " make cfw_install_jb Install CFW + JB extensions (jetsam/procursus/basebin)"
|
||||
@echo " make cfw_install_exp Install CFW + JB + EXP experimental (hv_vmm rename, post-restore DT, build spoof)"
|
||||
@echo ""
|
||||
@echo "Variables: VM_DIR=$(VM_DIR) CPU=$(CPU) MEMORY=$(MEMORY) DISK_SIZE=$(DISK_SIZE)"
|
||||
|
||||
@@ -123,18 +131,21 @@ setup_machine:
|
||||
@if count=0; \
|
||||
[ -n "$(filter 1 true yes YES TRUE,$(JB))" ] && count=$$((count+1)); \
|
||||
[ -n "$(filter 1 true yes YES TRUE,$(DEV))" ] && count=$$((count+1)); \
|
||||
[ -n "$(filter 1 true yes YES TRUE,$(EXP))" ] && count=$$((count+1)); \
|
||||
[ -n "$(filter 1 true yes YES TRUE,$(LESS))" ] && count=$$((count+1)); \
|
||||
[ $$count -gt 1 ]; then \
|
||||
echo "Error: JB=1, DEV=1, and LESS=1 are mutually exclusive"; \
|
||||
echo "Error: JB=1, DEV=1, EXP=1, and LESS=1 are mutually exclusive"; \
|
||||
exit 1; \
|
||||
fi
|
||||
SUDO_PASSWORD="$(SUDO_PASSWORD)" \
|
||||
NONE_INTERACTIVE="$(NONE_INTERACTIVE)" \
|
||||
NO_BINPACK="$(NO_BINPACK)" \
|
||||
NO_VPHONED="$(NO_VPHONED)" \
|
||||
SPOOF_BUILD="$(SPOOF_BUILD)" \
|
||||
zsh $(SCRIPTS)/setup_machine.sh \
|
||||
$(if $(filter 1 true yes YES TRUE,$(JB)),--jb,) \
|
||||
$(if $(filter 1 true yes YES TRUE,$(DEV)),--dev,) \
|
||||
$(if $(filter 1 true yes YES TRUE,$(EXP)),--exp,) \
|
||||
$(if $(filter 1 true yes YES TRUE,$(LESS)),--less,) \
|
||||
$(if $(filter 1 true yes YES TRUE,$(SKIP_PROJECT_SETUP)),--skip-project-setup,)
|
||||
|
||||
@@ -342,6 +353,9 @@ fw_patch_dev: patcher_build
|
||||
fw_patch_jb: patcher_build
|
||||
"$(CURDIR)/$(PATCHER_BINARY)" patch-firmware --vm-directory "$(CURDIR)/$(VM_DIR)" --variant jb
|
||||
|
||||
fw_patch_exp: patcher_build
|
||||
"$(CURDIR)/$(PATCHER_BINARY)" patch-firmware --vm-directory "$(CURDIR)/$(VM_DIR)" --variant exp
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
# Restore
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
@@ -429,7 +443,7 @@ ramdisk_send:
|
||||
# CFW
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
.PHONY: cfw_install cfw_install_dev cfw_install_jb
|
||||
.PHONY: cfw_install cfw_install_dev cfw_install_jb cfw_install_exp
|
||||
|
||||
cfw_install:
|
||||
cd $(VM_DIR) && $(if $(SSH_PORT),SSH_PORT="$(SSH_PORT)") _VPHONE_PATH="$$PATH" zsh "$(CURDIR)/$(SCRIPTS)/cfw_install.sh" .
|
||||
@@ -439,3 +453,6 @@ cfw_install_dev:
|
||||
|
||||
cfw_install_jb:
|
||||
cd $(VM_DIR) && $(if $(SSH_PORT),SSH_PORT="$(SSH_PORT)") _VPHONE_PATH="$$PATH" zsh "$(CURDIR)/$(SCRIPTS)/cfw_install_jb.sh" .
|
||||
|
||||
cfw_install_exp:
|
||||
cd $(VM_DIR) && $(if $(SSH_PORT),SSH_PORT="$(SSH_PORT)") $(if $(SPOOF_BUILD),SPOOF_BUILD="$(SPOOF_BUILD)") _VPHONE_PATH="$$PATH" zsh "$(CURDIR)/$(SCRIPTS)/cfw_install_exp.sh" .
|
||||
|
||||
@@ -17,17 +17,20 @@ Boot a virtual iPhone (iOS 26) via Apple's Virtualization.framework using PCC re
|
||||
|
||||
## Firmware Variants
|
||||
|
||||
Four patch variants are available with increasing levels of security bypass:
|
||||
Five patch variants are available with increasing levels of security bypass:
|
||||
|
||||
| Variant | Boot Chain | CFW | Make Targets |
|
||||
| --------------- | :---------: | :-------: | ----------------------------------- |
|
||||
| **Patchless** | 3 patches | 2 phases | `fw_patch_less` + `boot_less` |
|
||||
| **Regular** | 41 patches | 10 phases | `fw_patch` + `cfw_install` |
|
||||
| **Development** | 52 patches | 12 phases | `fw_patch_dev` + `cfw_install_dev` |
|
||||
| **Jailbreak** | 112 patches | 14 phases | `fw_patch_jb` + `cfw_install_jb` |
|
||||
| Variant | Boot Chain | CFW | Make Targets |
|
||||
| ---------------- | :------------: | :--------: | ----------------------------------- |
|
||||
| **Patchless** | 3 patches | 2 phases | `fw_patch_less` + `boot_less` |
|
||||
| **Regular** | 41 patches | 10 phases | `fw_patch` + `cfw_install` |
|
||||
| **Development** | 52 patches | 12 phases | `fw_patch_dev` + `cfw_install_dev` |
|
||||
| **Jailbreak** | 112 patches | 14 phases | `fw_patch_jb` + `cfw_install_jb` |
|
||||
| **Experimental** | 140 patches | 18 phases | `fw_patch_exp` + `cfw_install_exp` |
|
||||
|
||||
> JB finalization (symlinks, Sileo, apt, TrollStore) runs automatically on first boot via `/cores/vphone_jb_setup.sh` LaunchDaemon. Monitor progress: `/var/log/vphone_jb_setup.log`.
|
||||
|
||||
> **Experimental (EXP)** is a JB superset that patches the kernel and DSC to make some Apple services think the device is not a VM, while keeping VM-specific services (graphics passthrough, compute/accel fast paths) working correctly. Other variants are deliberately NOT affected.
|
||||
|
||||
See [research/0_binary_patch_comparison.md](./research/0_binary_patch_comparison.md) for the detailed per-component breakdown.
|
||||
|
||||
## Prerequisites
|
||||
@@ -115,6 +118,8 @@ make setup_machine # full automation through "First Boot" (includes r
|
||||
# LESS=1 for patchless variant (- AMFI, SSV, Img4, TXM bypasses)
|
||||
# DEV=1 for dev variant (+ TXM entitlement/debug bypasses)
|
||||
# JB=1 for jailbreak variant (+ full security bypass)
|
||||
# EXP=1 for experimental variant (JB + research patches: hv_vmm rename, DT identity, post-restore rewrite)
|
||||
# SPOOF_BUILD=<id> (EXP only) Rewrite SystemVersion.plist ProductBuildVersion to <id>, e.g. 23F77
|
||||
```
|
||||
|
||||
## Manual Setup
|
||||
@@ -129,6 +134,7 @@ make fw_patch # patch boot chain (regular variant)
|
||||
# or: sudo make fw_patch_less # patchless variant (- AMFI, SSV, Img4, TXM bypasses)
|
||||
# or: make fw_patch_dev # dev variant (+ TXM entitlement/debug bypasses)
|
||||
# or: make fw_patch_jb # jailbreak variant (+ full security bypass)
|
||||
# or: make fw_patch_exp # experimental variant (JB + research stack)
|
||||
```
|
||||
|
||||
### Cleaning
|
||||
@@ -198,6 +204,8 @@ python3 -m pymobiledevice3 usbmux forward 2222 22
|
||||
# terminal 2
|
||||
make cfw_install
|
||||
# or: make cfw_install_jb # jailbreak variant
|
||||
# or: make cfw_install_exp # experimental variant (JB + research stack)
|
||||
# or: SPOOF_BUILD=23F77 make cfw_install_exp # additionally rewrite ProductBuildVersion
|
||||
```
|
||||
|
||||
## First Boot
|
||||
|
||||
+15
-7
@@ -17,17 +17,20 @@ Apple の Virtualization.framework と PCC の研究用 VM インフラを使用
|
||||
|
||||
## ファームウェアバリアント
|
||||
|
||||
セキュリティバイパスのレベルが異なる4つのパッチバリアントが利用可能です:
|
||||
セキュリティバイパスのレベルが異なる5つのパッチバリアントが利用可能です:
|
||||
|
||||
| バリアント | ブートチェーン | CFW | Make ターゲット |
|
||||
| ---------- | :------------: | :---------: | -------------------------------------------- |
|
||||
| **Patchless** | 3 パッチ | 2 フェーズ | `fw_patch_less` + `boot_less` |
|
||||
| **通常版** | 41 パッチ | 10 フェーズ | `fw_patch` + `cfw_install` |
|
||||
| **開発版** | 52 パッチ | 12 フェーズ | `fw_patch_dev` + `cfw_install_dev` |
|
||||
| **脱獄版** | 112 パッチ | 14 フェーズ | `fw_patch_jb` + `cfw_install_jb` |
|
||||
| バリアント | ブートチェーン | CFW | Make ターゲット |
|
||||
| ------------- | :----------------: | :----------: | -------------------------------------------- |
|
||||
| **Patchless** | 3 パッチ | 2 フェーズ | `fw_patch_less` + `boot_less` |
|
||||
| **通常版** | 41 パッチ | 10 フェーズ | `fw_patch` + `cfw_install` |
|
||||
| **開発版** | 52 パッチ | 12 フェーズ | `fw_patch_dev` + `cfw_install_dev` |
|
||||
| **脱獄版** | 112 パッチ | 14 フェーズ | `fw_patch_jb` + `cfw_install_jb` |
|
||||
| **実験版** | 脱獄 + EXP 専用 | 脱獄 + EXP | `fw_patch_exp` + `cfw_install_exp` |
|
||||
|
||||
> JB最終設定(シンボリックリンク、Sileo、apt、TrollStore)は `/cores/vphone_jb_setup.sh` LaunchDaemon により初回起動時に自動実行されます。進捗確認:`/var/log/vphone_jb_setup.log`。
|
||||
|
||||
> **実験版(EXP)** は脱獄版の上位集合で、リサーチブランチの実験的パッチを追加で実行します:カーネルの `hv_vmm_present` sysctl リネーム + カーネル内部呼び出し元の改変(`KernelEXPPatcher`)、サインインブラックリスト付きの DSC バイト5改変 + スロット再認証、watchdogd 精密 2 命令パッチ(EXP-JB-3.5)、fw_patch 時点での DeviceTree アイデンティティプロパティ 8 件、復元後の DT アイデンティティ書き換え(EXP-JB-6)、`SPOOF_BUILD=<id>` によるオプトイン式の `SystemVersion.plist` `ProductBuildVersion` 書き換え(EXP-JB-7)。他のバリアントは意図的に影響を受けません。
|
||||
|
||||
詳細なコンポーネントごとの内訳については [research/0_binary_patch_comparison.md](../research/0_binary_patch_comparison.md) を参照してください。
|
||||
|
||||
## 前提条件
|
||||
@@ -103,6 +106,8 @@ make setup_machine # 初回起動までを完全自動化(復元/
|
||||
# LESS=1 で patchless バリアント(- AMFI, SSV, Img4, TXM バイパス)
|
||||
# DEV=1 で開発バリアント(+ TXM entitlement/デバッグバイパス)
|
||||
# JB=1 で脱獄バリアント(dev + 完全セキュリティバイパス)
|
||||
# EXP=1 で実験バリアント(脱獄 + リサーチパッチ: hv_vmm リネーム、DT アイデンティティ、復元後書き換え)
|
||||
# SPOOF_BUILD=<id>(EXP 限定)SystemVersion.plist の ProductBuildVersion を <id> に書き換え、例: 23F77
|
||||
```
|
||||
|
||||
## 手動セットアップ
|
||||
@@ -117,6 +122,7 @@ make fw_patch # ブートチェーンのパッチ当て(通常
|
||||
# または: sudo make fw_patch_less # patchless バリアント(- AMFI, SSV, Img4, TXM バイパス)
|
||||
# または: make fw_patch_dev # 開発バリアント(+ TXM entitlement/デバッグバイパス)
|
||||
# または: make fw_patch_jb # 脱獄バリアント(dev + 完全セキュリティバイパス)
|
||||
# または: make fw_patch_exp # 実験バリアント(脱獄 + リサーチパッチスタック)
|
||||
```
|
||||
|
||||
### クリーンアップ
|
||||
@@ -186,6 +192,8 @@ python3 -m pymobiledevice3 usbmux forward 2222 22
|
||||
# ターミナル 2
|
||||
make cfw_install
|
||||
# または: make cfw_install_jb # 脱獄バリアント
|
||||
# または: make cfw_install_exp # 実験バリアント(脱獄 + リサーチパッチスタック)
|
||||
# または: SPOOF_BUILD=23F77 make cfw_install_exp # ProductBuildVersion も書き換え
|
||||
```
|
||||
|
||||
## 初回起動
|
||||
|
||||
+15
-7
@@ -17,17 +17,20 @@ PCC 리서치 VM 인프라와 Apple의 Virtualization.framework를 사용하여
|
||||
|
||||
## 펌웨어 변형
|
||||
|
||||
보안 우회 수준이 다른 4가지 패치 변형을 사용할 수 있습니다:
|
||||
보안 우회 수준이 다른 5가지 패치 변형을 사용할 수 있습니다:
|
||||
|
||||
| 변형 | 부트 체인 | CFW | Make 타겟 |
|
||||
| ------------ | :-------: | :-------: | ------------------------------------------- |
|
||||
| **Patchless** | 3 패치 | 2 페이즈 | `fw_patch_less` + `boot_less` |
|
||||
| **일반** | 41 패치 | 10 페이즈 | `fw_patch` + `cfw_install` |
|
||||
| **개발** | 52 패치 | 12 페이즈 | `fw_patch_dev` + `cfw_install_dev` |
|
||||
| **탈옥** | 112 패치 | 14 페이즈 | `fw_patch_jb` + `cfw_install_jb` |
|
||||
| 변형 | 부트 체인 | CFW | Make 타겟 |
|
||||
| -------------- | :---------------: | :--------: | ------------------------------------------- |
|
||||
| **Patchless** | 3 패치 | 2 페이즈 | `fw_patch_less` + `boot_less` |
|
||||
| **일반** | 41 패치 | 10 페이즈 | `fw_patch` + `cfw_install` |
|
||||
| **개발** | 52 패치 | 12 페이즈 | `fw_patch_dev` + `cfw_install_dev` |
|
||||
| **탈옥** | 112 패치 | 14 페이즈 | `fw_patch_jb` + `cfw_install_jb` |
|
||||
| **실험** | 탈옥 + EXP 전용 | 탈옥 + EXP | `fw_patch_exp` + `cfw_install_exp` |
|
||||
|
||||
> JB 최종 설정(심볼릭 링크, Sileo, apt, TrollStore)은 `/cores/vphone_jb_setup.sh` LaunchDaemon을 통해 첫 번째 부팅 시 자동으로 실행됩니다. 진행 상황 확인: `/var/log/vphone_jb_setup.log`.
|
||||
|
||||
> **실험(EXP)** 변형은 탈옥 변형의 상위 집합으로, 연구 브랜치의 실험적 패치를 추가로 실행합니다: 커널 `hv_vmm_present` sysctl 이름 변경 + 커널 내부 호출자 변조(`KernelEXPPatcher`), 로그인 블랙리스트가 있는 DSC 바이트 5 변조 + 슬롯 재인증, watchdogd 정밀 2개 명령어 패치(EXP-JB-3.5), 펌웨어 패치 단계의 DeviceTree 식별 속성 8개, 복원 후 DT 식별 재작성(EXP-JB-6), 그리고 `SPOOF_BUILD=<id>`를 통한 옵트인 `SystemVersion.plist` `ProductBuildVersion` 재작성(EXP-JB-7). 다른 변형은 의도적으로 영향을 받지 않습니다.
|
||||
|
||||
컴포넌트별 상세 분류는 [research/0_binary_patch_comparison.md](../research/0_binary_patch_comparison.md)를 참조하세요.
|
||||
|
||||
## 사전 요구 사항
|
||||
@@ -103,6 +106,8 @@ make setup_machine # "First Boot"까지의 전체 과정 자동화 (
|
||||
# LESS=1 Patchless 변형 (- AMFI, SSV, Img4, TXM 우회)
|
||||
# DEV=1 개발 변형 (+ TXM 권한/디버그 우회)
|
||||
# JB=1 탈옥 변형 (dev + 전체 보안 우회)
|
||||
# EXP=1 실험 변형 (탈옥 + 연구 패치: hv_vmm 이름 변경, DT 식별, 복원 후 재작성)
|
||||
# SPOOF_BUILD=<id> (EXP 전용) SystemVersion.plist의 ProductBuildVersion을 <id>로 재작성, 예: 23F77
|
||||
```
|
||||
|
||||
## 수동 설정
|
||||
@@ -117,6 +122,7 @@ make fw_patch # 부트 체인 패치 (일반 변형)
|
||||
# 또는: sudo make fw_patch_less # Patchless 변형 (- AMFI, SSV, Img4, TXM 우회)
|
||||
# 또는: make fw_patch_dev # 개발 변형 (+ TXM 권한/디버그 우회)
|
||||
# 또는: make fw_patch_jb # 탈옥 변형 (dev + 전체 보안 우회)
|
||||
# 또는: make fw_patch_exp # 실험 변형 (탈옥 + 연구 패치 스택)
|
||||
```
|
||||
|
||||
### 정리
|
||||
@@ -186,6 +192,8 @@ python3 -m pymobiledevice3 usbmux forward 2222 22
|
||||
# 터미널 2
|
||||
make cfw_install
|
||||
# 또는: make cfw_install_jb # 탈옥 변형
|
||||
# 또는: make cfw_install_exp # 실험 변형 (탈옥 + 연구 패치 스택)
|
||||
# 또는: SPOOF_BUILD=23F77 make cfw_install_exp # 추가로 ProductBuildVersion 재작성
|
||||
```
|
||||
|
||||
## 첫 부팅
|
||||
|
||||
+15
-7
@@ -17,17 +17,20 @@
|
||||
|
||||
## 固件变体
|
||||
|
||||
提供四种补丁变体,安全绕过级别逐步递增:
|
||||
提供五种补丁变体,安全绕过级别逐步递增:
|
||||
|
||||
| 变体 | 启动链 | 自定义固件 | Make 目标 |
|
||||
| ------------- | :--------: | :--------: | ------------------------------------------- |
|
||||
| **Patchless** | 3 个补丁 | 2 个阶段 | `fw_patch_less` + `boot_less` |
|
||||
| **常规版** | 41 个补丁 | 10 个阶段 | `fw_patch` + `cfw_install` |
|
||||
| **开发版** | 52 个补丁 | 12 个阶段 | `fw_patch_dev` + `cfw_install_dev` |
|
||||
| **越狱版** | 112 个补丁 | 14 个阶段 | `fw_patch_jb` + `cfw_install_jb` |
|
||||
| 变体 | 启动链 | 自定义固件 | Make 目标 |
|
||||
| -------------- | :--------------: | :--------: | ------------------------------------------- |
|
||||
| **Patchless** | 3 个补丁 | 2 个阶段 | `fw_patch_less` + `boot_less` |
|
||||
| **常规版** | 41 个补丁 | 10 个阶段 | `fw_patch` + `cfw_install` |
|
||||
| **开发版** | 52 个补丁 | 12 个阶段 | `fw_patch_dev` + `cfw_install_dev` |
|
||||
| **越狱版** | 112 个补丁 | 14 个阶段 | `fw_patch_jb` + `cfw_install_jb` |
|
||||
| **实验版** | 越狱 + EXP 专属 | 越狱 + EXP | `fw_patch_exp` + `cfw_install_exp` |
|
||||
|
||||
> 越狱最终配置(符号链接、Sileo、apt、TrollStore)通过 `/cores/vphone_jb_setup.sh` LaunchDaemon 在首次启动时自动运行。查看进度:`/var/log/vphone_jb_setup.log`。
|
||||
|
||||
> **实验版(EXP)** 是越狱版的超集,额外运行研究分支中的实验性补丁:内核 `hv_vmm_present` sysctl 重命名 + 内核内部调用者改写(`KernelEXPPatcher`)、带登录黑名单的 DSC 字节 5 改写 + slot 重新认证、watchdogd 精准 2 条指令补丁(EXP-JB-3.5)、固件补丁阶段的 8 项 DeviceTree 身份属性、还原后 DT 身份重写(EXP-JB-6)、以及通过 `SPOOF_BUILD=<id>` 可选启用的 `SystemVersion.plist` `ProductBuildVersion` 改写(EXP-JB-7)。其他变体不受影响。
|
||||
|
||||
详见 [research/0_binary_patch_comparison.md](../research/0_binary_patch_comparison.md) 了解各组件的详细分项对比。
|
||||
|
||||
## 先决条件
|
||||
@@ -103,6 +106,8 @@ make setup_machine # 完全自动化完成"首次启动"流程(包
|
||||
# LESS=1 patchless 变体(- AMFI、SSV、Img4、TXM 绕过)
|
||||
# DEV=1 开发变体(+ TXM 权限/调试绕过)
|
||||
# JB=1 越狱变体(dev + 完整安全绕过)
|
||||
# EXP=1 实验变体(越狱 + 研究补丁:hv_vmm 重命名、DT 身份、还原后重写)
|
||||
# SPOOF_BUILD=<id> (仅 EXP)将 SystemVersion.plist 的 ProductBuildVersion 改写为 <id>,例如 23F77
|
||||
```
|
||||
|
||||
## 手动设置
|
||||
@@ -117,6 +122,7 @@ make fw_patch # 修补启动链(常规变体)
|
||||
# 或:sudo make fw_patch_less # patchless 变体(- AMFI、SSV、Img4、TXM 绕过)
|
||||
# 或:make fw_patch_dev # 开发变体(+ TXM 权限/调试绕过)
|
||||
# 或:make fw_patch_jb # 越狱变体(dev + 完整安全绕过)
|
||||
# 或:make fw_patch_exp # 实验变体(越狱 + 研究补丁栈)
|
||||
```
|
||||
|
||||
### 清理
|
||||
@@ -186,6 +192,8 @@ python3 -m pymobiledevice3 usbmux forward 2222 22
|
||||
# 终端 2
|
||||
make cfw_install
|
||||
# 或:make cfw_install_jb # 越狱变体
|
||||
# 或:make cfw_install_exp # 实验变体(越狱 + 研究补丁栈)
|
||||
# 或:SPOOF_BUILD=23F77 make cfw_install_exp # 同时改写 ProductBuildVersion
|
||||
```
|
||||
|
||||
## 首次启动
|
||||
|
||||
Executable
+427
@@ -0,0 +1,427 @@
|
||||
#!/bin/zsh
|
||||
# cfw_install_exp.sh — Install base CFW + JB extensions + EXP experimental
|
||||
# patches on vphone via SSH ramdisk.
|
||||
#
|
||||
# Currently runs the same set of phases as the JB install script (JB-1..JB-5):
|
||||
# launchd jetsam patch, dylib injection, procursus bootstrap, BaseBin hook
|
||||
# deployment, first-boot setup. Subsequent commits in this branch add the
|
||||
# EXP-only experimental phases (DSC pre-step, EXP-JB-3.5 watchdogd, EXP-JB-6
|
||||
# post-restore DT, EXP-JB-7 build-version) on top.
|
||||
#
|
||||
# Prerequisites (in addition to cfw_install.sh requirements):
|
||||
# - cfw_jb_input/ or resources/cfw_jb_input.tar.zst present
|
||||
# - zstd (for bootstrap decompression)
|
||||
#
|
||||
# Usage: make cfw_install_exp
|
||||
set -euo pipefail
|
||||
|
||||
# ── Restore caller's PATH — Nix /etc/zshenv resets PATH on zsh startup ─
|
||||
[[ -n "${_VPHONE_PATH:-}" ]] && export PATH="$_VPHONE_PATH"
|
||||
VM_DIR="${1:-.}"
|
||||
SCRIPT_DIR="${0:a:h}"
|
||||
|
||||
# ── Python resolver — prefer project venv over whatever is in PATH ─
|
||||
# Resolves to .venv/bin/python3 relative to the project root (parent of
|
||||
# scripts/), falling back to the system python3 when the venv is absent.
|
||||
_resolve_python3() {
|
||||
local venv_py="${SCRIPT_DIR:h}/.venv/bin/python3"
|
||||
if [[ -x "$venv_py" ]]; then
|
||||
echo "$venv_py"
|
||||
else
|
||||
command -v python3 || true
|
||||
fi
|
||||
}
|
||||
PYTHON3="$(_resolve_python3)"
|
||||
|
||||
# ════════════════════════════════════════════════════════════════
|
||||
# Step 1: Run base CFW install (skip halt — we continue with JB phases)
|
||||
# ════════════════════════════════════════════════════════════════
|
||||
echo "[*] cfw_install_exp.sh — Installing CFW + JB extensions + EXP experimental patches..."
|
||||
echo ""
|
||||
CFW_SKIP_HALT=1 zsh "$SCRIPT_DIR/cfw_install.sh" "$VM_DIR"
|
||||
|
||||
# ════════════════════════════════════════════════════════════════
|
||||
# Step 2: JB-specific phases
|
||||
# ════════════════════════════════════════════════════════════════
|
||||
|
||||
# Resolve absolute paths (same as base script)
|
||||
VM_DIR="$(cd "${VM_DIR}" && pwd)"
|
||||
|
||||
# ── Configuration ───────────────────────────────────────────────
|
||||
CFW_INPUT="cfw_input"
|
||||
CFW_JB_INPUT="cfw_jb_input"
|
||||
CFW_JB_ARCHIVE="cfw_jb_input.tar.zst"
|
||||
TEMP_DIR="$VM_DIR/.cfw_temp"
|
||||
|
||||
SSH_PORT="${SSH_PORT:-2222}"
|
||||
SSH_PASS="alpine"
|
||||
SSH_USER="root"
|
||||
SSH_HOST="localhost"
|
||||
SSH_RETRY="${SSH_RETRY:-3}"
|
||||
SSHPASS_BIN=""
|
||||
SSH_OPTS=(
|
||||
-o StrictHostKeyChecking=no
|
||||
-o UserKnownHostsFile=/dev/null
|
||||
-o PreferredAuthentications=password
|
||||
-o ConnectTimeout=30
|
||||
-q
|
||||
)
|
||||
|
||||
# ── Helpers ─────────────────────────────────────────────────────
|
||||
die() {
|
||||
echo "[-] $*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
check_prerequisites() {
|
||||
local missing=()
|
||||
command -v sshpass &>/dev/null || missing+=("sshpass")
|
||||
command -v ldid &>/dev/null || missing+=("ldid (brew install ldid-procursus)")
|
||||
command -v xcrun &>/dev/null || missing+=("xcrun (Xcode command line tools)")
|
||||
if ((${#missing[@]} > 0)); then
|
||||
die "Missing required tools: ${missing[*]}. Run: make setup_tools"
|
||||
fi
|
||||
SSHPASS_BIN="$(command -v sshpass)"
|
||||
}
|
||||
|
||||
_sshpass() {
|
||||
"$SSHPASS_BIN" -p "$SSH_PASS" "$@"
|
||||
}
|
||||
|
||||
_ssh_retry() {
|
||||
local attempt rc label
|
||||
label=${2:-cmd}
|
||||
for ((attempt = 1; attempt <= SSH_RETRY; attempt++)); do
|
||||
"$@" && return 0
|
||||
rc=$?
|
||||
[[ $rc -ne 255 ]] && return $rc # real command failure — don't retry
|
||||
echo " [${label}] connection lost (attempt $attempt/$SSH_RETRY), retrying in 3s..." >&2
|
||||
sleep 3
|
||||
done
|
||||
return 255
|
||||
}
|
||||
|
||||
ssh_cmd() {
|
||||
_ssh_retry _sshpass ssh "${SSH_OPTS[@]}" -p "$SSH_PORT" "$SSH_USER@$SSH_HOST" "$@"
|
||||
}
|
||||
scp_to() {
|
||||
_ssh_retry _sshpass scp -q "${SSH_OPTS[@]}" -P "$SSH_PORT" -r "$1" "$SSH_USER@$SSH_HOST:$2"
|
||||
}
|
||||
scp_from() {
|
||||
_ssh_retry _sshpass scp -q "${SSH_OPTS[@]}" -P "$SSH_PORT" "$SSH_USER@$SSH_HOST:$1" "$2"
|
||||
}
|
||||
|
||||
remote_file_exists() {
|
||||
ssh_cmd "test -f '$1'" 2>/dev/null
|
||||
}
|
||||
|
||||
ldid_sign() {
|
||||
local file="$1" bundle_id="${2:-}"
|
||||
local args=(-S -M "-K$VM_DIR/$CFW_INPUT/signcert.p12")
|
||||
[[ -n "$bundle_id" ]] && args+=("-I$bundle_id")
|
||||
ldid "${args[@]}" "$file"
|
||||
}
|
||||
|
||||
ldid_sign_ent() {
|
||||
local file="$1" entitlements_plist="$2" bundle_id="${3:-}"
|
||||
local args=("-S$entitlements_plist" "-K$VM_DIR/$CFW_INPUT/signcert.p12")
|
||||
[[ -n "$bundle_id" ]] && args+=("-I$bundle_id")
|
||||
ldid "${args[@]}" "$file"
|
||||
}
|
||||
|
||||
build_tweakloader() {
|
||||
local src="$SCRIPT_DIR/tweakloader/TweakLoader.m"
|
||||
local out="$TEMP_DIR/TweakLoader.dylib"
|
||||
local sdk cc
|
||||
|
||||
[[ -f "$src" ]] || die "Missing tweak loader source at $src"
|
||||
|
||||
sdk="$(xcrun --sdk iphoneos --show-sdk-path)"
|
||||
cc="$(xcrun --sdk iphoneos -f clang)"
|
||||
|
||||
"$cc" -isysroot "$sdk" \
|
||||
-arch arm64 -arch arm64e \
|
||||
-miphoneos-version-min=15.0 \
|
||||
-dynamiclib \
|
||||
-fobjc-arc -O3 \
|
||||
-framework Foundation \
|
||||
-o "$out" \
|
||||
"$src"
|
||||
|
||||
ldid_sign "$out"
|
||||
echo "$out"
|
||||
}
|
||||
|
||||
remote_mount() {
|
||||
local dev="$1" mnt="$2" opts="${3:-rw}"
|
||||
ssh_cmd "/bin/mkdir -p $mnt"
|
||||
if ssh_cmd "/sbin/mount | /usr/bin/grep -q ' on $mnt '"; then
|
||||
return 0
|
||||
fi
|
||||
ssh_cmd "/sbin/mount_apfs -o $opts $dev $mnt 2>/dev/null || true"
|
||||
if ! ssh_cmd "/sbin/mount | /usr/bin/grep -q ' on $mnt '"; then
|
||||
die "Failed to mount $dev at $mnt (opts=$opts). Make sure the ramdisk was booted with the expected patched kernel."
|
||||
fi
|
||||
}
|
||||
|
||||
get_boot_manifest_hash() {
|
||||
ssh_cmd "/bin/ls /mnt5 2>/dev/null" | awk 'length($0)==96{print; exit}'
|
||||
}
|
||||
|
||||
# ── Setup JB input resources ──────────────────────────────────
|
||||
setup_cfw_jb_input() {
|
||||
[[ -d "$VM_DIR/$CFW_JB_INPUT" ]] && return
|
||||
local archive
|
||||
for search_dir in "$SCRIPT_DIR/resources" "$SCRIPT_DIR" "$VM_DIR"; do
|
||||
archive="$search_dir/$CFW_JB_ARCHIVE"
|
||||
if [[ -f "$archive" ]]; then
|
||||
echo " Extracting $CFW_JB_ARCHIVE..."
|
||||
tar --zstd -xf "$archive" -C "$VM_DIR"
|
||||
return
|
||||
fi
|
||||
done
|
||||
die "JB mode: neither $CFW_JB_INPUT/ nor $CFW_JB_ARCHIVE found"
|
||||
}
|
||||
|
||||
# ── Apply dev overlay (replace rpcserver_ios in iosbinpack64) ──
|
||||
apply_dev_overlay() {
|
||||
local dev_bin
|
||||
for search_dir in "$SCRIPT_DIR/resources/cfw_dev" "$SCRIPT_DIR/cfw_dev"; do
|
||||
dev_bin="$search_dir/rpcserver_ios"
|
||||
if [[ -f "$dev_bin" ]]; then
|
||||
echo " Applying dev overlay (rpcserver_ios)..."
|
||||
local iosbinpack="$VM_DIR/$CFW_INPUT/jb/iosbinpack64.tar"
|
||||
local tmpdir="$VM_DIR/.iosbinpack_tmp"
|
||||
mkdir -p "$tmpdir"
|
||||
tar -xf "$iosbinpack" -C "$tmpdir"
|
||||
cp "$dev_bin" "$tmpdir/iosbinpack64/usr/local/bin/rpcserver_ios"
|
||||
(cd "$tmpdir" && tar -cf "$iosbinpack" iosbinpack64)
|
||||
rm -rf "$tmpdir"
|
||||
return
|
||||
fi
|
||||
done
|
||||
die "Dev overlay not found (cfw_dev/rpcserver_ios)"
|
||||
}
|
||||
|
||||
# ── Check JB prerequisites ────────────────────────────────────
|
||||
command -v zstd >/dev/null 2>&1 || die "'zstd' not found (required for JB bootstrap phase)"
|
||||
|
||||
setup_cfw_jb_input
|
||||
JB_INPUT_DIR="$VM_DIR/$CFW_JB_INPUT"
|
||||
echo ""
|
||||
echo "[+] JB input resources: $JB_INPUT_DIR"
|
||||
check_prerequisites
|
||||
|
||||
mkdir -p "$TEMP_DIR"
|
||||
|
||||
# Mount device rootfs (may already be mounted from base install)
|
||||
remote_mount /dev/disk1s1 /mnt1
|
||||
|
||||
# ═══════════ JB-1 PATCH LAUNCHD (JETSAM + DYLIB INJECTION) ════
|
||||
echo ""
|
||||
echo "[JB-1] Patching launchd (jetsam guard + hook injection)..."
|
||||
|
||||
if ! remote_file_exists "/mnt1/sbin/launchd.bak"; then
|
||||
echo " Creating backup..."
|
||||
ssh_cmd "/bin/cp /mnt1/sbin/launchd /mnt1/sbin/launchd.bak"
|
||||
fi
|
||||
|
||||
scp_from "/mnt1/sbin/launchd.bak" "$TEMP_DIR/launchd"
|
||||
|
||||
# Extract original entitlements before patching (must preserve for spawn permissions)
|
||||
echo " Extracting original entitlements..."
|
||||
ldid -e "$TEMP_DIR/launchd" > "$TEMP_DIR/launchd.entitlements" 2>/dev/null || true
|
||||
if [[ -s "$TEMP_DIR/launchd.entitlements" ]]; then
|
||||
echo " [+] Preserved launchd entitlements"
|
||||
else
|
||||
echo " [!] No entitlements found on original launchd"
|
||||
fi
|
||||
|
||||
# Inject launchdhook via short root alias to avoid Mach-O header overflow.
|
||||
# Keep the full /cores/launchdhook.dylib copy on disk for compatibility, but
|
||||
# load /b from launchd because this launchd sample only has room for a short
|
||||
# LC_LOAD_DYLIB command after stripping LC_CODE_SIGNATURE.
|
||||
if [[ -d "$JB_INPUT_DIR/basebin" ]]; then
|
||||
echo " Injecting LC_LOAD_DYLIB for /b (short launchdhook alias)..."
|
||||
"$PYTHON3" "$SCRIPT_DIR/patchers/cfw.py" inject-dylib "$TEMP_DIR/launchd" "/b"
|
||||
fi
|
||||
|
||||
"$PYTHON3" "$SCRIPT_DIR/patchers/cfw.py" patch-launchd-jetsam "$TEMP_DIR/launchd"
|
||||
|
||||
# Re-sign with original entitlements to avoid "operation not permitted" on spawn
|
||||
if [[ -s "$TEMP_DIR/launchd.entitlements" ]]; then
|
||||
ldid -S"$TEMP_DIR/launchd.entitlements" -M "-K$VM_DIR/$CFW_INPUT/signcert.p12" "$TEMP_DIR/launchd"
|
||||
else
|
||||
ldid_sign "$TEMP_DIR/launchd"
|
||||
fi
|
||||
scp_to "$TEMP_DIR/launchd" "/mnt1/sbin/launchd"
|
||||
ssh_cmd "/bin/chmod 0755 /mnt1/sbin/launchd"
|
||||
|
||||
echo " [+] launchd patched"
|
||||
|
||||
# ═══════════ JB-2 INSTALL IOSBINPACK64 ════════════════════════
|
||||
echo ""
|
||||
echo "[JB-2] Installing iosbinpack64..."
|
||||
|
||||
apply_dev_overlay
|
||||
scp_to "$VM_DIR/$CFW_INPUT/jb/iosbinpack64.tar" "/mnt1"
|
||||
ssh_cmd "/usr/bin/tar --preserve-permissions --no-overwrite-dir \
|
||||
-xf /mnt1/iosbinpack64.tar -C /mnt1"
|
||||
ssh_cmd "/bin/rm -f /mnt1/iosbinpack64.tar"
|
||||
|
||||
echo " [+] iosbinpack64 installed"
|
||||
|
||||
# ═══════════ JB-3 PATCH debugserver entitlements ════
|
||||
echo ""
|
||||
echo "[JB-3] Patching debugserver entitlements..."
|
||||
|
||||
scp_from "/mnt1/usr/libexec/debugserver" "$TEMP_DIR/debugserver"
|
||||
ldid -e "$TEMP_DIR/debugserver" > "$TEMP_DIR/debugserver-entitlements.plist"
|
||||
plutil -remove seatbelt-profiles "$TEMP_DIR/debugserver-entitlements.plist" || true
|
||||
plutil -insert task_for_pid-allow -bool YES "$TEMP_DIR/debugserver-entitlements.plist" || true
|
||||
ldid_sign_ent "$TEMP_DIR/debugserver" "$TEMP_DIR/debugserver-entitlements.plist"
|
||||
scp_to "$TEMP_DIR/debugserver" "/mnt1/usr/libexec/debugserver"
|
||||
ssh_cmd "/bin/chmod 0755 /mnt1/usr/libexec/debugserver"
|
||||
|
||||
echo " [+] debugserver entitlements patched"
|
||||
|
||||
|
||||
# ═══════════ JB-4 INSTALL PROCURSUS BOOTSTRAP ══════════════════
|
||||
echo ""
|
||||
echo "[JB-4] Installing procursus bootstrap..."
|
||||
|
||||
remote_mount /dev/disk1s5 /mnt5
|
||||
BOOT_HASH="$(get_boot_manifest_hash)"
|
||||
[[ -n "$BOOT_HASH" ]] || die "Could not find 96-char boot manifest hash in /mnt5"
|
||||
echo " Boot manifest hash: $BOOT_HASH"
|
||||
|
||||
BOOTSTRAP_ZST="$JB_INPUT_DIR/jb/bootstrap-iphoneos-arm64.tar.zst"
|
||||
SILEO_DEB="$JB_INPUT_DIR/jb/org.coolstar.sileo_2.5.1_iphoneos-arm64.deb"
|
||||
[[ -f "$BOOTSTRAP_ZST" ]] || die "Missing $BOOTSTRAP_ZST"
|
||||
|
||||
BOOTSTRAP_TAR="$TEMP_DIR/bootstrap-iphoneos-arm64.tar"
|
||||
zstd -d -f "$BOOTSTRAP_ZST" -o "$BOOTSTRAP_TAR"
|
||||
|
||||
scp_to "$BOOTSTRAP_TAR" "/mnt5/$BOOT_HASH/bootstrap-iphoneos-arm64.tar"
|
||||
if [[ -f "$SILEO_DEB" ]]; then
|
||||
scp_to "$SILEO_DEB" "/mnt5/$BOOT_HASH/org.coolstar.sileo_2.5.1_iphoneos-arm64.deb"
|
||||
fi
|
||||
|
||||
JB_DIR_NAME="jb-vphone"
|
||||
ssh_cmd "/bin/rm -rf /mnt5/$BOOT_HASH/jb"
|
||||
ssh_cmd "/bin/rm -rf /mnt5/$BOOT_HASH/$JB_DIR_NAME"
|
||||
ssh_cmd "/bin/mkdir -p /mnt5/$BOOT_HASH/$JB_DIR_NAME"
|
||||
ssh_cmd "/bin/chmod 0755 /mnt5/$BOOT_HASH/$JB_DIR_NAME"
|
||||
ssh_cmd "/usr/sbin/chown 0:0 /mnt5/$BOOT_HASH/$JB_DIR_NAME"
|
||||
ssh_cmd "/usr/bin/tar --preserve-permissions -xf /mnt5/$BOOT_HASH/bootstrap-iphoneos-arm64.tar \
|
||||
-C /mnt5/$BOOT_HASH/$JB_DIR_NAME/"
|
||||
ssh_cmd "/bin/mv /mnt5/$BOOT_HASH/$JB_DIR_NAME/var /mnt5/$BOOT_HASH/$JB_DIR_NAME/procursus"
|
||||
ssh_cmd "/bin/mv /mnt5/$BOOT_HASH/$JB_DIR_NAME/procursus/jb/* /mnt5/$BOOT_HASH/$JB_DIR_NAME/procursus 2>/dev/null || true"
|
||||
ssh_cmd "/bin/rm -rf /mnt5/$BOOT_HASH/$JB_DIR_NAME/procursus/jb"
|
||||
ssh_cmd "/bin/rm -f /mnt5/$BOOT_HASH/bootstrap-iphoneos-arm64.tar"
|
||||
rm -f "$BOOTSTRAP_TAR"
|
||||
|
||||
# NOTE: /var/jb symlink is created at runtime by launchdhook.dylib
|
||||
# (Data volume is encrypted and not mountable from ramdisk).
|
||||
|
||||
echo " [+] procursus bootstrap installed"
|
||||
|
||||
# ═══════════ JB-4 DEPLOY BASEBIN HOOKS ═════════════════════════
|
||||
BASEBIN_DIR="$JB_INPUT_DIR/basebin"
|
||||
|
||||
if [[ -d "$BASEBIN_DIR" ]]; then
|
||||
echo ""
|
||||
echo "[JB-4] Deploying BaseBin hooks to /cores/..."
|
||||
|
||||
# Clean previous dylibs before re-uploading
|
||||
echo " Cleaning old /cores/ dylibs..."
|
||||
ssh_cmd "/bin/rm -rf /mnt1/cores"
|
||||
ssh_cmd "/bin/mkdir -p /mnt1/cores"
|
||||
ssh_cmd "/bin/chmod 0755 /mnt1/cores"
|
||||
|
||||
# Install all pre-built dylibs from basebin payload
|
||||
for dylib in "$BASEBIN_DIR"/*.dylib; do
|
||||
[[ -f "$dylib" ]] || continue
|
||||
dylib_name="$(basename "$dylib")"
|
||||
echo " Installing $dylib_name..."
|
||||
ldid_sign "$dylib"
|
||||
scp_to "$dylib" "/mnt1/cores/$dylib_name"
|
||||
ssh_cmd "/bin/chmod 0755 /mnt1/cores/$dylib_name"
|
||||
done
|
||||
|
||||
# Short alias for launchdhook (header space is tight)
|
||||
if [[ -f "$BASEBIN_DIR/launchdhook.dylib" ]]; then
|
||||
echo " Installing short launchdhook alias at /b..."
|
||||
cp "$BASEBIN_DIR/launchdhook.dylib" "$TEMP_DIR/b"
|
||||
ldid_sign "$TEMP_DIR/b"
|
||||
ssh_cmd "/bin/rm -f /mnt1/b"
|
||||
scp_to "$TEMP_DIR/b" "/mnt1/b"
|
||||
ssh_cmd "/bin/chmod 0755 /mnt1/b"
|
||||
fi
|
||||
|
||||
echo " [+] BaseBin hooks deployed"
|
||||
fi
|
||||
|
||||
# ═══════════ JB-4 INSTALL TWEAKLOADER ════════════════════════════
|
||||
echo ""
|
||||
echo "[JB-4] Building and installing TweakLoader..."
|
||||
|
||||
TWEAKLOADER_OUT="$(build_tweakloader)"
|
||||
ssh_cmd "/bin/mkdir -p /mnt5/$BOOT_HASH/$JB_DIR_NAME/procursus/usr/lib"
|
||||
scp_to "$TWEAKLOADER_OUT" "/mnt5/$BOOT_HASH/$JB_DIR_NAME/procursus/usr/lib/TweakLoader.dylib"
|
||||
ssh_cmd "/usr/sbin/chown 0:0 /mnt5/$BOOT_HASH/$JB_DIR_NAME/procursus/usr/lib/TweakLoader.dylib"
|
||||
ssh_cmd "/bin/chmod 0755 /mnt5/$BOOT_HASH/$JB_DIR_NAME/procursus/usr/lib/TweakLoader.dylib"
|
||||
|
||||
echo " [+] TweakLoader installed to procursus/usr/lib/TweakLoader.dylib"
|
||||
|
||||
# ═══════════ JB-5 DEPLOY FIRST-BOOT SETUP ══════════════════════
|
||||
echo ""
|
||||
echo "[JB-5] Deploying first-boot setup..."
|
||||
|
||||
# Deploy first-boot JB setup script + LaunchDaemon
|
||||
SETUP_SCRIPT="$SCRIPT_DIR/vphone_jb_setup.sh"
|
||||
SETUP_PLIST="$SCRIPT_DIR/vphone_jb_setup.plist"
|
||||
if [[ -f "$SETUP_SCRIPT" ]]; then
|
||||
scp_to "$SETUP_SCRIPT" "/mnt1/cores/vphone_jb_setup.sh"
|
||||
ssh_cmd "/bin/chmod 0755 /mnt1/cores/vphone_jb_setup.sh"
|
||||
echo " [+] vphone_jb_setup.sh -> /cores/"
|
||||
fi
|
||||
if [[ -f "$SETUP_PLIST" ]]; then
|
||||
scp_to "$SETUP_PLIST" "/mnt1/System/Library/LaunchDaemons/com.vphone.jb-setup.plist"
|
||||
ssh_cmd "/bin/chmod 0644 /mnt1/System/Library/LaunchDaemons/com.vphone.jb-setup.plist"
|
||||
|
||||
# Inject into launchd.plist so launchd starts it at boot
|
||||
echo " Injecting com.vphone.jb-setup into launchd.plist..."
|
||||
scp_from "/mnt1/System/Library/xpc/launchd.plist" "$TEMP_DIR/launchd.plist"
|
||||
"$PYTHON3" -c "
|
||||
import plistlib, sys
|
||||
with open(sys.argv[1], 'rb') as f:
|
||||
target = plistlib.load(f)
|
||||
with open(sys.argv[2], 'rb') as f:
|
||||
daemon = plistlib.load(f)
|
||||
target.setdefault('LaunchDaemons', {})['/System/Library/LaunchDaemons/com.vphone.jb-setup.plist'] = daemon
|
||||
with open(sys.argv[1], 'wb') as f:
|
||||
plistlib.dump(target, f, sort_keys=False)
|
||||
" "$TEMP_DIR/launchd.plist" "$SETUP_PLIST"
|
||||
scp_to "$TEMP_DIR/launchd.plist" "/mnt1/System/Library/xpc/launchd.plist"
|
||||
ssh_cmd "/bin/chmod 0644 /mnt1/System/Library/xpc/launchd.plist"
|
||||
echo " [+] com.vphone.jb-setup.plist injected into launchd.plist"
|
||||
fi
|
||||
|
||||
# ═══════════ CLEANUP ═════════════════════════════════════════
|
||||
echo ""
|
||||
echo "[*] Unmounting device filesystems..."
|
||||
ssh_cmd "/sbin/umount /mnt1 2>/dev/null || true"
|
||||
ssh_cmd "/sbin/umount /mnt3 2>/dev/null || true"
|
||||
ssh_cmd "/sbin/umount /mnt5 2>/dev/null || true"
|
||||
|
||||
echo "[*] Cleaning up temp binaries..."
|
||||
rm -f "$TEMP_DIR/launchd" \
|
||||
"$TEMP_DIR/bootstrap-iphoneos-arm64.tar"
|
||||
|
||||
echo ""
|
||||
echo "[+] CFW + JB + EXP installation complete!"
|
||||
echo " Reboot the device for changes to take effect."
|
||||
echo " After boot, SSH will be available on port 22222 (password: alpine)"
|
||||
|
||||
ssh_cmd "/sbin/halt" || true
|
||||
@@ -62,6 +62,7 @@ NO_VPHONED_RAW="${NO_VPHONED:-0}"
|
||||
NO_VPHONED=0
|
||||
JB_MODE=0
|
||||
DEV_MODE=0
|
||||
EXP_MODE=0
|
||||
LESS_MODE=0
|
||||
SKIP_PROJECT_SETUP=0
|
||||
|
||||
@@ -962,6 +963,9 @@ parse_args() {
|
||||
--dev)
|
||||
DEV_MODE=1
|
||||
;;
|
||||
--exp)
|
||||
EXP_MODE=1
|
||||
;;
|
||||
--less)
|
||||
LESS_MODE=1
|
||||
;;
|
||||
@@ -970,11 +974,15 @@ parse_args() {
|
||||
;;
|
||||
-h|--help)
|
||||
cat <<'EOF'
|
||||
Usage: setup_machine.sh [--jb] [--dev] [--skip-project-setup]
|
||||
Usage: setup_machine.sh [--jb] [--dev] [--exp] [--less] [--skip-project-setup]
|
||||
|
||||
Options:
|
||||
--jb Use jailbreak firmware patching + jailbreak CFW install.
|
||||
--dev Use dev firmware patching + dev CFW install.
|
||||
--exp Use experimental firmware patching + EXP CFW install
|
||||
(JB + kernel hv_vmm rename, DSC byte-5 mangle, watchdogd
|
||||
surgical patch, DT identity properties, post-restore DT
|
||||
rewrite, opt-in build-version spoof via SPOOF_BUILD).
|
||||
--less Use patchless firmware patching + CFW install.
|
||||
--skip-project-setup Skip setup_tools/build stage.
|
||||
|
||||
@@ -983,6 +991,8 @@ Environment:
|
||||
SUDO_PASSWORD=... Preload sudo credential via askpass.
|
||||
NO_BINPACK=1 Excludes the SSH, VNC, ... binaries from being installed (patchless-only, currently)
|
||||
NO_VPHONED=1 Excludes vphoned from being installed (patchless-only, currently)
|
||||
SPOOF_BUILD=<id> (EXP only) Rewrite SystemVersion.plist ProductBuildVersion
|
||||
to <id> (e.g. 23F77). Omitted/empty -> skipped.
|
||||
EOF
|
||||
exit 0
|
||||
;;
|
||||
@@ -1010,8 +1020,8 @@ main() {
|
||||
local cfw_install_target="cfw_install"
|
||||
local mode_label="base"
|
||||
|
||||
if (( JB_MODE + DEV_MODE + LESS_MODE > 1 )); then
|
||||
die "--jb, --dev, and --less are mutually exclusive"
|
||||
if (( JB_MODE + DEV_MODE + EXP_MODE + LESS_MODE > 1 )); then
|
||||
die "--jb, --dev, --exp, and --less are mutually exclusive"
|
||||
fi
|
||||
|
||||
if [[ "$JB_MODE" -eq 1 ]]; then
|
||||
@@ -1022,6 +1032,10 @@ main() {
|
||||
fw_patch_target="fw_patch_dev"
|
||||
cfw_install_target="cfw_install_dev"
|
||||
mode_label="dev"
|
||||
elif [[ "$EXP_MODE" -eq 1 ]]; then
|
||||
fw_patch_target="fw_patch_exp"
|
||||
cfw_install_target="cfw_install_exp"
|
||||
mode_label="experimental"
|
||||
elif [[ "$LESS_MODE" -eq 1 ]]; then
|
||||
fw_patch_target="fw_patch_less"
|
||||
cfw_install_target=""
|
||||
@@ -1118,7 +1132,7 @@ main() {
|
||||
BOOT_FIFO=""
|
||||
fi
|
||||
|
||||
if [[ "$JB_MODE" -eq 1 ]]; then
|
||||
if [[ "$JB_MODE" -eq 1 || "$EXP_MODE" -eq 1 ]]; then
|
||||
echo ""
|
||||
echo "=== JB Finalize ==="
|
||||
echo "[*] JB finalization will run automatically on first normal boot"
|
||||
|
||||
@@ -0,0 +1,279 @@
|
||||
// KernelEXPPatchHvVmmRename.swift — EXP kernel patch: rename the
|
||||
// `hv_vmm_present` sysctl OID name AND mangle every kernel-internal
|
||||
// `kern.hv_vmm_present` caller cstring so they keep working after the
|
||||
// rename.
|
||||
//
|
||||
// Variant scope: `.exp` only. JB and other variants are NOT affected.
|
||||
//
|
||||
// Two-part design:
|
||||
//
|
||||
// Part A (OID rename): single byte change at offset 0 of the
|
||||
// 14-byte NUL-delimited cstring `\0hv_vmm_present\0` (the OID's
|
||||
// `oid_name` value): 'h' (0x68) → 'X' (0x58). After this, the
|
||||
// kernel resolves `sysctlbyname("kern.hv_vmm_present", ...)` as
|
||||
// ENOENT and `sysctlbyname("kern.Xv_vmm_present", ...)` to the
|
||||
// OID's real int value (1 on this VM).
|
||||
//
|
||||
// Part B (internal caller mangle): single byte change at offset 5
|
||||
// of every kernel-internal NUL-delimited cstring
|
||||
// `\0kern.hv_vmm_present\0`: the `'h'` after `kern.` → `'X'`. This
|
||||
// covers cstrings that the kernel itself, AMFI, or other kexts
|
||||
// hard-code as the full sysctl name when they call a kernel-side
|
||||
// sysctlbyname-equivalent. Without this mangle those callers
|
||||
// query the now-ENOENT name and take their "not in a VM" branch,
|
||||
// which on this hardware causes AMFI to panic with
|
||||
// "AMFI: No PMGR?" (ConfigurationSettings.cpp:388) during ramdisk
|
||||
// boot. Mangling those callers to the new name makes them see
|
||||
// the truthful `1` again — same value they got pre-Part-A.
|
||||
//
|
||||
// User-mode counterpart
|
||||
// ---------------------
|
||||
// `scripts/patchers/cfw_patch_hv_vmm_dsc.py` applies the same byte-5
|
||||
// mangle to DSC dylibs that are NOT in `DONT_PATCH_INSTALL_NAMES`
|
||||
// (graphics + accel passthrough). Blacklisted dylibs keep the
|
||||
// original cstring and hit ENOENT after Part A. The two halves
|
||||
// (kernel + user-mode) together implement the blacklist-flip
|
||||
// design: callers that should see `1` (graphics, accel, AMFI,
|
||||
// other kexts) get the byte-5 mangle; callers that should see
|
||||
// ENOENT-which-defensively-caches-0 (sign-in / device-likeness
|
||||
// libs) get left alone.
|
||||
//
|
||||
// Reveal strategy
|
||||
// ---------------
|
||||
// The kernelcache's LC_SYMTAB is fully stripped on this build, so
|
||||
// symbol-based reveals don't apply. Both parts work by byte search
|
||||
// for cstrings in `buffer.data`:
|
||||
//
|
||||
// - Part A's needle is `\0hv_vmm_present\0` (16 bytes). We require
|
||||
// exactly one match — there's only one OID with this short name.
|
||||
// - Part B searches for two byte-aligned forms of the
|
||||
// `kern.hv_vmm_present` name:
|
||||
// (i) `\0kern.hv_vmm_present\0` (21 bytes) — the standard
|
||||
// NUL-delimited cstring form used by code in
|
||||
// `__TEXT,__cstring` of every kext that calls
|
||||
// `sysctlbyname` by full name. Verified consumers on
|
||||
// iPhone17,3 / iOS 26.1: AMFI, IOCryptoAcceleratorFamily,
|
||||
// sandbox (regular cstring), apfs.
|
||||
// (ii) `kern.hv_vmm_present\x0f` (20 bytes, no leading or
|
||||
// trailing NUL) — a sandbox-profile name-token. The
|
||||
// compiled sandbox-profile format inside
|
||||
// com.apple.security.sandbox stores OID names this way;
|
||||
// without this needle, Part B misses one occurrence at
|
||||
// file offset 0xa6618b. The token's terminator byte
|
||||
// `\x0f` is the sandbox-profile end-of-name marker.
|
||||
// Each match is mangled at byte 5 of the cstring/token (the
|
||||
// `'h'` after `kern.`) → `'X'`, producing `kern.Xv_vmm_present`,
|
||||
// so sysctlbyname-by-name callers and sandbox-profile name
|
||||
// matches both continue to resolve correctly after Part A's
|
||||
// OID rename.
|
||||
//
|
||||
// Idempotence
|
||||
// -----------
|
||||
// Re-running this patch is a no-op:
|
||||
// - Part A: detects post-patch `\0Xv_vmm_present\0` and bails.
|
||||
// - Part B: each cstring's byte 5 is checked before write; if it
|
||||
// is already 'X' the patcher skips that occurrence.
|
||||
|
||||
import Foundation
|
||||
|
||||
extension KernelEXPPatcher {
|
||||
/// Apply Part A (OID rename) + Part B (kernel-internal caller
|
||||
/// byte-5 mangle). Returns true if any byte was modified.
|
||||
@discardableResult
|
||||
func patchHvVmmRename() -> Bool {
|
||||
log("\n[EXP] hv_vmm_present sysctl: OID rename + kernel-internal caller mangle")
|
||||
let aChanged = renameOidNameCstring()
|
||||
let bChanged = mangleKernelInternalCallers()
|
||||
return aChanged || bChanged
|
||||
}
|
||||
|
||||
// MARK: - Part A: rename the OID's name cstring
|
||||
|
||||
/// Rename `\0hv_vmm_present\0` → `\0Xv_vmm_present\0` (single byte at
|
||||
/// offset 0 of the 14-byte cstring). Exactly one occurrence is required.
|
||||
private func renameOidNameCstring() -> Bool {
|
||||
// Bytes: '\0' 'h' 'v' '_' 'v' 'm' 'm' '_' 'p' 'r' 'e' 's' 'e' 'n' 't' '\0'
|
||||
let needle = Data([
|
||||
0x00, 0x68, 0x76, 0x5F, 0x76, 0x6D, 0x6D, 0x5F,
|
||||
0x70, 0x72, 0x65, 0x73, 0x65, 0x6E, 0x74, 0x00,
|
||||
])
|
||||
let patched = Data([
|
||||
0x00, 0x58, 0x76, 0x5F, 0x76, 0x6D, 0x6D, 0x5F,
|
||||
0x70, 0x72, 0x65, 0x73, 0x65, 0x6E, 0x74, 0x00,
|
||||
])
|
||||
|
||||
let originalHits = buffer.findAll(needle)
|
||||
let patchedHits = buffer.findAll(patched)
|
||||
|
||||
if originalHits.isEmpty, patchedHits.count == 1 {
|
||||
log(" [.] Part A: OID name already renamed at foff 0x"
|
||||
+ String(format: "%X", patchedHits[0] + 1))
|
||||
return false
|
||||
}
|
||||
|
||||
guard !originalHits.isEmpty else {
|
||||
log(" [-] Part A: no NUL-delimited 'hv_vmm_present' cstring "
|
||||
+ "found — OID may have a different name shape on this build")
|
||||
return false
|
||||
}
|
||||
|
||||
guard originalHits.count == 1 else {
|
||||
let list = originalHits.map { String(format: "0x%X", $0 + 1) }
|
||||
.joined(separator: ", ")
|
||||
log(" [-] Part A: expected exactly 1 NUL-delimited "
|
||||
+ "'hv_vmm_present' cstring, found \(originalHits.count) "
|
||||
+ "(\(list)) — refusing to rename ambiguously")
|
||||
return false
|
||||
}
|
||||
|
||||
if !patchedHits.isEmpty {
|
||||
log(" [-] Part A: both original and renamed cstrings present "
|
||||
+ "(\(originalHits.count) + \(patchedHits.count)) — refusing")
|
||||
return false
|
||||
}
|
||||
|
||||
let cstringStart = originalHits[0] + 1
|
||||
let firstByte = buffer.data[cstringStart]
|
||||
guard firstByte == 0x68 else {
|
||||
log(" [-] Part A: unexpected first byte at 0x"
|
||||
+ String(format: "%X", cstringStart)
|
||||
+ " (found 0x\(String(format: "%02X", firstByte)), "
|
||||
+ "expected 0x68 'h') — refusing")
|
||||
return false
|
||||
}
|
||||
|
||||
let va = fileOffsetToVA(cstringStart)
|
||||
emit(cstringStart,
|
||||
Data([0x58]),
|
||||
patchID: "kernelcache_exp.hv_vmm_oid_rename",
|
||||
virtualAddress: va,
|
||||
description: "Part A: rename OID name 'h' -> 'X' "
|
||||
+ "('hv_vmm_present' -> 'Xv_vmm_present')")
|
||||
return true
|
||||
}
|
||||
|
||||
// MARK: - Part B: mangle kernel-internal `kern.hv_vmm_present` callers
|
||||
|
||||
/// For every kernel-internal occurrence of `kern.hv_vmm_present`
|
||||
/// inside the kernelcache buffer (kernel proper + kexts + fileset
|
||||
/// entries + compiled sandbox-profile blobs), flip byte 5 of the
|
||||
/// cstring/token from 'h' to 'X' so it becomes
|
||||
/// `kern.Xv_vmm_present`. Matches Part A's OID rename, so the
|
||||
/// caller's runtime sysctlbyname call (or the sandbox profile's
|
||||
/// name-token comparison) now hits the renamed OID and gets the
|
||||
/// truthful 1.
|
||||
///
|
||||
/// Two byte-aligned forms are handled:
|
||||
/// - NUL-delimited cstring `\0kern.hv_vmm_present\0` (4 known
|
||||
/// occurrences: AMFI, IOCryptoAcceleratorFamily, sandbox
|
||||
/// cstring, apfs).
|
||||
/// - Sandbox-profile name-token `kern.hv_vmm_present\x0f` —
|
||||
/// trailing `\x0f` is the sandbox-profile end-of-name marker,
|
||||
/// no leading NUL. 1 known occurrence inside
|
||||
/// com.apple.security.sandbox.
|
||||
private func mangleKernelInternalCallers() -> Bool {
|
||||
// ── Form (i): NUL-delimited cstring.
|
||||
// 21 bytes total (leading NUL + 19 cstring bytes + trailing NUL).
|
||||
// Mangle offset within the needle: 1 (skip leading NUL) + 5 = 6.
|
||||
let cstrOriginalNeedle = Data([
|
||||
0x00, // \0
|
||||
0x6B, 0x65, 0x72, 0x6E, 0x2E, // "kern."
|
||||
0x68, 0x76, 0x5F, 0x76, 0x6D, 0x6D, 0x5F, // "hv_vmm_"
|
||||
0x70, 0x72, 0x65, 0x73, 0x65, 0x6E, 0x74, // "present"
|
||||
0x00, // \0
|
||||
])
|
||||
let cstrPatchedNeedle = Data([
|
||||
0x00,
|
||||
0x6B, 0x65, 0x72, 0x6E, 0x2E, // "kern."
|
||||
0x58, 0x76, 0x5F, 0x76, 0x6D, 0x6D, 0x5F, // "Xv_vmm_"
|
||||
0x70, 0x72, 0x65, 0x73, 0x65, 0x6E, 0x74,
|
||||
0x00,
|
||||
])
|
||||
|
||||
// ── Form (ii): sandbox-profile name-token.
|
||||
// 20 bytes total (19 name bytes + trailing \x0f). No leading byte
|
||||
// in the needle. Mangle offset within the needle: 5.
|
||||
let tlvOriginalNeedle = Data([
|
||||
0x6B, 0x65, 0x72, 0x6E, 0x2E, // "kern."
|
||||
0x68, 0x76, 0x5F, 0x76, 0x6D, 0x6D, 0x5F, // "hv_vmm_"
|
||||
0x70, 0x72, 0x65, 0x73, 0x65, 0x6E, 0x74, // "present"
|
||||
0x0F, // sandbox EOT
|
||||
])
|
||||
let tlvPatchedNeedle = Data([
|
||||
0x6B, 0x65, 0x72, 0x6E, 0x2E,
|
||||
0x58, 0x76, 0x5F, 0x76, 0x6D, 0x6D, 0x5F, // "Xv_vmm_"
|
||||
0x70, 0x72, 0x65, 0x73, 0x65, 0x6E, 0x74,
|
||||
0x0F,
|
||||
])
|
||||
|
||||
// (mangle position relative to the needle's start, originalByte)
|
||||
let sites: [(needle: Data, patched: Data, mangleDelta: Int, label: String)] = [
|
||||
(cstrOriginalNeedle, cstrPatchedNeedle, 6, "cstring"),
|
||||
(tlvOriginalNeedle, tlvPatchedNeedle, 5, "sandbox-profile token"),
|
||||
]
|
||||
|
||||
var totalOriginal = 0
|
||||
var totalPatched = 0
|
||||
var totalWritten = 0
|
||||
|
||||
for site in sites {
|
||||
let originalHits = buffer.findAll(site.needle)
|
||||
let patchedHits = buffer.findAll(site.patched)
|
||||
totalOriginal += originalHits.count
|
||||
totalPatched += patchedHits.count
|
||||
|
||||
log(" [.] Part B (\(site.label)): \(originalHits.count) "
|
||||
+ "original, \(patchedHits.count) already-mangled")
|
||||
|
||||
for needleOff in originalHits {
|
||||
let mangleOffset = needleOff + site.mangleDelta
|
||||
|
||||
// Defensive re-check: the byte we're about to flip must
|
||||
// be 'h' (0x68). If it isn't, something has changed
|
||||
// structurally — skip this occurrence and log it.
|
||||
let byteHere = buffer.data[mangleOffset]
|
||||
guard byteHere == 0x68 else {
|
||||
log(" [-] Part B (\(site.label)): byte at 0x"
|
||||
+ String(format: "%X", mangleOffset)
|
||||
+ " is 0x\(String(format: "%02X", byteHere)), "
|
||||
+ "expected 0x68 'h' — skipping")
|
||||
continue
|
||||
}
|
||||
|
||||
let va = fileOffsetToVA(mangleOffset)
|
||||
emit(mangleOffset,
|
||||
Data([0x58]),
|
||||
patchID: "kernelcache_exp.hv_vmm_internal_caller_mangle",
|
||||
virtualAddress: va,
|
||||
description: "Part B (\(site.label)): byte-5 mangle "
|
||||
+ "'h' -> 'X' at foff 0x"
|
||||
+ String(format: "%X", mangleOffset)
|
||||
+ " ('kern.hv_vmm_present' -> 'kern.Xv_vmm_present')")
|
||||
totalWritten += 1
|
||||
}
|
||||
}
|
||||
|
||||
let totalKnown = totalOriginal + totalPatched
|
||||
log(" [.] Part B summary: \(totalOriginal) original + "
|
||||
+ "\(totalPatched) already-mangled (total \(totalKnown)) — "
|
||||
+ "wrote \(totalWritten) byte(s)")
|
||||
|
||||
if totalOriginal == 0 {
|
||||
if totalPatched == 0 {
|
||||
log(" [.] Part B: no kernel-internal callers found — "
|
||||
+ "either no kext queries the sysctl by full name, "
|
||||
+ "or they all live elsewhere")
|
||||
} else {
|
||||
log(" [.] Part B: all \(totalPatched) kernel-internal "
|
||||
+ "occurrence(s) already mangled — nothing to do")
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if totalWritten > 0 {
|
||||
log(" [+] Part B: mangled \(totalWritten) kernel-internal "
|
||||
+ "occurrence(s) (cstring + sandbox-profile combined)")
|
||||
}
|
||||
return totalWritten > 0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// KernelEXPPatcher.swift — Experimental kernel patcher orchestrator.
|
||||
//
|
||||
// Runs after KernelPatcher + KernelJBPatcher for the `.exp` firmware variant
|
||||
// only. JB and other variants are NOT affected by patches owned here.
|
||||
//
|
||||
// Current contents:
|
||||
// - patchHvVmmRename (Part A + Part B): rename the kern.hv_vmm_present
|
||||
// sysctl OID cstring AND mangle every kernel-internal caller. See
|
||||
// EXPPatches/KernelEXPPatchHvVmmRename.swift for the implementation.
|
||||
|
||||
import Foundation
|
||||
|
||||
/// Experimental kernel patcher.
|
||||
///
|
||||
/// Inherits the JB infrastructure (symbol table, ADRP/BL indices, branch
|
||||
/// encoders, code-cave finder, string-anchored function finders, etc.) from
|
||||
/// `KernelJBPatcherBase` so EXP-specific patches can use the same helpers
|
||||
/// as JB ones without duplicating them.
|
||||
public final class KernelEXPPatcher: KernelJBPatcherBase, Patcher {
|
||||
public let component = "kernelcache_exp"
|
||||
|
||||
public func findAll() throws -> [PatchRecord] {
|
||||
try parseMachO()
|
||||
buildADRPIndex()
|
||||
buildBLIndex()
|
||||
buildSymbolTable()
|
||||
findPanic()
|
||||
|
||||
// Experimental patches (EXP variant only)
|
||||
patchHvVmmRename()
|
||||
|
||||
return patches
|
||||
}
|
||||
|
||||
public func apply() throws -> Int {
|
||||
let records = try (patches.isEmpty ? findAll() : patches)
|
||||
for record in records {
|
||||
buffer.writeBytes(at: record.fileOffset, bytes: record.patchedBytes)
|
||||
}
|
||||
return records.count
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,9 @@
|
||||
// .regular — base patchers only
|
||||
// .dev — TXMDevPatcher instead of TXMPatcher
|
||||
// .jb — TXMDevPatcher + IBootJBPatcher (iBSS) + KernelJBPatcher
|
||||
// .exp — JB + experimental: KernelEXPPatcher (hv_vmm rename) +
|
||||
// DeviceTreePatcher identity properties (D47AP/iPhone17,3).
|
||||
// Other variants are NOT affected by experimental patches.
|
||||
|
||||
import Darwin
|
||||
import Foundation
|
||||
@@ -28,6 +31,7 @@ public final class FirmwarePipeline {
|
||||
case regular
|
||||
case dev
|
||||
case jb
|
||||
case exp
|
||||
}
|
||||
|
||||
// MARK: - Firmware Loader (pluggable IM4P support)
|
||||
@@ -174,7 +178,8 @@ public final class FirmwarePipeline {
|
||||
func buildComponentList() -> [ComponentDescriptor] {
|
||||
var components: [ComponentDescriptor] = []
|
||||
|
||||
// 1. AVPBooter — always present, lives in VM root
|
||||
// 1. AVPBooter — always present, lives in VM root.
|
||||
// Patched for every non-less variant (regular/dev/jb/exp).
|
||||
components.append(ComponentDescriptor(
|
||||
name: "AVPBooter",
|
||||
inRestoreDir: false,
|
||||
@@ -191,7 +196,7 @@ public final class FirmwarePipeline {
|
||||
}()
|
||||
))
|
||||
|
||||
// 2. iBSS — JB variant runs the base iBSS patcher, then the nonce-skip extension.
|
||||
// 2. iBSS — JB and EXP variants run the base iBSS patcher, then the nonce-skip extension.
|
||||
components.append(ComponentDescriptor(
|
||||
name: "iBSS",
|
||||
inRestoreDir: true,
|
||||
@@ -204,7 +209,7 @@ public final class FirmwarePipeline {
|
||||
[{ data, verbose in
|
||||
IBootPatcher(data: data, mode: .ibss, verbose: verbose)
|
||||
}]
|
||||
case .jb:
|
||||
case .jb, .exp:
|
||||
[
|
||||
{ data, verbose in
|
||||
IBootPatcher(data: data, mode: .ibss, verbose: verbose)
|
||||
@@ -237,7 +242,7 @@ public final class FirmwarePipeline {
|
||||
}]
|
||||
))
|
||||
|
||||
// 5. TXM — dev/jb variants use TXMDevPatcher (adds entitlements, debugger, dev-mode)
|
||||
// 5. TXM — dev/jb/exp variants use TXMDevPatcher (adds entitlements, debugger, dev-mode)
|
||||
components.append(ComponentDescriptor(
|
||||
name: "TXM",
|
||||
inRestoreDir: true,
|
||||
@@ -250,7 +255,7 @@ public final class FirmwarePipeline {
|
||||
[{ data, verbose in
|
||||
TXMPatcher(data: data, verbose: verbose)
|
||||
}]
|
||||
case .dev, .jb:
|
||||
case .dev, .jb, .exp:
|
||||
[{ data, verbose in
|
||||
TXMDevPatcher(data: data, verbose: verbose)
|
||||
}]
|
||||
@@ -259,6 +264,7 @@ public final class FirmwarePipeline {
|
||||
))
|
||||
|
||||
// 6. Kernel — JB variant runs base kernel patches first, then JB extensions.
|
||||
// EXP variant runs base + JB + experimental extensions (hv_vmm rename).
|
||||
components.append(ComponentDescriptor(
|
||||
name: "kernelcache",
|
||||
inRestoreDir: true,
|
||||
@@ -284,11 +290,23 @@ public final class FirmwarePipeline {
|
||||
KernelJBPatcher(data: data, verbose: verbose)
|
||||
},
|
||||
]
|
||||
case .exp:
|
||||
[
|
||||
{ data, verbose in
|
||||
KernelPatcher(data: data, verbose: verbose, isDev: false)
|
||||
},
|
||||
{ data, verbose in
|
||||
KernelJBPatcher(data: data, verbose: verbose)
|
||||
},
|
||||
{ data, verbose in
|
||||
KernelEXPPatcher(data: data, verbose: verbose)
|
||||
},
|
||||
]
|
||||
}
|
||||
}()
|
||||
))
|
||||
|
||||
// 7. DeviceTree — same for all variants
|
||||
// 7. DeviceTree — same base property patches for every variant.
|
||||
components.append(ComponentDescriptor(
|
||||
name: "DeviceTree",
|
||||
inRestoreDir: true,
|
||||
@@ -309,7 +327,7 @@ public final class FirmwarePipeline {
|
||||
[{ data, verbose in
|
||||
CryptexFilesystemPatcher(buildManiest: data, restoreDir: try! self.findRestoreDirectory(), verbose: verbose, noBinpack: self.noBinpack, noVphoned: self.noVphoned)
|
||||
}]
|
||||
case .regular, .dev, .jb:
|
||||
case .regular, .dev, .jb, .exp:
|
||||
[]
|
||||
}
|
||||
}()
|
||||
@@ -326,7 +344,7 @@ public final class FirmwarePipeline {
|
||||
[{ data, verbose in
|
||||
ManifestHashPatcher(data: data, restoreDir: try? self.findRestoreDirectory(), verbose: verbose)
|
||||
}]
|
||||
case .regular, .dev, .jb:
|
||||
case .regular, .dev, .jb, .exp:
|
||||
[]
|
||||
}
|
||||
}()
|
||||
@@ -446,6 +464,7 @@ public final class FirmwarePipeline {
|
||||
if let txm = patcher as? TXMPatcher { return txm.buffer.data }
|
||||
if let kp = patcher as? KernelPatcher { return kp.buffer.data }
|
||||
if let kjb = patcher as? KernelJBPatcher { return kjb.buffer.data }
|
||||
if let kexp = patcher as? KernelEXPPatcher { return kexp.buffer.data }
|
||||
if let dt = patcher as? DeviceTreePatcher { return dt.patchedData }
|
||||
|
||||
// Fallback: apply records manually to a copy of the original data.
|
||||
|
||||
@@ -120,6 +120,7 @@ struct PatchFirmwareCLI: ParsableCommand {
|
||||
case regular
|
||||
case dev
|
||||
case jb
|
||||
case exp
|
||||
|
||||
var pipelineVariant: FirmwarePipeline.Variant {
|
||||
switch self {
|
||||
@@ -127,15 +128,17 @@ struct PatchFirmwareCLI: ParsableCommand {
|
||||
case .regular: .regular
|
||||
case .dev: .dev
|
||||
case .jb: .jb
|
||||
case .exp: .exp
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var virtualMachineVariant: VPhoneVirtualMachine.Variant {
|
||||
switch self {
|
||||
case .less: .less
|
||||
case .regular: .regular
|
||||
case .dev: .dev
|
||||
case .jb: .jb
|
||||
case .exp: .exp
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ class VPhoneVirtualMachine: NSObject, VZVirtualMachineDelegate {
|
||||
case regular
|
||||
case dev
|
||||
case jb
|
||||
case exp
|
||||
}
|
||||
|
||||
struct Options {
|
||||
|
||||
Reference in New Issue
Block a user