iOS Penetration Testing: A Step-by-Step Guide for 2026

The pentest report landed on a mobile lead’s desk last quarter and the first finding was a single sentence: the application accepts any HTTPS certificate after a one-line Frida hook. Within ninety seconds of attaching to the running process, the tester had captured login traffic in plaintext, lifted a session token, and pivoted to the backend admin API. The app had passed an automated SAST scan two weeks earlier. iOS pentesting in 2026 is still mostly the same playbook β€” jailbreak, hook, intercept, dump β€” with newer obstacles around pointer authentication, hardened keychain access, and stricter App Transport Security. This guide walks through the workflow end to end, with the tooling and commands actually used on engagements.

Setting Up the Lab

You need a real test device or a cloud emulator. Two viable paths in 2026: a jailbroken iPhone running a checkra1n / palera1n compatible iOS version (iPhone X and earlier are still the workhorses because of the BootROM exploit), or a Corellium virtual device for everything newer. The simulator that ships with Xcode is useless for serious work β€” it does not reflect the real ARM64e runtime, no Keychain semantics, no jailbreak-detection surface to test against.

# Core toolchain on the workstation (macOS recommended)
brew install ideviceinstaller libimobiledevice ios-deploy
brew install --cask xcodes
pip3 install frida-tools objection

# On the jailbroken device (via Sileo / Zebra)
# - OpenSSH (root login over USB or Wi-Fi)
# - Frida server (matching the frida-tools version)
# - AppSync Unified (sideload unsigned IPAs)
# - SSL Kill Switch 3 (kernel-level pinning bypass)
# - Filza (filesystem browser on-device)

# Verify Frida sees the device
frida-ls-devices
# id                                        type    name
# ----------------------------------------  ------  --------------
# local                                     local   Local System
# 00008030-001A...                          usb     iPhone

# List running apps on the device
frida-ps -Uai

Step 1: Acquiring and Decrypting the IPA

App Store binaries are FairPlay-encrypted. You cannot disassemble them directly β€” the __TEXT segment is encrypted at rest and only decrypted in memory at launch. The standard move is to dump the decrypted binary from a running process on the jailbroken device.

# Option A β€” frida-ios-dump (most reliable in 2026)
git clone https://github.com/AloneMonkey/frida-ios-dump
cd frida-ios-dump
pip3 install -r requirements.txt
./dump.py "Target App"
# Produces TargetApp.ipa with decrypted Mach-O inside Payload/

# Option B β€” manual via lldb on-device
ssh root@iphone.local
ps aux | grep TargetApp
lldb -p PID
(lldb) image list -o -f         # find load address & cryptid
(lldb) memory read --outfile /tmp/dec.bin --binary 0x100000000 0x200000000

# Verify decryption worked
otool -l Payload/TargetApp.app/TargetApp | grep -A4 LC_ENCRYPTION_INFO
# cryptid 0  =  decrypted

Step 2: Static Analysis

Once you have a decrypted Mach-O, the goal is to map the attack surface before touching the running app: hardcoded secrets, exposed URL schemes, weak crypto primitives, debug flags left in production, third-party SDKs with known CVEs.

# MobSF β€” fastest first pass, runs in Docker
docker run -it --rm -p 8000:8000 opensecurity/mobile-security-framework-mobsf:latest
# Drop the IPA into the web UI β€” it produces a full report:
# Info.plist analysis, ATS exceptions, URL schemes, hardcoded strings, SBOM

# class-dump β€” Objective-C class hierarchy and method signatures
class-dump -H Payload/TargetApp.app/TargetApp -o ./headers/
grep -r "password\|token\|secret\|api_key" ./headers/

# Hopper / Ghidra / IDA β€” deeper reverse engineering
# Look for: strncmp on hardcoded keys, system() / popen() calls,
# weak crypto (DES, ECB, MD5), custom obfuscation routines

# Strings sweep
strings -a -n 8 Payload/TargetApp.app/TargetApp | \
  grep -Ei "https?://|aws_|firebase|api[_-]?key|bearer"

# Info.plist β€” check for ATS bypasses
plutil -p Payload/TargetApp.app/Info.plist | grep -A20 NSAppTransportSecurity
# NSAllowsArbitraryLoads = true β†’ ATS disabled, all HTTP allowed
# NSExceptionDomains β†’ per-domain exemptions worth investigating

Step 3: Dynamic Analysis with Frida and Objection

Static analysis tells you what the binary could do; dynamic analysis tells you what it actually does at runtime. Frida is still the dominant instrumentation framework in 2026, with Objection as the high-level wrapper that covers ninety percent of routine tasks without writing JavaScript hooks by hand.

# Spawn the app under Objection (USB-attached device)
objection -g "Target App" explore

# Inside the Objection REPL β€” the high-value commands
ios info binary                       # binary protections (PIE, ARC, stack canary)
ios keychain dump                     # all Keychain items the app can see
ios nsuserdefaults get                # NSUserDefaults plist values
ios plist cat /var/mobile/.../prefs.plist
ios cookies get                       # WKWebView / NSURLSession cookies
ios sslpinning disable                # generic SSL pinning bypass
ios jailbreak disable                 # hide jailbreak from detection routines
ios hooking list classes              # all Obj-C classes loaded in process
ios hooking watch class TargetVC      # log every method call on a class
ios hooking watch method "-[LoginVC submit:]" --dump-args --dump-return

# Custom Frida hook β€” dump arguments to a sensitive selector
frida -U -n "Target App" -l hook.js
# hook.js
# Interceptor.attach(ObjC.classes.LoginVC['- submit:'].implementation, {
#   onEnter: function(args) {
#     console.log('submit called with: ' + ObjC.Object(args[2]));
#   }
# });

Step 4: Network Interception and SSL Pinning Bypass

Most modern iOS apps pin their TLS certificates. A clean Burp Suite or mitmproxy intercept will fail with TLS errors until pinning is bypassed. Three layers usually have to be defeated: NSURLSession delegate validation, third-party SDK pinning (Alamofire, AFNetworking, TrustKit), and any custom logic that compares public-key hashes.

# 1) Configure the proxy
#    Burp: Proxy > Options > add 0.0.0.0:8080
#    Install the Burp CA on the device:
#      Settings > General > VPN & Device Management > Trust Profile
#      Settings > General > About > Certificate Trust Settings > enable Burp CA

# 2) Bypass pinning β€” fastest path is SSL Kill Switch 3 (system-wide)
#    Or via Objection: ios sslpinning disable
#    Or via Frida script for stubborn pinners:

frida -U -n "Target App" -l ssl-bypass.js
# ssl-bypass.js β€” hooks the four common iOS pinning points
# - SecTrustEvaluateWithError β†’ always return 0 (success)
# - SSL_CTX_set_verify β†’ force SSL_VERIFY_NONE
# - AFSecurityPolicy.evaluateServerTrust β†’ return YES
# - TrustKit.pinValidator β†’ return PinValidationResultSuccess

# 3) Verify in Burp β€” you should see decrypted requests
# Look for: bearer tokens in custom headers, JWT in body, unsigned APIs,
# sensitive data over HTTP (NSAllowsArbitraryLoads), GraphQL introspection

Step 5: Local Storage β€” Keychain, NSUserDefaults, Plist, Core Data

iOS gives developers half a dozen ways to persist data and developers regularly pick the wrong one. The Keychain is the only correct place for credentials and tokens; everything else β€” NSUserDefaults, plain Plist files, unencrypted SQLite, Core Data without an encryption layer β€” is fair game for an attacker with file-system access on a stolen or jailbroken device.

# App data live here on-device
ssh root@iphone.local
cd /var/mobile/Containers/Data/Application//
# Library/Preferences/.plist  β†’ NSUserDefaults
# Documents/                          β†’ user files (often unencrypted)
# Library/Caches/                     β†’ cookies, response caches
# Library/Application Support/        β†’ SQLite DBs, Core Data stores

# Dump the Keychain (Objection)
objection -g "Target App" explore -s "ios keychain dump"
# Watch for: kSecAttrAccessibleAlways (data accessible even when device locked)
# Best practice is kSecAttrAccessibleWhenUnlockedThisDeviceOnly

# Inspect SQLite stores
sqlite3 Library/Application\ Support/store.sqlite ".tables"
sqlite3 store.sqlite "SELECT * FROM ZUSER;"

# Find sensitive blobs across the container
grep -ri "password\|token\|jwt\|sessionid" .

# Check Data Protection class on every file
ls -laO@ Documents/
# 'NSFileProtectionComplete' is the strongest; 'None' means readable always

Step 6: IPC and URL Scheme Abuse

iOS apps expose inter-process surface through custom URL schemes, Universal Links, app extensions, pasteboards, and shared keychains. A sloppy URL handler that does not validate parameters can be triggered by another app on the device, by a phishing link, or by a QR code β€” and end up performing privileged actions on behalf of the attacker.

# Enumerate URL schemes from Info.plist
plutil -p Info.plist | grep -A6 CFBundleURLTypes
# Example: targetapp://  β†’ followed by handler logic in AppDelegate

# Trigger handlers from another app or from Safari
# Safari URL bar: targetapp://transfer?to=attacker&amount=5000
# Or via the simulator/device shell:
xcrun simctl openurl booted "targetapp://transfer?to=attacker&amount=5000"

# Watch the handler with Frida
frida -U -n "Target App" -l urlhook.js
# Interceptor.attach(ObjC.classes.AppDelegate['- application:openURL:options:'].implementation, {
#   onEnter: function(args) { console.log('openURL: ' + ObjC.Object(args[3])); }
# });

# Universal Links (apple-app-site-association)
curl https://target.com/.well-known/apple-app-site-association | jq .
# Map the path patterns the app claims β€” those are the entry points

Step 7: Reporting

A finding without a reproducer is a suggestion, not a vulnerability. Every issue in the report should include the exact tooling and command sequence used, a screenshot of the captured request or dumped artifact, the affected file path or class/method, and a CWE/OWASP MASVS mapping. The 2026 OWASP MASVS is the de facto framework β€” reference the specific control IDs (MSTG-STORAGE-1, MSTG-NETWORK-3, MSTG-AUTH-8) so the dev team can find the matching guidance.

The Toolchain at a Glance

For a typical engagement the working set looks like this: a jailbroken iPhone or Corellium device, Frida and Objection for runtime instrumentation, MobSF for the static first pass, Hopper or Ghidra for deep reversing, Burp Suite Pro with SSL Kill Switch 3 or a Frida pinning-bypass script for traffic, frida-ios-dump for IPA decryption, and class-dump plus a few lines of strings/grep for the inevitable hardcoded secret hunt. Every one of these except Burp Pro and Hopper is free.

iOS app security in 2026 is no longer about whether jailbreak detection is present β€” it almost always is, and it almost always falls to a one-line Objection command. The real findings are deeper: storage that bypasses the Keychain, IPC that trusts attacker-controlled URL parameters, network code that pins the wrong layer, and authentication tokens that outlive their session. Run the seven steps above on every iOS engagement, and the same handful of high-impact vulnerability classes will surface every time.