已知:编译时虚拟机用的target是
arm64-apple-ios-simulator,真机是arm64-apple-ios。那么他们有什么区别?
即答:没区别。
于是就有了这篇文章。
对于一个脱壳下来的ipa包,他就是一个纯粹的zip。所以第一步就是直接unzip。
结构如下:Payload/(Target.app)/…
然后可以通过vtool看到,这个包里的二进制都是给iOS使用的,
Bash
这是一个给最小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然后就可以看到报错了