Files
Anthropic-Cybersecurity-Skills/skills/reverse-engineering-ios-app-with-frida/scripts/agent.py
T
mukul975 c21af3347e Complete folder anatomy for all 649 cybersecurity skills + update LICENSE to Mahipal
- Add scripts/agent.py and references/api-reference.md to all remaining skills
- Update all 648 LICENSE files: copyright now reads 'Mahipal'
- Add implementing-security-monitoring-with-datadog (new skill with full anatomy)
- All 649 skills now have: SKILL.md, LICENSE, scripts/agent.py, references/api-reference.md
2026-03-11 00:22:12 +01:00

162 lines
5.9 KiB
Python

#!/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
import re
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()