iOS Application Security Testing Fundamentals

Mobile API OSINT
Time it takes to read this article 5 minutes.

Legal & Ethical Disclaimer: This article is for education and authorized security testing only. Only test applications you own or have explicit written permission to assess. Reverse engineering and tampering with apps you do not control may violate the DMCA, the CFAA, App Store terms, and local law. Do not jailbreak devices that hold third-party data without consent.

Introduction / Overview

iOS application testing intimidates a lot of pentesters because the platform is closed, binaries are encrypted, and Apple ships strong sandboxing. But the fundamentals map cleanly onto the OWASP MASVS and the same playbook you already use on the web: enumerate the attack surface, inspect storage, then instrument the runtime. This post walks through the core workflow — pulling an IPA, parsing plist files, dumping the keychain, and hooking methods with frida ios tooling and objection — on a jailbroken test device.

How it works / Background

An iOS app distributes as an IPA, which is just a ZIP archive. Inside Payload/AppName.app/ you'll find the Mach-O executable, the Info.plist, embedded frameworks, and resources. App Store binaries are encrypted with Apple's FairPlay DRM (the cryptid flag in the LC_ENCRYPTION_INFO load command), so static analysis requires a decrypted binary dumped from memory at runtime.

Sensitive data on iOS typically lands in three places:

  • Keychain — the OS-backed secret store (SecItemAdd/SecItemCopyMatching), protected by kSecAttrAccessible* classes.
  • plist / NSUserDefaults — XML or binary property lists under the app's Library/Preferences/. Developers frequently and wrongly store tokens here.
  • Data Protection files — files in the sandbox tagged with NSFileProtection* classes.

Dynamic analysis relies on Frida, a dynamic instrumentation toolkit that injects a JavaScript engine into the target process. objection is a Frida-powered runtime exploration framework that wraps common iOS tasks (keychain dumps, SSL pinning bypass, jailbreak-detection bypass) behind a friendly REPL.

Prerequisites / Lab setup

  • A jailbroken iOS device (e.g. checkra1n/palera1n for checkm8-vulnerable hardware, or Dopamine for arm64e on supported versions) with OpenSSH installed via Cydia/Sileo.
  • A host with Python 3 and the toolchain installed.
# Host tooling
pip3 install frida-tools objection

# On the jailbroken device add the Frida repo in Sileo:
#   https://build.frida.re
# then install the "Frida" package, which runs frida-server on boot.

# Verify the device is reachable over USB
frida-ps -Ua
Bash

frida-ps -Ua lists running apps with their bundle identifiers — confirm you see your target before continuing.

Walkthrough / PoC

1. Pull and decrypt the IPA

The cleanest way to get a decrypted IPA is frida-ios-dump, which dumps the in-memory (post-FairPlay) Mach-O.

git clone https://github.com/AloneMonkey/frida-ios-dump
cd frida-ios-dump
# forward SSH from device to host (default jailbreak creds: root / alpine)
iproxy 2222 22 &
python3 dump.py -H 127.0.0.1 -p 2222 com.example.target
Bash

You now have target.ipa. Unzip it and confirm decryption:

unzip -q target.ipa -d target_app
otool -l "target_app/Payload/Target.app/Target" | grep -A4 LC_ENCRYPTION_INFO
# cryptid 0  => decrypted and ready for static analysis
Bash

2. Inspect plist and the bundle

# Convert binary plist to readable XML
plutil -convert xml1 -o - "target_app/Payload/Target.app/Info.plist" | head -n 40

# Hunt for misconfigured ATS exceptions and URL schemes
plutil -p "target_app/Payload/Target.app/Info.plist" | grep -iE "NSAllowsArbitraryLoads|CFBundleURLSchemes"
Bash

NSAllowsArbitraryLoads = true disables App Transport Security, allowing cleartext HTTP. Loot the binary for hardcoded secrets too:

strings -a "target_app/Payload/Target.app/Target" | grep -iE "api[_-]?key|secret|https://"
Bash

3. Dump the keychain and local storage with objection

Attach objection to the running app and explore:

objection -g com.example.target explore
Bash

Inside the objection REPL:

# Dump every keychain item the app can read
ios keychain dump

# Locate the sandbox and inspect on-disk storage
env
ios nsurlsession dump
ios plist cat Library/Preferences/com.example.target.plist
Plaintext

ios keychain dump reveals stored credentials, session tokens, and the kSecAttrAccessible class of each entry — items marked AccessibleAlways are the highest risk because they survive even when the device is locked.

4. Bypass jailbreak detection and SSL pinning

Many apps refuse to run on a jailbreak or pin their TLS certificate. objection's built-in hooks neutralize both:

ios jailbreak disable
ios sslpinning disable
Plaintext

For bespoke checks, write a frida ios hook directly. This example traces and overrides a Swift/ObjC jailbreak check returning a BOOL:

// jb-bypass.js  -- run with: frida -U -f com.example.target -l jb-bypass.js
Interceptor.attach(ObjC.classes.JailbreakDetector["- isJailbroken"].implementation, {
  onLeave: function (retval) {
    console.log("[*] isJailbroken original:", retval);
    retval.replace(ptr("0x0"));   // force NO
  }
});
JavaScript

With pinning disabled, route traffic through Burp/mitmproxy and you have full visibility of the app's API — the real prize for most engagements.

Mermaid diagram

iOS Application Security Testing Fundamentals diagram 1

The diagram shows the end-to-end flow: connect the device, obtain a decrypted binary, perform static review, then pivot into runtime instrumentation and traffic interception.

Detection & Defense (Blue Team)

Defending iOS apps means assuming the device may be compromised and making instrumentation expensive. Mitigations, weighted equally to the offense above:

  • Keychain hygiene. Never store long-lived secrets in plaintext plist/NSUserDefaults. Use the keychain with the strictest accessibility class your UX allows — prefer kSecAttrAccessibleWhenUnlockedThisDeviceOnly over kSecAttrAccessibleAlways, and add kSecAccessControlBiometryCurrentSet via SecAccessControl so a token is bound to the current biometric enrollment.
  • Enforce ATS. Remove NSAllowsArbitraryLoads; require TLS 1.2+ for all endpoints so cleartext interception fails by default.
  • Server-side controls win. Client-side jailbreak detection and SSL pinning slow attackers but cannot be trusted — anyone with objection/Frida flips them. Treat every request as hostile: short-lived tokens, server-side rate limiting, anomaly detection, and device attestation via App Attest / DeviceCheck (DCAppAttestService) which produces a server-verifiable hardware-backed assertion that Frida cannot forge.
  • Anti-tamper and anti-debug. Detect injected Frida (scan loaded dyld images and listening ports such as the default 27042), call ptrace(PT_DENY_ATTACH) / sysctl debugger checks, and validate the Mach-O code signature at runtime. Layer these — single checks are trivially patched, as the hook above demonstrates.
  • Detection telemetry. Forward jailbreak/attestation failures and pinning errors to your SIEM. Maps to MITRE ATT&CK for Mobile: Compromise Application Executable (T1577), Code Injection (T1540), and Keychain credential access (T1634).
  • Pen-test continuously. Run MobSF in CI for automated static checks and gate releases on MASVS resilience requirements.

Conclusion

iOS testing is approachable once you internalize the loop: get a decrypted IPA, read the plist and strings, then instrument with frida ios hooks and objection to dump the keychain and defeat client-side jailbreak/pinning checks. The offensive steps are fast, which is exactly why defenders must push trust to the server and treat on-device controls as speed bumps rather than walls. If you came from desktop reversing, see my notes on Ghidra for binary analysis and intercepting traffic with Burp Suite; the Android equivalent of this workflow overlaps heavily.

References

You Might Also Like

If you found this useful, these related deep-dives cover adjacent techniques and their defenses:

Comments

Copied title and URL