The UDID override used to reach misagent and installd alone; Xcode, lockdown
and usbmuxd kept seeing the guest's own, so a paid team's profile could not
name the VM. The host reads the UDID in three places, and each is reachable
from userspace:
- lockdownd and remoted join vpIsMISFixTarget, so the spawn hooks insert
libmisfix into them. Only the MobileGestalt interpose acts there
(MISFixProcessOnlyNeedsIdentity keeps the MIS detours out). The hook now
matches the obfuscated key remoted asks with, re6Zb+zwFKJNlkQTUeT+/w.
- MGCopyAnswerWithError takes three arguments; the hook declared two and
crashed remoted, the first hooked caller of that spelling.
- vphoned sets the USB serial string, which is what usbmuxd names a device
by (vphoned_usb.m, com.apple.private.usbdevice.setdescription, with
AllowMultipleCreates), goes off the bus and back, and reapplies it at boot
once the USB device exists.
- udid.set/clear SIGKILL the hooked daemons (remoted ignores SIGTERM) and
always re-enumerate, which is also what relaunches remoted.
Measured on test-27.0: idevice_id, lockdown and the RSD handshake over both
transports report the override after udid.set and after a reboot, and the
guest's own after udid.clear.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
vphoned reports `setup_pending` in /v1/health and serves `setup.status`
and `setup.skip`. Device › Skip Setup Assistant… is enabled while the
guest reports it pending, and asks before it runs.
SpringBoard decides whether to run Setup once, when it starts. It does
while `SetupDone` in com.apple.purplebuddy is not true, and runs the flow
shown after a software update while `SetupVersion` is below
SetupAssistant.framework's BYBuddyIOSCurrentVersion (an int32, 11 on 26.4
and 27.0). Writing the keys while Setup is on screen does not dismiss it,
and killing Setup only makes SpringBoard start it again. So the skip
writes `SetupDone`, `SetupFinishedAllSteps` and `SetupVersion` through
cfprefsd for user mobile, then restarts SpringBoard. With every other key
in the domain removed, those three still reach the Home Screen, and no
later panes appear.
The experiments are in Research/Guest/setup_assistant_skip.md, including
how to send a guest back to Setup for testing. MCInstall's
SetCloudConfiguration, which pymobiledevice3, go-ios and cfgutil use,
works only on an erased device, so it does not fit here.
Verified on test-26.4 with this bundle: after deleting `SetupDone`,
Setup's hello screen appeared and setup.status reported pending true and
running true. setup.skip without force was refused. With force it
returned pending false, SpringBoard restarted and unlocked to the Home
Screen. Deleting `SetupVersion` then skipping also reached the Home
Screen. Afterwards the domain matched its state before the test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
libmisfix answers misagent's UniqueDeviceID from /var/db/vphone/misfix.plist
(then /usr/lib/libmisfix.plist) and rereads it when it changes. vphoned now
serves udid.get, udid.set and udid.clear (capability udid_override): set
writes the data-volume plist atomically, keeping its other keys, reads it
back the way the hook resolves it, and sends SIGTERM to misagent so launchd
starts it fresh. installd is left alone and the guest is not rebooted. clear
removes the key but keeps the file, so a stale /usr/lib value cannot win.
The VM window's Device menu gets Set UDID… (prefilled, checks the 8-16 or
40 hex digit forms) and Reset UDID, enabled when the guest reports the
capability. Only misagent's profile check sees the value; Xcode, devicectl
and lockdown still see the guest's own UDID.
Also bumps Launchpad to 2.2.0.
Not yet verified on a running guest: that misagent's sandbox can read the
data-volume plist, and that launchd restarts misagent on the next check.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
vphoned gains display.orientation, which asks SpringBoard's
activeInterfaceOrientation through AXSpringBoardServer instead of
capturing the screen, so the host can poll it every second. Guests that
report display_orientation are polled; others stay portrait.
The window's content view now holds the VM view turned to that
orientation. The VM view keeps its portrait bounds, so touch mapping is
unchanged: AppKit's conversion undoes the rotation. A windowed VM swaps
its sides around its center and shrinks to fit the screen; full screen
letterboxes the turned panel. A frame saved while sideways is turned
back to portrait at launch.
The Device menu gains Rotate Left (⌘←), Rotate Right (⌘→) and an
Orientation submenu checked by the current orientation, enabled only
while the agent reports display, so the keys otherwise reach the guest.
An orientation the front app refuses is skipped by the rotate keys and
reported by the submenu. New strings are translated for ja, ko, vi and
zh-Hans.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Device keeps the phone's buttons, input and Restart Guest; a new Features
menu holds the Location, Battery and Camera overrides. Guest becomes Data:
browsers, preferences and every clipboard action, including typing the Mac
clipboard. Bootstrap install and uninstall move to Apps, and Ping and the
agent hash go into a Guest Agent submenu under Diagnostics.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#519: vphoned wrote jbroot/dev as the link text /rootfs/dev. The kernel
resolves link text, not vroot paths, so it dangled: every shell failed on
/dev/null and sshd never started. It is now /dev, an existing
/rootfs/dev link is replaced, and rootfs -> / is created.
#520: Irisin unpacks packages without RootHide dpkg's hook, so nothing
linked .jbroot in deeper package directories, and sudo could not load
libsudo_util from usr/libexec/sudo.
- vphoned walks the bootstrap for directories holding Mach-O files and
links each one, at install, at startup, and one second after the root's
Library/dpkg changes. That pass also runs the base steps that waited for
pwd_mkdb or ssh-keygen, so sshd has host keys once openssh is installed.
- The spawn hooks follow the executable's LC_RPATH and LC_LOAD_DYLIB
entries inside the root and link each dependency's directory, within a
fixed bound. SystemHook does the same for TweakLoader before its dlopen.
- launchd starts xpcproxy and bootstrap daemons through posix_spawnp, which
the launchd hook now interposes. SystemHook is chain-loaded into every
child whatever its environment; DISABLE_TWEAKS and safe mode only keep
ElleKit out.
The installer moves to Daemon/Bootstrap, with RootHide and rootless code
in their own folders.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Issue #438: guests built after the former EXP patches joined the JB flow
lost location. standard is now the JB baseline plus the virtual camera.
- watchdogd.hv_vmm_cache joins the hv_vmm_present concealment group and
loses bootEssential: watchdogd only panics once the OID is renamed.
- The eight DeviceTree identity rewrites and the Preboot DeviceTree
rewrite, newly declared as preboot_devicetree.identity, are blocked in
standard. cfw install now asks the plan before the Preboot rewrite.
- Camera DT nodes, cam offsets and camera_dsc stay on.
- libvlocation.dylib and its SystemHook load are removed. location.set,
clear and current call IcliKit directly again, which confirms a request
by reading back a fresh, software-simulated fix at that coordinate.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The proxy's exponential backoff kept the guest API down until about
24 s after launchd started it: early-boot workers exited and the proxy
waited 1, 2, 4 and 8 s between them. Retry after a fixed second, for as
long as the proxy runs, and log each exit's status or signal.
The plist sends stdout and stderr to /var/log/vphoned.log, where those
lines were previously discarded, sets ThrottleInterval to 1 so launchd
restarts the proxy after a second, and marks the job Interactive.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Post Darwin notifications from the Controls panel
vphoned gains notify.post {name, state?} and notify.state {name}, thin
calls into IcliKit 0.7.0's postDarwinNotification and
darwinNotificationState. The state is a full UInt64, so it is accepted
as a decimal string as well as a number.
The Controls panel gets a Darwin Notification section: a name field with
presets, an optional state, and Post and Read State buttons. This
replaces #248, which targeted the removed ObjC daemon and sources/ tree.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Refuse a JSON boolean as a notification state
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Publish validated location state to authorized CoreLocation clients, keep guest hooks in sync, and add a Release artifact workflow for unsigned CI packages.
A repo-wide security review found issues in the root CFW install, the
Launchpad helper, VM bundle handling and the guest boundary. This fixes
them and applies swift format.
- cfw install mounts guest volumes nosuid,nodev,nobrowse in a root-only
temp folder and does every guest read and write through an open folder
handle, never following links. BuildManifest cryptex paths must stay in
the restore folder, and only a private copy is attached.
- Root no longer chowns or chmods the whole VM folder after an install.
The shared walk skips hard links, symlinks, special files and other
volumes.
- Every Launchpad helper action needs administrator authorization. Only
the user who started a CFW install can cancel it. The helper refuses
setuid, hard-linked, special or escaping-symlink entries in a bundle.
- Manifest file names must be single names in the bundle and point to
regular files. vm import refuses links that leave the bundle.
- Guest file names from the file browser, QuickLook, drag-out and crash
logs are validated and written exclusively, without overwriting, and
are quarantined.
- The guest HTTP client no longer traps on a bare Content-Length, caps
bodies and enforces a per-request deadline.
- The --api-listen proxy needs a per-launch token. vphoned refuses
browser-origin and non-local Host requests.
- vphoned stops following links when it sets up Irisin and the camera
files.
Thanks to fresh-fx59 for reporting the guest file name, HTTP parsing,
cfw install and manifest path issues in #469.
Co-authored-by: Aleksey Aksenov <5788874+fresh-fx59@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The camera hooks were built and shipped but never reached the guest, so
Camera.app could not show a streamed frame. cfw install now places
libvcamcaptured.dylib and libcamfix.dylib in /usr/lib beside the launchd
hook and SystemHook. SystemHook treats /usr/libexec/cameracaptured as an
injection target and loads the daemon hook there, and loads libcamfix into
app processes that have AVFoundation loaded. Both hooks install their own
Objective-C method replacements, so neither needs ElleKit or a bootstrap.
A running guest gets changed copies of those four libraries through the
new vphoned environment update. environment.status reports the SHA-256 of
each library in /usr/lib; environment.install checks the uploaded copies,
remounts / read-write when needed, renames each library into place and
remounts / read-only again, because jailbreak detection reads a writable
root as rootful. It stops cameracaptured when a camera hook or SystemHook
changed and reports when the launchd hook needs a guest restart. The VM
process runs the update once per connection, after the vphoned
self-update, uploading only libraries whose hashes differ.
Not yet verified in a guest; the validation steps are in
Research/0_binary_patch_comparison.md and Research/vphoned_http_api.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Run a small launchd-owned proxy that posix_spawns the same signed binary in --io mode, reaps/restarts it, and exits the worker when the proxy dies. Preserve cached guest updates with a bounded streaming hash.
Seven new guest windows sit on the expanded vphoned API, each a SwiftUI
Table or Form in the Guest Tools style with its own model:
- Device Info: device, hardware, power, display, security, jailbreak
environment, agent and a network interface table.
- Processes: sortable process table with memory, CPU time and jetsam
bands, and signals behind a confirmation.
- Console: the guest unified log, captured back to back, with level and
process filters, search, pause and save.
- Crash Logs: CrashReporter and DiagnosticReports with an .ips header
summary, re-indented body, copy and export.
- Services: launchd services with state, last exit and launchd's own
description; start, stop, restart, enable, disable, signal, remove.
- UI Inspector: accessibility elements and OCR text drawn over a guest
screenshot, with hit-testing and tap.
- Controls: brightness, rotation, volume, low power, hardware buttons
and keyboard input.
The App Browser gains an App Info inspector (bundle, entitlements, URL
schemes, data container, network policy), uninstall, launch, terminate,
install and open URL, and can open the File Browser at a data container.
The menu bar is now vphone, Edit, Device, Apps, Guest, Diagnostics,
Capture, Window. Device merges Keys with the device overrides and opens
Controls; Guest holds files, Keychain, clipboard and preferences;
Diagnostics is a top-level menu for the inspection windows with ⌥⌘
shortcuts and the Developer Mode, ping and agent hash checks. Panel
items are enabled from the connected agent's capabilities.
VPhoneGuestControl is observable, so every window follows the
connection state, and its probe writes only changed values. vphoned adds
memory.pressure, the three memory sysctls without jetsam's priority list,
for panels that poll. New strings are translated into Simplified
Chinese, Japanese, Korean and Vietnamese, including the %arg twins
Xcode 27 extracts beside existing printf-style keys.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>