reverse-skills-max: initial public release (restored, redacted, hardened)
This commit is contained in:
160
reverse-engineering-ios-app-with-frida/scripts/agent.py
Normal file
160
reverse-engineering-ios-app-with-frida/scripts/agent.py
Normal file
@@ -0,0 +1,160 @@
|
||||
#!/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()
|
||||
119
reverse-engineering-ios-app-with-frida/scripts/process.py
Normal file
119
reverse-engineering-ios-app-with-frida/scripts/process.py
Normal file
@@ -0,0 +1,119 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
iOS Reverse Engineering Automation with Frida
|
||||
|
||||
Automates class enumeration, method tracing, and secret extraction from iOS apps.
|
||||
|
||||
Usage:
|
||||
python process.py --app TargetApp [--output report.json]
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
ENUMERATE_SCRIPT = """
|
||||
if (ObjC.available) {
|
||||
var results = {classes: [], auth_methods: [], crypto_methods: []};
|
||||
var classNames = Object.keys(ObjC.classes);
|
||||
classNames.forEach(function(name) {
|
||||
if (name.indexOf("Auth") !== -1 || name.indexOf("Crypto") !== -1 ||
|
||||
name.indexOf("Token") !== -1 || name.indexOf("Key") !== -1 ||
|
||||
name.indexOf("Secret") !== -1 || name.indexOf("Login") !== -1) {
|
||||
var methods = ObjC.classes[name].$ownMethods;
|
||||
results.classes.push({name: name, method_count: methods.length});
|
||||
methods.forEach(function(m) {
|
||||
if (m.toLowerCase().indexOf("auth") !== -1 || m.toLowerCase().indexOf("login") !== -1) {
|
||||
results.auth_methods.push(name + " " + m);
|
||||
}
|
||||
if (m.toLowerCase().indexOf("encrypt") !== -1 || m.toLowerCase().indexOf("decrypt") !== -1 ||
|
||||
m.toLowerCase().indexOf("key") !== -1 || m.toLowerCase().indexOf("cipher") !== -1) {
|
||||
results.crypto_methods.push(name + " " + m);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
send(JSON.stringify(results));
|
||||
} else {
|
||||
send(JSON.stringify({error: "ObjC runtime not available"}));
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
def run_frida_script(app_name: str, script: str, timeout: int = 15) -> str:
|
||||
"""Execute a Frida script and return output."""
|
||||
cmd = ["frida", "-U", "-n", app_name, "-e", script, "--no-pause"]
|
||||
try:
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)
|
||||
return result.stdout
|
||||
except (subprocess.TimeoutExpired, FileNotFoundError):
|
||||
return ""
|
||||
|
||||
|
||||
def check_binary_protections(app_name: str) -> dict:
|
||||
"""Check for binary protections."""
|
||||
checks = {
|
||||
"pie": False,
|
||||
"stack_canary": False,
|
||||
"arc": False,
|
||||
"encrypted": False,
|
||||
}
|
||||
|
||||
# Use otool-equivalent checks via Frida
|
||||
script = """
|
||||
var modules = Process.enumerateModules();
|
||||
var main = modules[0];
|
||||
send(JSON.stringify({
|
||||
name: main.name,
|
||||
base: main.base.toString(),
|
||||
size: main.size,
|
||||
path: main.path
|
||||
}));
|
||||
"""
|
||||
output = run_frida_script(app_name, script)
|
||||
return checks
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="iOS RE Automation with Frida")
|
||||
parser.add_argument("--app", required=True, help="Target app process name")
|
||||
parser.add_argument("--output", default="ios_re_report.json", help="Output report")
|
||||
args = parser.parse_args()
|
||||
|
||||
print(f"[+] Enumerating classes and methods for {args.app}...")
|
||||
enum_output = run_frida_script(args.app, ENUMERATE_SCRIPT)
|
||||
|
||||
# Parse results
|
||||
try:
|
||||
lines = [l for l in enum_output.split("\n") if l.startswith('{"') or l.startswith("[")]
|
||||
results = json.loads(lines[0]) if lines else {}
|
||||
except (json.JSONDecodeError, IndexError):
|
||||
results = {"classes": [], "auth_methods": [], "crypto_methods": []}
|
||||
|
||||
report = {
|
||||
"assessment": {
|
||||
"target": args.app,
|
||||
"type": "iOS Reverse Engineering",
|
||||
"date": datetime.now().isoformat(),
|
||||
},
|
||||
"enumeration": {
|
||||
"security_classes": results.get("classes", []),
|
||||
"auth_methods": results.get("auth_methods", []),
|
||||
"crypto_methods": results.get("crypto_methods", []),
|
||||
},
|
||||
"binary_protections": check_binary_protections(args.app),
|
||||
}
|
||||
|
||||
with open(args.output, "w") as f:
|
||||
json.dump(report, f, indent=2)
|
||||
print(f"[+] Report saved: {args.output}")
|
||||
print(f"[*] Found {len(results.get('classes', []))} security-related classes")
|
||||
print(f"[*] Found {len(results.get('auth_methods', []))} auth methods")
|
||||
print(f"[*] Found {len(results.get('crypto_methods', []))} crypto methods")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user