From c6ed3a1732de7a9894a38ff6c8fcf6ffdffc75d5 Mon Sep 17 00:00:00 2001 From: xiahouzhen Date: Wed, 1 Jul 2026 12:14:10 +0800 Subject: [PATCH] Fix dropbear host keys on writable Data --- scripts/cfw_install.sh | 14 +++++++- scripts/cfw_install_dev.sh | 14 +++++++- scripts/patchers/cfw.py | 15 +++++++-- scripts/patchers/cfw_daemons.py | 44 +++++++++++++++++++++++- scripts/vphone_jb_setup.sh | 15 +++++++++ tests/test_dropbear_plist.py | 60 +++++++++++++++++++++++++++++++++ 6 files changed, 156 insertions(+), 6 deletions(-) create mode 100644 tests/test_dropbear_plist.py diff --git a/scripts/cfw_install.sh b/scripts/cfw_install.sh index 517a98b..3a9be98 100755 --- a/scripts/cfw_install.sh +++ b/scripts/cfw_install.sh @@ -411,6 +411,12 @@ 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 " Preparing dropbear host keys on Data volume..." +ssh_cmd "/bin/mkdir -p /mnt3/dropbear" +ssh_cmd "if [ ! -f /mnt3/dropbear/dropbear_rsa_host_key ]; then /mnt1/iosbinpack64/usr/local/bin/dropbearkey -t rsa -f /mnt3/dropbear/dropbear_rsa_host_key >/dev/null; fi" +ssh_cmd "if [ ! -f /mnt3/dropbear/dropbear_ecdsa_host_key ]; then /mnt1/iosbinpack64/usr/local/bin/dropbearkey -t ecdsa -f /mnt3/dropbear/dropbear_ecdsa_host_key >/dev/null; fi" +ssh_cmd "/bin/chmod 600 /mnt3/dropbear/dropbear_rsa_host_key /mnt3/dropbear/dropbear_ecdsa_host_key" + echo " [+] iosbinpack64 installed" # ═══════════ 5/7 PATCH LAUNCHD_CACHE_LOADER ══════════════════ @@ -493,7 +499,13 @@ echo " [+] vphoned installed (signed copy at .vphoned.signed)" # Send daemon plists (overwrite on re-run) for plist in bash.plist dropbear.plist trollvnc.plist rpcserver_ios.plist; do - scp_to "$INPUT_DIR/jb/LaunchDaemons/$plist" "/mnt1/System/Library/LaunchDaemons/" + plist_src="$INPUT_DIR/jb/LaunchDaemons/$plist" + if [[ "$plist" == "dropbear.plist" ]]; then + plist_src="$TEMP_DIR/dropbear.plist" + cp "$INPUT_DIR/jb/LaunchDaemons/dropbear.plist" "$plist_src" + "$PYTHON3" "$SCRIPT_DIR/patchers/cfw.py" patch-dropbear-plist "$plist_src" + fi + scp_to "$plist_src" "/mnt1/System/Library/LaunchDaemons/" ssh_cmd "/bin/chmod 0644 /mnt1/System/Library/LaunchDaemons/$plist" done scp_to "$VPHONED_SRC/vphoned.plist" "/mnt1/System/Library/LaunchDaemons/" diff --git a/scripts/cfw_install_dev.sh b/scripts/cfw_install_dev.sh index 99d18da..877fa75 100755 --- a/scripts/cfw_install_dev.sh +++ b/scripts/cfw_install_dev.sh @@ -408,6 +408,12 @@ 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 " Preparing dropbear host keys on Data volume..." +ssh_cmd "/bin/mkdir -p /mnt3/dropbear" +ssh_cmd "if [ ! -f /mnt3/dropbear/dropbear_rsa_host_key ]; then /mnt1/iosbinpack64/usr/local/bin/dropbearkey -t rsa -f /mnt3/dropbear/dropbear_rsa_host_key >/dev/null; fi" +ssh_cmd "if [ ! -f /mnt3/dropbear/dropbear_ecdsa_host_key ]; then /mnt1/iosbinpack64/usr/local/bin/dropbearkey -t ecdsa -f /mnt3/dropbear/dropbear_ecdsa_host_key >/dev/null; fi" +ssh_cmd "/bin/chmod 600 /mnt3/dropbear/dropbear_rsa_host_key /mnt3/dropbear/dropbear_ecdsa_host_key" + echo " [+] iosbinpack64 installed" # ═══════════ 5/7 PATCH LAUNCHD_CACHE_LOADER ══════════════════ @@ -487,7 +493,13 @@ echo " [+] vphoned installed (signed copy at .vphoned.signed)" # Send daemon plists (overwrite on re-run) for plist in bash.plist dropbear.plist trollvnc.plist rpcserver_ios.plist; do - scp_to "$INPUT_DIR/jb/LaunchDaemons/$plist" "/mnt1/System/Library/LaunchDaemons/" + plist_src="$INPUT_DIR/jb/LaunchDaemons/$plist" + if [[ "$plist" == "dropbear.plist" ]]; then + plist_src="$TEMP_DIR/dropbear.plist" + cp "$INPUT_DIR/jb/LaunchDaemons/dropbear.plist" "$plist_src" + "$PYTHON3" "$SCRIPT_DIR/patchers/cfw.py" patch-dropbear-plist "$plist_src" + fi + scp_to "$plist_src" "/mnt1/System/Library/LaunchDaemons/" ssh_cmd "/bin/chmod 0644 /mnt1/System/Library/LaunchDaemons/$plist" done scp_to "$VPHONED_SRC/vphoned.plist" "/mnt1/System/Library/LaunchDaemons/" diff --git a/scripts/patchers/cfw.py b/scripts/patchers/cfw.py index ed3a37a..95f05aa 100755 --- a/scripts/patchers/cfw.py +++ b/scripts/patchers/cfw.py @@ -52,6 +52,9 @@ Commands: inject-daemons Inject bash/dropbear/trollvnc into launchd.plist. + patch-dropbear-plist + Rewrite dropbear ProgramArguments to use /var/dropbear host keys. + inject-dylib Inject LC_LOAD_DYLIB into Mach-O binary (thin or universal). Equivalent to: optool install -c load -p -t @@ -76,7 +79,7 @@ if __name__ == "__main__": from patchers.cfw_patch_hv_vmm_dsc import patch_hv_vmm_in_dsc from patchers.cfw_patch_camera_dsc import apply_all_camera_patches from patchers.cfw_patch_watchdogd import patch_watchdogd - from patchers.cfw_daemons import parse_cryptex_paths, inject_daemons + from patchers.cfw_daemons import parse_cryptex_paths, inject_daemons, patch_dropbear_plist else: from .cfw_patch_seputil import patch_seputil from .cfw_patch_cache_loader import patch_launchd_cache_loader @@ -85,7 +88,7 @@ else: from .cfw_patch_hv_vmm_dsc import patch_hv_vmm_in_dsc from .cfw_patch_camera_dsc import apply_all_camera_patches from .cfw_patch_watchdogd import patch_watchdogd - from .cfw_daemons import parse_cryptex_paths, inject_daemons + from .cfw_daemons import parse_cryptex_paths, inject_daemons, patch_dropbear_plist def main(): @@ -169,6 +172,12 @@ def main(): sys.exit(1) inject_daemons(sys.argv[2], sys.argv[3]) + elif cmd == "patch-dropbear-plist": + if len(sys.argv) < 3: + print("Usage: patch_cfw.py patch-dropbear-plist ") + sys.exit(1) + patch_dropbear_plist(sys.argv[2]) + elif cmd == "inject-dylib": if len(sys.argv) < 4: print("Usage: patch_cfw.py inject-dylib ") @@ -194,7 +203,7 @@ def main(): print(f"Unknown command: {cmd}") print("Commands: cryptex-paths, patch-seputil, patch-launchd-cache-loader, patch-camera-dsc,") print(" patch-mobileactivationd, patch-launchd-jetsam,") - print(" patch-hv-vmm-dsc, patch-watchdogd, inject-daemons, inject-dylib") + print(" patch-hv-vmm-dsc, patch-watchdogd, inject-daemons, patch-dropbear-plist, inject-dylib") sys.exit(1) diff --git a/scripts/patchers/cfw_daemons.py b/scripts/patchers/cfw_daemons.py index 890ff91..62a8afd 100644 --- a/scripts/patchers/cfw_daemons.py +++ b/scripts/patchers/cfw_daemons.py @@ -4,6 +4,14 @@ from .cfw_asm import * import os import plistlib + +DROPBEAR_KEY_ARGS = [ + "-r", + "/var/dropbear/dropbear_rsa_host_key", + "-r", + "/var/dropbear/dropbear_ecdsa_host_key", +] + def parse_cryptex_paths(manifest_path): """Extract Cryptex DMG paths from BuildManifest.plist. @@ -53,6 +61,9 @@ def inject_daemons(plist_path, daemon_dir): with open(src, "rb") as f: daemon = plistlib.load(f) + if name == "dropbear": + patch_dropbear_daemon(daemon) + key = f"/System/Library/LaunchDaemons/{name}.plist" target.setdefault("LaunchDaemons", {})[key] = daemon print(f" [+] Injected {name}") @@ -61,7 +72,38 @@ def inject_daemons(plist_path, daemon_dir): plistlib.dump(target, f, sort_keys=False) +def patch_dropbear_daemon(daemon): + """Make dropbear use host keys on writable Data instead of read-only /etc.""" + args = list(daemon.get("ProgramArguments", [])) + if not args: + return + + # -R generates default keys under /etc/dropbear, which is read-only during + # normal VM boot. Use explicit /var/dropbear keys seeded by cfw_install. + cleaned = [] + idx = 0 + while idx < len(args): + arg = args[idx] + if arg == "-R": + idx += 1 + continue + if arg == "-r": + idx += 2 + continue + cleaned.append(arg) + idx += 1 + args = cleaned + DROPBEAR_KEY_ARGS + daemon["ProgramArguments"] = args + + +def patch_dropbear_plist(plist_path): + with open(plist_path, "rb") as f: + daemon = plistlib.load(f) + patch_dropbear_daemon(daemon) + with open(plist_path, "wb") as f: + plistlib.dump(daemon, f, sort_keys=False) + + # ══════════════════════════════════════════════════════════════════ # CLI # ══════════════════════════════════════════════════════════════════ - diff --git a/scripts/vphone_jb_setup.sh b/scripts/vphone_jb_setup.sh index d7fa980..5fb480d 100755 --- a/scripts/vphone_jb_setup.sh +++ b/scripts/vphone_jb_setup.sh @@ -98,6 +98,21 @@ chown -R 501:501 /var/jb/var/mobile/Library/Preferences chmod 0755 /var/jb/var/mobile/Library/Preferences log " Ownership set" +log "[2a/8] Preparing dropbear host keys..." +mkdir -p /var/dropbear +DROPBEARKEY="" +for p in /iosbinpack64/usr/local/bin/dropbearkey /iosbinpack64/usr/local/dropbearkey /var/jb/usr/bin/dropbearkey; do + [ -x "$p" ] && DROPBEARKEY="$p" && break +done +if [ -n "$DROPBEARKEY" ]; then + [ -f /var/dropbear/dropbear_rsa_host_key ] || "$DROPBEARKEY" -t rsa -f /var/dropbear/dropbear_rsa_host_key >/dev/null + [ -f /var/dropbear/dropbear_ecdsa_host_key ] || "$DROPBEARKEY" -t ecdsa -f /var/dropbear/dropbear_ecdsa_host_key >/dev/null + chmod 0600 /var/dropbear/dropbear_rsa_host_key /var/dropbear/dropbear_ecdsa_host_key + log " dropbear host keys ready" +else + log " WARNING: dropbearkey not found" +fi + # ═══════════ 3/7 RUN prep_bootstrap.sh ═══════════════════════ log "[3/8] Running prep_bootstrap.sh..." if [ -f /var/jb/prep_bootstrap.sh ]; then diff --git a/tests/test_dropbear_plist.py b/tests/test_dropbear_plist.py new file mode 100644 index 0000000..4006cf3 --- /dev/null +++ b/tests/test_dropbear_plist.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +"""Unit tests for CFW LaunchDaemon plist rewrites.""" + +import os +import sys +import unittest + + +ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +sys.path.insert(0, os.path.join(ROOT, "scripts")) + +from patchers.cfw_daemons import DROPBEAR_KEY_ARGS, patch_dropbear_daemon # noqa: E402 + + +class DropbearPlistTests(unittest.TestCase): + def test_rewrites_readonly_root_key_generation(self): + daemon = { + "ProgramArguments": [ + "/iosbinpack64/usr/local/bin/dropbear", + "--shell", + "/iosbinpack64/bin/bash", + "-R", + "-E", + "-F", + "-p", + "22222", + "-a", + ] + } + + patch_dropbear_daemon(daemon) + + args = daemon["ProgramArguments"] + self.assertNotIn("-R", args) + self.assertEqual(args[-len(DROPBEAR_KEY_ARGS):], DROPBEAR_KEY_ARGS) + + def test_replaces_stale_explicit_key_paths(self): + daemon = { + "ProgramArguments": [ + "dropbear", + "-r", + "/etc/dropbear/dropbear_rsa_host_key", + "-E", + "-r", + "/tmp/old_ecdsa_key", + "-p", + "22222", + ] + } + + patch_dropbear_daemon(daemon) + + args = daemon["ProgramArguments"] + self.assertNotIn("/etc/dropbear/dropbear_rsa_host_key", args) + self.assertNotIn("/tmp/old_ecdsa_key", args) + self.assertEqual(args, ["dropbear", "-E", "-p", "22222", *DROPBEAR_KEY_ARGS]) + + +if __name__ == "__main__": + unittest.main()