ios-pentesting-without-jailbreak.md (17356B)
1 --- 2 title: "iOS Pentesting without Jailbreak" 3 section: "Mobile" 4 sectionSlug: "mobile-pentesting" 5 sourcePath: "src/mobile-pentesting/ios-pentesting/ios-pentesting-without-jailbreak.md" 6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/mobile-pentesting/ios-pentesting/ios-pentesting-without-jailbreak.md" 7 sha: "188de82beb54e70956b2952367a0af91d26758b8" 8 isIndex: false 9 modified: true 10 license: "CC-BY-NC-4.0" 11 --- 12 13 # iOS Pentesting without Jailbreak 14 15 ## Main idea 16 17 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.<sup>[[10]](#references)</sup> 18 19 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.<sup>[[1]](#references)</sup> 20 21 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. 22 23 ## Obtain decrypted IPA 24 25 ### Get it from Apple<sup>[[1]](#references)</sup> 26 27 1. Install the app to test on the iPhone. 28 2. Install and launch [Apple Configurator](https://apps.apple.com/au/app/apple-configurator/id1037126344?mt=12) on macOS. 29 3. 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. 30 4. You should see your iOS device. Double-click on it, and then click Add + → Apps from the top menu bar. 31 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. 32 6. Retrieve the downloaded IPA from `/Users/[username]/Library/Group\\ Containers/K36BKF7T3D.group.com.apple.configurator/Library/Caches/Assets/TemporaryItems/MobileApps`. 33 34 Check [https://dvuln.com/blog/modern-ios-pentesting-no-jailbreak-needed](https://dvuln.com/blog/modern-ios-pentesting-no-jailbreak-needed) for more detailed information about this process.<sup>[[1]](#references)</sup> 35 36 ### Decrypting the app 37 38 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. 39 40 So, in order to install it, just unzip the IPA: 41 42 ```bash 43 unzip redacted.ipa -d unzipped 44 ``` 45 46 Check the `Info.plist` for the minimum supported version and if your device is older than that, change the value so it's supported. 47 48 Zip back the IPA: 49 50 ```bash 51 cd unzipped 52 zip -r ../no-min-version.ipa * 53 ``` 54 55 Then, install the IPA for example with: 56 57 ```bash 58 ideviceinstaller -i no-min-version.ipa -w 59 ``` 60 61 Note that you might need **AppSync Unified tweak** from Cydia to prevent any `invalid signature` errors. 62 63 Once installed, you can use **Iridium tweak** from Cydia in order to obtain the decrypted IPA. 64 65 ### Patch entitlements & re-sign 66 67 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. 68 69 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`. 70 71 With the re-signed IPA, it's time to install it in the device to pentest it: 72 73 ```bash 74 ideviceinstaller -i resigned.ipa -w 75 ``` 76 77 --- 78 79 ### IPA patching + DYLIB injection + free Apple ID re-sign (CLI) 80 81 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.<sup>[[4]](#references)</sup> 82 83 Typical flow: 84 85 ```bash 86 # Build the implant (macOS for build step) 87 make 88 89 # Patch the IPA to inject the DYLIB 90 python3 tools/patcher.py patch --ipa MyApp.ipa --dylib libShell.dylib 91 # -> MyApp_patched.ipa 92 93 # Patch + sign + install in one step (free Apple ID) 94 python3 tools/patcher.py full \ 95 --ipa MyApp.ipa \ 96 --dylib libShell.dylib \ 97 --apple-id user@example.com \ 98 --install \ 99 --udid <device-udid> 100 ``` 101 102 Notes: 103 104 - Free Apple ID signing usually expires in **7 days** and is limited to **3 App IDs per week** and **10 sideloaded apps**.<sup>[[9]](#references)</sup> 105 - 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). 106 - 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. 107 108 ### USB-only access to the injected implant 109 110 If the injected DYLIB exposes a local TCP control channel, you can keep traffic **off Wi-Fi/cellular** and forward it over USB:<sup>[[4]](#references)</sup> 111 112 ```bash 113 # Forward device-local TCP port to host 114 iproxy 8080 8080 115 116 # Example client commands (host side) 117 python3 client.py "ls" 118 python3 client.py "pwd" 119 python3 client.py "scp -r Documents host:./downloads" 120 ``` 121 122 If the implant includes keychain helpers, you can dump items **accessible to that app**: 123 124 ```bash 125 python3 client.py "keychain dump" 126 python3 client.py "keychain dump --filter self" 127 python3 client.py "keychain dump --class generic" 128 ``` 129 130 ### Enable Developer Mode (iOS 16+) 131 132 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. 133 134 1. Install or push **any** developer-signed IPA to the phone. 135 2. Navigate to **Settings → Privacy & Security → Developer Mode** and toggle it on. 136 3. The device will reboot; after entering the passcode you will be asked to **Turn On** Developer Mode. 137 138 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](https://developer.apple.com/documentation/xcode/enabling-developer-mode-on-a-device) explains the security implications.<sup>[[2]](#references)</sup> 139 140 ### Modern sideloading options 141 142 There are now several mature ways to sideload and keep re-signed IPAs up-to-date without a jailbreak: 143 144 | Tool | Requirements | Strengths | Limitations | 145 |------|--------------|-----------|-------------| 146 | **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<sup>[[9]](#references)</sup> | 147 | **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<sup>[[11]](#references)</sup> | 148 149 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. 150 151 ### iOS 17+ developer-service transport (CoreDevice / RSD tunnels) 152 153 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:<sup>[[7]](#references)</sup> 154 155 ```bash 156 python3 -m pip install -U pymobiledevice3 157 158 # iOS 17.4+ usually works directly (no root/admin needed) 159 pymobiledevice3 developer dvt ls / 160 pymobiledevice3 developer dvt device-information 161 pymobiledevice3 syslog live 162 ``` 163 164 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: 165 166 ```bash 167 sudo python3 -m pymobiledevice3 remote tunneld 168 pymobiledevice3 developer dvt ls / --tunnel '' 169 170 # Optional: allow later Wi-Fi access after the first trusted USB session 171 pymobiledevice3 lockdown wifi-connections on 172 ``` 173 174 If you prefer a single cross-platform binary, **go-ios** exposes the same CoreDevice-era services and file operations once its tunnel is up:<sup>[[8]](#references)</sup> 175 176 ```bash 177 npm install -g go-ios 178 sudo ios tunnel start 179 ios apps 180 ios file ls --app <bundle-id> --path / 181 ``` 182 183 This is especially useful when chaining **Frida**, **syslog/oslog**, **debugserver/LLDB**, or file exfiltration from Linux/Windows without having to open Xcode. 184 185 ### Quick sandbox triage over USB (before Frida) 186 187 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. 188 189 With **`pymobiledevice3`**, you can talk to the app container through **House Arrest**: 190 191 ```bash 192 # Interactive shell into the app container 193 pymobiledevice3 apps afc com.example.target 194 195 # Pull interesting files/directories 196 pymobiledevice3 apps pull com.example.target Library/Preferences/com.example.target.plist ./target.plist 197 pymobiledevice3 apps pull com.example.target Documents ./Documents --documents 198 199 # Push a modified file back into the sandbox 200 pymobiledevice3 apps push com.example.target ./evil.db "Library/Application Support/app.db" 201 ``` 202 203 On **iOS 17+**, **go-ios** can do the same over its RemoteXPC tunnel and is especially handy for enumerating **App Group** storage: 204 205 ```bash 206 ios file ls --app <bundle-id> --path / 207 ios file pull --app <bundle-id> \ 208 --remote Library/Preferences/<bundle-id>.plist \ 209 --local ./prefs.plist 210 ios file ls --app-group <group-id> --path / 211 ``` 212 213 If full-container vending fails, retry with **Documents-only** access and then fall back to in-app exfiltration via your injected DYLIB/Frida hooks. 214 215 ### Hooking / dynamic instrumentation 216 217 You can hook your app exactly as on a jailbroken device once it is signed with `get_task_allow` **and** Developer Mode is on: 218 219 ```bash 220 # Spawn & attach with objection 221 objection -g "com.example.target" explore 222 223 # Or plain Frida 224 frida -U -f com.example.target -l my_script.js --no-pause 225 ``` 226 227 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. 228 229 For more Frida-specific workflows and tracing recipes, check [iOS Frida Configuration](/hacktricks/mobile-pentesting/ios-pentesting/frida-configuration-in-ios). 230 231 ### Frida jailed mode without permanently patching the IPA 232 233 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: 234 235 - The app is re-signed with **`get-task-allow`** and launches with **Developer Mode** enabled. 236 - 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.<sup>[[6]](#references)</sup> 237 - The host-side Frida tools and the cached `FridaGadget.dylib` are on the **same version** (for example, do not mix an old gadget with a newly upgraded CLI). 238 239 A quick smoke test is to trace a high-signal library instead of starting with a custom hook: 240 241 ```bash 242 frida-trace -U MyApp -I "libcommonCrypto*" 243 ``` 244 245 If this works, move to your normal **Frida** or **Objection** scripts. If not, check these common failure modes before repatching the IPA: 246 247 - **`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. 248 - **`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. 249 250 If it still fails, fall back to the explicit **Frida Gadget** IPA patching flow below. 251 252 ### Frida Gadget injection in non-jailbroken IPAs (listen mode) 253 254 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). 255 256 A practical workflow is to use **GadgetInjector** (Python tool) to inject `FridaGadget.dylib` and generate a listen-mode configuration:<sup>[[5]](#references)</sup> 257 258 ```bash 259 python3 gadget_injector.py MyApp.ipa 260 # Output: MyApp-frida-listen.ipa 261 ``` 262 263 **Re-signing constraints** (important for non-jailbroken installs): 264 265 - Sign **all embedded dylibs** with the **same Team ID**. 266 - Do **not** add extra entitlements to `FridaGadget.dylib`. 267 268 After re-signing and installing the IPA, attach in listen mode: 269 270 ```bash 271 # (Optional) start the app paused 272 xcrun devicectl device process launch \ 273 --device <UDID> \ 274 --start-stopped <bundle-id> 275 276 # Forward Frida listen port over USB (default 27042) 277 pymobiledevice3 usbmux forward 27042 27042 278 279 # Objection 280 objection -g <bundle-id> explore 281 282 # Or Frida CLI 283 frida -H 127.0.0.1:27042 -n MyApp 284 ``` 285 286 ### Automated dynamic analysis with MobSF (no jailbreak) 287 288 [MobSF](https://mobsf.github.io/Mobile-Security-Framework-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.<sup>[[3]](#references)</sup> The quickest way is to run MobSF in Docker and then plug your iPhone via USB: 289 290 ```bash 291 docker pull opensecurity/mobile-security-framework-mobsf:latest 292 docker run -p 8000:8000 --privileged \ 293 -v /var/run/usbmuxd:/var/run/usbmuxd \ 294 opensecurity/mobile-security-framework-mobsf:latest 295 # Browse to http://127.0.0.1:8000 and upload your resigned IPA 296 ``` 297 298 MobSF will automatically deploy the binary, enable a Frida server inside the app sandbox and generate an interactive report. 299 300 ### iOS 17+ caveats 301 302 * **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. 303 * 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. 304 305 ## References 306 307 - [1] [Modern iOS Pentesting: No Jailbreak Needed](https://dvuln.com/blog/modern-ios-pentesting-no-jailbreak-needed) 308 - [2] [Apple Developer Documentation – Enabling Developer Mode on a Device](https://developer.apple.com/documentation/xcode/enabling-developer-mode-on-a-device) 309 - [3] [Mobile Security Framework (MobSF)](https://mobsf.github.io/Mobile-Security-Framework-MobSF/) 310 - [4] [iOS Sandbox Explorer – iOS DYLIB injection tool for non-jailbreak devices with remote sandbox explorer](https://github.com/test1ng-guy/iOS-sandbox-explorer) 311 - [5] [GadgetInjector – Frida Gadget injector for iOS 17/18 IPAs](https://github.com/Saurabh221662/GadgetInjector) 312 - [6] [Frida – iOS documentation](https://frida.re/docs/ios/) 313 - [7] [pymobiledevice3 – iOS 17+ Developer Services via Tunnel](https://github.com/doronz88/pymobiledevice3/blob/master/docs/guides/ios17-tunnels.md) 314 - [8] [go-ios](https://github.com/danielpaulus/go-ios) 315 - [9] [SideStore FAQ](https://docs.sidestore.io/docs/faq) 316 - [10] [Apple Developer Documentation – Debugging tool entitlement](https://developer.apple.com/documentation/bundleresources/entitlements/com.apple.security.cs.debugger) 317 - [11] [TrollStore – Supported versions and CoreTrust behavior](https://github.com/opa334/TrollStore)