iOS App Internals
IPAs, the app sandbox, the Keychain, and the protections you have to work around.
What you will learn
- -Unpack an IPA and understand why the App Store binary is encrypted
- -Locate an app's sandbox container and its data stores
- -Explain what the Keychain protects and how developers still misuse it
An IPA has the same trick as an APK: it is a ZIP. Unzip it and you a Payload/ folder with the .app bundle inside, which holds the Mach-O executable, an Info.plist, the code under _CodeSignature/, and the provisioning profile at embedded.mobileprovision. The provisioning profile tells you whether this is a development, enterprise, or App Store build, which matters more than it sounds.
Here is the catch that trips up people coming from Android. An App Store binary is encrypted with Apple's FairPlay DRM, so the Mach-O on disk is . Run strings on it and you get noise. To read the code you decrypt it from memory on a device where it is running, using frida-ios-dump or a similar tool on a jailbroken device. Development and enterprise builds are usually not FairPlay-encrypted, so those you can pull straight into Hopper or . Knowing which build you have saves you an hour of confusion.
$unzip app.ipa -d app_extracted$otool -l app_extracted/Payload/AppName.app/AppName | grep -A4 LC_ENCRYPTION_INFO$plutil -p app_extracted/Payload/AppName.app/Info.plistThe sandbox and where data lives
Every iOS app runs in its own , and its writable data sits in a at /var/mobile/Containers/Data/Application/<UUID>/. The UUID changes per install, so you find it by package identifier. Inside, three directories carry almost everything:
- Documents/ user data, and the first place to check for unprotected files.
- Library/ app support data, including
Preferences/whereNSUserDefaultswrites a plist, andCaches/which survives longer than developers assume. - tmp/ scratch space.
The bundle itself lives under a separate Bundle/Application/<UUID>/ path and is read-only at runtime. That split, data container versus app bundle, is worth keeping straight because your hunt is in the data container while your code is in the bundle.
$objection -g AppName exploreThe Keychain, and why it is not a magic vault
The Keychain is iOS's encrypted store for small secrets: , , keys. It is hardware-backed on modern devices through the Secure Enclave, and it is the right place to put . So far so good.
The misuse is in the accessibility attribute. Each Keychain item carries a class that decides when it can be read. kSecAttrAccessibleWhenUnlocked means the item is available only while the device is unlocked, which is what you usually want. kSecAttrAccessibleAlways (deprecated for good reason) leaves the secret readable even when locked. Worse, some developers skip the Keychain entirely and drop the auth token into NSUserDefaults, which is a plaintext plist any Keychain-dumping tool reads instantly on a jailbroken device. Finding a token in Library/Preferences/ instead of the Keychain is a finding you will write up often.
The techniques here assume a device you own, jailbroken for testing, running an app you are authorized to assess. Dumping the Keychain or decrypting a binary from memory on someone else's phone is unauthorized access. Apple's own protections exist to stop exactly that, so keep this to your test device, the OWASP iGoat app, or the OWASP MAS test apps.
Two platform protections are worth naming because they shape what you can and cannot do. iOS files can be encrypted with Data Protection classes tied to the passcode, so a file marked complete-protection is unreadable while the device is locked even with filesystem access. And App Transport Security (ATS) blocks plaintext by default, which pushes the interesting findings toward apps that added an ATS exception to allow it.
You unzip an App Store IPA, run strings on the main binary, and see almost nothing readable. Why?