Troy is a rootless semi-jailbreak for iOS 27 using the TSS cryptex "bug" (allowing any cryptex to be signed for retail devices). It installs a persistent Procursus bootstrap at /var/jb and provides process launching, tweak injection, trust-cache loading, and Mach-O development tools.
DISCLAIMERS: The bug is now patched and Apple no longer signs custom Cryptex1 images, so new Troy builds cannot be personalized or installed on any device or iOS version. The code was written with AI assistance.
The cryptex build & deploy code has been redacted, to be revealed at a later time.
- Persistent Procursus environment stored on the active Preboot volume.
- During a userspace reboot, Troy redirects launchd's
setexecto the resigned/var/jb/sbin/launchd, which becomes the new PID 1 with Troy's launch hook already loaded. - The replacement launchd bind-mounts a copy of
/usr/libcontaining Troy's patched dyld andexec-hook.dylibover/usr/lib. - The bootstrap, replacement launchd, patched dyld, and launch daemons are kept in Preboot so they remain available if the Cryptex unmounts.
- OpenSSH on ports 22 and 2222.
- Sileo, ElleKit, PreferenceLoader, and OpenSSH installed through
dpkg. - Launch hooks for
posix_spawn,posix_spawnp, andexecve. - Direct execution of trusted
/var/jbbinaries and atroy-execmainfallback that loads unsupported Mach-O executables into its own process and calls their entry point with the original arguments and environment. - Redirection of stock executable paths to matching
/var/jbmirrors while preserving the original process identity exposed to user space. - A privileged
troydservice that marks suspended children as debugged, answers executable trust queries, and lets approved setuid tools run commands as root, passing their fds to the child and returning its exit status. - Dylib injection into running processes.
- A sandbox extension gives processes carrying Troy's launch hooks read access to
/var/jb. - Troy's patched dyld allows
DYLD_INSERT_LIBRARIESto work withoutget-task-allowand lets processes marked as debugged bytroydload unsigned executable code through itsmmapandfcntlpatches. It also allows main executables to be loaded as if they were libraries (otherwise, dylibify is required to convert executables to libraries) troy-sign-index scanadds the entitlements required for dylib injection, resigns stock executables in memory, and records their cdhashes for the trust cache.troy-sign-index materializewrites those transformed executables as/var/jbmirrors.- A trust cache shipped with the Cryptex, plus creation and loading of additional personalized Cryptex1 trust caches on-device.
Put a copy of your device's launchd and dyld inside Resources. Then run Scripts/build_cryptex.sh locally on a Mac with cryptexctl.research installed or use the Build Troy Cryptex GitHub Actions workflow. Both produce an unsigned Cryptex bundle, build summary, trust-cache dump, and SSH key pair.
Set TROY_AUTHORIZED_KEYS to embed an existing authorized_keys file. Without it, the build generates a key pair. TROY_DEBUG controls detailed launch-hook logging, but enabling it in the current build causes userspace reboot to stall on a black screen and eventually trigger a watchdog panic; leave it at the default value of 0. TROY_DAEMON_DEBUG separately controls troyd logging and defaults to 1.
Place the unsigned artifact in out/ and connect the target device.
Optionally create an isolated Python environment:
python3 -m venv .venv
source .venv/bin/activateInstall the dependencies:
python3 -m pip install -r requirements.txtThen deploy:
python3 Scripts/deploy.pyPersistent replacement:
python3 Scripts/deploy.py --persistent --replaceUse --udid DEVICE_UDID to select a device and --artifact PATH to select an artifact outside out/.
On first launch, troy-bootstrap installs the embedded Procursus tree under /private/preboot/<boot-manifest-hash>/troy/procursus, links /var/jb to it, prepares the root account for login and user lookup, installs the bundled Sileo, ElleKit, PreferenceLoader, and OpenSSH packages through dpkg, and registers troyd and OpenSSH to start from /var/jb after userspace reboots. Updates merge Troy-owned payload files without replacing mutable package and user data.
SSH accepts the generated key and initially permits the root password alpine. Change the password after installation.
Run an executable through Troy:
troy-exec /path/to/executable argument1 argument2Inject a dylib into a running process:
inject PID /absolute/path/to/library.dylibDylibs injected into launchd must already be trusted.
troy-trustcache-load [-v] trust PATH
troy-trustcache-load [-v] hashes FILE
troy-trustcache-load [-v] reload CACHE
-v enables verbose output, including scanned cdhashes, kernel trust-query results, the Cryptex1 nonce, ticket-cache activity, TSS requests, and trust-cache loading results.
Scan a file or directory and load a personalized trust cache for every signed Mach-O that is not already trusted:
troy-trustcache-load trust /var/jb/pathLoad cdhashes from a text file:
troy-trustcache-load hashes /path/to/cdhashes.txtReload a cached signed response without contacting TSS:
troy-trustcache-load reload /var/jb/var/cache/troy/trustcache/CACHEThe default ticket cache is /var/jb/var/cache/troy/trustcache. Binaries used
with dynamic trust caches must carry a valid ad-hoc signature, including a
CodeDirectory with CS_ADHOC set.
troy-sign-index prepares stock executables for launch redirection and dylib
injection. Its scan mode calculates resigned hashes and cdhashes for
inclusion in Troy's trust cache without keeping modified copies:
troy-sign-index scan \
--manifest /var/tmp/troy-system-machos.json \
--cdhashes /var/tmp/troy-system-cdhashes.txtWith no roots specified, scan uses /Applications, /System/Applications,
/System/Library/CoreServices, /System/Library/PrivateFrameworks, /usr/bin,
/usr/libexec, and /usr/sbin. To scan specific roots, append each path after
the --cdhashes output path:
troy-sign-index scan \
--manifest /var/tmp/troy-system-machos.json \
--cdhashes /var/tmp/troy-system-cdhashes.txt \
/usr/libexec /usr/sbinIts materialize mode writes a matching resigned mirror under /var/jb:
troy-sign-index materialize /usr/libexec/exampledThe default destination is /var/jb/<absolute source path>. Add generated system cdhashes to TrustCache/system-cdhashes.txt before building the Cryptex that will use the materialized files.
exec-hook.dylib propagates to eligible descendant processes and intercepts spawn and exec operations. For binaries under /var/jb, it asks troyd to check every cdhash against the kernel trust cache. Trusted binaries execute normally; other supported binaries are routed through troy-execmain. troyd attaches to suspended children, marks them as debugged, detaches, and resumes them.
The final build I personalized before signing stopped has a troyd that cannot open the AMFI user client. Because I can no longer personalize a new build, I did not bother to fix it. That existing installation checks /var/jb/etc/troy/trusted-cdhashes.bin instead of querying the kernel trust cache.
The lsd hook remains in the source tree but is not injected automatically. It was introduced when embedded app registration appeared to require an lsd entitlement bypass, disabled when that bypass proved unnecessary, re-enabled when an unidentified bug broke direct registration, and disabled again after the bug was found to be signing mistakenly stripping uicache entitlements and was fixed.
- New builds cannot be installed because Apple no longer personalizes custom Cryptex1 images.
TROY_DEBUG=1can stall userspace reboot on a black screen and trigger a watchdog panic.- My existing installation uses
/var/jb/etc/troy/trusted-cdhashes.binbecause itstroydcannot query the kernel trust cache. - Dylibs injected into launchd must already be trusted.
- LaunchServices database rebuilds do not automatically re-register apps from
/var/jb; runuicache -aagain afterward.
- Troy operates entirely in userland and has no kernel read/write capability.
- Mach-O files loaded in processes without CS_DEBUGGED or executables ran directly must carry a valid ad-hoc signature with the CS_ADHOC flag.
- All non-Apple code must be authorized by a trust cache. Troy has no amfid patch because the kernel since iOS 15 (and now TXM) verifies provisioning profiles too instead of trusting amfid's result. Troy can run unsigned code through troy-exec or by injecting to a debugged process, but cannot grant that code arbitrary entitlements.
Scripts/build_cryptex.sh: builds the unsigned Cryptex and bundled payload.Scripts/deploy.py: personalizes and installs the Cryptex.Sources/LaunchRedirect: launch hooks,troyd, IPC, and path translation.Sources/ExecMainBinary: executable loader and dyld validation support.Sources/troy-trustcache-load.m: on-device trust-cache personalization and loading.Sources/troy-sign-index.mm: scanning, signing, and materialization.Sources/troy-bootstrap.c: persistent bootstrap installation.Sources/Injector: the modified opainject-derived injector core.Tools/update-cdhash-index.c: merges cached trust-cache entries into the fallback cdhash index.Scripts/attach_troyd.pyandScripts/lldb_remote_exports.py: helper functions to attach a debugger to troyd and use it to run code as root. I needed this because I messed up the bootstrap right before the bug was patched.
- opa334 for Dopamine, opainject and litehook.
- ElleKit for tweak injection.
- Procursus for the rootless bootstrap.
- ldid for Mach-O signing.
- LiveContainer's fork of dylibify for chained fixup fixes.
- 0xjohnny, GenericCoding, Varik, htrowii, forcequit, and Duy Tran, for their own projects using this bug & discussions we had.