← Back to writing
Mobile / iOS

iOS SSL Pinning Bypass

Oct 30, 2024
3 min read
lawbyte

SSL pinning on iOS works similarly to Android but uses different APIs. Most apps use NSURLSession or a third-party library like Alamofire. This guide covers bypassing each implementation.

Quick Bypass with Objection

objection -g com.target.app explore
# In REPL:
ios sslpinning disable

This patches the most common implementations automatically. If traffic still doesn’t appear in Burp, you need a custom bypass.


NSURLSession — Default Pinning

// Hook NSURLSessionDelegate
// Most apps implement URLSession:didReceiveChallenge:completionHandler:

var NSURLCredential = ObjC.classes.NSURLCredential;
var SecTrustRef = ObjC.classes.__NSCFType;

// Hook the challenge handler
var hook = ObjC.classes.NSURLSession['- URLSession:didReceiveChallenge:completionHandler:'];
if (hook) {
Interceptor.attach(hook.implementation, {
onEnter: function(args) {
var completion = new ObjC.Block(args[4]);
var originalImpl = completion.implementation;
completion.implementation = function(disposition, credential) {
// NSURLSessionAuthChallengeUseCredential = 0
// Create a credential from the server's certificate
var challenge = new ObjC.Object(args[3]);
var trust = challenge.protectionSpace().serverTrust();
var newCred = NSURLCredential.credentialForTrust_(trust);
return originalImpl(0, newCred);
};
}
});
}

SecTrustEvaluate — Native API Hook

This is the lowest-level hook — catches all pinning implementations:

var SecTrustEvaluate = Module.findExportByName("Security", "SecTrustEvaluate");
if (SecTrustEvaluate) {
Interceptor.replace(SecTrustEvaluate, new NativeCallback(function(trust, result) {
// Set result to kSecTrustResultProceed (1)
Memory.writeU32(result, 1);
return 0; // errSecSuccess
}, 'int', ['pointer', 'pointer']));
console.log('[+] SecTrustEvaluate hooked');
}

// Also hook newer API
var SecTrustEvaluateWithError = Module.findExportByName("Security", "SecTrustEvaluateWithError");
if (SecTrustEvaluateWithError) {
Interceptor.replace(SecTrustEvaluateWithError, new NativeCallback(function(trust, error) {
return 1; // true = trusted
}, 'bool', ['pointer', 'pointer']));
console.log('[+] SecTrustEvaluateWithError hooked');
}

TrustKit Bypass

// Hook TrustKit's TSKPinningValidator
try {
var TSKPinningValidator = ObjC.classes.TSKPinningValidator;
var method = TSKPinningValidator['+ evaluateTrust:forHostname:'];
Interceptor.attach(method.implementation, {
onLeave: function(retval) {
retval.replace(0x0); // TSKTrustEvaluationSuccess
}
});
console.log('[+] TrustKit bypassed');
} catch(e) {
console.log('[-] TrustKit not found');
}

Alamofire Bypass

// Hook Alamofire's ServerTrustPolicyManager
try {
var block = ObjC.Block({
retType: 'bool',
argTypes: [],
implementation: function() { return 1; }
});

var cls = ObjC.classes.SessionManager;
cls['- serverTrustPolicy'].implementation = function() {
return null;
};
} catch(e) {}

Universal iOS SSL Bypass Script

// universal-ios-ssl-bypass.js
// Hooks multiple SSL pinning implementations

(function() {

// ─── 1. SecTrustEvaluate ─────────────────────────────────────────
['SecTrustEvaluate', 'SecTrustEvaluateWithError', 'SecTrustEvaluateAsync'].forEach(function(name) {
var fn = Module.findExportByName('Security', name);
if (fn) {
Interceptor.replace(fn, new NativeCallback(function() {
var args = Array.prototype.slice.call(arguments);
if (name === 'SecTrustEvaluateWithError') return 1;
if (args[1]) Memory.writeU32(args[1], 1);
return 0;
}, 'int', ['pointer', 'pointer']));
console.log('[+] ' + name + ' hooked');
}
});

// ─── 2. NSURLSession delegate ────────────────────────────────────
if (ObjC.available) {
// Hook challenge handler
var resolve = function(args) {
var challenge = new ObjC.Object(args[3]);
var space = challenge.protectionSpace();
var trust = space.serverTrust();
var NSURLCredential = ObjC.classes.NSURLCredential;
var cred = NSURLCredential.credentialForTrust_(trust);
var block = new ObjC.Block(args[4]);
block(0, cred);
return null;
};

// NSURLSession
try {
var m1 = ObjC.classes.NSURLSession['- URLSession:didReceiveChallenge:completionHandler:'];
Interceptor.attach(m1.implementation, { onEnter: resolve });
} catch(e) {}

// NSURLConnection
try {
var m2 = ObjC.classes.NSURLConnection['- connection:willSendRequestForAuthenticationChallenge:'];
Interceptor.attach(m2.implementation, {
onEnter: function(args) {
var challenge = new ObjC.Object(args[3]);
var trust = challenge.protectionSpace().serverTrust();
var cred = ObjC.classes.NSURLCredential.credentialForTrust_(trust);
new ObjC.Object(args[2]).useCredential_forAuthenticationChallenge_(cred, challenge);
}
});
} catch(e) {}
}

console.log('[*] iOS SSL pinning bypass loaded');

})();
frida -U -f com.target.app -l universal-ios-ssl-bypass.js --no-pause

SSL Kill Switch 2 (Jailbreak Tweak)

For jailbroken devices, install SSL Kill Switch 2 from Cydia:

Source: https://cloud.githubusercontent.com/assets/581994/
Package: SSL Kill Switch 2

Enable in Settings → SSL Kill Switch 2 → Disable Certificate Validation.


Rebuilding IPA without Pinning (Static Patch)

For apps where Frida is detected:

# Extract IPA
unzip target.ipa -d extracted/
# Find and patch the binary using hex editor or Ghidra
# Look for SecTrustEvaluate calls and patch to always return 0
# Re-sign and install

# Re-sign with your cert
codesign -f -s "iPhone Developer: Your Name" extracted/Payload/*.app
zip -r patched.ipa extracted/
ideviceinstaller -i patched.ipa

Jailbreak Detection Bypass (iOS)

Java.perform(function() {}); // Frida iOS uses ObjC.perform equivalent

ObjC.schedule(ObjC.mainQueue, function() {
// Hook common jailbreak checks
var methods = [
'isJailbroken', 'checkJailbreak', 'isDeviceJailbroken',
'jailbreakDetection', 'detectJailbreak'
];

ObjC.enumerateLoadedClasses({
onMatch: function(name, handle) {
var cls = ObjC.classes[name];
methods.forEach(function(method) {
try {
var m = cls['- ' + method];
if (m) {
Interceptor.attach(m.implementation, {
onLeave: function(retval) {
retval.replace(0); // false
}
});
}
} catch(e) {}
});
},
onComplete: function() {}
});

// Hook file existence checks
var NSFileManager = ObjC.classes.NSFileManager;
NSFileManager['- fileExistsAtPath:'].implementation = function(path) {
var jbPaths = ['/Applications/Cydia.app', '/usr/bin/ssh', '/etc/apt',
'/bin/bash', '/private/var/lib/apt', '/private/var/jb'];
if (jbPaths.some(p => path.toString().includes(p))) {
return 0; // NO
}
return this['- fileExistsAtPath:'](path);
};
});

Discussion

Leave a comment · All fields required · No spam

No comments yet. Be the first.