add testing ramdisk: boot chain only (no rootfs, kernel will panic)

Sign patched firmware components (iBSS/iBEC/SPTM/DeviceTree/SEP/TXM/
kernelcache) into IMG4 without ramdisk or trustcache. Useful for
verifying boot chain patches in isolation.
This commit is contained in:
Lakr
2026-03-04 17:28:06 +08:00
parent 62b1564e20
commit 4692a9bee4
3 changed files with 370 additions and 1 deletions

View File

@@ -71,6 +71,8 @@ help:
@echo "Ramdisk:"
@echo " make ramdisk_build Build signed SSH ramdisk"
@echo " make ramdisk_send Send ramdisk to device"
@echo " make testing_ramdisk_build Build boot chain only (no rootfs, kernel will panic)"
@echo " make testing_ramdisk_send Send testing boot chain to device"
@echo ""
@echo "CFW:"
@echo " make cfw_install Install CFW mods via SSH"
@@ -217,7 +219,7 @@ restore:
# Ramdisk
# ═══════════════════════════════════════════════════════════════════
.PHONY: ramdisk_build ramdisk_send
.PHONY: ramdisk_build ramdisk_send testing_ramdisk_build testing_ramdisk_send
ramdisk_build:
cd $(VM_DIR) && $(PYTHON) "$(CURDIR)/$(SCRIPTS)/ramdisk_build.py" .
@@ -225,6 +227,12 @@ ramdisk_build:
ramdisk_send:
cd $(VM_DIR) && IRECOVERY="$(CURDIR)/$(IRECOVERY)" zsh "$(CURDIR)/$(SCRIPTS)/ramdisk_send.sh"
testing_ramdisk_build:
cd $(VM_DIR) && $(PYTHON) "$(CURDIR)/$(SCRIPTS)/testing_ramdisk_build.py" .
testing_ramdisk_send:
cd $(VM_DIR) && IRECOVERY="$(CURDIR)/$(IRECOVERY)" zsh "$(CURDIR)/$(SCRIPTS)/testing_ramdisk_send.sh"
# ═══════════════════════════════════════════════════════════════════
# CFW
# ═══════════════════════════════════════════════════════════════════

View File

@@ -0,0 +1,303 @@
#!/usr/bin/env python3
"""
fw_build_testing_ramdisk.py — Build a minimal signed boot chain for testing.
Packs only firmware components (iBSS, iBEC, SPTM, DeviceTree, SEP, TXM,
kernelcache) into signed IMG4 files. No ramdisk, no trustcache.
The kernel is expected to boot and then panic (no rootfs). This is useful
for verifying that patched boot-chain components (iBSS/iBEC/LLB/iBoot/TXM/
kernelcache) work correctly.
Usage:
python3 fw_build_testing_ramdisk.py [vm_directory]
Prerequisites:
pip install pyimg4
Run fw_patch.py first to patch boot-chain components.
"""
import glob
import gzip
import os
import shutil
import subprocess
import sys
_SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
if _SCRIPT_DIR not in sys.path:
sys.path.insert(0, _SCRIPT_DIR)
from pyimg4 import IM4M, IM4P, IMG4
from fw_patch import (
load_firmware,
_save_im4p_with_payp,
find_restore_dir,
find_file,
)
# ══════════════════════════════════════════════════════════════════
# Configuration
# ══════════════════════════════════════════════════════════════════
OUTPUT_DIR = "TestingRamdisk"
TEMP_DIR = "testing_ramdisk_temp"
# IM4P fourccs for restore mode
TXM_FOURCC = "trxm"
KERNEL_FOURCC = "rkrn"
# ══════════════════════════════════════════════════════════════════
# SHSH / signing helpers
# ══════════════════════════════════════════════════════════════════
def find_shsh(shsh_dir):
"""Find first SHSH blob in directory."""
for ext in ("*.shsh", "*.shsh2"):
matches = sorted(glob.glob(os.path.join(shsh_dir, ext)))
if matches:
return matches[0]
return None
def extract_im4m(shsh_path, im4m_path):
"""Extract IM4M manifest from SHSH blob (handles gzip-compressed)."""
raw = open(shsh_path, "rb").read()
if raw[:2] == b"\x1f\x8b":
raw = gzip.decompress(raw)
tmp = shsh_path + ".tmp"
try:
open(tmp, "wb").write(raw)
subprocess.run(
["pyimg4", "im4m", "extract", "-i", tmp, "-o", im4m_path],
check=True,
capture_output=True,
)
finally:
if os.path.exists(tmp):
os.remove(tmp)
def sign_img4(im4p_path, img4_path, im4m_path, tag=None):
"""Create IMG4 from IM4P + IM4M using pyimg4 Python API."""
im4p = IM4P(open(im4p_path, "rb").read())
if tag:
im4p.fourcc = tag
im4m = IM4M(open(im4m_path, "rb").read())
img4 = IMG4(im4p=im4p, im4m=im4m)
with open(img4_path, "wb") as f:
f.write(img4.output())
# ══════════════════════════════════════════════════════════════════
# Firmware extraction
# ══════════════════════════════════════════════════════════════════
def extract_to_raw(src_path, raw_path):
"""Extract IM4P payload to .raw file. Returns (im4p_obj, data, original_raw)."""
im4p, data, was_im4p, original_raw = load_firmware(src_path)
with open(raw_path, "wb") as f:
f.write(bytes(data))
return im4p, data, original_raw
def create_im4p_uncompressed(raw_data, fourcc, description, output_path):
"""Create uncompressed IM4P from raw data."""
new_im4p = IM4P(
fourcc=fourcc,
description=description,
payload=bytes(raw_data),
)
with open(output_path, "wb") as f:
f.write(new_im4p.output())
# ══════════════════════════════════════════════════════════════════
# Main
# ══════════════════════════════════════════════════════════════════
def main():
vm_dir = os.path.abspath(sys.argv[1] if len(sys.argv) > 1 else os.getcwd())
if not os.path.isdir(vm_dir):
print(f"[-] Not a directory: {vm_dir}")
sys.exit(1)
# Find SHSH
shsh_dir = os.path.join(vm_dir, "shsh")
shsh_path = find_shsh(shsh_dir)
if not shsh_path:
print(f"[-] No SHSH blob found in {shsh_dir}/")
print(" Place your .shsh file in the shsh/ directory.")
sys.exit(1)
# Find restore directory
restore_dir = find_restore_dir(vm_dir)
if not restore_dir:
print(f"[-] No *Restore* directory found in {vm_dir}")
sys.exit(1)
# Create temp and output directories
temp_dir = os.path.join(vm_dir, TEMP_DIR)
output_dir = os.path.join(vm_dir, OUTPUT_DIR)
for d in (temp_dir, output_dir):
if os.path.exists(d):
shutil.rmtree(d)
os.makedirs(d)
print(f"[*] Testing ramdisk — boot chain only (no rootfs, kernel will panic)")
print(f"[*] VM directory: {vm_dir}")
print(f"[*] Restore directory: {restore_dir}")
print(f"[*] SHSH blob: {shsh_path}")
# Extract IM4M from SHSH
im4m_path = os.path.join(temp_dir, "vphone.im4m")
print(f"\n[*] Extracting IM4M from SHSH...")
extract_im4m(shsh_path, im4m_path)
# ── 1. iBSS (already patched — extract & sign) ───────────────
print(f"\n{'=' * 60}")
print(f" 1. iBSS (already patched — extract & sign)")
print(f"{'=' * 60}")
ibss_src = find_file(
restore_dir,
["Firmware/dfu/iBSS.vresearch101.RELEASE.im4p"],
"iBSS",
)
ibss_raw = os.path.join(temp_dir, "iBSS.raw")
ibss_im4p = os.path.join(temp_dir, "iBSS.im4p")
im4p_obj, data, _ = extract_to_raw(ibss_src, ibss_raw)
create_im4p_uncompressed(data, im4p_obj.fourcc, im4p_obj.description, ibss_im4p)
sign_img4(
ibss_im4p,
os.path.join(output_dir, "iBSS.vresearch101.RELEASE.img4"),
im4m_path,
)
print(f" [+] iBSS.vresearch101.RELEASE.img4")
# ── 2. iBEC (already patched — sign as-is, no boot-args change)
print(f"\n{'=' * 60}")
print(f" 2. iBEC (already patched — sign as-is)")
print(f"{'=' * 60}")
ibec_src = find_file(
restore_dir,
["Firmware/dfu/iBEC.vresearch101.RELEASE.im4p"],
"iBEC",
)
ibec_raw = os.path.join(temp_dir, "iBEC.raw")
ibec_im4p = os.path.join(temp_dir, "iBEC.im4p")
im4p_obj, data, _ = extract_to_raw(ibec_src, ibec_raw)
create_im4p_uncompressed(data, im4p_obj.fourcc, im4p_obj.description, ibec_im4p)
sign_img4(
ibec_im4p,
os.path.join(output_dir, "iBEC.vresearch101.RELEASE.img4"),
im4m_path,
)
print(f" [+] iBEC.vresearch101.RELEASE.img4")
# ── 3. SPTM (sign only) ─────────────────────────────────────
print(f"\n{'=' * 60}")
print(f" 3. SPTM (sign only)")
print(f"{'=' * 60}")
sptm_src = find_file(
restore_dir,
["Firmware/sptm.vresearch1.release.im4p"],
"SPTM",
)
sign_img4(
sptm_src,
os.path.join(output_dir, "sptm.vresearch1.release.img4"),
im4m_path,
tag="sptm",
)
print(f" [+] sptm.vresearch1.release.img4")
# ── 4. DeviceTree (sign only) ────────────────────────────────
print(f"\n{'=' * 60}")
print(f" 4. DeviceTree (sign only)")
print(f"{'=' * 60}")
dt_src = find_file(
restore_dir,
["Firmware/all_flash/DeviceTree.vphone600ap.im4p"],
"DeviceTree",
)
sign_img4(
dt_src,
os.path.join(output_dir, "DeviceTree.vphone600ap.img4"),
im4m_path,
tag="rdtr",
)
print(f" [+] DeviceTree.vphone600ap.img4")
# ── 5. SEP (sign only) ───────────────────────────────────────
print(f"\n{'=' * 60}")
print(f" 5. SEP (sign only)")
print(f"{'=' * 60}")
sep_src = find_file(
restore_dir,
["Firmware/all_flash/sep-firmware.vresearch101.RELEASE.im4p"],
"SEP",
)
sign_img4(
sep_src,
os.path.join(output_dir, "sep-firmware.vresearch101.RELEASE.img4"),
im4m_path,
tag="rsep",
)
print(f" [+] sep-firmware.vresearch101.RELEASE.img4")
# ── 6. TXM (already patched — repack & sign) ─────────────────
print(f"\n{'=' * 60}")
print(f" 6. TXM (already patched — repack & sign)")
print(f"{'=' * 60}")
txm_src = find_file(
restore_dir,
["Firmware/txm.iphoneos.research.im4p"],
"TXM",
)
txm_raw = os.path.join(temp_dir, "txm.raw")
im4p_obj, data, original_raw = extract_to_raw(txm_src, txm_raw)
txm_im4p = os.path.join(temp_dir, "txm.im4p")
_save_im4p_with_payp(txm_im4p, TXM_FOURCC, data, original_raw)
sign_img4(txm_im4p, os.path.join(output_dir, "txm.img4"), im4m_path)
print(f" [+] txm.img4")
# ── 7. Kernelcache (already patched — repack as rkrn) ────────
print(f"\n{'=' * 60}")
print(f" 7. Kernelcache (already patched — repack as rkrn)")
print(f"{'=' * 60}")
kc_src = find_file(
restore_dir,
["kernelcache.research.vphone600"],
"kernelcache",
)
kc_raw = os.path.join(temp_dir, "kcache.raw")
im4p_obj, data, original_raw = extract_to_raw(kc_src, kc_raw)
print(f" format: IM4P, {len(data)} bytes")
kc_im4p = os.path.join(temp_dir, "krnl.im4p")
_save_im4p_with_payp(kc_im4p, KERNEL_FOURCC, data, original_raw)
sign_img4(kc_im4p, os.path.join(output_dir, "krnl.img4"), im4m_path)
print(f" [+] krnl.img4")
# ── Cleanup ──────────────────────────────────────────────────
print(f"\n[*] Cleaning up {TEMP_DIR}/...")
shutil.rmtree(temp_dir, ignore_errors=True)
# ── Summary ──────────────────────────────────────────────────
print(f"\n{'=' * 60}")
print(f" Testing ramdisk build complete!")
print(f" Output: {output_dir}/")
print(f" Note: kernel will panic after boot (no rootfs — expected)")
print(f"{'=' * 60}")
for f in sorted(os.listdir(output_dir)):
size = os.path.getsize(os.path.join(output_dir, f))
print(f" {f:45s} {size:>10,} bytes")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,58 @@
#!/bin/zsh
# fw_send_testing_ramdisk.sh — Send testing boot chain to device via irecovery.
#
# Usage: ./fw_send_testing_ramdisk.sh [testing_ramdisk_dir]
#
# Expects device in DFU mode. Loads iBSS/iBEC, then boots with
# SPTM, TXM, device tree, SEP, and kernel. No ramdisk or trustcache.
# Kernel will panic after boot (no rootfs — expected).
set -euo pipefail
IRECOVERY="${IRECOVERY:-irecovery}"
RAMDISK_DIR="${1:-TestingRamdisk}"
if [[ ! -d "$RAMDISK_DIR" ]]; then
echo "[-] Testing ramdisk directory not found: $RAMDISK_DIR"
echo " Run 'make testing_ramdisk_build' first."
exit 1
fi
echo "[*] Sending testing boot chain from $RAMDISK_DIR ..."
echo " (no rootfs — kernel will panic after boot)"
# 1. Load iBSS + iBEC (DFU → recovery)
echo " [1/6] Loading iBSS..."
"$IRECOVERY" -f "$RAMDISK_DIR/iBSS.vresearch101.RELEASE.img4"
echo " [2/6] Loading iBEC..."
"$IRECOVERY" -f "$RAMDISK_DIR/iBEC.vresearch101.RELEASE.img4"
"$IRECOVERY" -c go
sleep 1
# 2. Load SPTM
echo " [3/6] Loading SPTM..."
"$IRECOVERY" -f "$RAMDISK_DIR/sptm.vresearch1.release.img4"
"$IRECOVERY" -c firmware
# 3. Load TXM
echo " [4/6] Loading TXM..."
"$IRECOVERY" -f "$RAMDISK_DIR/txm.img4"
"$IRECOVERY" -c firmware
# 4. Load device tree
echo " [5/6] Loading DeviceTree..."
"$IRECOVERY" -f "$RAMDISK_DIR/DeviceTree.vphone600ap.img4"
"$IRECOVERY" -c devicetree
# 5. Load SEP
echo " [6/6] Loading SEP..."
"$IRECOVERY" -f "$RAMDISK_DIR/sep-firmware.vresearch101.RELEASE.img4"
"$IRECOVERY" -c firmware
# 6. Load kernel and boot
echo " [*] Booting kernel..."
"$IRECOVERY" -f "$RAMDISK_DIR/krnl.img4"
"$IRECOVERY" -c bootx
echo "[+] Boot sequence sent. Kernel should boot and then panic (no rootfs)."