Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 101 additions & 0 deletions scripts/fw_prepare.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
# - direct iPhone IPSW URLs or local file paths
# - version/build selectors for the target device
# - listing of all downloadable IPSWs for the target device
# - The "VARIANT" environment variable to download apfs_sealvolume for the patchless mode
#
# Listing and selection are resolved through the `ipsw` CLI already used
# elsewhere in this repo, so the script can work with the full downloadable
Expand Down Expand Up @@ -410,6 +411,99 @@ extract() {
cp -R "$cache" "$out"
}

download_apfs_sealvolume() {
local src="$1"
local base ios_version filename PROJECT_DIR TOOLS_PREFIX TMP_DIR bn ver BUILD BUILD_MANIFEST RAMDISK_PATH RAMDISK_IM4P RAMDISK MOUNT

base="$(basename "$src")"
ios_version="$(awk -F_ 'NF >= 2 { print $2 }' <<<"$base")"

if [[ -z "$ios_version" ]]; then
echo "Error: could not determine iOS version from filename: $base" >&2
return 1
fi

filename="apfs_sealvolume_${ios_version}"

Comment on lines +415 to +427
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
TOOLS_PREFIX="$PROJECT_DIR/.tools"

if [[ -f "$TOOLS_PREFIX/$filename" ]]; then
echo "$filename already present"
else
echo "Downloading $filename"
(
TMP_DIR="$(mktemp -d)"
trap 'rm -rf "$TMP_DIR"' EXIT

# List matching macOS version to the iOS version
while IFS= read -r url; do
bn="$(basename "$url")"
ver="${bn#*_}"
ver="${ver%%_*}"

[[ "$ver" == "$ios_version" ]] || continue

BUILD="$(awk -F_ '{print $3}' <<<"$bn")"
break
done < <(
ipsw download appledb \
--os macOS \
--version $ios_version \
--urls
)

if [[ -z "${BUILD:-}" ]]; then
echo "Error: failed to determine macOS build from available URLs" >&2
exit 1
fi

# Download BuildManifest first
ipsw download appledb \
--os macOS \
--build $BUILD \
--pattern "^BuildManifest.plist\$" \
--output "$TMP_DIR"

BUILD_MANIFEST="$(find "$TMP_DIR" -name BuildManifest.plist -print -quit)"
if [ -z "$BUILD_MANIFEST" ]; then
echo "Failed to locate BuildManifest.plist"
exit 1
fi

RAMDISK_PATH="$(/usr/bin/plutil -extract 'BuildIdentities.0.Manifest.RestoreRamDisk.Info.Path' raw -o - "$BUILD_MANIFEST")"
if [ -z "$RAMDISK_PATH" ]; then
echo "Failed to read RestoreRamDisk path from BuildManifest"
exit 1
fi

# Download the ramdisk referenced by BuildManifest
ipsw download appledb \
--os macOS \
--build $BUILD \
--pattern "$RAMDISK_PATH" \
--output "$TMP_DIR"

RAMDISK_IM4P="$(find "$TMP_DIR" -path "*${RAMDISK_PATH}" -print -quit)"
if [ -z "$RAMDISK_IM4P" ]; then
echo "Failed to locate downloaded ramdisk: $RAMDISK_PATH"
exit 1
fi

RAMDISK="$TMP_DIR/ramdisk.dmg"
ipsw img4 im4p extract --output "$RAMDISK" "$RAMDISK_IM4P"

MOUNT=$(hdiutil attach -readonly -nobrowse "$RAMDISK" | awk 'END{ print$NF}')
cp "$MOUNT/System/Library/Filesystems/apfs.fs/Contents/Resources/apfs_sealvolume" \
"$TOOLS_PREFIX/$filename"
hdiutil detach "$MOUNT" >/dev/null 2>&1 || true
)
echo " Downloaded: $TOOLS_PREFIX/$filename"
echo " Resigning $filename"
codesign --force --sign - "$TOOLS_PREFIX/$filename"
fi
}

LIST_FIRMWARES="${LIST_FIRMWARES:-0}"
IPHONE_DEVICE="${IPHONE_DEVICE:-$DEFAULT_IPHONE_DEVICE}"
IPHONE_VERSION="${IPHONE_VERSION:-}"
Expand Down Expand Up @@ -537,6 +631,13 @@ echo ""
fetch "$IPHONE_SOURCE" "$IPHONE_IPSW_PATH"
fetch "$CLOUDOS_SOURCE" "$CLOUDOS_IPSW_PATH"

VARIANT="${VARIANT:-}"
if [[ "$VARIANT" == "less" ]]; then
download_apfs_sealvolume "$IPHONE_SOURCE"
else
echo "==> Downloading apfs sealvolume (skipped — patchless variant only)"
Comment on lines +634 to +638
fi

IPHONE_CACHE="${IPSW_DIR}/${IPHONE_DIR}"
CLOUDOS_CACHE="${IPSW_DIR}/${CLOUDOS_DIR}"

Expand Down
9 changes: 3 additions & 6 deletions scripts/setup_machine.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1053,11 +1053,7 @@ main() {
install_brew_deps
ensure_python_linked

if [[ "$LESS_MODE" -eq 1 ]]; then
VARIANT=less run_make "Project setup" setup_tools
else
run_make "Project setup" setup_tools
fi
run_make "Project setup" setup_tools
run_make "Project setup" build
fi

Expand All @@ -1066,10 +1062,11 @@ main() {
export PATH="$PROJECT_ROOT/.venv/bin:$PATH"

run_make "Firmware prep" vm_new
run_make "Firmware prep" fw_prepare
if [[ "$LESS_MODE" -eq 0 ]]; then
run_make "Firmware prep" fw_prepare
run_make "Firmware patch" "$fw_patch_target"
else
VARIANT=less run_make "Firmware prep" fw_prepare
run_make_sudo "Firmware patch" "$fw_patch_target"
fi

Expand Down
41 changes: 4 additions & 37 deletions scripts/setup_tools.sh
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ ensure_repo_submodule() {

# ── Brew packages ──────────────────────────────────────────────

echo "[1/5] Checking brew packages..."
echo "[1/4] Checking brew packages..."

BREW_PACKAGES=(aria2 gnu-tar openssl@3 ldid-procursus sshpass zstd)
BREW_MISSING=()
Expand All @@ -45,7 +45,7 @@ fi

# ── Trustcache ─────────────────────────────────────────────────

echo "[2/5] trustcache"
echo "[2/4] trustcache"

TRUSTCACHE_BIN="$TOOLS_PREFIX/bin/trustcache"
if [[ -x "$TRUSTCACHE_BIN" ]]; then
Expand Down Expand Up @@ -74,7 +74,7 @@ fi

# ── insert_dylib ───────────────────────────────────────────────

echo "[3/5] insert_dylib"
echo "[3/4] insert_dylib"

INSERT_DYLIB_BIN="$TOOLS_PREFIX/bin/insert_dylib"
if [[ -x "$INSERT_DYLIB_BIN" ]]; then
Expand All @@ -90,41 +90,8 @@ fi

# ── Python venv ────────────────────────────────────────────────

echo "[4/5] Python venv"
echo "[4/4] Python venv"
zsh "$SCRIPT_DIR/setup_venv.sh"

# ── APFS sealvolume (patchless variant only) ──────────────────────

VARIANT="${VARIANT:-}"

if [[ "$VARIANT" == "less" ]]; then
echo "[5/5] apfs sealvolume"
if [[ -f "$TOOLS_PREFIX/apfs_sealvolume" ]]; then
echo " apfs_sealvolume already present"
else
TMP_DIR="$(mktemp -d)"
ipsw download appledb \
--os macOS \
--build 25D2140 \
--pattern "094-33864-054.dmg" \
--output "$TMP_DIR"

RAMDISK_IM4P="$TMP_DIR/25D2140__MacOS/094-33864-054.dmg"
RAMDISK="$TMP_DIR/ramdisk.dmg"
ipsw img4 im4p extract --output "$RAMDISK" "$RAMDISK_IM4P"

MOUNT=$(hdiutil attach -readonly -nobrowse "$RAMDISK" | awk 'END{ print$NF}')
cp "$MOUNT/System/Library/Filesystems/apfs.fs/Contents/Resources/apfs_sealvolume" \
"$TOOLS_PREFIX/apfs_sealvolume"
hdiutil detach "$MOUNT" >/dev/null 2>&1 || true
rm -rf "$TMP_DIR"
echo " Downloaded: $TOOLS_PREFIX/apfs_sealvolume"
echo " Resigning apfs_sealvolume"
codesign --force --sign - "$TOOLS_PREFIX/apfs_sealvolume"
fi
else
echo "[5/5] apfs sealvolume (skipped — patchless variant only)"
fi

echo ""
echo "All tools installed."
27 changes: 25 additions & 2 deletions sources/FirmwarePatcher/Filesystem/CryptexFilesystemPatcher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import Img4tool

enum ProcessError: Error {
case failed(Int32, String)
case notExecutable(String)
}

extension Data {
Expand Down Expand Up @@ -394,6 +395,18 @@ public final class CryptexFilesystemPatcher: Patcher {
return im4pPath
}

private func identifyApfsSealvolume() throws -> URL {
let iosVersion = try getProductVersion()
let path = self.vphoneCliDirectory.appending(path: ".tools/apfs_sealvolume_\(iosVersion)")
guard FileManager.default.fileExists(atPath: path.path) else {
throw FirmwareManifest.ManifestError.fileNotFound(path.path)
}
guard FileManager.default.isExecutableFile(atPath: path.path) else {
throw ProcessError.notExecutable(path.path)
}
return path
}

func createDigestAndHash(filesystem: URL, mtree: URL, remap: Bool) throws -> (URL, URL) {
let (device, mount) = try attachImage(path: filesystem)
defer { try? detachImage(deviceNode: device) }
Expand All @@ -402,6 +415,7 @@ public final class CryptexFilesystemPatcher: Patcher {
let digestDbPath = tmpDir.appending(path: "digest.db")
let rootHashPath = tmpDir.appending(path: "root_hash")
let mtreeRemapPath = tmpDir.appending(path: "mtree_remap.xml")
let sealLogPath = tmpDir.appending(path: "seal.log")

// We want to get the nanosecond timestamp of the last modification before the mtree collection.
// We know that we remove directories in /private/var in removeSpecificSystemFiles last.
Expand All @@ -423,13 +437,14 @@ public final class CryptexFilesystemPatcher: Patcher {
FileManager.default.createFile(atPath: mtreeRemapPath.path, contents: remapContent.data(using: .utf8))

try unmount(mount: mount)
let sealvolume = self.vphoneCliDirectory.appending(path: ".tools/apfs_sealvolume")
let sealvolume = try identifyApfsSealvolume()
FileManager.default.createFile(atPath: sealLogPath.path, contents: nil)
_ = try runProcess(sealvolume.path, [
"-R", mtreeRemapPath.path,
"-U", digestDbPath.path, // Save digest records
"-M", rootHashPath.path, // Save root hash
device
])
], output: sealLogPath)
return (digestDbPath, rootHashPath)
}

Expand Down Expand Up @@ -615,6 +630,14 @@ public final class CryptexFilesystemPatcher: Patcher {
return remaining.joined(separator: "/")
}

func getProductVersion() throws -> String {
let root = try parsePlist(data: buildManiest)
guard let productVersion = root["ProductVersion"] as? String else {
throw FirmwareManifest.ManifestError.missingKey("ProductVersion in BuildManifest")
}
return productVersion
}

func getTrustcachePath() throws -> String {
let path = self.restoreDir.appending(path: "iPhone-BuildManifest.plist")
let manifest = try getBuildIdentityManifest(path: path)
Expand Down