403 lines
16 KiB
Python
403 lines
16 KiB
Python
|
|
"""
|
||
|
|
iPhone Local USB - Device access via libimobiledevice
|
||
|
|
"""
|
||
|
|
|
||
|
|
DESCRIPTION = "iPhone USB exploitation (info, backup, extract, apps, profiles)"
|
||
|
|
AUTHOR = "AUTARCH"
|
||
|
|
VERSION = "1.0"
|
||
|
|
CATEGORY = "hardware"
|
||
|
|
|
||
|
|
import sys
|
||
|
|
from pathlib import Path
|
||
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||
|
|
|
||
|
|
|
||
|
|
class IPhoneLocal:
|
||
|
|
"""Interactive menu for iPhone USB device access."""
|
||
|
|
|
||
|
|
def __init__(self):
|
||
|
|
from core.iphone_exploit import get_iphone_manager
|
||
|
|
self.mgr = get_iphone_manager()
|
||
|
|
self.udid = None
|
||
|
|
|
||
|
|
def _select_device(self):
|
||
|
|
devices = self.mgr.list_devices()
|
||
|
|
if not devices:
|
||
|
|
print(" No iOS devices connected.")
|
||
|
|
return
|
||
|
|
if len(devices) == 1:
|
||
|
|
self.udid = devices[0]['udid']
|
||
|
|
print(f" Selected: {devices[0].get('name','')} ({self.udid[:12]}...)")
|
||
|
|
return
|
||
|
|
print("\n Select device:")
|
||
|
|
for i, d in enumerate(devices, 1):
|
||
|
|
print(f" {i}) {d.get('name','')} - {d.get('model','')} iOS {d.get('ios_version','')} [{d['udid'][:12]}...]")
|
||
|
|
try:
|
||
|
|
choice = int(input(" > ").strip())
|
||
|
|
if 1 <= choice <= len(devices):
|
||
|
|
self.udid = devices[choice - 1]['udid']
|
||
|
|
except (ValueError, EOFError, KeyboardInterrupt):
|
||
|
|
pass
|
||
|
|
|
||
|
|
def _ensure_device(self):
|
||
|
|
if not self.udid:
|
||
|
|
self._select_device()
|
||
|
|
return self.udid is not None
|
||
|
|
|
||
|
|
def show_menu(self):
|
||
|
|
status = self.mgr.get_status()
|
||
|
|
print(f"\n{'='*60}")
|
||
|
|
print(" iPhone USB Exploitation")
|
||
|
|
print(f"{'='*60}")
|
||
|
|
print(f" Tools: {status['found']}/{status['total']} available")
|
||
|
|
print(f" Device: {self.udid[:16] + '...' if self.udid else '(none)'}")
|
||
|
|
print()
|
||
|
|
print(" ── Device ──")
|
||
|
|
print(" [1] List Devices")
|
||
|
|
print(" [2] Device Info")
|
||
|
|
print(" [3] Full Fingerprint")
|
||
|
|
print(" [4] Pair / Validate")
|
||
|
|
print(" [5] Get/Set Device Name")
|
||
|
|
print(" [6] Restart / Shutdown / Sleep")
|
||
|
|
print()
|
||
|
|
print(" ── Capture ──")
|
||
|
|
print(" [10] Screenshot")
|
||
|
|
print(" [11] Syslog Dump")
|
||
|
|
print(" [12] Syslog Grep (sensitive)")
|
||
|
|
print(" [13] Crash Reports")
|
||
|
|
print()
|
||
|
|
print(" ── Apps ──")
|
||
|
|
print(" [20] List Apps")
|
||
|
|
print(" [21] Install IPA")
|
||
|
|
print(" [22] Uninstall App")
|
||
|
|
print()
|
||
|
|
print(" ── Backup & Extraction ──")
|
||
|
|
print(" [30] Create Backup")
|
||
|
|
print(" [31] List Backups")
|
||
|
|
print(" [32] Extract SMS/iMessage")
|
||
|
|
print(" [33] Extract Contacts")
|
||
|
|
print(" [34] Extract Call Log")
|
||
|
|
print(" [35] Extract Notes")
|
||
|
|
print(" [36] Browse Backup Files")
|
||
|
|
print(" [37] Extract Backup File")
|
||
|
|
print()
|
||
|
|
print(" ── Filesystem & Profiles ──")
|
||
|
|
print(" [40] Mount Filesystem (ifuse)")
|
||
|
|
print(" [41] Mount App Documents")
|
||
|
|
print(" [42] Unmount")
|
||
|
|
print(" [43] List Profiles")
|
||
|
|
print(" [44] Install Profile")
|
||
|
|
print(" [45] Remove Profile")
|
||
|
|
print()
|
||
|
|
print(" ── Network ──")
|
||
|
|
print(" [50] Port Forward (iproxy)")
|
||
|
|
print(" [51] Export Recon Report")
|
||
|
|
print()
|
||
|
|
print(" [s] Select Device")
|
||
|
|
print(" [0] Back")
|
||
|
|
print()
|
||
|
|
|
||
|
|
def _pick_backup(self):
|
||
|
|
backups = self.mgr.list_backups()
|
||
|
|
if not backups['backups']:
|
||
|
|
print(" No backups found. Create one first.")
|
||
|
|
return None
|
||
|
|
print("\n Available backups:")
|
||
|
|
for i, b in enumerate(backups['backups'], 1):
|
||
|
|
name = b.get('device_name', b['udid'][:12])
|
||
|
|
size = b.get('size_mb', 0)
|
||
|
|
print(f" {i}) {name} - {b.get('ios_version','')} ({size:.0f} MB)")
|
||
|
|
try:
|
||
|
|
choice = int(input(" > ").strip())
|
||
|
|
if 1 <= choice <= len(backups['backups']):
|
||
|
|
return backups['backups'][choice - 1]['path']
|
||
|
|
except (ValueError, EOFError, KeyboardInterrupt):
|
||
|
|
pass
|
||
|
|
return None
|
||
|
|
|
||
|
|
def run_interactive(self):
|
||
|
|
while True:
|
||
|
|
self.show_menu()
|
||
|
|
try:
|
||
|
|
choice = input(" Select > ").strip().lower()
|
||
|
|
except (EOFError, KeyboardInterrupt):
|
||
|
|
break
|
||
|
|
if choice == '0':
|
||
|
|
break
|
||
|
|
elif choice == 's':
|
||
|
|
self._select_device()
|
||
|
|
continue
|
||
|
|
|
||
|
|
try:
|
||
|
|
self._dispatch(choice)
|
||
|
|
except (EOFError, KeyboardInterrupt):
|
||
|
|
continue
|
||
|
|
|
||
|
|
def _dispatch(self, choice):
|
||
|
|
m = self.mgr
|
||
|
|
# Device
|
||
|
|
if choice == '1':
|
||
|
|
devices = m.list_devices()
|
||
|
|
if not devices:
|
||
|
|
print(" No iOS devices connected.")
|
||
|
|
else:
|
||
|
|
print(f"\n {'UDID':<42} {'Name':<20} {'Model':<15} iOS")
|
||
|
|
print(f" {'-'*85}")
|
||
|
|
for d in devices:
|
||
|
|
print(f" {d['udid']:<42} {d.get('name',''):<20} {d.get('model',''):<15} {d.get('ios_version','')}")
|
||
|
|
elif choice == '2':
|
||
|
|
if not self._ensure_device(): return
|
||
|
|
info = m.device_info(self.udid)
|
||
|
|
if 'error' in info:
|
||
|
|
print(f" Error: {info['error']}")
|
||
|
|
else:
|
||
|
|
for k, v in list(info.items())[:40]:
|
||
|
|
print(f" {k:<35} {v}")
|
||
|
|
if len(info) > 40:
|
||
|
|
print(f" ... and {len(info)-40} more fields")
|
||
|
|
elif choice == '3':
|
||
|
|
if not self._ensure_device(): return
|
||
|
|
fp = m.full_fingerprint(self.udid)
|
||
|
|
for k, v in list(fp.items())[:50]:
|
||
|
|
if isinstance(v, dict):
|
||
|
|
print(f" {k}:")
|
||
|
|
for sk, sv in list(v.items())[:10]:
|
||
|
|
print(f" {sk}: {sv}")
|
||
|
|
else:
|
||
|
|
print(f" {k:<35} {v}")
|
||
|
|
elif choice == '4':
|
||
|
|
if not self._ensure_device(): return
|
||
|
|
action = input(" [p]air / [v]alidate / [u]npair? ").strip().lower()
|
||
|
|
if action == 'p':
|
||
|
|
r = m.pair_device(self.udid)
|
||
|
|
elif action == 'u':
|
||
|
|
r = m.unpair_device(self.udid)
|
||
|
|
else:
|
||
|
|
r = m.validate_pair(self.udid)
|
||
|
|
print(f" {r.get('output', r)}")
|
||
|
|
elif choice == '5':
|
||
|
|
if not self._ensure_device(): return
|
||
|
|
r = m.get_name(self.udid)
|
||
|
|
print(f" Current name: {r['name']}")
|
||
|
|
new = input(" New name (Enter to keep): ").strip()
|
||
|
|
if new:
|
||
|
|
m.set_name(self.udid, new)
|
||
|
|
print(f" Name set to: {new}")
|
||
|
|
elif choice == '6':
|
||
|
|
if not self._ensure_device(): return
|
||
|
|
action = input(" [r]estart / [s]hutdown / s[l]eep? ").strip().lower()
|
||
|
|
if action == 'r':
|
||
|
|
r = m.restart_device(self.udid)
|
||
|
|
elif action == 's':
|
||
|
|
r = m.shutdown_device(self.udid)
|
||
|
|
elif action == 'l':
|
||
|
|
r = m.sleep_device(self.udid)
|
||
|
|
else:
|
||
|
|
print(" Invalid."); return
|
||
|
|
print(f" {r.get('output', 'Done')}")
|
||
|
|
# Capture
|
||
|
|
elif choice == '10':
|
||
|
|
if not self._ensure_device(): return
|
||
|
|
r = m.screenshot(self.udid)
|
||
|
|
if r['success']:
|
||
|
|
print(f" Screenshot: {r['path']} ({r['size']} bytes)")
|
||
|
|
else:
|
||
|
|
print(f" Error: {r['error']}")
|
||
|
|
elif choice == '11':
|
||
|
|
if not self._ensure_device(): return
|
||
|
|
dur = input(" Duration [5]: ").strip()
|
||
|
|
r = m.syslog_dump(self.udid, duration=int(dur) if dur else 5)
|
||
|
|
if r['success']:
|
||
|
|
print(f" Syslog: {r['path']} ({r['lines']} lines)")
|
||
|
|
else:
|
||
|
|
print(f" Error: {r['error']}")
|
||
|
|
elif choice == '12':
|
||
|
|
if not self._ensure_device(): return
|
||
|
|
pattern = input(" Grep pattern [password|token|key]: ").strip() or 'password|token|key|secret'
|
||
|
|
dur = input(" Duration [5]: ").strip()
|
||
|
|
r = m.syslog_grep(self.udid, pattern, duration=int(dur) if dur else 5)
|
||
|
|
print(f" {r['count']} matches:")
|
||
|
|
for line in r.get('matches', [])[:20]:
|
||
|
|
print(f" {line[:120]}")
|
||
|
|
elif choice == '13':
|
||
|
|
if not self._ensure_device(): return
|
||
|
|
r = m.crash_reports(self.udid)
|
||
|
|
if r['success']:
|
||
|
|
print(f" {r['count']} crash reports in {r['output_dir']}")
|
||
|
|
else:
|
||
|
|
print(f" Error: {r['error']}")
|
||
|
|
# Apps
|
||
|
|
elif choice == '20':
|
||
|
|
if not self._ensure_device(): return
|
||
|
|
t = input(" Type [user/system/all]: ").strip() or 'user'
|
||
|
|
r = m.list_apps(self.udid, app_type=t)
|
||
|
|
if r['success']:
|
||
|
|
print(f" {r['count']} apps:")
|
||
|
|
for a in r['apps']:
|
||
|
|
print(f" {a.get('bundle_id',''):<40} {a.get('name','')}")
|
||
|
|
else:
|
||
|
|
print(f" Error: {r['error']}")
|
||
|
|
elif choice == '21':
|
||
|
|
if not self._ensure_device(): return
|
||
|
|
path = input(" IPA path: ").strip()
|
||
|
|
if path:
|
||
|
|
r = m.install_app(self.udid, path)
|
||
|
|
print(f" {r.get('output', 'Done')}")
|
||
|
|
elif choice == '22':
|
||
|
|
if not self._ensure_device(): return
|
||
|
|
bid = input(" Bundle ID to remove: ").strip()
|
||
|
|
if bid:
|
||
|
|
r = m.uninstall_app(self.udid, bid)
|
||
|
|
print(f" {r.get('output', 'Done')}")
|
||
|
|
# Backup
|
||
|
|
elif choice == '30':
|
||
|
|
if not self._ensure_device(): return
|
||
|
|
enc = input(" Encrypted backup? [y/N]: ").strip().lower() == 'y'
|
||
|
|
pwd = ''
|
||
|
|
if enc:
|
||
|
|
pwd = input(" Backup password: ").strip()
|
||
|
|
print(" Creating backup (this may take several minutes)...")
|
||
|
|
r = m.create_backup(self.udid, encrypted=enc, password=pwd)
|
||
|
|
if r['success']:
|
||
|
|
print(f" Backup saved: {r['backup_path']}")
|
||
|
|
else:
|
||
|
|
print(f" Error: {r.get('output', 'Failed')}")
|
||
|
|
elif choice == '31':
|
||
|
|
r = m.list_backups()
|
||
|
|
print(f" {r['count']} backups:")
|
||
|
|
for b in r['backups']:
|
||
|
|
name = b.get('device_name', b['udid'][:12])
|
||
|
|
print(f" {name} - iOS {b.get('ios_version','')} - {b.get('size_mb',0):.0f}MB - {b.get('date','')}")
|
||
|
|
elif choice == '32':
|
||
|
|
bp = self._pick_backup()
|
||
|
|
if bp:
|
||
|
|
r = m.extract_backup_sms(bp)
|
||
|
|
if r['success']:
|
||
|
|
print(f" {r['count']} messages:")
|
||
|
|
for msg in r['messages'][:20]:
|
||
|
|
d = 'ME' if msg['is_from_me'] else msg['handle']
|
||
|
|
print(f" [{msg['date']}] {d}: {msg['text'][:60]}")
|
||
|
|
else:
|
||
|
|
print(f" Error: {r['error']}")
|
||
|
|
elif choice == '33':
|
||
|
|
bp = self._pick_backup()
|
||
|
|
if bp:
|
||
|
|
r = m.extract_backup_contacts(bp)
|
||
|
|
if r['success']:
|
||
|
|
print(f" {r['count']} contacts:")
|
||
|
|
for c in r['contacts'][:30]:
|
||
|
|
print(f" {c['first']} {c['last']} {c.get('organization','')} - {', '.join(c['values'][:3])}")
|
||
|
|
else:
|
||
|
|
print(f" Error: {r['error']}")
|
||
|
|
elif choice == '34':
|
||
|
|
bp = self._pick_backup()
|
||
|
|
if bp:
|
||
|
|
r = m.extract_backup_call_log(bp)
|
||
|
|
if r['success']:
|
||
|
|
print(f" {r['count']} calls:")
|
||
|
|
for c in r['calls'][:20]:
|
||
|
|
print(f" [{c['date']}] {c['type']:<10} {c['address']} ({c['duration']}s)")
|
||
|
|
else:
|
||
|
|
print(f" Error: {r['error']}")
|
||
|
|
elif choice == '35':
|
||
|
|
bp = self._pick_backup()
|
||
|
|
if bp:
|
||
|
|
r = m.extract_backup_notes(bp)
|
||
|
|
if r['success']:
|
||
|
|
print(f" {r['count']} notes:")
|
||
|
|
for n in r['notes'][:15]:
|
||
|
|
print(f" [{n['date']}] {n['title']}")
|
||
|
|
if n['body']:
|
||
|
|
print(f" {n['body'][:80]}")
|
||
|
|
else:
|
||
|
|
print(f" Error: {r['error']}")
|
||
|
|
elif choice == '36':
|
||
|
|
bp = self._pick_backup()
|
||
|
|
if bp:
|
||
|
|
domain = input(" Domain filter (or Enter): ").strip()
|
||
|
|
path_f = input(" Path filter (or Enter): ").strip()
|
||
|
|
r = m.list_backup_files(bp, domain=domain, path_filter=path_f)
|
||
|
|
if r['success']:
|
||
|
|
print(f" {r['count']} files:")
|
||
|
|
for f in r['files'][:30]:
|
||
|
|
print(f" [{f['domain']}] {f['path']}")
|
||
|
|
else:
|
||
|
|
print(f" Error: {r['error']}")
|
||
|
|
elif choice == '37':
|
||
|
|
bp = self._pick_backup()
|
||
|
|
if bp:
|
||
|
|
fhash = input(" File hash: ").strip()
|
||
|
|
name = input(" Output filename (or Enter): ").strip() or None
|
||
|
|
if fhash:
|
||
|
|
r = m.extract_backup_file(bp, fhash, output_name=name)
|
||
|
|
if r['success']:
|
||
|
|
print(f" Extracted: {r['path']} ({r['size']} bytes)")
|
||
|
|
else:
|
||
|
|
print(f" Error: {r['error']}")
|
||
|
|
# Filesystem
|
||
|
|
elif choice == '40':
|
||
|
|
if not self._ensure_device(): return
|
||
|
|
r = m.mount_filesystem(self.udid)
|
||
|
|
if r['success']:
|
||
|
|
print(f" Mounted at: {r['mountpoint']}")
|
||
|
|
else:
|
||
|
|
print(f" Error: {r.get('error', r.get('output'))}")
|
||
|
|
elif choice == '41':
|
||
|
|
if not self._ensure_device(): return
|
||
|
|
bid = input(" Bundle ID: ").strip()
|
||
|
|
if bid:
|
||
|
|
r = m.mount_app_documents(self.udid, bid)
|
||
|
|
if r['success']:
|
||
|
|
print(f" Mounted at: {r['mountpoint']}")
|
||
|
|
else:
|
||
|
|
print(f" Error: {r.get('error', r.get('output'))}")
|
||
|
|
elif choice == '42':
|
||
|
|
mp = input(" Mountpoint to unmount: ").strip()
|
||
|
|
if mp:
|
||
|
|
m.unmount_filesystem(mp)
|
||
|
|
print(" Unmounted.")
|
||
|
|
elif choice == '43':
|
||
|
|
if not self._ensure_device(): return
|
||
|
|
r = m.list_profiles(self.udid)
|
||
|
|
if r['success']:
|
||
|
|
print(f" {r['count']} profiles:")
|
||
|
|
for p in r['profiles']:
|
||
|
|
print(f" {p.get('id','')} - {p.get('name','')}")
|
||
|
|
else:
|
||
|
|
print(f" Error: {r['error']}")
|
||
|
|
elif choice == '44':
|
||
|
|
if not self._ensure_device(): return
|
||
|
|
path = input(" Profile path (.mobileprovision/.mobileconfig): ").strip()
|
||
|
|
if path:
|
||
|
|
r = m.install_profile(self.udid, path)
|
||
|
|
print(f" {r.get('output', 'Done')}")
|
||
|
|
elif choice == '45':
|
||
|
|
if not self._ensure_device(): return
|
||
|
|
pid = input(" Profile ID to remove: ").strip()
|
||
|
|
if pid:
|
||
|
|
r = m.remove_profile(self.udid, pid)
|
||
|
|
print(f" {r.get('output', 'Done')}")
|
||
|
|
# Network
|
||
|
|
elif choice == '50':
|
||
|
|
if not self._ensure_device(): return
|
||
|
|
lp = input(" Local port: ").strip()
|
||
|
|
dp = input(" Device port: ").strip()
|
||
|
|
if lp and dp:
|
||
|
|
r = m.port_forward(self.udid, int(lp), int(dp))
|
||
|
|
if r['success']:
|
||
|
|
print(f" Forwarding localhost:{lp} -> device:{dp} (PID: {r['pid']})")
|
||
|
|
else:
|
||
|
|
print(f" Error: {r['error']}")
|
||
|
|
elif choice == '51':
|
||
|
|
if not self._ensure_device(): return
|
||
|
|
r = m.export_recon_report(self.udid)
|
||
|
|
if r['success']:
|
||
|
|
print(f" Report: {r['report_path']}")
|
||
|
|
else:
|
||
|
|
print(" Invalid choice.")
|
||
|
|
|
||
|
|
|
||
|
|
def run():
|
||
|
|
m = IPhoneLocal()
|
||
|
|
m.run_interactive()
|