Remove temporary testing scripts and Makefile targets

These were used for per-patch kernel JB debugging (C23 bisect,
single-patch boot test, batch testing). No longer needed now that
A2, C23, and C24 are all boot-tested and fixed.
This commit is contained in:
Lakr
2026-03-04 21:43:02 +08:00
parent abaa039496
commit cb63ffa3b2
10 changed files with 1 additions and 1290 deletions
+1 -30
View File
@@ -71,11 +71,6 @@ 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 SSH, no CFW)"
@echo " make testing_ramdisk_send Send testing boot chain to device"
@echo " make testing_do_save Full pipeline + save base kernel backup"
@echo " make testing_do_patch PATCH=name Test single JB kernel patch (fast)"
@echo " make testing_kernel_patch PATCH=name Restore+patch kernel only (no boot)"
@echo ""
@echo "CFW:"
@echo " make cfw_install Install CFW mods via SSH"
@@ -222,7 +217,7 @@ restore:
# Ramdisk
# ═══════════════════════════════════════════════════════════════════
.PHONY: ramdisk_build ramdisk_send testing_ramdisk_build testing_ramdisk_send testing_do testing_do_save testing_kernel_patch testing_do_patch testing_c23_bisect
.PHONY: ramdisk_build ramdisk_send
ramdisk_build:
cd $(VM_DIR) && $(PYTHON) "$(CURDIR)/$(SCRIPTS)/ramdisk_build.py" .
@@ -230,30 +225,6 @@ 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"
testing_do:
zsh "$(CURDIR)/$(SCRIPTS)/testing_do.sh"
testing_do_save:
zsh "$(CURDIR)/$(SCRIPTS)/testing_do_save.sh"
testing_kernel_patch:
cd $(VM_DIR) && $(PYTHON) "$(CURDIR)/$(SCRIPTS)/testing_kernel_patch.py" . $(PATCH)
testing_do_patch:
zsh "$(CURDIR)/$(SCRIPTS)/testing_do_patch.sh" $(PATCH)
testing_batch:
zsh "$(CURDIR)/$(SCRIPTS)/testing_batch.sh" $(PATCHES)
testing_c23_bisect:
cd $(VM_DIR) && $(PYTHON) "$(CURDIR)/$(SCRIPTS)/testing_c23_bisect.py" . $(VARIANT)
# ═══════════════════════════════════════════════════════════════════
# CFW
# ═══════════════════════════════════════════════════════════════════
-254
View File
@@ -1,254 +0,0 @@
#!/usr/bin/env zsh
set -euo pipefail
# ═══════════════════════════════════════════════════════════════════
# testing_batch.sh — Batch-test kernel JB patches one at a time.
#
# Prerequisite: run `make testing_do_save` first to create base backup.
#
# For each patch:
# 1. Restore base kernel + apply single patch
# 2. Build ramdisk
# 3. Boot DFU (vphone-cli --no-graphics --dfu)
# 4. Send boot chain via irecovery
# 5. Wait up to 2 minutes, watching for panic
# 6. Save output → testing_results/<patch_name>.log
# 7. Kill everything, move to next patch
#
# Usage:
# ./testing_batch.sh # test all uncommented B/C patches
# ./testing_batch.sh patch_mac_mount patch_dounmount # test specific patches
# ═══════════════════════════════════════════════════════════════════
PROJECT_DIR="$(cd "$(dirname "${0:a:h}")" && pwd)"
cd "$PROJECT_DIR"
VM_DIR="$PROJECT_DIR/${VM_DIR:-vm}"
RESULTS_DIR="$PROJECT_DIR/testing_results"
TIMEOUT_SECS=120 # 2 minutes
BINARY="$PROJECT_DIR/.build/release/vphone-cli"
IRECOVERY="$PROJECT_DIR/.limd/bin/irecovery"
PYTHON="$PROJECT_DIR/.venv/bin/python3"
mkdir -p "$RESULTS_DIR"
# ─── Default patch list (all B + C patches) ────────────────────────
ALL_PATCHES=(
patch_post_validation_additional # B5
patch_proc_security_policy # B6
patch_proc_pidinfo # B7
patch_convert_port_to_map # B8
patch_vm_fault_enter_prepare # B9
patch_vm_map_protect # B10
patch_mac_mount # B11
patch_dounmount # B12
patch_bsd_init_auth # B13
patch_spawn_validate_persona # B14
patch_task_for_pid # B15
patch_load_dylinker # B16
patch_shared_region_map # B17
patch_nvram_verify_permission # B18
patch_io_secure_bsd_root # B19
patch_thid_should_crash # B20
patch_cred_label_update_execve # C21
patch_syscallmask_apply_to_proc # C22
patch_hook_cred_label_update_execve # C23
patch_kcall10 # C24
)
# Use args if provided, otherwise test all
if (( $# > 0 )); then
PATCHES=("$@")
else
PATCHES=("${ALL_PATCHES[@]}")
fi
echo "═══════════════════════════════════════════════════════════════"
echo " Batch kernel JB patch tester"
echo " Testing ${#PATCHES[@]} patch(es), ${TIMEOUT_SECS}s timeout each"
echo " Results → $RESULTS_DIR/"
echo "═══════════════════════════════════════════════════════════════"
echo ""
# ─── Verify backup exists ──────────────────────────────────────────
RESTORE_DIR=$(find "$VM_DIR" -maxdepth 1 -type d -name '*Restore*' | head -1)
KERNEL_BACKUP=$(find "$RESTORE_DIR" -name 'kernelcache.research.vphone600.base_backup' 2>/dev/null | head -1)
if [[ -z "$KERNEL_BACKUP" ]]; then
echo "[-] No kernel backup found. Run 'make testing_do_save' first."
exit 1
fi
echo "[*] Kernel backup: $KERNEL_BACKUP"
echo ""
# ─── Summary file ──────────────────────────────────────────────────
SUMMARY="$RESULTS_DIR/_summary.txt"
echo "Batch test run: $(date)" > "$SUMMARY"
echo "Timeout: ${TIMEOUT_SECS}s" >> "$SUMMARY"
echo "─────────────────────────────────────────" >> "$SUMMARY"
# ─── Test each patch ───────────────────────────────────────────────
PASS_COUNT=0
FAIL_COUNT=0
ERROR_COUNT=0
TOTAL=${#PATCHES[@]}
for i in {1..$TOTAL}; do
PATCH="${PATCHES[$i]}"
LOG="$RESULTS_DIR/${PATCH}.log"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " [$i/$TOTAL] Testing: $PATCH"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
# Clean up any prior vphone-cli
pkill -9 vphone-cli 2>/dev/null || true
sleep 1
# Header in log
{
echo "Patch: $PATCH"
echo "Date: $(date)"
echo "Timeout: ${TIMEOUT_SECS}s"
echo "══════════════════════════════════════════"
} > "$LOG"
# Step 1: Restore kernel + apply patch
echo " [1/4] Patching kernel..."
{
echo ""
echo "── kernel patch ──"
"$PYTHON" "$PROJECT_DIR/scripts/testing_kernel_patch.py" "$VM_DIR" "$PATCH" 2>&1
} >> "$LOG" 2>&1
PATCH_EXIT=$?
if (( PATCH_EXIT != 0 )); then
echo " [-] Patch failed (exit $PATCH_EXIT) — skipping"
echo "$PATCH ERROR patch_failed" >> "$SUMMARY"
ERROR_COUNT=$(( ERROR_COUNT + 1 ))
continue
fi
# Step 2: Build ramdisk
echo " [2/4] Building ramdisk..."
{
echo ""
echo "── ramdisk build ──"
make -C "$PROJECT_DIR" testing_ramdisk_build VM_DIR="$VM_DIR" 2>&1
} >> "$LOG" 2>&1
# Step 3: Boot DFU (capture output)
echo " [3/4] Booting DFU..."
{
echo ""
echo "── boot output ──"
} >> "$LOG"
# Start vphone-cli in background, capturing output
"$BINARY" \
--rom "$VM_DIR/AVPBooter.vresearch1.bin" \
--disk "$VM_DIR/Disk.img" \
--nvram "$VM_DIR/nvram.bin" \
--machine-id "$VM_DIR/machineIdentifier.bin" \
--cpu 8 --memory 8192 \
--sep-rom "$VM_DIR/AVPSEPBooter.vresearch1.bin" \
--sep-storage "$VM_DIR/SEPStorage" \
--no-graphics --dfu \
>> "$LOG" 2>&1 &
VM_PID=$!
# Step 4: Send boot chain
echo " [4/4] Sending boot chain..."
{
echo ""
echo "── ramdisk send ──"
make -C "$PROJECT_DIR" testing_ramdisk_send VM_DIR="$VM_DIR" 2>&1
} >> "$LOG" 2>&1 || true
# ─── Monitor for panic / success / timeout ─────────────────────
echo " [*] Monitoring for ${TIMEOUT_SECS}s..."
RESULT="TIMEOUT"
START_TIME=$SECONDS
while (( SECONDS - START_TIME < TIMEOUT_SECS )); do
# Check if VM died
if ! kill -0 "$VM_PID" 2>/dev/null; then
RESULT="VM_DIED"
break
fi
# Check log for panic
if grep -qi 'panic' "$LOG" 2>/dev/null; then
sleep 5 # let full panic log flush
RESULT="PANIC"
break
fi
# Check log for successful boot (lockdownd socket accepted)
if grep -q 'sock.*accepted.*62078' "$LOG" 2>/dev/null; then
RESULT="BOOT_OK"
break
fi
sleep 3
done
ELAPSED=$(( SECONDS - START_TIME ))
# Kill VM
kill -9 "$VM_PID" 2>/dev/null || true
wait "$VM_PID" 2>/dev/null || true
# Also kill any orphaned vphone-cli
pkill -9 vphone-cli 2>/dev/null || true
# Append result to log
{
echo ""
echo "══════════════════════════════════════════"
echo "RESULT: $RESULT"
echo "ELAPSED: ${ELAPSED}s"
} >> "$LOG"
# Record
case "$RESULT" in
BOOT_OK)
echo " [+] BOOT OK after ${ELAPSED}s → $LOG"
echo "$PATCH BOOT_OK ${ELAPSED}s" >> "$SUMMARY"
PASS_COUNT=$(( PASS_COUNT + 1 ))
;;
PANIC)
echo " [X] PANIC after ${ELAPSED}s → $LOG"
echo "$PATCH PANIC ${ELAPSED}s" >> "$SUMMARY"
FAIL_COUNT=$(( FAIL_COUNT + 1 ))
;;
VM_DIED)
echo " [X] VM died after ${ELAPSED}s → $LOG"
echo "$PATCH VM_DIED ${ELAPSED}s" >> "$SUMMARY"
FAIL_COUNT=$(( FAIL_COUNT + 1 ))
;;
TIMEOUT)
echo " [?] TIMEOUT after ${TIMEOUT_SECS}s → $LOG"
echo "$PATCH TIMEOUT ${TIMEOUT_SECS}s" >> "$SUMMARY"
ERROR_COUNT=$(( ERROR_COUNT + 1 ))
;;
esac
echo ""
sleep 2
done
# ─── Final summary ─────────────────────────────────────────────────
{
echo ""
echo "═══════════════════════════════════════════"
echo "TOTAL: $TOTAL PASS: $PASS_COUNT FAIL: $FAIL_COUNT ERROR: $ERROR_COUNT"
} >> "$SUMMARY"
echo ""
echo "═══════════════════════════════════════════════════════════════"
echo " DONE — $TOTAL tested: $PASS_COUNT pass, $FAIL_COUNT fail, $ERROR_COUNT error"
echo " Summary: $SUMMARY"
echo " Logs: $RESULTS_DIR/<patch_name>.log"
echo "═══════════════════════════════════════════════════════════════"
cat "$SUMMARY"
-258
View File
@@ -1,258 +0,0 @@
#!/usr/bin/env python3
"""
testing_c23_bisect.py — Bisect C23 shellcode to find which part causes PAC panic.
Usage:
python3 testing_c23_bisect.py <vm_dir> <variant>
Variants (progressive complexity):
A — PACIBSP + save/restore regs + B hook+4 (stack frame, no calls)
B — A + mrs tpidr_el1 + vfs_context build (register reads, no calls)
C — B + BL vnode_getattr (external function call)
D — C + ownership propagation (uid/gid/csflags writes)
E — full shellcode (same as kernel_jb_patch_hook_cred_label.py)
Each variant is strictly additive — if A boots, B adds only the next layer.
"""
import os
import shutil
import sys
from fw_patch import find_file, find_restore_dir, load_firmware, save_firmware
from patchers.kernel_jb import KernelJBPatcher
from patchers.kernel_jb_base import asm, _rd32, _rd64, NOP
PACIBSP = bytes([0x7F, 0x23, 0x03, 0xD5])
def build_variant(kp, variant, cave, orig_hook, vnode_getattr_off):
"""Build shellcode for the given variant, return list of 4-byte parts."""
# Helper: encode BL/B
def bl(src, dst):
return kp._encode_bl(src, dst)
def b(src, dst):
return kp._encode_b(src, dst)
# B resume always at the last slot
# We'll pad all variants to 46 slots for consistency.
#
# Variant A: stack frame only
# Variant B: + tpidr_el1 / vfs_context
# Variant C: + BL vnode_getattr
# Variant D: + ownership propagation
# Variant E: full (same as production)
parts = []
if variant in ("A", "B", "C", "D", "E"):
parts.append(PACIBSP) # 0: relocated from hook
# In full shellcode, slot 1 is: cbz x3, #0xb0 → slot 45
# For variant A, skip the cbz (just NOP), so we always enter the frame
if variant == "A":
parts.append(NOP) # 1
else:
parts.append(asm("cbz x3, #0xb0")) # 1: if vp==NULL → slot 45
parts.append(asm("sub sp, sp, #0x400")) # 2
parts.append(asm("stp x29, x30, [sp]")) # 3
parts.append(asm("stp x0, x1, [sp, #16]")) # 4
parts.append(asm("stp x2, x3, [sp, #32]")) # 5
parts.append(asm("stp x4, x5, [sp, #48]")) # 6
parts.append(asm("stp x6, x7, [sp, #64]")) # 7
if variant in ("B", "C", "D", "E"):
# Build vfs_context
parts.append(asm("mrs x8, tpidr_el1")) # 8: current_thread
parts.append(asm("stp x8, x0, [sp, #0x70]")) # 9: {thread, cred}
parts.append(asm("add x2, sp, #0x70")) # 10: ctx = &vfs_ctx
# Setup vnode_getattr args
parts.append(asm("ldr x0, [sp, #0x28]")) # 11: x0 = vp (saved x3)
parts.append(asm("add x1, sp, #0x80")) # 12: x1 = &vattr
parts.append(asm("mov w8, #0x380")) # 13: vattr size
parts.append(asm("stp xzr, x8, [x1]")) # 14: init vattr
parts.append(asm("stp xzr, xzr, [x1, #0x10]")) # 15: init vattr+16
parts.append(NOP) # 16
parts.append(NOP) # 17
elif variant == "A":
# Pad slots 8-17 with NOP
for _ in range(10):
parts.append(NOP)
if variant in ("C", "D", "E"):
# BL vnode_getattr
vnode_bl_off = cave + 18 * 4
vnode_bl = bl(vnode_bl_off, vnode_getattr_off)
if not vnode_bl:
print(" [-] BL to vnode_getattr out of range")
return None
parts.append(vnode_bl) # 18: BL vnode_getattr
elif variant in ("A", "B"):
parts.append(NOP) # 18
if variant in ("C", "D", "E"):
# After BL, check result — jump to restore on error
parts.append(asm("cbnz x0, #0x4c")) # 19: error → slot 38
elif variant in ("A", "B"):
parts.append(NOP) # 19
if variant in ("D", "E"):
# Ownership propagation
parts.append(asm("mov w2, #0")) # 20: changed = 0
parts.append(asm("ldr w8, [sp, #0xCC]")) # 21: va_mode
parts.append(bytes([0xA8, 0x00, 0x58, 0x36])) # 22: tbz w8,#11
parts.append(asm("ldr w8, [sp, #0xC4]")) # 23: va_uid
parts.append(asm("ldr x0, [sp, #0x18]")) # 24: new_cred
parts.append(asm("str w8, [x0, #0x18]")) # 25: cred->uid
parts.append(asm("mov w2, #1")) # 26: changed = 1
parts.append(asm("ldr w8, [sp, #0xCC]")) # 27: va_mode
parts.append(bytes([0xA8, 0x00, 0x50, 0x36])) # 28: tbz w8,#10
parts.append(asm("mov w2, #1")) # 29: changed = 1
parts.append(asm("ldr w8, [sp, #0xC8]")) # 30: va_gid
parts.append(asm("ldr x0, [sp, #0x18]")) # 31: new_cred
parts.append(asm("str w8, [x0, #0x28]")) # 32: cred->gid
parts.append(asm("cbz w2, #0x14")) # 33: if !changed → slot 38
parts.append(asm("ldr x0, [sp, #0x20]")) # 34: proc
parts.append(asm("ldr w8, [x0, #0x454]")) # 35: p_csflags
parts.append(asm("orr w8, w8, #0x100")) # 36: CS_VALID
parts.append(asm("str w8, [x0, #0x454]")) # 37: store
elif variant in ("A", "B", "C"):
# Pad slots 20-37 with NOP
for _ in range(18):
parts.append(NOP)
# Restore and resume — always present (slots 38-45)
if variant in ("A", "B", "C", "D", "E"):
parts.append(asm("ldp x0, x1, [sp, #16]")) # 38
parts.append(asm("ldp x2, x3, [sp, #32]")) # 39
parts.append(asm("ldp x4, x5, [sp, #48]")) # 40
parts.append(asm("ldp x6, x7, [sp, #64]")) # 41
parts.append(asm("ldp x29, x30, [sp]")) # 42
parts.append(asm("add sp, sp, #0x400")) # 43
parts.append(NOP) # 44
# B hook+4
b_resume_off = cave + 45 * 4
b_resume = b(b_resume_off, orig_hook + 4)
if not b_resume:
print(" [-] B to hook+4 out of range")
return None
parts.append(b_resume) # 45
assert len(parts) == 46, f"Expected 46 parts, got {len(parts)}"
return parts
def main():
if len(sys.argv) < 3:
print(f"Usage: {sys.argv[0]} <vm_dir> <variant>")
print(f" Variants: A B C D E")
sys.exit(1)
vm_dir = os.path.abspath(sys.argv[1])
variant = sys.argv[2].upper()
if variant not in ("A", "B", "C", "D", "E"):
print(f"[-] Unknown variant: {variant}")
sys.exit(1)
restore_dir = find_restore_dir(vm_dir)
if not restore_dir:
print(f"[-] No *Restore* directory found in {vm_dir}")
sys.exit(1)
kernel_path = find_file(restore_dir, ["kernelcache.research.vphone600"], "kernelcache")
backup_path = kernel_path + ".base_backup"
if not os.path.exists(backup_path):
print(f"[-] No backup found: {backup_path}")
sys.exit(1)
# Restore from backup
shutil.copy2(backup_path, kernel_path)
print(f"[*] Restored kernel from backup")
# Load
im4p, data, was_im4p, original_raw = load_firmware(kernel_path)
print(f"[*] Loaded: {len(data)} bytes")
kp = KernelJBPatcher(data)
# ── Find vnode_getattr ──
vnode_getattr_off = kp._resolve_symbol("_vnode_getattr")
if vnode_getattr_off < 0:
vnode_getattr_off = kp._find_vnode_getattr_via_string()
if vnode_getattr_off < 0:
print("[-] vnode_getattr not found")
sys.exit(1)
print(f"[+] vnode_getattr at 0x{vnode_getattr_off:X}")
# ── Find sandbox ops table ──
ops_table = kp._find_sandbox_ops_table_via_conf()
if ops_table is None:
print("[-] sandbox ops table not found")
sys.exit(1)
# ── Find hook (largest in ops[0:30]) ──
hook_index = -1
orig_hook = -1
best_size = 0
for idx in range(0, 30):
entry = kp._read_ops_entry(ops_table, idx)
if entry is None or entry <= 0:
continue
if not any(s <= entry < e for s, e in kp.code_ranges):
continue
fend = kp._find_func_end(entry, 0x2000)
fsize = fend - entry
if fsize > best_size:
best_size = fsize
hook_index = idx
orig_hook = entry
if hook_index < 0 or best_size < 1000:
print(f"[-] hook not found (best: idx={hook_index}, size={best_size})")
sys.exit(1)
print(f"[+] hook at ops[{hook_index}] = 0x{orig_hook:X} ({best_size} bytes)")
# Verify PACIBSP
first_insn = data[orig_hook:orig_hook + 4]
if first_insn != PACIBSP:
print(f"[-] first insn not PACIBSP (got 0x{_rd32(data, orig_hook):08X})")
sys.exit(1)
# ── Find code cave (200 bytes) ──
cave = kp._find_code_cave(200)
if cave < 0:
print("[-] no code cave found")
sys.exit(1)
print(f"[+] code cave at 0x{cave:X}")
# ── Build variant shellcode ──
print(f"\n[*] Building variant {variant}")
parts = build_variant(kp, variant, cave, orig_hook, vnode_getattr_off)
if parts is None:
sys.exit(1)
# Write shellcode to data
for i, part in enumerate(parts):
off = cave + i * 4
data[off:off + 4] = part
# Patch function entry: PACIBSP → B cave
b_to_cave = kp._encode_b(orig_hook, cave)
if not b_to_cave:
print("[-] B to cave out of range")
sys.exit(1)
data[orig_hook:orig_hook + 4] = b_to_cave
print(f"[+] Variant {variant}: {len(parts)} instructions written to cave")
print(f"[+] Trampoline: B 0x{cave:X} at 0x{orig_hook:X}")
# Save
save_firmware(kernel_path, im4p, data, was_im4p, original_raw)
print(f"[+] Saved: {kernel_path}")
if __name__ == "__main__":
main()
-66
View File
@@ -1,66 +0,0 @@
#!/usr/bin/env zsh
set -euo pipefail
# Bisect C23 shellcode — restore, patch variant, rebuild ramdisk, boot.
#
# Usage: ./testing_c23_bisect.sh <variant>
# or: make testing_c23_bisect_boot VARIANT=A
#
# Variants: A B C D E (see testing_c23_bisect.py)
typeset -a CHILD_PIDS=()
cleanup() {
echo "\n[c23] cleaning up..."
for pid in "${CHILD_PIDS[@]}"; do
if kill -0 "$pid" 2>/dev/null; then
echo "[c23] killing PID $pid"
kill -9 "$pid" 2>/dev/null || true
fi
done
exit 0
}
trap cleanup EXIT INT TERM
PROJECT_DIR="$(cd "$(dirname "${0:a:h}")" && pwd)"
cd "$PROJECT_DIR"
VARIANT="${1:-}"
if [[ -z "$VARIANT" ]]; then
echo "Usage: $0 <variant>"
echo " Variants: A B C D E"
exit 1
fi
VM_DIR="${VM_DIR:-vm}"
echo "[c23] ═══════════════════════════════════════════"
echo "[c23] Bisect variant: $VARIANT"
echo "[c23] ═══════════════════════════════════════════"
# Kill existing
echo "[c23] killing existing vphone-cli..."
pkill -9 vphone-cli 2>/dev/null || true
sleep 1
# Restore + patch variant
echo "[c23] restoring base kernel + applying variant $VARIANT"
make testing_c23_bisect VARIANT="$VARIANT"
# Rebuild ramdisk
echo "[c23] testing_ramdisk_build..."
make testing_ramdisk_build
# Send ramdisk in background
echo "[c23] testing_ramdisk_send (background)..."
make testing_ramdisk_send &
CHILD_PIDS+=($!)
# Boot
echo "[c23] boot_dfu..."
make boot_dfu &
CHILD_PIDS+=($!)
echo "[c23] waiting for boot (PID ${CHILD_PIDS[-1]})..."
wait "${CHILD_PIDS[-1]}" 2>/dev/null || true
-49
View File
@@ -1,49 +0,0 @@
#!/usr/bin/env zsh
set -euo pipefail
# ─── Track child PIDs for cleanup ───────────────────────────────────
typeset -a CHILD_PIDS=()
cleanup() {
echo "\n[testing_do] cleaning up..."
for pid in "${CHILD_PIDS[@]}"; do
if kill -0 "$pid" 2>/dev/null; then
echo "[testing_do] killing PID $pid"
kill -9 "$pid" 2>/dev/null || true
fi
done
exit 0
}
trap cleanup EXIT INT TERM
PROJECT_DIR="$(cd "$(dirname "${0:a:h}")" && pwd)"
cd "$PROJECT_DIR"
# ─── Kill existing vphone-cli ──────────────────────────────────────
echo "[testing_do] killing existing vphone-cli..."
pkill -9 vphone-cli 2>/dev/null || true
sleep 1
# ─── Build pipeline ───────────────────────────────────────────────
echo "[testing_do] fw_prepare..."
make fw_prepare
echo "[testing_do] fw_patch_jb..."
make fw_patch_jb
echo "[testing_do] testing_ramdisk_build..."
make testing_ramdisk_build
# ─── Send ramdisk in background ───────────────────────────────────
echo "[testing_do] testing_ramdisk_send (background)..."
make testing_ramdisk_send &
CHILD_PIDS+=($!)
# ─── Boot DFU ─────────────────────────────────────────────────────
echo "[testing_do] boot_dfu..."
make boot_dfu &
CHILD_PIDS+=($!)
echo "[testing_do] waiting for boot_dfu (PID ${CHILD_PIDS[-1]})..."
wait "${CHILD_PIDS[-1]}" 2>/dev/null || true
-62
View File
@@ -1,62 +0,0 @@
#!/usr/bin/env zsh
set -euo pipefail
# Fast test a single kernel JB patch.
# Restores base kernel backup, applies one patch, rebuilds ramdisk, boots.
#
# Usage: ./testing_do_patch.sh <patch_name>
# or: make testing_do_patch PATCH=patch_vm_fault_enter_prepare
# ─── Track child PIDs for cleanup ───────────────────────────────────
typeset -a CHILD_PIDS=()
cleanup() {
echo "\n[patch] cleaning up..."
for pid in "${CHILD_PIDS[@]}"; do
if kill -0 "$pid" 2>/dev/null; then
echo "[patch] killing PID $pid"
kill -9 "$pid" 2>/dev/null || true
fi
done
exit 0
}
trap cleanup EXIT INT TERM
PROJECT_DIR="$(cd "$(dirname "${0:a:h}")" && pwd)"
cd "$PROJECT_DIR"
PATCH_NAME="${1:-}"
if [[ -z "$PATCH_NAME" ]]; then
echo "Usage: $0 <patch_name>"
echo " e.g. $0 patch_vm_fault_enter_prepare"
exit 1
fi
VM_DIR="${VM_DIR:-vm}"
# ─── Kill existing vphone-cli ──────────────────────────────────────
echo "[patch] killing existing vphone-cli..."
pkill -9 vphone-cli 2>/dev/null || true
sleep 1
# ─── Restore kernel + apply single patch ───────────────────────────
echo "[patch] restoring base kernel + applying: $PATCH_NAME"
make testing_kernel_patch PATCH="$PATCH_NAME"
# ─── Rebuild ramdisk ────────────────────────────────────────────────
echo "[patch] testing_ramdisk_build..."
make testing_ramdisk_build
# ─── Send ramdisk in background ────────────────────────────────────
echo "[patch] testing_ramdisk_send (background)..."
make testing_ramdisk_send &
CHILD_PIDS+=($!)
# ─── Boot DFU ──────────────────────────────────────────────────────
echo "[patch] boot_dfu..."
make boot_dfu &
CHILD_PIDS+=($!)
echo "[patch] waiting for boot_dfu (PID ${CHILD_PIDS[-1]})..."
wait "${CHILD_PIDS[-1]}" 2>/dev/null || true
-58
View File
@@ -1,58 +0,0 @@
#!/usr/bin/env zsh
set -euo pipefail
# Save a base-patched kernel for fast per-patch testing.
# Run once, then use testing_do_patch.sh <patch_name> to test individual patches.
PROJECT_DIR="$(cd "$(dirname "${0:a:h}")" && pwd)"
cd "$PROJECT_DIR"
VM_DIR="${VM_DIR:-vm}"
# ─── Kill existing vphone-cli ──────────────────────────────────────
echo "[save] killing existing vphone-cli..."
pkill -9 vphone-cli 2>/dev/null || true
sleep 1
# ─── Full pipeline with base patches only ──────────────────────────
echo "[save] fw_prepare..."
make fw_prepare
echo "[save] fw_patch_jb..."
make fw_patch_jb
# ─── Find and save kernelcache backup ──────────────────────────────
RESTORE_DIR=$(find "$VM_DIR" -maxdepth 1 -type d -name '*Restore*' | head -1)
KERNEL_PATH=$(find "$RESTORE_DIR" -name 'kernelcache.research.vphone600' | head -1)
if [[ -z "$KERNEL_PATH" ]]; then
echo "[-] kernelcache not found in $RESTORE_DIR"
exit 1
fi
BACKUP_PATH="${KERNEL_PATH}.base_backup"
cp "$KERNEL_PATH" "$BACKUP_PATH"
echo "[save] kernel backup saved: $BACKUP_PATH ($(wc -c < "$BACKUP_PATH") bytes)"
echo "[save] done. Now use: make testing_do_patch PATCH=<name>"
echo ""
echo "Available patch names:"
echo " patch_post_validation_additional (B5)"
echo " patch_proc_security_policy (B6)"
echo " patch_proc_pidinfo (B7)"
echo " patch_convert_port_to_map (B8)"
echo " patch_vm_fault_enter_prepare (B9)"
echo " patch_vm_map_protect (B10)"
echo " patch_mac_mount (B11)"
echo " patch_dounmount (B12)"
echo " patch_bsd_init_auth (B13)"
echo " patch_spawn_validate_persona (B14)"
echo " patch_task_for_pid (B15)"
echo " patch_load_dylinker (B16)"
echo " patch_shared_region_map (B17)"
echo " patch_nvram_verify_permission (B18)"
echo " patch_io_secure_bsd_root (B19)"
echo " patch_thid_should_crash (B20)"
echo " patch_cred_label_update_execve (C21)"
echo " patch_syscallmask_apply_to_proc (C22)"
echo " patch_hook_cred_label_update_execve (C23)"
echo " patch_kcall10 (C24)"
-82
View File
@@ -1,82 +0,0 @@
#!/usr/bin/env python3
"""
testing_kernel_patch.py — Restore base kernel backup and apply a single JB patch.
Usage:
python3 testing_kernel_patch.py <vm_directory> <patch_name> [patch_name2 ...]
Example:
python3 testing_kernel_patch.py vm patch_vm_fault_enter_prepare
python3 testing_kernel_patch.py vm patch_mac_mount patch_dounmount
"""
import os
import shutil
import sys
from fw_patch import find_file, find_restore_dir, load_firmware, save_firmware
from patchers.kernel_jb import KernelJBPatcher
def main():
if len(sys.argv) < 3:
print(f"Usage: {sys.argv[0]} <vm_dir> <patch_name> [patch_name2 ...]")
sys.exit(1)
vm_dir = os.path.abspath(sys.argv[1])
patch_names = sys.argv[2:]
restore_dir = find_restore_dir(vm_dir)
if not restore_dir:
print(f"[-] No *Restore* directory found in {vm_dir}")
sys.exit(1)
kernel_path = find_file(restore_dir, ["kernelcache.research.vphone600"], "kernelcache")
backup_path = kernel_path + ".base_backup"
if not os.path.exists(backup_path):
print(f"[-] No backup found: {backup_path}")
print(f" Run 'make testing_do_save' first.")
sys.exit(1)
# Restore from backup
shutil.copy2(backup_path, kernel_path)
print(f"[*] Restored kernel from backup ({os.path.getsize(backup_path)} bytes)")
# Load the kernel
im4p, data, was_im4p, original_raw = load_firmware(kernel_path)
fmt = "IM4P" if was_im4p else "raw"
print(f"[*] Loaded: {fmt}, {len(data)} bytes")
# Create patcher (inherits from KernelJBPatcherBase which inherits from KernelPatcher)
kp = KernelJBPatcher(data)
# Apply each requested patch
applied = 0
for patch_name in patch_names:
method = getattr(kp, patch_name, None)
if method is None:
print(f"[-] Unknown patch: {patch_name}")
print(f" Available patches:")
for name in sorted(dir(kp)):
if name.startswith("patch_") and callable(getattr(kp, name)):
print(f" {name}")
sys.exit(1)
print(f"\n[*] Applying: {patch_name}")
method()
# Apply the collected patches
for off, patch_bytes, _ in kp.patches:
data[off : off + len(patch_bytes)] = patch_bytes
applied += 1
print(f"\n[+] {applied} patch(es) applied from {len(patch_names)} method(s)")
# Save
save_firmware(kernel_path, im4p, data, was_im4p, original_raw)
print(f"[+] Saved: {kernel_path}")
if __name__ == "__main__":
main()
-362
View File
@@ -1,362 +0,0 @@
#!/usr/bin/env python3
"""
testing_ramdisk_build.py — Build a minimal signed boot chain for testing.
Packs firmware components (iBSS, iBEC, SPTM, DeviceTree, SEP, TXM,
kernelcache) with the stock ramdisk into signed IMG4 files. No SSH
tools or CFW — just the base boot chain for quick patch verification.
Usage:
python3 testing_ramdisk_build.py [vm_directory]
Prerequisites:
pip install pyimg4
Run fw_patch.py first to patch boot-chain components.
"""
import glob
import gzip
import os
import plistlib
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 SSH, no CFW)")
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")
# ── 8. Base ramdisk + trustcache ─────────────────────────────
print(f"\n{'=' * 60}")
print(f" 8. Base ramdisk + trustcache")
print(f"{'=' * 60}")
tc_bin = shutil.which("trustcache")
if not tc_bin:
print("[-] trustcache not found. Run: make setup_tools")
sys.exit(1)
# Read RestoreRamDisk path from BuildManifest
bm_path = os.path.join(restore_dir, "BuildManifest.plist")
with open(bm_path, "rb") as f:
bm = plistlib.load(f)
ramdisk_rel = bm["BuildIdentities"][0]["Manifest"]["RestoreRamDisk"]["Info"]["Path"]
ramdisk_src = os.path.join(restore_dir, ramdisk_rel)
# Extract base ramdisk DMG
ramdisk_raw = os.path.join(temp_dir, "ramdisk.raw.dmg")
subprocess.run(
["pyimg4", "im4p", "extract", "-i", ramdisk_src, "-o", ramdisk_raw],
check=True,
capture_output=True,
)
# Mount base ramdisk, build trustcache from its contents
mountpoint = os.path.join(vm_dir, "testing_ramdisk_mnt")
os.makedirs(mountpoint, exist_ok=True)
try:
subprocess.run(
["sudo", "hdiutil", "attach", "-mountpoint", mountpoint,
ramdisk_raw, "-owners", "off"],
check=True,
)
print(" Building trustcache from base ramdisk...")
tc_raw = os.path.join(temp_dir, "ramdisk.tc")
tc_im4p = os.path.join(temp_dir, "trustcache.im4p")
subprocess.run([tc_bin, "create", tc_raw, mountpoint], check=True, capture_output=True)
subprocess.run(
["pyimg4", "im4p", "create", "-i", tc_raw, "-o", tc_im4p, "-f", "rtsc"],
check=True,
capture_output=True,
)
sign_img4(tc_im4p, os.path.join(output_dir, "trustcache.img4"), im4m_path)
print(f" [+] trustcache.img4")
finally:
subprocess.run(
["sudo", "hdiutil", "detach", "-force", mountpoint], capture_output=True
)
# Sign base ramdisk as-is
rd_im4p = os.path.join(temp_dir, "ramdisk.im4p")
subprocess.run(
["pyimg4", "im4p", "create", "-i", ramdisk_raw, "-o", rd_im4p, "-f", "rdsk"],
check=True,
capture_output=True,
)
sign_img4(rd_im4p, os.path.join(output_dir, "ramdisk.img4"), im4m_path)
print(f" [+] ramdisk.img4 (base, unmodified)")
# ── 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: boot chain only — no SSH, no CFW")
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()
-69
View File
@@ -1,69 +0,0 @@
#!/bin/zsh
# testing_ramdisk_send.sh — Send testing boot chain to device via irecovery.
#
# Usage: ./testing_ramdisk_send.sh [testing_ramdisk_dir]
#
# Expects device in DFU mode. Loads iBSS/iBEC, then boots with
# SPTM, TXM, trustcache, ramdisk, device tree, SEP, and kernel.
# Boot chain only — no SSH, no CFW.
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 " (boot chain only — no SSH, no CFW)"
# 1. Load iBSS + iBEC (DFU → recovery)
echo " [1/8] Loading iBSS..."
"$IRECOVERY" -f "$RAMDISK_DIR/iBSS.vresearch101.RELEASE.img4"
echo " [2/8] Loading iBEC..."
"$IRECOVERY" -f "$RAMDISK_DIR/iBEC.vresearch101.RELEASE.img4"
"$IRECOVERY" -c go
sleep 1
# 2. Load SPTM
echo " [3/8] Loading SPTM..."
"$IRECOVERY" -f "$RAMDISK_DIR/sptm.vresearch1.release.img4"
"$IRECOVERY" -c firmware
# 3. Load TXM
echo " [4/8] Loading TXM..."
"$IRECOVERY" -f "$RAMDISK_DIR/txm.img4"
"$IRECOVERY" -c firmware
# 4. Load trustcache
echo " [5/8] Loading trustcache..."
"$IRECOVERY" -f "$RAMDISK_DIR/trustcache.img4"
"$IRECOVERY" -c firmware
# 5. Load ramdisk
echo " [6/8] Loading ramdisk..."
"$IRECOVERY" -f "$RAMDISK_DIR/ramdisk.img4"
sleep 2
"$IRECOVERY" -c ramdisk
# 6. Load device tree
echo " [7/8] Loading device tree..."
"$IRECOVERY" -f "$RAMDISK_DIR/DeviceTree.vphone600ap.img4"
"$IRECOVERY" -c devicetree
# 7. Load SEP
echo " [8/8] Loading SEP..."
"$IRECOVERY" -f "$RAMDISK_DIR/sep-firmware.vresearch101.RELEASE.img4"
"$IRECOVERY" -c firmware
# 8. Load kernel and boot
echo " [*] Booting kernel..."
"$IRECOVERY" -f "$RAMDISK_DIR/krnl.img4"
"$IRECOVERY" -c bootx
echo "[+] Boot sequence sent."