#!/usr/bin/env python3 """Agent for iOS app reverse engineering with Frida. Uses frida-tools to attach to iOS processes, hook Objective-C methods, bypass SSL pinning, dump keychain entries, and trace API calls for security assessment. """ import subprocess import json import sys from datetime import datetime from pathlib import Path class FridaIOSAgent: """Reverse engineers iOS applications using Frida.""" def __init__(self, target_app, device_id=None, output_dir="./frida_ios"): self.target_app = target_app self.device_id = device_id self.output_dir = Path(output_dir) self.output_dir.mkdir(parents=True, exist_ok=True) self.findings = [] def _frida_cmd(self, script_code, timeout=60): cmd = ["frida", "-U"] if self.device_id: cmd.extend(["-D", self.device_id]) cmd.extend(["-n", self.target_app, "-q", "-e", script_code]) try: result = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout) return {"stdout": result.stdout, "stderr": result.stderr, "returncode": result.returncode} except (FileNotFoundError, subprocess.TimeoutExpired) as exc: return {"error": str(exc)} def list_running_apps(self): """List running applications on the connected iOS device.""" cmd = ["frida-ps", "-Ua"] if self.device_id: cmd.extend(["-D", self.device_id]) try: result = subprocess.run(cmd, capture_output=True, text=True, timeout=15) return {"apps": result.stdout} except (FileNotFoundError, subprocess.TimeoutExpired) as exc: return {"error": str(exc)} def bypass_ssl_pinning(self): """Inject Frida script to bypass SSL certificate pinning.""" script = """ var m = ObjC.classes.NSURLSessionConfiguration; Interceptor.attach(m['- setTLSMinimumSupportedProtocol:'].implementation, { onEnter: function(args) { console.log('[*] TLS config intercepted'); } }); try { var SSLSetPeerDomainName = Module.findExportByName(null, 'SSLSetPeerDomainName'); if (SSLSetPeerDomainName) { Interceptor.attach(SSLSetPeerDomainName, { onEnter: function(args) { }, onLeave: function(retval) { retval.replace(0); } }); console.log('[+] SSL pinning bypassed'); } } catch(e) { console.log('[-] ' + e); } """ result = self._frida_cmd(script) if "SSL pinning bypassed" in result.get("stdout", ""): self.findings.append({"type": "SSL Pinning Bypass", "severity": "Medium", "details": "SSL pinning can be bypassed with Frida"}) return result def dump_keychain(self): """Dump iOS Keychain entries accessible by the app.""" script = """ var kSecClass = ObjC.classes.NSString.stringWithString_('kSecClass'); var query = ObjC.classes.NSMutableDictionary.dictionary(); query.setObject_forKey_(ObjC.classes.NSString.stringWithString_('kSecClassGenericPassword'), kSecClass); query.setObject_forKey_(ObjC.classes.NSNumber.numberWithBool_(true), ObjC.classes.NSString.stringWithString_('kSecReturnAttributes')); query.setObject_forKey_(ObjC.classes.NSString.stringWithString_('kSecMatchLimitAll'), ObjC.classes.NSString.stringWithString_('kSecMatchLimit')); var result = new ObjC.Object(ptr(0)); var status = ObjC.classes.NSDictionary.alloc(); console.log('[*] Keychain query executed'); """ result = self._frida_cmd(script) return result def trace_objc_methods(self, class_name): """Trace all method calls on a specific Objective-C class.""" script = f""" var target = ObjC.classes.{class_name}; if (target) {{ var methods = target.$ownMethods; console.log('[*] Tracing ' + methods.length + ' methods on {class_name}'); methods.forEach(function(method) {{ try {{ Interceptor.attach(target[method].implementation, {{ onEnter: function(args) {{ console.log('[CALL] {class_name} ' + method); }} }}); }} catch(e) {{}} }}); }} else {{ console.log('[-] Class {class_name} not found'); }} """ return self._frida_cmd(script, timeout=30) def check_jailbreak_detection(self): """Test if app has jailbreak detection and attempt bypass.""" script = """ var paths = ['/Applications/Cydia.app', '/usr/sbin/sshd', '/bin/bash', '/usr/bin/ssh', '/etc/apt']; var NSFileManager = ObjC.classes.NSFileManager; Interceptor.attach(NSFileManager['- fileExistsAtPath:'].implementation, { onEnter: function(args) { var path = ObjC.Object(args[2]).toString(); for (var i = 0; i < paths.length; i++) { if (path.indexOf(paths[i]) !== -1) { console.log('[*] Jailbreak check: ' + path); } } }, onLeave: function(retval) {} }); console.log('[+] Jailbreak detection hooks installed'); """ result = self._frida_cmd(script, timeout=15) if "Jailbreak check" in result.get("stdout", ""): self.findings.append({"type": "Jailbreak Detection Present", "severity": "Info"}) return result def generate_report(self): report = { "target_app": self.target_app, "report_date": datetime.utcnow().isoformat(), "findings": self.findings, } report_path = self.output_dir / "frida_ios_report.json" with open(report_path, "w") as f: json.dump(report, f, indent=2) print(json.dumps(report, indent=2)) return report def main(): app = sys.argv[1] if len(sys.argv) > 1 else "TargetApp" agent = FridaIOSAgent(app) agent.list_running_apps() agent.bypass_ssl_pinning() agent.check_jailbreak_detection() agent.generate_report() if __name__ == "__main__": main()