已知:编译时虚拟机用的target是arm64-apple-ios-simulator,真机是arm64-apple-ios。那么他们有什么区别?
即答:没区别。
于是就有了这篇文章。
对于一个脱壳下来的ipa包,他就是一个纯粹的zip。所以第一步就是直接unzip。
结构如下:Payload/(Target.app)/…
然后可以通过vtool看到,这个包里的二进制都是给iOS使用的,
$ vtool -arch arm64 -show Payload/Twitter.app/Twitter
Payload/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.3
Load 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
于是尝试一下:
$ 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/Twitter
Payload/Twitter.app/Twitter (architecture arm64):
Load command 11
cmd LC_SOURCE_VERSION
cmdsize 16
version 0.0
Load 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才能用的)
$ codesign --remove file
$ codesign -s - -- force --deep file 这一步需要对每一个二进制,以及外面的包(app文件夹)做。
做完了之后用xcrun simctl 安装上就好了。搓了一个python脚本。
import sys
import os
import subprocess
import shutil
import tempfile
def 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 None
def 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() 全剧终。
当然如果你想问有些程序为什么运行不了可以别问我…因为我也有很多程序运行不了…想要直接查看日志的话,可以这样看:
$ xcrun simctl launch --console booted bundleId 然后就可以看到报错了