iOS Pentesting without Jailbreak

Main idea

Applications signed with the entitlement get_task_allow allow third party applications to run a function called task_for_pid() with the process ID of the initial application as argument in order to get the task port over it (be able to control it and access its memory).

However, it’s not as easy as just pulling the IPA, re-signing it with the entitlement, and flashing it back to your device. This is because of FairPlay protection. When the signature of the app changes, the DRM (Digital Rights Management) key is invalidated and the app won't work.

With an old jailbroken device, it's possible to install the IPA, decrypt it using your favourite tool (such as Iridium or frida-ios-dump), and pull it back off the device. Although, if possible, it's recommended to just ask the client for the decrypted IPA.

Obtain decrypted IPA

Get it from Apple

  1. Install the app to pentest in the iPhone
  2. Install and launch Apple Configurator inside your macos
  3. Open Terminalon your Mac, and cd to /Users/[username]/Library/Group\\ Containers/K36BKF7T3D.group.com.apple.configurator/Library/Caches/Assets/TemporaryItems/MobileApps. The IPA will appear in this folder later.
  4. You should see your iOS device. Double-click on it, and then click Add + → Apps from the top menu bar.
  5. 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.
  6. The IPA should be downloaded inside /Users/[username]/Library/Group\\ Containers/K36BKF7T3D.group.com.apple.configurator/Library/Caches/Assets/TemporaryItems/MobileAppsfrom where you can grab it

Check https://dvuln.com/blog/modern-ios-pentesting-no-jailbreak-needed for more detailed information about this process.

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

In order to re-sign the application with the get-task-allow entitlement there are several tools available like app-signer, codesign, and iResign. app-signer has a very user-friendly interface that allows to very easily re-sign an IPA file indicating the IPA to re-sign, to enable get-task-allow and the certificate and provisioning profile to use.

Regarding the certificate and signing profiles, Apple offers free developer signing profiles for all accounts through Xcode. Just create an app and configure one. Then, configure the iPhone to trust the developer apps by navigating to SettingsPrivacy & Security, and click on Developer Mode.

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.

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/week and 10 sideloaded apps.
  • 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:

# 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.

  1. Install or push any developer-signed IPA to the phone.
  2. Navigate to Settings → Privacy & Security → Developer Mode and toggle it on.
  3. 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.

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; Apple still enforces the 3 App IDs / week cap
TrollStore 1/2 Device on a firmware supported by the CoreTrust bug (currently 14.0 beta 2 – 16.6.1, 16.7 RC, and 17.0) Permanent signing (no 7-day refresh); excellent for long assessments on vulnerable devices Firmware-specific and unavailable on fully patched modern releases

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 a trusted tunnel. If devicectl, DVT-backed tooling, or other USB debugging helpers cannot talk to the phone from Linux/Windows, establish the tunnel first and reuse the printed RSD address/port for later commands:

python3 -m pip install -U pymobiledevice3

# iOS 17.4+
sudo pymobiledevice3 lockdown start-tunnel

# iOS 17.0 - 17.3.1 fallback
sudo pymobiledevice3 remote start-tunnel

# Optional: allow later Wi-Fi access after the first trusted USB session
pymobiledevice3 lockdown wifi-connections on

This is especially useful when chaining Frida, AFC access, or other developer services from non-macOS hosts without having to open Xcode.

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

Recent Frida releases (>=16) automatically handle pointer authentication and other iOS 17 mitigations, so most existing scripts work out-of-the-box.

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-allow and launches with Developer Mode enabled.
  • The host has the current Apple developer services available (historically the Developer Disk Image; on modern Xcode/iOS 17+ setups this is handled through the current developer-service stack when the device is paired).
  • The latest FridaGadget.dylib exists in the user cache, e.g. ~/.cache/frida/gadget-ios.dylib on macOS.

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, 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:

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. 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 & Lockdown Mode caveats

  • Lockdown Mode (Settings → Privacy & Security) blocks the dynamic linker from loading unsigned or externally signed dynamic libraries. When testing devices that might have this mode enabled make sure it is disabled or your Frida/objection sessions will terminate immediately.
  • Pointer Authentication (PAC) is enforced system-wide on A12+ devices. Frida ≥16 transparently handles PAC stripping — just keep both frida-server and the Python/CLI toolchain up-to-date when a new major iOS version ships.

References