iOS Pentesting without Jailbreak
Main idea
Applications signed with the get-task-allow entitlement permit an appropriately entitled debugger to call task_for_pid() with the application’s process ID and obtain its task port, which enables debugging and memory access.[10]
However, it is not as easy as pulling the IPA, re-signing it with the entitlement, and installing it on your device. FairPlay protects App Store applications, and changing the signature invalidates the DRM key, so the encrypted application will not run.[1]
With an old jailbroken device, it is possible to install the IPA, decrypt it with a tool such as Iridium or frida-ios-dump, and pull it back off the device. When possible, ask the client for a decrypted IPA instead.
Obtain decrypted IPA
Get it from Apple[1]
- Install the app to test on the iPhone.
- Install and launch Apple Configurator on macOS.
- Open Terminal on the Mac and change to
/Users/[username]/Library/Group\\ Containers/K36BKF7T3D.group.com.apple.configurator/Library/Caches/Assets/TemporaryItems/MobileApps. The IPA will appear in this folder later. - You should see your iOS device. Double-click on it, and then click Add + → Apps from the top menu bar.
- After clicking Add, Configurator will download the IPA from Apple, and attempt to push it to your device. If you followed my recommendation earlier and installed the IPA already, a prompt asking you to reinstall the app will appear.
- Retrieve the downloaded IPA from
/Users/[username]/Library/Group\\ Containers/K36BKF7T3D.group.com.apple.configurator/Library/Caches/Assets/TemporaryItems/MobileApps.
Check https://dvuln.com/blog/modern-ios-pentesting-no-jailbreak-needed for more detailed information about this process.[1]
Decrypting the app
In order to decrypt the IPA we are going to install it. However, if you have an old jailbroken iPhone, the application may not support its iOS version because many apps only support the latest releases.
So, in order to install it, just unzip the IPA:
unzip redacted.ipa -d unzipped
Check the Info.plist for the minimum supported version and if your device is older than that, change the value so it’s supported.
Zip back the IPA:
cd unzipped
zip -r ../no-min-version.ipa *
Then, install the IPA for example with:
ideviceinstaller -i no-min-version.ipa -w
Note that you might need AppSync Unified tweak from Cydia to prevent any invalid signature errors.
Once installed, you can use Iridium tweak from Cydia in order to obtain the decrypted IPA.
Patch entitlements & re-sign
Several tools can re-sign the application with the get-task-allow entitlement, including app-signer, codesign, and iResign. The app-signer interface lets you select the IPA, enable get-task-allow, and choose the certificate and provisioning profile.
Apple offers free developer signing profiles through Xcode. Create an app to configure a profile, and enable Developer Mode on the iPhone under Settings → Privacy & Security.
With the re-signed IPA, it’s time to install it in the device to pentest it:
ideviceinstaller -i resigned.ipa -w
IPA patching + DYLIB injection + free Apple ID re-sign (CLI)
If you already have a decrypted IPA, you can patch it to load a custom DYLIB, add entitlements (e.g., network), and re-sign it without Xcode using a free Apple ID. This is useful for in-app instrumentation on non-jailbroken devices.[4]
Typical flow:
# Build the implant (macOS for build step)
make
# Patch the IPA to inject the DYLIB
python3 tools/patcher.py patch --ipa MyApp.ipa --dylib libShell.dylib
# -> MyApp_patched.ipa
# Patch + sign + install in one step (free Apple ID)
python3 tools/patcher.py full \
--ipa MyApp.ipa \
--dylib libShell.dylib \
--apple-id user@example.com \
--install \
--udid <device-udid>
Notes:
- Free Apple ID signing usually expires in 7 days and is limited to 3 App IDs per week and 10 sideloaded apps.[9]
- The tool can re-sign cross-platform by authenticating with Apple via SRP and generating a free dev certificate + provisioning profile. Apple’s anisette headers are handled per platform (macOS via
AOSKit.framework, Linux via Anisette.py, Windows via an external anisette server). - This does not bypass the sandbox. The injected code runs inside the app process and can only access the app’s sandbox and keychain access groups.
USB-only access to the injected implant
If the injected DYLIB exposes a local TCP control channel, you can keep traffic off Wi-Fi/cellular and forward it over USB:[4]
# Forward device-local TCP port to host
iproxy 8080 8080
# Example client commands (host side)
python3 client.py "ls"
python3 client.py "pwd"
python3 client.py "scp -r Documents host:./downloads"
If the implant includes keychain helpers, you can dump items accessible to that app:
python3 client.py "keychain dump"
python3 client.py "keychain dump --filter self"
python3 client.py "keychain dump --class generic"
Enable Developer Mode (iOS 16+)
Since iOS 16 Apple introduced Developer Mode: any binary that carries get_task_allow or is signed with a development certificate will refuse to launch until Developer Mode is enabled on the device. You will also not be able to attach Frida/LLDB unless this flag is on.
- Install or push any developer-signed IPA to the phone.
- Navigate to Settings → Privacy & Security → Developer Mode and toggle it on.
- The device will reboot; after entering the passcode you will be asked to Turn On Developer Mode.
Developer Mode remains active until you disable it or wipe the phone, so this step only needs to be performed once per device. Apple documentation explains the security implications.[2]
Modern sideloading options
There are now several mature ways to sideload and keep re-signed IPAs up-to-date without a jailbreak:
| Tool | Requirements | Strengths | Limitations |
|---|---|---|---|
| AltStore 2 / SideStore | Free Apple ID + companion workflow (AltServer) or the on-device SideStore flow | Familiar UX, automatic refresh, practical on current devices | Free profiles still expire after 7 days; free Apple IDs are usually limited to 3 installed apps and roughly 10 App IDs in a 7-day window[9] |
| TrollStore 1/2 | Device on firmware officially supported by the CoreTrust bug (14.0 beta 2 – 16.6.1, 16.7 RC (20H18), and 17.0) | Permanent signing (no 7-day refresh); excellent for long assessments on vulnerable devices | Firmware-specific; 17.0.1+ and most fully patched modern releases are out of scope unless a new CoreTrust bug appears[11] |
For routine pentests on current iOS versions AltStore/SideStore are usually the most practical choice, while TrollStore is the best option when the target test device happens to be on a compatible firmware.
iOS 17+ developer-service transport (CoreDevice / RSD tunnels)
On iOS 17+, many developer services moved behind CoreDevice / RemoteXPC. Current pymobiledevice3 builds can usually create a no-root userspace tunnel automatically on iOS 17.4+, so you can often run developer commands directly instead of manually starting a tunnel first:[7]
python3 -m pip install -U pymobiledevice3
# iOS 17.4+ usually works directly (no root/admin needed)
pymobiledevice3 developer dvt ls /
pymobiledevice3 developer dvt device-information
pymobiledevice3 syslog live
If you need a kernel-routable tunnel for an external tool (for example lldb / debugserver) or the device is on iOS 17.0 - 17.3.1, run privileged tunneld and point later commands at it:
sudo python3 -m pymobiledevice3 remote tunneld
pymobiledevice3 developer dvt ls / --tunnel ''
# Optional: allow later Wi-Fi access after the first trusted USB session
pymobiledevice3 lockdown wifi-connections on
If you prefer a single cross-platform binary, go-ios exposes the same CoreDevice-era services and file operations once its tunnel is up:[8]
npm install -g go-ios
sudo ios tunnel start
ios apps
ios file ls --app <bundle-id> --path /
This is especially useful when chaining Frida, syslog/oslog, debugserver/LLDB, or file exfiltration from Linux/Windows without having to open Xcode.
Quick sandbox triage over USB (before Frida)
Before fighting anti-Frida / anti-debug logic, dump the low-hanging data directly from the app container over USB: Library/Preferences/*.plist, Documents/, Library/Application Support/, Caches/, tmp/, and any App Group container referenced in the entitlements. This often gives you tokens, feature flags, cached API responses, SQLite databases, and jailbreak/debug toggles before you start instrumenting code.
With pymobiledevice3, you can talk to the app container through House Arrest:
# Interactive shell into the app container
pymobiledevice3 apps afc com.example.target
# Pull interesting files/directories
pymobiledevice3 apps pull com.example.target Library/Preferences/com.example.target.plist ./target.plist
pymobiledevice3 apps pull com.example.target Documents ./Documents --documents
# Push a modified file back into the sandbox
pymobiledevice3 apps push com.example.target ./evil.db "Library/Application Support/app.db"
On iOS 17+, go-ios can do the same over its RemoteXPC tunnel and is especially handy for enumerating App Group storage:
ios file ls --app <bundle-id> --path /
ios file pull --app <bundle-id> \
--remote Library/Preferences/<bundle-id>.plist \
--local ./prefs.plist
ios file ls --app-group <group-id> --path /
If full-container vending fails, retry with Documents-only access and then fall back to in-app exfiltration via your injected DYLIB/Frida hooks.
Hooking / dynamic instrumentation
You can hook your app exactly as on a jailbroken device once it is signed with get_task_allow and Developer Mode is on:
# Spawn & attach with objection
objection -g "com.example.target" explore
# Or plain Frida
frida -U -f com.example.target -l my_script.js --no-pause
Current Frida releases handle PAC and the post-iOS 17 dyld changes much better than older builds, but keep the CLI, Python bindings, and cached/embedded FridaGadget.dylib on the same version to avoid hard-to-debug attach failures.
For more Frida-specific workflows and tracing recipes, check iOS Frida Configuration.
Frida jailed mode without permanently patching the IPA
When the target IPA is already debuggable (get-task-allow), recent Frida builds can inject Frida Gadget automatically instead of requiring you to permanently patch the app bundle first. In practice, the jailed workflow is reliable only if the following prerequisites are met:
- The app is re-signed with
get-task-allowand launches with Developer Mode enabled. - The host has the current Apple developer services available. Official Frida documentation still calls out a mounted Developer Disk Image as a requirement; on modern iOS 17+ setups this normally maps to having the current CoreDevice / developer-service stack available.[6]
- The host-side Frida tools and the cached
FridaGadget.dylibare on the same version (for example, do not mix an old gadget with a newly upgraded CLI).
A quick smoke test is to trace a high-signal library instead of starting with a custom hook:
frida-trace -U MyApp -I "libcommonCrypto*"
If this works, move to your normal Frida or Objection scripts. If not, check these common failure modes before repatching the IPA:
Failed to enumerate processes: this feature requires an iOS Developer Disk Image to be mounted→ briefly open Xcode once, or otherwise make sure the matching developer services are mounted/available for that OS build.Failed to attach: unsupported iOS version (initializeMainExecutable not found)on iOS 18.4+ → your Frida/Gadget build is too old for the current dyld layout, so upgrade both the host tools and the cached/embedded gadget.
If it still fails, fall back to the explicit Frida Gadget IPA patching flow below.
Frida Gadget injection in non-jailbroken IPAs (listen mode)
If you can modify and re-sign an IPA, you can embed Frida Gadget and patch the Mach-O to load it via @rpath at startup. This enables Frida/Objection without a jailbreak (the device must accept the re-signed IPA).
A practical workflow is to use GadgetInjector (Python tool) to inject FridaGadget.dylib and generate a listen-mode configuration:[5]
python3 gadget_injector.py MyApp.ipa
# Output: MyApp-frida-listen.ipa
Re-signing constraints (important for non-jailbroken installs):
- Sign all embedded dylibs with the same Team ID.
- Do not add extra entitlements to
FridaGadget.dylib.
After re-signing and installing the IPA, attach in listen mode:
# (Optional) start the app paused
xcrun devicectl device process launch \
--device <UDID> \
--start-stopped <bundle-id>
# Forward Frida listen port over USB (default 27042)
pymobiledevice3 usbmux forward 27042 27042
# Objection
objection -g <bundle-id> explore
# Or Frida CLI
frida -H 127.0.0.1:27042 -n MyApp
Automated dynamic analysis with MobSF (no jailbreak)
MobSF can instrument a dev-signed IPA on a real device using the same technique (get_task_allow) and provides a web UI with filesystem browser, traffic capture and Frida console.[3] The quickest way is to run MobSF in Docker and then plug your iPhone via USB:
docker pull opensecurity/mobile-security-framework-mobsf:latest
docker run -p 8000:8000 --privileged \
-v /var/run/usbmuxd:/var/run/usbmuxd \
opensecurity/mobile-security-framework-mobsf:latest
# Browse to http://127.0.0.1:8000 and upload your resigned IPA
MobSF will automatically deploy the binary, enable a Frida server inside the app sandbox and generate an interactive report.
iOS 17+ caveats
- Lockdown Mode can interfere with wired trust/developer-service workflows, especially if the phone gets locked mid-session. Keep the device unlocked while pairing, tunneling, or attaching, and if the workflow is still unstable, temporarily disable Lockdown Mode for the test window.
- Pointer Authentication (PAC) is enforced system-wide on A12+ devices. Current Frida releases handle this well, but keep the host tools, any embedded Frida Gadget, and the testing device’s developer-service stack up-to-date when a new iOS major version ships.
References
- [1] Modern iOS Pentesting: No Jailbreak Needed
- [2] Apple Developer Documentation – Enabling Developer Mode on a Device
- [3] Mobile Security Framework (MobSF)
- [4] iOS Sandbox Explorer – iOS DYLIB injection tool for non-jailbreak devices with remote sandbox explorer
- [5] GadgetInjector – Frida Gadget injector for iOS 17/18 IPAs
- [6] Frida – iOS documentation
- [7] pymobiledevice3 – iOS 17+ Developer Services via Tunnel
- [8] go-ios
- [9] SideStore FAQ
- [10] Apple Developer Documentation – Debugging tool entitlement
- [11] TrollStore – Supported versions and CoreTrust behavior