IOSAPP脱壳工具Frida-ios-dump脚本修改

admin 2025-12-22 04:44:18 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文介绍了修改frida-ios-dump脚本以支持Windows环境下通过网络连接而非USB连接进行IOSAPP脱壳的方法。作者提供了完整的dump.py脚本代码,该脚本支持远程Frida连接、列出应用、直接通过PID附加等功能,并修复了Windows环境下的一些兼容性问题。文章适用于那些使用Windows系统与IOS设备进行逆向分析的开发者,特别是当USB连接不可用时,可以通过网络连接完成APP脱壳工作。 综合评分: 91 文章分类: 移动安全,逆向分析,安全工具,实战经验


cover_image

IOS APP 脱壳工具Frida-ios-dump脚本修改

原创

十月的进阶之路

十月的进阶之路

2025年12月13日 11:59 甘肃

0x01、背景

紧接着上一篇《Frida 在Windows+IOS环境下的崩溃》后,我需要使用frida-ios-dump脚本针对IOS APP进行脱壳,但是标准的frida-ios-dump需要使用USB连接的方式来获取设备信息。但还记得么,我这个iphone 6+ios 12.5.7+Windows的搭配缺少一些东西,无法通过USB连接的方式通信。

因此基于官方的firda-ios-dump脚本(官方:https://github.com/AloneMonkey/frida-ios-dump.git)进行一些修正,使得整个脚本可以通过纯粹的网络连接完成脱壳。

0x02、dump.py脚本内容

仅仅修改了下dump.py脚本,其他文件保持一致,内容如下:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
Modified frida-ios-dump:
- Support remote Frida connection (-R host:port)
- Support listing apps (-l)
- Support direct attach by PID (--pid)
- Keep SSH/SCP pulling behavior to fetch dumped files and package into .ipa
Fixes for Windows:
- Use os.chmod instead of external 'chmod'
- Handle missing 'app' key when packaging IPA
- Robust rmtree with onerror handler to fix permissions before deleting
"""

from __future__ import print_function, unicode_literals
import sys
import codecs
import frida
import threading
import os
import shutil
import time
import argparse
import tempfile
import subprocess
import re
import paramiko
from paramiko import SSHClient
from scp import SCPClient
from tqdm import tqdm
import traceback
import stat

IS_PY2 = sys.version_info[0] < 3
if&nbsp;IS_PY2:
&nbsp; &nbsp; reload(sys)
&nbsp; &nbsp; sys.setdefaultencoding('utf8')

script_dir = os.path.dirname(os.path.realpath(__file__))
DUMP_JS = os.path.join(script_dir,&nbsp;'dump.js')

# default SSH (iPhone) params - can be overridden by CLI
User =&nbsp;'root'
Password =&nbsp;'alpine'
Host =&nbsp;'localhost'
Port = 22
KeyFileName = None

TEMP_DIR = tempfile.gettempdir()
PAYLOAD_DIR =&nbsp;'Payload'
PAYLOAD_PATH = os.path.join(TEMP_DIR, PAYLOAD_DIR)
file_dict = {}

finished = threading.Event()
ssh = None &nbsp;# will be set in main before dump

def get_usb_iphone():
&nbsp; &nbsp;&nbsp;"""原始 USB 获取设备(保留以兼容)"""
&nbsp; &nbsp; Type =&nbsp;'usb'
&nbsp; &nbsp; try:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;int(frida.__version__.split('.')[0]) < 12:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Type =&nbsp;'tether'
&nbsp; &nbsp; except Exception:
&nbsp; &nbsp; &nbsp; &nbsp; Type =&nbsp;'usb'
&nbsp; &nbsp; device_manager = frida.get_device_manager()
&nbsp; &nbsp; changed = threading.Event()

&nbsp; &nbsp; def on_changed():
&nbsp; &nbsp; &nbsp; &nbsp; changed.set()

&nbsp; &nbsp; device_manager.on('changed', on_changed)

&nbsp; &nbsp; device = None
&nbsp; &nbsp;&nbsp;while&nbsp;device is None:
&nbsp; &nbsp; &nbsp; &nbsp; devices = [dev&nbsp;for&nbsp;dev&nbsp;in&nbsp;device_manager.enumerate_devices()&nbsp;if&nbsp;dev.type == Type]
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;len(devices) == 0:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print('Waiting for USB device...')
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; changed.wait()
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;else:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; device = devices[0]

&nbsp; &nbsp; device_manager.off('changed', on_changed)

&nbsp; &nbsp;&nbsp;return&nbsp;device

def safe_chmod(path, mode):
&nbsp; &nbsp;&nbsp;"""Cross-platform safe chmod (ignore failures on Windows)."""
&nbsp; &nbsp; try:
&nbsp; &nbsp; &nbsp; &nbsp; os.chmod(path, mode)
&nbsp; &nbsp; except Exception:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# On Windows this may fail for many reasons; ignore.
&nbsp; &nbsp; &nbsp; &nbsp; pass

def on_message(message, data):
&nbsp; &nbsp;&nbsp;"""Handle messages from dump.js; pull files over SCP using global ssh."""
&nbsp; &nbsp; t = tqdm(unit='B', unit_scale=True, unit_divisor=1024, miniters=1)
&nbsp; &nbsp; last_sent = [0]

&nbsp; &nbsp; def progress(filename, size, sent):
&nbsp; &nbsp; &nbsp; &nbsp; baseName = os.path.basename(filename)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;IS_PY2 or isinstance(baseName, bytes):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; t.desc = baseName.decode("utf-8")
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;else:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; t.desc = baseName
&nbsp; &nbsp; &nbsp; &nbsp; t.total = size
&nbsp; &nbsp; &nbsp; &nbsp; t.update(sent - last_sent[0])
&nbsp; &nbsp; &nbsp; &nbsp; last_sent[0] = 0&nbsp;if&nbsp;size == sent&nbsp;else&nbsp;sent

&nbsp; &nbsp; try:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if'payload'in&nbsp;message:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; payload = message['payload']
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# payload structures:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# { dump: "/var/.../something.fid", path: "/.../AppName.app/..." }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# { app: "/var/.../AppName.app" }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# { done: "ok" }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if'dump'in&nbsp;payload:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; origin_path = payload.get('path')
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; dump_path = payload.get('dump')
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; scp_from = dump_path
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; scp_to = PAYLOAD_PATH +&nbsp;'/'

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# ensure ssh exists
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;ssh is None:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print("SSH client not ready - cannot SCP")
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;else:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; try:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; with SCPClient(ssh.get_transport(), progress=progress, socket_timeout=60) as scp:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; scp.get(scp_from, scp_to)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; except Exception as e:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print("SCP get failed:", e)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; traceback.print_exc()

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# set permission using os.chmod (cross-platform)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; chmod_dir = os.path.join(PAYLOAD_PATH, os.path.basename(dump_path))
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; try:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# try set file mode to rw-r-x-r-x (0o655) similar to original intent
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; safe_chmod(chmod_dir, 0o655)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; except Exception:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; pass

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# map dumped filename -> relative path inside app (after ".app/")
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;origin_path:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; index = origin_path.find('.app/')
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;index != -1:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; file_dict[os.path.basename(dump_path)] = origin_path[index + 5:]
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;else:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; file_dict[os.path.basename(dump_path)] = origin_path
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;else:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; file_dict[os.path.basename(dump_path)] = os.path.basename(dump_path)

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if'app'in&nbsp;payload:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; app_path = payload.get('app')
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; scp_from = app_path
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; scp_to = PAYLOAD_PATH +&nbsp;'/'

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;ssh is None:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print("SSH client not ready - cannot SCP app dir")
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;else:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; try:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; with SCPClient(ssh.get_transport(), progress=progress, socket_timeout=60) as scp:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; scp.get(scp_from, scp_to, recursive=True)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; except Exception as e:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print("SCP get (app) failed:", e)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; traceback.print_exc()

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; chmod_dir = os.path.join(PAYLOAD_PATH, os.path.basename(app_path))
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# try to set directory and children perms
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; try:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;root,&nbsp;dirs, files&nbsp;in&nbsp;os.walk(chmod_dir):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; safe_chmod(root, 0o755)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;f&nbsp;in&nbsp;files:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; safe_chmod(os.path.join(root, f), 0o655)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; except Exception:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; pass

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; file_dict['app'] = os.path.basename(app_path)

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if'done'in&nbsp;payload:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; finished.set()
&nbsp; &nbsp; except Exception as e:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print("on_message exception:", e)
&nbsp; &nbsp; &nbsp; &nbsp; traceback.print_exc()
&nbsp; &nbsp; finally:
&nbsp; &nbsp; &nbsp; &nbsp; t.close()

def compare_applications(a, b):
&nbsp; &nbsp; a_is_running = a.pid != 0
&nbsp; &nbsp; b_is_running = b.pid != 0
&nbsp; &nbsp;&nbsp;if&nbsp;a_is_running == b_is_running:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;a.name > b.name:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;1
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;elif&nbsp;a.name < b.name:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;-1
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;else:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;0
&nbsp; &nbsp;&nbsp;elif&nbsp;a_is_running:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;-1
&nbsp; &nbsp;&nbsp;else:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;1

def cmp_to_key(mycmp):
&nbsp; &nbsp;&nbsp;"""Convert a cmp= function into a key= function"""
&nbsp; &nbsp; class K:
&nbsp; &nbsp; &nbsp; &nbsp; def __init__(self, obj):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; self.obj = obj

&nbsp; &nbsp; &nbsp; &nbsp; def __lt__(self, other):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;mycmp(self.obj, other.obj) < 0

&nbsp; &nbsp; &nbsp; &nbsp; def __gt__(self, other):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;mycmp(self.obj, other.obj) > 0

&nbsp; &nbsp; &nbsp; &nbsp; def __eq__(self, other):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;mycmp(self.obj, other.obj) == 0

&nbsp; &nbsp; &nbsp; &nbsp; def __le__(self, other):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;mycmp(self.obj, other.obj) <= 0

&nbsp; &nbsp; &nbsp; &nbsp; def __ge__(self, other):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;mycmp(self.obj, other.obj) >= 0

&nbsp; &nbsp; &nbsp; &nbsp; def __ne__(self, other):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;mycmp(self.obj, other.obj) != 0

&nbsp; &nbsp;&nbsp;return&nbsp;K

def get_applications(device):
&nbsp; &nbsp; try:
&nbsp; &nbsp; &nbsp; &nbsp; apps = device.enumerate_applications()
&nbsp; &nbsp; except Exception as e:
&nbsp; &nbsp; &nbsp; &nbsp; raise RuntimeError("Failed to enumerate applications: %s"&nbsp;% e)
&nbsp; &nbsp;&nbsp;return&nbsp;apps

def list_applications(device):
&nbsp; &nbsp;&nbsp;"""列出应用;如果为空则退回列进程,帮助诊断远程设备状态"""
&nbsp; &nbsp; attempts = 5
&nbsp; &nbsp; apps = []
&nbsp; &nbsp;&nbsp;for&nbsp;i&nbsp;in&nbsp;range(attempts):
&nbsp; &nbsp; &nbsp; &nbsp; try:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; apps = device.enumerate_applications()
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;apps:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;break
&nbsp; &nbsp; &nbsp; &nbsp; except Exception as e:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;i == attempts - 1:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print("Failed to enumerate applications after retries: %s"&nbsp;% e)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; apps = []
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;break
&nbsp; &nbsp; &nbsp; &nbsp; time.sleep(0.5)

&nbsp; &nbsp;&nbsp;if&nbsp;not apps:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print("No installed-app list returned via enumerate_applications(). Trying enumerate_processes() to help diagnose:")
&nbsp; &nbsp; &nbsp; &nbsp; try:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; procs = device.enumerate_processes()
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; pid_column_width = max(map(lambda p: len(str(p.pid)), procs))&nbsp;if&nbsp;procs&nbsp;else&nbsp;0
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; name_column_width = max(map(lambda p: len(p.name), procs))&nbsp;if&nbsp;procs&nbsp;else&nbsp;0
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; header_format =&nbsp;'%'&nbsp;+ str(pid_column_width) +&nbsp;'s &nbsp;'&nbsp;+&nbsp;'%-'&nbsp;+ str(name_column_width) +&nbsp;'s'
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print(header_format % ('PID',&nbsp;'Process Name'))
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print('%s &nbsp;%s'&nbsp;% (pid_column_width *&nbsp;'-', name_column_width *&nbsp;'-'))
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;p&nbsp;in&nbsp;sorted(procs, key=lambda x: x.name):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print(header_format % (p.pid, p.name))
&nbsp; &nbsp; &nbsp; &nbsp; except Exception as e:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print("Also failed to enumerate processes: %s"&nbsp;% e)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return

&nbsp; &nbsp; pid_column_width = max(map(lambda app: len('{}'.format(app.pid)), apps))
&nbsp; &nbsp; name_column_width = max(map(lambda app: len(app.name), apps))
&nbsp; &nbsp; identifier_column_width = max(map(lambda app: len(app.identifier), apps))

&nbsp; &nbsp; header_format =&nbsp;'%'&nbsp;+ str(pid_column_width) +&nbsp;'s &nbsp;'&nbsp;+&nbsp;'%-'&nbsp;+ str(name_column_width) +&nbsp;'s &nbsp;'&nbsp;+&nbsp;'%-'&nbsp;+ str(identifier_column_width) +&nbsp;'s'
&nbsp; &nbsp;&nbsp;print(header_format % ('PID',&nbsp;'Name',&nbsp;'Identifier'))
&nbsp; &nbsp;&nbsp;print('%s &nbsp;%s &nbsp;%s'&nbsp;% (pid_column_width *&nbsp;'-', name_column_width *&nbsp;'-', identifier_column_width *&nbsp;'-'))
&nbsp; &nbsp; line_format = header_format
&nbsp; &nbsp;&nbsp;for&nbsp;application&nbsp;in&nbsp;sorted(apps, key=cmp_to_key(compare_applications)):
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;application.pid == 0:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print(line_format % ('-', application.name, application.identifier))
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;else:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print(line_format % (application.pid, application.name, application.identifier))

def load_js_file(session, filename):
&nbsp; &nbsp;&nbsp;source&nbsp;=&nbsp;''
&nbsp; &nbsp; with codecs.open(filename,&nbsp;'r',&nbsp;'utf-8') as f:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;source&nbsp;=&nbsp;source&nbsp;+ f.read()
&nbsp; &nbsp; script = session.create_script(source)
&nbsp; &nbsp; script.on('message', on_message)
&nbsp; &nbsp; script.load()
&nbsp; &nbsp;&nbsp;return&nbsp;script

def create_dir(path):
&nbsp; &nbsp; path = path.strip()
&nbsp; &nbsp; path = path.rstrip('\\')
&nbsp; &nbsp;&nbsp;if&nbsp;os.path.exists(path):
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# remove previous payload path safely
&nbsp; &nbsp; &nbsp; &nbsp; def on_rm_error(func, path2, exc_info):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; try:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; os.chmod(path2, stat.S_IWRITE)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; func(path2)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; except Exception:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; pass
&nbsp; &nbsp; &nbsp; &nbsp; shutil.rmtree(path, onerror=on_rm_error)
&nbsp; &nbsp; try:
&nbsp; &nbsp; &nbsp; &nbsp; os.makedirs(path)
&nbsp; &nbsp; except os.error as err:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print(err)

def open_target_app(device, name_or_bundleid):
&nbsp; &nbsp;&nbsp;"""
&nbsp; &nbsp; Flexible matching:
&nbsp; &nbsp; - exact match on identifier or name
&nbsp; &nbsp; - substring match (case-insensitive)
&nbsp; &nbsp; """
&nbsp; &nbsp;&nbsp;print('Start the target app {}'.format(name_or_bundleid))

&nbsp; &nbsp; pid =&nbsp;''
&nbsp; &nbsp; session = None
&nbsp; &nbsp; display_name =&nbsp;''
&nbsp; &nbsp; bundle_identifier =&nbsp;''

&nbsp; &nbsp; try:
&nbsp; &nbsp; &nbsp; &nbsp; apps = get_applications(device)
&nbsp; &nbsp; except Exception as e:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print("Failed to get applications for matching: %s"&nbsp;% e)
&nbsp; &nbsp; &nbsp; &nbsp; apps = []

&nbsp; &nbsp; needle = name_or_bundleid or&nbsp;''
&nbsp; &nbsp; needle_lower = needle.lower()

&nbsp; &nbsp;&nbsp;for&nbsp;application&nbsp;in&nbsp;apps:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;needle == application.identifier or needle == application.name:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; pid = application.pid
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; display_name = application.name
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; bundle_identifier = application.identifier
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;break
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;needle_lower and (needle_lower&nbsp;in&nbsp;application.identifier.lower() or needle_lower&nbsp;in&nbsp;application.name.lower()):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; pid = application.pid
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; display_name = application.name
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; bundle_identifier = application.identifier
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;break

&nbsp; &nbsp; try:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;not pid:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;bundle_identifier:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; pid = device.spawn([bundle_identifier])
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; session = device.attach(pid)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; device.resume(pid)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;else:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; pid = device.spawn([name_or_bundleid])
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; session = device.attach(pid)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; device.resume(pid)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;else:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; session = device.attach(pid)
&nbsp; &nbsp; except Exception as e:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print("Error while spawning/attaching: %s"&nbsp;% e)

&nbsp; &nbsp;&nbsp;return&nbsp;session, display_name, bundle_identifier

def start_dump(session, ipa_name):
&nbsp; &nbsp;&nbsp;print('Dumping {} to {}'.format(ipa_name, TEMP_DIR))

&nbsp; &nbsp; script = load_js_file(session, DUMP_JS)
&nbsp; &nbsp; script.post('dump')
&nbsp; &nbsp;&nbsp;# wait until dump.js signals done
&nbsp; &nbsp; finished.wait()
&nbsp; &nbsp; generate_ipa(PAYLOAD_PATH, ipa_name)

&nbsp; &nbsp;&nbsp;if&nbsp;session:
&nbsp; &nbsp; &nbsp; &nbsp; try:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; session.detach()
&nbsp; &nbsp; &nbsp; &nbsp; except Exception:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; pass

def generate_ipa(path, display_name):
&nbsp; &nbsp;&nbsp;"""
&nbsp; &nbsp; Create Payload/<AppName>/... based on file_dict mappings, then zip into <display_name>.ipa
&nbsp; &nbsp; Robust for cases where 'app' key missing from file_dict.
&nbsp; &nbsp; """
&nbsp; &nbsp; ipa_filename = display_name +&nbsp;'.ipa'
&nbsp; &nbsp;&nbsp;print('Generating "{}"'.format(ipa_filename))
&nbsp; &nbsp; try:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# determine app_name (this is the folder name under Payload)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if'app'in&nbsp;file_dict:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; app_name = file_dict['app'] &nbsp;# this is basename like 'iEC-O2O-Buyer.app'
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;else:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# try find any .app dir in PAYLOAD_PATH (scp may have copied whole .app)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; candidates = [d&nbsp;for&nbsp;d&nbsp;in&nbsp;os.listdir(path)&nbsp;if&nbsp;d.endswith('.app')]
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;candidates:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; app_name = candidates[0]
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;else:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# fallback: use display_name + ".app"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; app_name = display_name +&nbsp;'.app'

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# create the payload app dir
&nbsp; &nbsp; &nbsp; &nbsp; payload_app_dir = os.path.join(path, app_name)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;not os.path.exists(payload_app_dir):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; os.makedirs(payload_app_dir, exist_ok=True)

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# move each downloaded file into correct relative place under Payload/<app_name>/
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;key, rel&nbsp;in&nbsp;list(file_dict.items()):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;key ==&nbsp;'app':
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;continue
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; src = os.path.join(path, key)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# if rel is an absolute origin path (no .app/ found earlier), put under root of app
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;rel is None:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; rel = os.path.basename(src)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; target_rel_path = rel
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# make sure directories exist
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; dest = os.path.join(payload_app_dir, target_rel_path)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; dest_dir = os.path.dirname(dest)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;not os.path.exists(dest_dir):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; os.makedirs(dest_dir, exist_ok=True)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# if src exists, move it; otherwise ignore
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;os.path.exists(src):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; try:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; shutil.move(src, dest)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; except Exception as e:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# if move fails, try copy then remove
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; try:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; shutil.copy2(src, dest)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; os.remove(src)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; except Exception as e2:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print("Failed to move or copy {} -> {}: {}".format(src, dest, e2))
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;else:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# sometimes scp may have created a directory instead -- try handle dir
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;os.path.isdir(os.path.join(path, key)):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; try:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; shutil.move(os.path.join(path, key), dest)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; except Exception as e:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print("Failed to move dir {} -> {}: {}".format(os.path.join(path, key), dest, e))

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# now we need a Payload directory at TEMP_DIR/Payload (path already points to it)
&nbsp; &nbsp; &nbsp; &nbsp; target_dir = os.path.join(os.path.dirname(path), PAYLOAD_DIR)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# Ensure the payload dir exists and contains the app folder (payload_app_dir already in path)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# Create zip at current working directory
&nbsp; &nbsp; &nbsp; &nbsp; zip_out = os.path.join(os.getcwd(), ipa_filename)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# Use zip command if available, otherwise use Python zipfile module
&nbsp; &nbsp; &nbsp; &nbsp; try:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# prefer system zip (if installed)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; subprocess.check_call(['zip',&nbsp;'-qr', zip_out, PAYLOAD_DIR], cwd=os.path.dirname(path))
&nbsp; &nbsp; &nbsp; &nbsp; except Exception:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# fallback to zipfile
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; import zipfile
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; def zipdir(folder, ziph):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;root,&nbsp;dirs, files&nbsp;in&nbsp;os.walk(folder):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;f&nbsp;in&nbsp;files:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fullpath = os.path.join(root, f)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; arcname = os.path.relpath(fullpath, os.path.dirname(path))
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ziph.write(fullpath, arcname)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; with zipfile.ZipFile(zip_out,&nbsp;'w', zipfile.ZIP_DEFLATED) as zipf:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; zipdir(os.path.join(os.path.dirname(path), PAYLOAD_DIR), zipf)

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# cleanup local payload path
&nbsp; &nbsp; &nbsp; &nbsp; def on_rm_error(func, p, exc_info):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; try:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; os.chmod(p, stat.S_IWRITE)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; func(p)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; except Exception:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; pass

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;os.path.exists(PAYLOAD_PATH):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; shutil.rmtree(PAYLOAD_PATH, onerror=on_rm_error)

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print("Generated:", ipa_filename)
&nbsp; &nbsp; except Exception as e:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print("generate_ipa error:", e)
&nbsp; &nbsp; &nbsp; &nbsp; traceback.print_exc()
&nbsp; &nbsp; &nbsp; &nbsp; finished.set()

def main():
&nbsp; &nbsp; global Host, Port, User, Password, KeyFileName, ssh

&nbsp; &nbsp; parser = argparse.ArgumentParser(description='frida-ios-dump (modified for robust remote Frida connection)')
&nbsp; &nbsp; parser.add_argument('-l',&nbsp;'--list', dest='list_applications', action='store_true',&nbsp;help='List the installed apps')
&nbsp; &nbsp; parser.add_argument('-o',&nbsp;'--output', dest='output_ipa',&nbsp;help='Specify name of the decrypted IPA')
&nbsp; &nbsp; parser.add_argument('-H',&nbsp;'--host', dest='ssh_host',&nbsp;help='Specify SSH hostname (for iPhone SSH)')
&nbsp; &nbsp; parser.add_argument('-p',&nbsp;'--port', dest='ssh_port',&nbsp;help='Specify SSH port',&nbsp;type=int)
&nbsp; &nbsp; parser.add_argument('-u',&nbsp;'--user', dest='ssh_user',&nbsp;help='Specify SSH username')
&nbsp; &nbsp; parser.add_argument('-P',&nbsp;'--password', dest='ssh_password',&nbsp;help='Specify SSH password')
&nbsp; &nbsp; parser.add_argument('-K',&nbsp;'--key_filename', dest='ssh_key_filename',&nbsp;help='Specify SSH private key file path')
&nbsp; &nbsp; parser.add_argument('-R',&nbsp;'--remote', dest='frida_remote',&nbsp;help='Specify Frida server address (host:port)')
&nbsp; &nbsp; parser.add_argument('--pid',&nbsp;type=int,&nbsp;help='PID of target process (attach directly)')
&nbsp; &nbsp; parser.add_argument('target', nargs='?',&nbsp;help='Bundle identifier or display name of the target app')

&nbsp; &nbsp; args = parser.parse_args()

&nbsp; &nbsp;&nbsp;# Validate minimal args
&nbsp; &nbsp;&nbsp;if&nbsp;not len(sys.argv[1:]):
&nbsp; &nbsp; &nbsp; &nbsp; parser.print_help()
&nbsp; &nbsp; &nbsp; &nbsp; sys.exit(0)

&nbsp; &nbsp;&nbsp;# Connect to Frida remote (if provided) or USB
&nbsp; &nbsp; device = None
&nbsp; &nbsp;&nbsp;if&nbsp;args.frida_remote:
&nbsp; &nbsp; &nbsp; &nbsp; parts = args.frida_remote.split(':')
&nbsp; &nbsp; &nbsp; &nbsp; frida_host = parts[0]
&nbsp; &nbsp; &nbsp; &nbsp; frida_port = int(parts[1])&nbsp;if&nbsp;len(parts) > 1 and parts[1]&nbsp;else&nbsp;27042
&nbsp; &nbsp; &nbsp; &nbsp; try:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; device = frida.get_remote_device(frida_host, frida_port)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print("Connected to remote Frida device at %s:%s (via frida.get_remote_device)"&nbsp;% (frida_host, frida_port))
&nbsp; &nbsp; &nbsp; &nbsp; except Exception as e1:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; try:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; manager = frida.get_device_manager()
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; device = manager.add_remote_device("%s:%s"&nbsp;% (frida_host, frida_port))
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print("Connected to remote Frida device at %s:%s (via DeviceManager.add_remote_device)"&nbsp;% (frida_host, frida_port))
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; except Exception as e2:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print("Failed to connect to remote Frida server (tried get_remote_device and add_remote_device).")
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print("get_remote_device error: %s"&nbsp;% e1)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print("add_remote_device error: %s"&nbsp;% e2)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sys.exit(1)
&nbsp; &nbsp;&nbsp;else:
&nbsp; &nbsp; &nbsp; &nbsp; device = get_usb_iphone()

&nbsp; &nbsp;&nbsp;# If user requested listing, do it now and exit
&nbsp; &nbsp;&nbsp;if&nbsp;args.list_applications:
&nbsp; &nbsp; &nbsp; &nbsp; try:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; list_applications(device)
&nbsp; &nbsp; &nbsp; &nbsp; except Exception as e:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print("Error listing applications: %s"&nbsp;% e)
&nbsp; &nbsp; &nbsp; &nbsp; sys.exit(0)

&nbsp; &nbsp;&nbsp;# Update SSH params from args
&nbsp; &nbsp;&nbsp;if&nbsp;args.ssh_host:
&nbsp; &nbsp; &nbsp; &nbsp; Host = args.ssh_host
&nbsp; &nbsp;&nbsp;if&nbsp;args.ssh_port:
&nbsp; &nbsp; &nbsp; &nbsp; Port = args.ssh_port
&nbsp; &nbsp;&nbsp;if&nbsp;args.ssh_user:
&nbsp; &nbsp; &nbsp; &nbsp; User = args.ssh_user
&nbsp; &nbsp;&nbsp;if&nbsp;args.ssh_password:
&nbsp; &nbsp; &nbsp; &nbsp; Password = args.ssh_password
&nbsp; &nbsp;&nbsp;if&nbsp;args.ssh_key_filename:
&nbsp; &nbsp; &nbsp; &nbsp; KeyFileName = args.ssh_key_filename

&nbsp; &nbsp; name_or_bundleid = args.target
&nbsp; &nbsp; output_ipa = args.output_ipa

&nbsp; &nbsp;&nbsp;# Establish SSH connection before starting dump (on_message will use ssh)
&nbsp; &nbsp; try:
&nbsp; &nbsp; &nbsp; &nbsp; ssh = SSHClient()
&nbsp; &nbsp; &nbsp; &nbsp; ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
&nbsp; &nbsp; &nbsp; &nbsp; ssh.connect(Host, port=Port, username=User, password=Password, key_filename=KeyFileName, timeout=20)
&nbsp; &nbsp; except Exception as e:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print("SSH connection failed:", e)
&nbsp; &nbsp; &nbsp; &nbsp; sys.exit(1)

&nbsp; &nbsp;&nbsp;# Prepare payload dir
&nbsp; &nbsp; create_dir(PAYLOAD_PATH)

&nbsp; &nbsp; session = None
&nbsp; &nbsp; display_name = None
&nbsp; &nbsp; bundle_identifier = None

&nbsp; &nbsp;&nbsp;# If PID provided, directly attach to that PID
&nbsp; &nbsp;&nbsp;if&nbsp;args.pid:
&nbsp; &nbsp; &nbsp; &nbsp; pid = args.pid
&nbsp; &nbsp; &nbsp; &nbsp; try:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print("Attaching to PID %d ..."&nbsp;% pid)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; session = device.attach(pid)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; display_name =&nbsp;"pid_%d"&nbsp;% pid
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; bundle_identifier = None
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print("Attached to PID %d"&nbsp;% pid)
&nbsp; &nbsp; &nbsp; &nbsp; except Exception as e:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print("Failed to attach to PID %d: %s"&nbsp;% (pid, e))
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ssh.close()
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sys.exit(1)
&nbsp; &nbsp;&nbsp;else:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# fallback to name/bundle-based attach (spawn/attach)
&nbsp; &nbsp; &nbsp; &nbsp; (session, display_name, bundle_identifier) = open_target_app(device, name_or_bundleid)

&nbsp; &nbsp;&nbsp;if&nbsp;output_ipa is None:
&nbsp; &nbsp; &nbsp; &nbsp; output_ipa = display_name or (name_or_bundleid&nbsp;if&nbsp;name_or_bundleid&nbsp;else"dumped_app")
&nbsp; &nbsp; output_ipa = re.sub(r'\.ipa$',&nbsp;'', output_ipa)

&nbsp; &nbsp;&nbsp;if&nbsp;session:
&nbsp; &nbsp; &nbsp; &nbsp; try:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; start_dump(session, output_ipa)
&nbsp; &nbsp; &nbsp; &nbsp; except Exception as e:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print("Dump failed:", e)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; traceback.print_exc()
&nbsp; &nbsp;&nbsp;else:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;print("No session created. Aborting.")
&nbsp; &nbsp; &nbsp; &nbsp; ssh.close()
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;os.path.exists(PAYLOAD_PATH):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# cleanup
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; def on_rm_error(func, p, exc):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; try:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; os.chmod(p, stat.S_IWRITE)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; func(p)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; except Exception:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; pass
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; shutil.rmtree(PAYLOAD_PATH, onerror=on_rm_error)
&nbsp; &nbsp; &nbsp; &nbsp; sys.exit(1)

&nbsp; &nbsp;&nbsp;# Cleanup SSH and payload
&nbsp; &nbsp;&nbsp;if&nbsp;ssh:
&nbsp; &nbsp; &nbsp; &nbsp; ssh.close()
&nbsp; &nbsp;&nbsp;if&nbsp;os.path.exists(PAYLOAD_PATH):
&nbsp; &nbsp; &nbsp; &nbsp; def on_rm_error(func, p, exc):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; try:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; os.chmod(p, stat.S_IWRITE)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; func(p)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; except Exception:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; pass
&nbsp; &nbsp; &nbsp; &nbsp; shutil.rmtree(PAYLOAD_PATH, onerror=on_rm_error)

if&nbsp;__name__ ==&nbsp;'__main__':
&nbsp; &nbsp; main()

依旧搭配上一篇的frida 14.2.10使用,通过目标APP名称以及pid运行如下命令即可在当前windows目录生成脱壳后的Mach-O文件。

python dump.py -H 192.168.148.224 -p 22 -R 192.168.148.224:1234 --pid 应用进程 -u root -P alpine -o 应用包名

其它命令保持原来的frida-ios-dump项目保持一致。

愿阅读到这里的你在这个寒冷的冬天有个惬意的周末,byby~~~


查看原文:《IOS APP 脱壳工具Frida-ios-dump脚本修改》

评论:0   参与:  2