已知:编译时虚拟机用的target是arm64-apple-ios-simulator,真机是arm64-apple-ios。那么他们有什么区别?
即答:没区别。

于是就有了这篇文章。

对于一个脱壳下来的ipa包,他就是一个纯粹的zip。所以第一步就是直接unzip。

结构如下:Payload/(Target.app)/…

然后可以通过vtool看到,这个包里的二进制都是给iOS使用的,

Bash
$ vtool -arch arm64 -show Payload/Twitter.app/TwitterPayload/Twitter.app/Twitter (architecture arm64):Load command 11      cmd LC_BUILD_VERSION  cmdsize 32 platform IOS    minos 15.0      sdk 18.1   ntools 1     tool LD  version 1115.7.3Load command 12      cmd LC_SOURCE_VERSION  cmdsize 16  version 0.0

这是一个给最小15.0的iOS使用的包。

同样我们也能发现vtool里有个命令:vtool -arch arm64 -set-build-version iossim 17.0 17.0 -replace -output xxx xxx

于是尝试一下:

Bash
$ vtool -arch arm64 -set-build-version iossim 17.0 17.0 -replace -output Payload/Twitter.app/Twitter Payload/Twitter.app/Twitter/Applications/Xcode-16.4.0.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/vtool warning: code signature will be invalid for Payload/Twitter.app/Twitter$ vtool -arch arm64 -show Payload/Twitter.app/TwitterPayload/Twitter.app/Twitter (architecture arm64):Load command 11      cmd LC_SOURCE_VERSION  cmdsize 16  version 0.0Load command 94      cmd LC_BUILD_VERSION  cmdsize 24 platform IOSSIMULATOR    minos 17.0      sdk 17.0   ntools 0

计划通。可以看到这里变成了最小17.0,使用17.0sdk的给模拟器使用的包。

理论上,我们只需要把所有的满足条件的Mach-O全部换掉即可?

于是成功运行了,吗?

如果你直接运行,会闪退的。原因是你有mobile provisioning文件和一个烂掉的签名。

于是下一步是删掉签名,重新签名。并且比较有意思的是,Simulator并不限制签名,所以ad-hoc即可使用(所有权限!包括本来需要entitlements才能用的)

Bash
$ codesign --remove file $ codesign -s - -- force --deep file

这一步需要对每一个二进制,以及外面的包(app文件夹)做。

做完了之后用xcrun simctl 安装上就好了。搓了一个python脚本。

Python
import sysimport osimport subprocessimport shutilimport tempfiledef system(cmd):    ret = os.system(cmd)    if ret != 0:        print(f"[-] Command failed: {cmd}")        sys.exit(1)def get_booted_simulator_info():    """Get UDID and version of currently booted iOS simulator"""    try:        result = subprocess.run(['xcrun', 'simctl', 'list'], capture_output=True, text=True, check=True)        lines = result.stdout.split('\n')                current_version = None                # Parse the output to find version sections and booted devices        for i, line in enumerate(lines):            # Track current iOS version section            if line.startswith('-- iOS '):                version_match = line.split('-- iOS ')[1].split(' --')[0]                current_version = version_match                continue                        # Look for booted device in current version section            if 'Booted' in line and current_version:                # Extract device name and UDID                line = line.strip()                device_name = line.split(' (')[0]                                # Extract UDID from line like "iPhone 15 Pro (12345678-1234-1234-1234-123456789012) (Booted)"                start = line.find('(') + 1                end = line.find(')', start)                if start > 0 and end > start:                    udid = line[start:end]                    return {                        'udid': udid,                        'device_name': device_name,                        'ios_version': current_version                    }                return None    except Exception as e:        print(f"[!] Error getting booted simulator: {e}")        return Nonedef resign(file_path):    """Resign a Mach-O file using codesign"""    try:        subprocess.run(['codesign', '--remove', file_path], stderr=subprocess.DEVNULL)        subprocess.run(['codesign', '-s', '-', '--force', '--deep', file_path], check=True)        os.chmod(file_path, 0o777)    except Exception as e:        print(f"[!] Error resigning {file_path}: {e}")def get_import_libs(file_path):    """Get list of imported libraries from a Mach-O file using otool"""    try:        result = subprocess.run(['otool', '-L', file_path], capture_output=True, text=True, check=True)        lines = result.stdout.split('\n')[1:]  # Skip the first line which is the file name        libs = []        for line in lines:            line = line.strip()            if line:                lib_name = line.split(' (')[0]                libs.append(lib_name)        return libs    except Exception as e:        print(f"[!] Error getting import libs for {file_path}: {e}")        return []def main():    if len(sys.argv) not in [2, 3]:        print("Usage: python install.py <filename> [udid]")        sys.exit(1)        # Create temporary working directory    temp_dir = tempfile.mkdtemp()    working_location = temp_dir    # working_location = "."    original_cwd = os.getcwd()        print(f"[i] working location: {working_location}")        # Change to temporary directory    os.chdir(working_location)        target_app = sys.argv[1]        # Get UDID: use provided one or auto-detect booted simulator    if len(sys.argv) == 3:        udid = sys.argv[2]        print(f"[i] using provided UDID: {udid}")    else:        print("[*] detecting booted iOS simulator...")        simulator_info = get_booted_simulator_info()        if not simulator_info:            print("[!] No booted iOS simulator found. Please boot a simulator first or provide UDID manually.")            sys.exit(1)                udid = simulator_info['udid']        print(f"[i] found booted simulator: {simulator_info['device_name']}")        print(f"[i] iOS version: {simulator_info['ios_version']}")        print(f"[i] UDID: {udid}")        # Convert to absolute path if it's relative    if not os.path.isabs(target_app):        target_app = os.path.join(original_cwd, target_app)        print(f"[i] target device UDID: {udid}")        # Handle IPA file extraction    if target_app.endswith('.ipa'):        print("[*] extracting application bundle from ipa file")        system(f"unzip -qq '{target_app}'")                # Find .app file in Payload directory        payload_files = os.listdir('Payload')        app_files = [f for f in payload_files if f.endswith('.app')]                if len(app_files) == 0:            print("ERROR: no .app file found in ipa")            sys.exit(1)        elif len(app_files) > 1:            print("ERROR: multiple .app files found in ipa file")            sys.exit(1)                # Copy app bundle and clean up        shutil.copytree(f'Payload/{app_files[0]}', app_files[0])        shutil.rmtree('Payload')        target_app = app_files[0]                print("[*] application bundle extracted")        print(f"[*] app bundle will be set to: {target_app}")        # Verify target app exists    if not os.path.isdir(target_app):        print("[E] please specify a valid application bundle location")        print("    usage: <application_bundle_location>/<ipa_file_location>")        sys.exit(1)        print("[*] preparing environment...")        # Remove quarantine attributes and clean up    system(f"xattr -r -d com.apple.quarantine '{target_app}' 2>/dev/null || true")        files_to_remove = [        f"{target_app}/embedded.mobileprovision",        f"{target_app}/_CodeSignature",        f"{target_app}/PlugIns",        f"{target_app}/SC_Info"    ]        for file_path in files_to_remove:        if os.path.exists(file_path):            print(f"[*] removing {file_path}...")            if os.path.isdir(file_path):                shutil.rmtree(file_path)            else:                os.remove(file_path)        print(f"[i] will make patch directly inside {target_app}")    print("[*] scanning files...")        # Process Mach-O files    processed = 0    for root, _, files in os.walk(target_app):        for file in files:            file_path = os.path.join(root, file)            if os.path.isfile(file_path):                # Check if file is Mach-O                try:                    file_output = subprocess.run(['file', file_path], capture_output=True, text=True)                    if 'Mach-O' in file_output.stdout:                        print(f"[*] processing {file_path}...")                                                # Use vtool to modify build version                        # system(f"vtool -arch arm64e -set-build-version iossim 17.0 17.0 -replace -output '{file_path}' '{file_path}'")                        subprocess.run(['vtool', '-arch', 'arm64', '-set-build-version', 'iossim', '17.0', '17.0', '-replace', '-output', file_path, file_path], check=True)                        resign(file_path)                        os.chmod(file_path, 0o777)                                                processed += 1                except Exception as e:                    print(f"[!] Error processing {file_path}: {e}")                    continue        if processed == 0:        print("[!] no mach object was found nor processed")        sys.exit(1)        print(f"[i] patch was made to {processed} files")    # Remove import of .prelib use otool    print("[*] removing .prelib imports...")    system(f"install_name_tool -change @loader_path/.prelib /usr/lib/libSystem.B.dylib '{target_app}/{target_app.split(".app")[0]}' || true")    resign(f"{target_app}/{target_app.split(".app")[0]}")    # system(f"codesign --remove '{target_app}/{target_app.split('.app')[0]}' 2>/dev/null || true")    # system(f"codesign -s - --force --deep '{target_app}/{target_app.split('.app')[0]}'")    # Final codesign of the entire app bundle    system(f"codesign -s - --force --deep '{target_app}'")        print("[*] verify code sign...")    system(f"codesign --verify --deep '{target_app}'")        # Get bundle ID for app installation    try:        bundle_id_output = subprocess.run(            ["plutil", "-extract", "CFBundleIdentifier", "xml1", "-o", "-", f"{target_app}/Info.plist"],            capture_output=True, text=True, check=True        )        bundle_id = bundle_id_output.stdout.strip()        if "<string>" in bundle_id and "</string>" in bundle_id:            bundle_id = bundle_id.split("<string>")[1].split("</string>")[0]        print(f"[+] Bundle ID: {bundle_id}")    except Exception as e:        print(f"[!] Could not extract bundle ID: {e}")        bundle_id = None        # Install app to simulator    print(f"[*] installing app from {target_app}")    # cp temp to current path    system(f"xcrun simctl install {udid} '{target_app}'")        # Grant permissions if bundle ID was extracted successfully    if bundle_id:        print(f"[*] granting permissions to {bundle_id}")        system(f"xcrun simctl privacy {udid} grant all '{bundle_id}'")    else:        print("[!] Skipping permission grant due to missing bundle ID")        print("[*] done")        # Clean up temporary directory    os.chdir(original_cwd)    try:        shutil.rmtree(working_location)        print(f"[i] cleaned up temporary directory: {working_location}")    except Exception as e:        print(f"[!] Failed to clean up temporary directory: {e}")if __name__ == "__main__":    main()

全剧终。

当然如果你想问有些程序为什么运行不了可以别问我…因为我也有很多程序运行不了…想要直接查看日志的话,可以这样看:

Bash
$ xcrun simctl launch --console booted bundleId

然后就可以看到报错了