
Guida per costruire un iPhone virtuale utilizzando componenti VPHONE600AP dal firmware PCC di Apple, con patch del firmware, modifica della bootchain e debug del kernel per la ricerca sulla sicurezza di iOS.
Verso la fine del 2024, Apple ha iniziato a introdurre Private Cloud Compute, sostenendo di aprire un nuovo orizzonte per la privacy dell'IA basata sul cloud. Poi, verso la fine del 2025, sono apparse alcune notizie interessanti: Apple aveva aggiunto di recente componenti relativi a vphone600ap al firmware PCC, a partire da cloudOS 26.

Fonte: https://x.com/matteyeux/status/2006339694783848660/photo/1
"Macchina virtuale per l'ambiente di ricerca iPhone"?
Si tratta di una mossa pianificata da Apple per costruire e distribuire un ambiente iPhone virtuale per altri ricercatori di sicurezza in futuro, o è stato semplicemente un errore? Considerando che il kernel di build DEVELOPMENT/KASAN è stato scoperto una volta negli OTA di iOS 15.0 beta a 15.1 beta3 nel 2021, la possibilità di un lapsus non può essere esclusa. All'epoca, il kernel è rimasto incluso per circa 4 mesi, approssimativamente da giugno a ottobre 2021.
Poi, intorno a gennaio di quest'anno, è stato pubblicato un tweet che mostrava un iPhone virtuale in fase di avvio utilizzando questi componenti relativi a vphone600ap.

Fonte: https://x.com/_inside/status/2008951845725548783

Da quello che ho visto, quasi tutto funzionava davvero in modo elegante. Rispetto al progetto QEMUAppleSilicon(Inferno) che avevo visto in precedenza, funziona molto più rapidamente e fluentemente. Inoltre, sembrava addirittura supportare l'accelerazione Metal. Alla fine, completamente affascinato, mi sono tuffato e ho iniziato a costruire il mio iPhone virtuale il 31 gennaio.

Il progetto di riferimento è security-pcc. Corrisponde al codice sorgente del binario /System/Library/SecurityResearch/usr/bin/vrevm. Un punto interessante è che utilizza metodi privati forniti da Virtualization.framework. Nella macchina virtuale utilizzata per la ricerca PCC, si può vedere che ISA e PlatformVersion sono specificati esplicitamente durante il processo di inizializzazione del modello hardware.

Per il bootrom, viene utilizzato AVPBooter.vresearch1.bin (/System/Library/Frameworks/Virtualization.framework/Resources/AVPBooter.vresearch1.bin)

e per il SEPROM (avpsepbooter), viene utilizzato AVPSEPBooter.vresearch1.bin, che carica separatamente un file SEPStorage che funziona in modo simile a AuxiliaryStorage. (/System/Library/Frameworks/Virtualization.framework/Versions/A/Resources/AVPSEPBooter.vresearch1.bin)
Un altro punto interessante è che se si guarda il codice per impostare la risoluzione, è impostata su 1290x2796, che corrisponde ai dispositivi iPhone 14 Pro Max, 15 Plus, 15 Pro Max e 16 Plus.

Con solo queste informazioni, dovrebbe essere più che sufficiente per modificare super-tart per avviare l'iPhone virtuale. Ho apportato le modifiche come mostrato di seguito.
/Sources/tart/VM.swift```swift ... class VM: NSObject, VZVirtualMachineDelegate, ObservableObject { ... // vzHardwareModel derives the VZMacHardwareModel config specific to the "platform type" // of the VM (currently only vresearch101 supported) static private func vzHardwareModel_VRESEARCH101() throws -> VZMacHardwareModel { var hw_model: VZMacHardwareModel
guard let hw_descriptor = _VZMacHardwareModelDescriptor() else { fatalError("Failed to create hardware descriptor") } hw_descriptor.setPlatformVersion(3) // .appleInternal4 = 3 hw_descriptor.setBoardID(0x90) hw_descriptor.setISA(2) hw_model = VZMacHardwareModel._hardwareModel(withDescriptor: hw_descriptor)
guard hw_model.isSupported else { fatalError("VM hardware config not supported (model.isSupported = false)") }
return hw_model }
static func craftConfiguration( diskURL: URL, nvramURL: URL, romURL: URL, sepromURL: URL? = nil, vmConfig: VMConfig, network: Network = NetworkShared(), additionalStorageDevices: [VZStorageDeviceConfiguration], directorySharingDevices: [VZDirectorySharingDeviceConfiguration], serialPorts: [VZSerialPortConfiguration], suspendable: Bool = false, nested: Bool = false, audio: Bool = true, clipboard: Bool = true, sync: VZDiskImageSynchronizationMode = .full, caching: VZDiskImageCachingMode? = nil ) throws -> VZVirtualMachineConfiguration { let configuration: VZVirtualMachineConfiguration = .init()
// Boot loader let bootloader = try vmConfig.platform.bootLoader(nvramURL: nvramURL) Dynamic(bootloader)._setROMURL(romURL) configuration.bootLoader = bootloader
// SEP ROM let homeURL = FileManager.default.homeDirectoryForCurrentUser var sepstoragePath = homeURL.appendingPathComponent(".tart/vms/vphone/SEPStorage").path let sepstorageURL = URL(fileURLWithPath: sepstoragePath) let sep_config = Dynamic._VZSEPCoprocessorConfiguration(storageURL: sepstorageURL) if let sepromURL { // default AVPSEPBooter.vresearch1.bin from VZ framework sep_config.romBinaryURL = sepromURL } sep_config.debugStub = Dynamic._VZGDBDebugStubConfiguration(port: 8001) configuration._setCoprocessors([sep_config.asObject])
// Some vresearch101 config let pconf = VZMacPlatformConfiguration() pconf.hardwareModel = try vzHardwareModel_VRESEARCH101()
let serial = Dynamic._VZMacSerialNumber.initWithString("AAAAAA1337") let identifier = Dynamic.VZMacMachineIdentifier._machineIdentifierWithECID(0x1111111111111111, serialNumber: serial.asObject) pconf.machineIdentifier = identifier.asObject as! VZMacMachineIdentifier
pconf._setProductionModeEnabled(true) var auxiliaryStoragePath = homeURL.appendingPathComponent(".tart/vms/vphone/nvram.bin").path let auxiliaryStorageURL = URL(fileURLWithPath: auxiliaryStoragePath) pconf.auxiliaryStorage = VZMacAuxiliaryStorage(url: auxiliaryStorageURL)
if #available(macOS 14, *) { let keyboard = VZUSBKeyboardConfiguration() configuration.keyboards = [keyboard] }
if #available(macOS 14, *) { let touch = _VZUSBTouchScreenConfiguration() configuration._setMultiTouchDevices([touch]) } ... configuration.platform = pconf
# Modifica del firmware
Il progetto di riferimento è [vma2pwn](https://github.com/nick-botticelli/vma2pwn). Specificamente per la versione 12.0.1, avvia una macchina virtuale Mac con quasi l'intera catena di avvio modificata.
Diamo prima un'occhiata allo script [prepare.sh](https://github.com/nick-botticelli/vma2pwn/blob/main/prepare.sh). Esso estrae i componenti del firmware, come il bootloader e il kernel compressi in formato IM4P, in formato RAW e applica patch alle istruzioni/dati ad indirizzi specifici hardcoded. RestoreRamdisk è il filesystem root utilizzato durante il ripristino del firmware, e AVPBooter è il BootROM utilizzato nella macchina virtuale.
In sintesi, estrae i singoli file inclusi nel firmware e applica patch ai controlli di integrità per consentire il ripristino di firmware personalizzato, oppure modifica i parametri boot-args per facilitare la visualizzazione dei log relativi all'avvio.
Infine, [vma2pwn.sh](https://github.com/nick-botticelli/vma2pwn/blob/main/vma2pwn.sh) è responsabile del ripristino del firmware personalizzato. Lo fa entrando prima in modalità DFU. Qui, la macchina virtuale utilizza qualcosa chiamato super-tart. Questa è una versione della macchina virtuale tart esistente con funzionalità aggiuntive come un bootrom personalizzato, output seriale, modalità DFU e debug GDB. (Nota: SIP/AMFI devono essere disabilitati affinché funzioni.)
L'ho usato in modo molto utile recentemente mentre [studiavo le vulnerabilità 1-day del kernel XNU (CVE-2021-30937, CVE-2021-30955)](https://github.com/wh1te4ever/xnu_1day_practice). È fantastico perché supporta il debugging live del kernel.
## Costruire un firmware personalizzato
Ho mescolato i componenti di cloudOS 26.1 (23B85) e iOS 26.1 (iPhone17,3; 23B85), uh,,, ma... non ricordo i dettagli esatti. Per essere precisi, ho dovuto mescolare correttamente i componenti relativi a iPhone 16 e vphone per creare il firmware personalizzato, ma ho dimenticato esattamente quali ho finito per mescolare. Da quello che ricordo:
- BuildManifest.plist:
Ho modificato gli elementi del dizionario sotto la chiave Manifest. L'ho configurato in modo che durante il processo di ripristino vengano utilizzati SystemVolume, SystemVolumeCanonicalMetadata, OS, StaticTrustCache, RestoreTrustCache e RestoreRamDisk del modello iPhone 16 (iOS 26.1). Il resto è stato impostato per utilizzare file relativi a vphone dal firmware PCC.
- Restore.plist:
Credo di aver aggiunto proprietà relative a DeviceMap o SupportedProductTypes, oppure cambiato l'elemento SystemRestoreImageFileSystems.
I file seguenti sono il risultato finale della mia miscela.
[Restore.plist](https://github.com/wh1te4ever/super-tart-vphone-writeup/blob/HEAD/contents/Restore.plist)
[BuildManifest.plist](https://github.com/wh1te4ever/super-tart-vphone-writeup/blob/HEAD/contents/BuildManifest.plist)
- get_fw.py (Partial)```python
...
# 3. Import things from cloudOS
# kernelcache
os.system("cp 399b664dd623358c3de118ffc114e42dcd51c9309e751d43bc949b98f4e31349_extracted/kernelcache.* iPhone17,3_26.1_23B85_Restore")
# agx, all_flash, ane, dfu, pmp...
os.system("cp 399b664dd623358c3de118ffc114e42dcd51c9309e751d43bc949b98f4e31349_extracted/Firmware/agx/* iPhone17,3_26.1_23B85_Restore/Firmware/agx")
os.system("cp 399b664dd623358c3de118ffc114e42dcd51c9309e751d43bc949b98f4e31349_extracted/Firmware/all_flash/* iPhone17,3_26.1_23B85_Restore/Firmware/all_flash")
os.system("cp 399b664dd623358c3de118ffc114e42dcd51c9309e751d43bc949b98f4e31349_extracted/Firmware/ane/* iPhone17,3_26.1_23B85_Restore/Firmware/ane")
os.system("cp 399b664dd623358c3de118ffc114e42dcd51c9309e751d43bc949b98f4e31349_extracted/Firmware/dfu/* iPhone17,3_26.1_23B85_Restore/Firmware/dfu")
os.system("cp 399b664dd623358c3de118ffc114e42dcd51c9309e751d43bc949b98f4e31349_extracted/Firmware/pmp/* iPhone17,3_26.1_23B85_Restore/Firmware/pmp")
# sptm, txm, etc...
os.system("cp 399b664dd623358c3de118ffc114e42dcd51c9309e751d43bc949b98f4e31349_extracted/Firmware/*.im4p iPhone17,3_26.1_23B85_Restore/Firmware")
# 4. TODO: parse what things needed from BuildManifest.plist, Restore.plist in cloudOS 26.1
# It will be really complicated, so import things from already parse completed
os.system("sudo cp custom_26.1/BuildManifest.plist iPhone17,3_26.1_23B85_Restore")
os.system("sudo cp custom_26.1/Restore.plist iPhone17,3_26.1_23B85_Restore")
os.system("echo 'Done, grabbed all needed components for restoring'")
Ho fatto riferimento a quel post. È necessario applicare una patch a image4_validate_property_callback per poter caricare un bootloader personalizzato in seguito. Utilizza semplicemente la funzione "Text-search (slow!)" in IDA Pro per cercare "0x4447" e applica una patch all'epilogo della funzione corrispondente in modo che restituisca sempre 0.

Prima di ripristinare il firmware, sono state necessarie alcune modifiche per supportare il modello vresearch101ap. Una volta compilato, il ripristino del firmware diventa possibile utilizzando lo strumento idevicerestore.
https://github.com/wh1te4ever/libirecovery

Similmente ad AVPBooter, i bootloader utilizzati per il ripristino, iBSS e iBEC, sono stati patchati per bypassare la verifica della firma. Ho anche abilitato l'output del log seriale in modo che, se ci fossero problemi di avvio, la causa possa essere identificata immediatamente.
Come vedrai in seguito, è necessario bypassare la verifica SSV (Signed System Volume) per caricare un Cryptex arbitrario. Questa operazione viene eseguita nell'LLB, che viene caricato quando si avvia in modalità normale anziché in modalità DFU, e la verifica viene talvolta eseguita anche nel kernel.
Inoltre, ho applicato una patch al TXM in modo che, anche se un binario/libreria non è registrato nel Trustcache, venga riconosciuto come se lo fosse.
patch(0x9D10, 0xd503201f) #nop patch(0x9D14, 0xd2800000) #mov x0, #0
patch(0x9D10, 0xd503201f) #nop patch(0x9D14, 0xd2800000) #mov x0, #0
patch(0x122d4, 0xd0000082) #adrp x2, #0x12000 patch(0x122d8, 0x9101c042) #add x2, x2, #0x70 patch(0x24070, "serial=3 -v debug=0x2014e %s")
patch(0xA0D8, 0xd503201f) #nop patch(0xA0DC, 0xd2800000) #mov x0, #0
patch(0x12888, 0xD0000082) #adrp x2, #0x12000 patch(0x1288C, 0x91264042) #add x2, x2, #0x990 patch(0x24990, "serial=3 -v debug=0x2014e %s")
patch(0x2BFE8, 0x1400000b) patch(0x2bca0, 0xd503201f) patch(0x2C03C, 0x17ffff6a) patch(0x2fcec, 0xd503201f) patch(0x2FEE8, 0x14000009)
patch(0x1AEE4, 0xd503201f) #nop
patch(0x2c1f8, 0xd2800000) #FFFFFFF0170301F8 patch(0x2bef4, 0xd2800000) #FFFFFFF01702FEF4 patch(0x2c060, 0xd2800000) #FFFFFFF017030060
patch(0x2476964, 0xd503201f) #FFFFFE000947A964
patch(0x23cfde4, 0xd503201f) #FFFFFE00093D3DE4
patch(0xf6d960, 0xd503201f) #FFFFFE0007F71960 ...
Dopo aver convertito nel formato RAW e averlo patchato, è necessario riconvertirlo in IM4P.
Nel caso del kernel o di TXM, esiste una struttura PAYP, quindi è stato necessario preservare tale struttura.
Di seguito il codice che converte IM4P → RAW → IM4P usando gli strumenti [pyimg4](https://pypi.org/project/pyimg4/), [img4tool](https://github.com/tihmstar/img4tool), [img4](https://github.com/xerub/img4lib).
- patch_fw.py (Contenuto parziale, Parte 2)```python
...
# Patch iBSS
if not os.path.exists("iPhone17,3_26.1_23B85_Restore/Firmware/dfu/iBSS.vresearch101.RELEASE.im4p.bak"):
os.system("cp iPhone17,3_26.1_23B85_Restore/Firmware/dfu/iBSS.vresearch101.RELEASE.im4p iPhone17,3_26.1_23B85_Restore/Firmware/dfu/iBSS.vresearch101.RELEASE.im4p.bak")
os.system("tools/img4 -i iPhone17,3_26.1_23B85_Restore/Firmware/dfu/iBSS.vresearch101.RELEASE.im4p.bak -o iBSS.vresearch101.RELEASE")
... # patch things from raw
os.system("tools/img4tool -c iPhone17,3_26.1_23B85_Restore/Firmware/dfu/iBSS.vresearch101.RELEASE.im4p -t ibss iBSS.vresearch101.RELEASE")
# Patch iBEC
if not os.path.exists("iPhone17,3_26.1_23B85_Restore/Firmware/dfu/iBEC.vresearch101.RELEASE.im4p.bak"):
os.system("cp iPhone17,3_26.1_23B85_Restore/Firmware/dfu/iBEC.vresearch101.RELEASE.im4p iPhone17,3_26.1_23B85_Restore/Firmware/dfu/iBEC.vresearch101.RELEASE.im4p.bak")
os.system("tools/img4 -i iPhone17,3_26.1_23B85_Restore/Firmware/dfu/iBEC.vresearch101.RELEASE.im4p.bak -o iBEC.vresearch101.RELEASE")
... # patch things from raw
os.system("tools/img4tool -c iPhone17,3_26.1_23B85_Restore/Firmware/dfu/iBEC.vresearch101.RELEASE.im4p -t ibec iBEC.vresearch101.RELEASE")
# Patch LLB
if not os.path.exists("iPhone17,3_26.1_23B85_Restore/Firmware/all_flash/LLB.vresearch101.RESEARCH_RELEASE.im4p.bak"):
os.system("cp iPhone17,3_26.1_23B85_Restore/Firmware/all_flash/LLB.vresearch101.RESEARCH_RELEASE.im4p iPhone17,3_26.1_23B85_Restore/Firmware/all_flash/LLB.vresearch101.RESEARCH_RELEASE.im4p.bak")
os.system("tools/img4 -i iPhone17,3_26.1_23B85_Restore/Firmware/all_flash/LLB.vresearch101.RESEARCH_RELEASE.im4p.bak -o LLB.vresearch101.RESEARCH_RELEASE")
... # patch things from raw
os.system("tools/img4tool -c iPhone17,3_26.1_23B85_Restore/Firmware/all_flash/LLB.vresearch101.RESEARCH_RELEASE.im4p -t illb LLB.vresearch101.RESEARCH_RELEASE")
# 6. Grab & Patch TXM
if not os.path.exists("iPhone17,3_26.1_23B85_Restore/Firmware/txm.iphoneos.research.im4p.bak"):
os.system("cp iPhone17,3_26.1_23B85_Restore/Firmware/txm.iphoneos.research.im4p iPhone17,3_26.1_23B85_Restore/Firmware/txm.iphoneos.research.im4p.bak")
os.system("pyimg4 im4p extract -i iPhone17,3_26.1_23B85_Restore/Firmware/txm.iphoneos.research.im4p.bak -o txm.raw")
... # patch things from raw
#create im4p
os.system("pyimg4 im4p create -i txm.raw -o txm.im4p -f trxm --lzfse")
# preserve payp structure
txm_im4p_data = Path('iPhone17,3_26.1_23B85_Restore/Firmware/txm.iphoneos.research.im4p.bak').read_bytes()
payp_offset = txm_im4p_data.rfind(b'PAYP')
if payp_offset == -1:
print("Couldn't find payp structure !!!")
sys.exit()
with open('txm.im4p', 'ab') as f:
f.write(txm_im4p_data[(payp_offset-10):])
payp_sz = len(txm_im4p_data[(payp_offset-10):])
print(f"payp sz: {payp_sz}")
txm_im4p_data = bytearray(open('txm.im4p', 'rb').read())
txm_im4p_data[2:5] = (int.from_bytes(txm_im4p_data[2:5], 'big') + payp_sz).to_bytes(3, 'big')
open('txm.im4p', 'wb').write(txm_im4p_data)
os.system("mv txm.im4p iPhone17,3_26.1_23B85_Restore/Firmware/txm.iphoneos.research.im4p")
# 7. Grab & patch kernelcache
if not os.path.exists("iPhone17,3_26.1_23B85_Restore/kernelcache.research.vphone600.bak"):
os.system("cp iPhone17,3_26.1_23B85_Restore/kernelcache.research.vphone600 iPhone17,3_26.1_23B85_Restore/kernelcache.research.vphone600.bak")
os.system("pyimg4 im4p extract -i iPhone17,3_26.1_23B85_Restore/kernelcache.research.vphone600.bak -o kcache.raw")
... # patch things from raw
#create im4p
os.system("pyimg4 im4p create -i kcache.raw -o krnl.im4p -f krnl --lzfse")
# preserve payp structure
kernel_im4p_data = Path('iPhone17,3_26.1_23B85_Restore/kernelcache.research.vphone600.bak').read_bytes()
payp_offset = kernel_im4p_data.rfind(b'PAYP')
if payp_offset == -1:
print("Couldn't find payp structure !!!")
sys.exit()
with open('krnl.im4p', 'ab') as f:
f.write(kernel_im4p_data[(payp_offset-10):])
payp_sz = len(kernel_im4p_data[(payp_offset-10):])
print(f"payp sz: {payp_sz}")
kernel_im4p_data = bytearray(open('krnl.im4p', 'rb').read())
kernel_im4p_data[2:5] = (int.from_bytes(kernel_im4p_data[2:5], 'big') + payp_sz).to_bytes(3, 'big')
open('krnl.im4p', 'wb').write(kernel_im4p_data)
os.system("mv krnl.im4p iPhone17,3_26.1_23B85_Restore/kernelcache.research.vphone600")
...
Una volta che tutto è pronto, mettiamo la macchina virtuale in modalità DFU e proviamo a ripristinarla.
Di seguito è riportata una schermata del panico che si verifica se il SEP non è configurato correttamente. Se lo configuri correttamente, dovrebbe superare questo punto senza problemi.

Dopo il completamento del ripristino, si riavvia automaticamente. Tuttavia, si verifica un panico nel processo launchd perché manca la libreria /usr/lib/libSystem.B.dylib. Questa libreria si trova all'interno della dyld_shared_cache sulla partizione Cryptex, e per qualche motivo la partizione Cryptex non è stata ripristinata. Come soluzione temporanea, è necessario creare un SSH Ramdisk per modificare il file system di root e iniettare i file necessari. Questo è esattamente il motivo per cui era necessaria la patch relativa alla verifica SSV.


Proverò a risolvere il problema di avvio utilizzando il ramdisk usato in https://github.com/verygenericname/SSHRD_Script.
Per caricare e avviare componenti come il bootloader o il kernel utilizzando lo strumento irecovery in modalità DFU, è necessaria un'immagine IMG4, che richiede un file IM4M. Pertanto, ho prima recuperato il file shsh usando lo strumento idevicerestore, e poi l'ho convertito in un file IM4M.```bash idevicerestore -e -y ./iPhone17,3_26.1_23B85_Restore -t
mv shsh/[ECID]-iPhone99,11-26.1.shsh shsh/[ECID]-iPhone99,11-26.1.shsh.gz
gunzip shsh/[ECID]-iPhone99,11-26.1.shsh.gz
...
pyimg4 im4m extract -i shsh/[ECID]-iPhone99,11-26.1.shsh -o vphone.im4m
Quindi, usando quel file IM4M, ho generato diversi file IMG4 per ciascuno dei componenti del firmware utilizzati, come iBSS, iBEC e il devicetree.```python
# 1. Grab & Patch iBSS
if not os.path.exists("iPhone17\\,3_26.1_23B85_Restore/Firmware/dfu/iBSS.vresearch101.RELEASE.im4p.bak"):
os.system("cp iPhone17\\,3_26.1_23B85_Restore/Firmware/dfu/iBSS.vresearch101.RELEASE.im4p iPhone17\\,3_26.1_23B85_Restore/Firmware/dfu/iBSS.vresearch101.RELEASE.im4p.bak")
os.system("tools/img4 -i iPhone17\\,3_26.1_23B85_Restore/Firmware/dfu/iBSS.vresearch101.RELEASE.im4p.bak -o iBSS.vresearch101.RELEASE")
... # patch things from raw
os.system("tools/img4tool -c iBSS.vresearch101.RELEASE.im4p -t ibss iBSS.vresearch101.RELEASE")
os.system("tools/img4 -i iBSS.vresearch101.RELEASE.im4p -o ./Ramdisk/iBSS.vresearch101.RELEASE.img4 -M ./vphone.im4m")
# 2. Grab & Patch iBEC
if not os.path.exists("iPhone17\\,3_26.1_23B85_Restore/Firmware/dfu/iBEC.vresearch101.RELEASE.im4p.bak"):
os.system("cp iPhone17\\,3_26.1_23B85_Restore/Firmware/dfu/iBEC.vresearch101.RELEASE.im4p iPhone17\\,3_26.1_23B85_Restore/Firmware/dfu/iBEC.vresearch101.RELEASE.im4p.bak")
os.system("tools/img4 -i iPhone17\\,3_26.1_23B85_Restore/Firmware/dfu/iBEC.vresearch101.RELEASE.im4p -o iBEC.vresearch101.RELEASE")
... # patch things from raw
os.system("tools/img4tool -c iBEC.vresearch101.RELEASE.im4p -t ibec iBEC.vresearch101.RELEASE")
os.system("tools/img4 -i iBEC.vresearch101.RELEASE.im4p -o Ramdisk/iBEC.vresearch101.RELEASE.img4 -M vphone.im4m")
# 3. Grab SPTM
os.system("tools/img4 -i iPhone17\\,3_26.1_23B85_Restore/Firmware/sptm.vresearch1.release.im4p -o Ramdisk/sptm.vresearch1.release.img4 -M vphone.im4m -T sptm")
# 4. Grab devicetree
os.system("tools/img4 -i iPhone17\\,3_26.1_23B85_Restore/Firmware/all_flash/DeviceTree.vphone600ap.im4p -o Ramdisk/DeviceTree.vphone600ap.img4 -M vphone.im4m -T rdtr")
# 5. Grab sep
os.system("tools/img4 -i iPhone17\\,3_26.1_23B85_Restore/Firmware/all_flash/sep-firmware.vresearch101.RELEASE.im4p -o Ramdisk/sep-firmware.vresearch101.RELEASE.img4 -M vphone.im4m -T rsep")
# 6. Grab & Patch TXM
if not os.path.exists("iPhone17\\,3_26.1_23B85_Restore/Firmware/txm.iphoneos.release.im4p.bak"):
os.system("cp iPhone17\\,3_26.1_23B85_Restore/Firmware/txm.iphoneos.release.im4p iPhone17\\,3_26.1_23B85_Restore/Firmware/txm.iphoneos.release.im4p.bak")
os.system("pyimg4 im4p extract -i iPhone17\\,3_26.1_23B85_Restore/Firmware/txm.iphoneos.release.im4p.bak -o txm.raw")
... # patch things from raw
#create im4p
os.system("pyimg4 im4p create -i txm.raw -o txm.im4p -f trxm --lzfse")
# preserve payp structure
txm_im4p_data = Path('iPhone17,3_26.1_23B85_Restore/Firmware/txm.iphoneos.release.im4p.bak').read_bytes()
payp_offset = txm_im4p_data.rfind(b'PAYP')
if payp_offset == -1:
print("Couldn't find payp structure !!!")
sys.exit()
with open('txm.im4p', 'ab') as f:
f.write(txm_im4p_data[(payp_offset-10):])
payp_sz = len(txm_im4p_data[(payp_offset-10):])
print(f"payp sz: {payp_sz}")
txm_im4p_data = bytearray(open('txm.im4p', 'rb').read())
txm_im4p_data[2:5] = (int.from_bytes(txm_im4p_data[2:5], 'big') + payp_sz).to_bytes(3, 'big')
open('txm.im4p', 'wb').write(txm_im4p_data)
# sign
os.system("pyimg4 img4 create -p txm.im4p -o Ramdisk/txm.img4 -m vphone.im4m")
# 7. Grab & patch kernelcache
if not os.path.exists("iPhone17\\,3_26.1_23B85_Restore/kernelcache.research.vphone600.bak"):
os.system("cp iPhone17\\,3_26.1_23B85_Restore/kernelcache.research.vphone600 iPhone17\\,3_26.1_23B85_Restore/kernelcache.research.vphone600.bak")
os.system("pyimg4 im4p extract -i iPhone17\\,3_26.1_23B85_Restore/kernelcache.research.vphone600.bak -o kcache.raw")
... # patch things from raw
#create im4p
os.system("pyimg4 im4p create -i kcache.raw -o krnl.im4p -f rkrn --lzfse")
# preserve payp structure
kernel_im4p_data = Path('iPhone17,3_26.1_23B85_Restore/kernelcache.research.vphone600.bak').read_bytes()
payp_offset = kernel_im4p_data.rfind(b'PAYP')
if payp_offset == -1:
print("Couldn't find payp structure !!!")
sys.exit()
with open('krnl.im4p', 'ab') as f:
f.write(kernel_im4p_data[(payp_offset-10):])
payp_sz = len(kernel_im4p_data[(payp_offset-10):])
print(f"payp sz: {payp_sz}")
kernel_im4p_data = bytearray(open('krnl.im4p', 'rb').read())
kernel_im4p_data[2:5] = (int.from_bytes(kernel_im4p_data[2:5], 'big') + payp_sz).to_bytes(3, 'big')
open('krnl.im4p', 'wb').write(kernel_im4p_data)
# sign
os.system("pyimg4 img4 create -p krnl.im4p -o Ramdisk/krnl.img4 -m vphone.im4m")
# 8. Grab ramdisk & build custom ramdisk
os.system("pyimg4 im4p extract -i iPhone17,3_26.1_23B85_Restore/043-53775-129.dmg -o ramdisk.dmg")
os.system("mkdir SSHRD")
os.system("sudo hdiutil attach -mountpoint SSHRD ramdisk.dmg -owners off")
os.system("sudo hdiutil create -size 254m -imagekey diskimage-class=CRawDiskImage -format UDZO -fs APFS -layout NONE -srcfolder SSHRD -copyuid root ramdisk1.dmg")
os.system("sudo hdiutil detach -force SSHRD")
os.system("sudo hdiutil attach -mountpoint SSHRD ramdisk1.dmg -owners off")
... #remove unneccessary files for expand space
#resign all things preserving ents
target_path= [
"SSHRD/usr/local/bin/*", "SSHRD/usr/local/lib/*",
"SSHRD/usr/bin/*", "SSHRD/bin/*",
"SSHRD/usr/lib/*", "SSHRD/sbin/*", "SSHRD/usr/sbin/*", "SSHRD/usr/libexec/*"
]
for pattern in target_path:
for path in glob.glob(pattern):
if os.path.isfile(path) and not os.path.islink(path):
if "Mach-O" in subprocess.getoutput(f"file \"{path}\""):
os.system(f"tools/ldid_macosx_arm64 -S -M -Cadhoc \"{path}\"")
#8-2. Grab & build custom ramdisk's trustcache while building custom ramdisk
os.system("pyimg4 im4p extract -i iPhone17,3_26.1_23B85_Restore/Firmware/043-53775-129.dmg.trustcache -o trustcache.raw")
os.system("tools/trustcache_macos_arm64 create sshrd.tc SSHRD")
os.system("pyimg4 im4p create -i sshrd.tc -o trustcache.im4p -f rtsc")
# sign
os.system("pyimg4 img4 create -p trustcache.im4p -o Ramdisk/trustcache.img4 -m vphone.im4m")
#8-2. end
os.system("sudo hdiutil detach -force SSHRD")
os.system("sudo hdiutil resize -sectors min ramdisk1.dmg")
# sign
os.system("pyimg4 im4p create -i ramdisk1.dmg -o ramdisk1.dmg.im4p -f rdsk")
os.system("pyimg4 img4 create -p ramdisk1.dmg.im4p -o Ramdisk/ramdisk.img4 -m vphone.im4m")
Una volta create tutte le immagini IMG4, carichiamole una per una e avviamo con il Ramdisk.
sleep 1; irecovery -f Ramdisk/sptm.vresearch1.release.img4 irecovery -c firmware
irecovery -f Ramdisk/txm.img4 irecovery -c firmware
irecovery -f Ramdisk/trustcache.img4 irecovery -c firmware irecovery -f Ramdisk/ramdisk.img4 irecovery -c ramdisk irecovery -f Ramdisk/DeviceTree.vphone600ap.img4 irecovery -c devicetree irecovery -f Ramdisk/sep-firmware.vresearch101.RELEASE.img4 irecovery -c firmware irecovery -f Ramdisk/krnl.img4 irecovery -c bootx
Quindi vedrai la faccia del Creeper di Minecraft nella terza finestra da sinistra, come mostrato di seguito.
Se controlli il menu USB nell'app Informazioni di Sistema e vedi "iPhone Research...",
ora puoi accedere alla shell dell'iPhone virtuale usando lo strumento [iproxy](https://github.com/libimobiledevice/libusbmuxd/blob/master/tools/iproxy.c). (`iproxy 2222 22 &`)

Per modificare il file system di root, rinomina lo snapshot.```python
ssh [email protected] -p2222
#pw: alpine
mount_apfs -o rw /dev/disk1s1 /mnt1
snaputil -l /mnt1
# (then will output will be printed with hash, result may be differ)
com.apple.os.update-8AAB8DBA5C8F1F756928411675F4A892087B04559CFB084B9E400E661ABAD119
snaputil -n <com.apple.os.update-hash> orig-fs /mnt1
umount /mnt1
exit
Decriptare il file AEA utilizzando lo strumento ipsw per creare un file dmg, montarlo e quindi trasferire i file dalla partizione Cryptex alla macchina virtuale. Oltre al trasferimento dei file, erano necessarie patch specifiche. Per comodità, ho aggiunto tre processi specifici da avviare all'avvio: bash, dropbear e trollvnc.
seputil aveva un problema per cui non riusciva a trovare correttamente il file gigalocker, quindi l'ho patchato per cercare sempre AA.gl. Inoltre, ho patchato launchd_cache_loader per garantire che il file /System/Library/xpc/launchd.plist modificato venga caricato correttamente.```python ... ========= INSTALL CRYPTEX(SystemOS, AppOS) =========
key = subprocess.check_output("ipsw fw aea --key iPhone17,3_26.1_23B85_Restore/043-54303-126.dmg.aea", shell=True, text=True).strip() print(f"key: {key}") os.system(f"aea decrypt -i iPhone17,3_26.1_23B85_Restore/043-54303-126.dmg.aea -o CryptexSystemOS.dmg -key-value '{key}'")
os.system(f"cp iPhone17,3_26.1_23B85_Restore/043-54062-129.dmg CryptexAppOS.dmg")
os.system("mkdir CryptexSystemOS") os.system("sudo hdiutil attach -mountpoint CryptexSystemOS CryptexSystemOS.dmg -owners off")
os.system("mkdir CryptexAppOS") os.system("sudo hdiutil attach -mountpoint CryptexAppOS CryptexAppOS.dmg -owners off")
remote_cmd("/sbin/mount_apfs -o rw /dev/disk1s1 /mnt1")
remote_cmd("/bin/rm -rf /mnt1/System/Cryptexes/App") remote_cmd("/bin/rm -rf /mnt1/System/Cryptexes/OS")
remote_cmd("/bin/mkdir -p /mnt1/System/Cryptexes/App") remote_cmd("/bin/chmod 0755 /mnt1/System/Cryptexes/App") remote_cmd("/bin/mkdir -p /mnt1/System/Cryptexes/OS") remote_cmd("/bin/chmod 0755 /mnt1/System/Cryptexes/OS")
print("Copying cryptexs to vphone! Will take about 3 mintues...") os.system("tools/sshpass -p 'alpine' scp -q -r -ostricthostkeychecking=false -ouserknownhostsfile=/dev/null -o StrictHostKeyChecking=no -P 2222 CryptexSystemOS/. '[email protected]:/mnt1/System/Cryptexes/OS'") os.system("tools/sshpass -p 'alpine' scp -q -r -ostricthostkeychecking=false -ouserknownhostsfile=/dev/null -o StrictHostKeyChecking=no -P 2222 CryptexAppOS/. '[email protected]:/mnt1/System/Cryptexes/App'")
remote_cmd("/bin/ln -sf ../../../System/Cryptexes/OS/System/Library/Caches/com.apple.dyld /mnt1/System/Library/Caches/com.apple.dyld")
remote_cmd("/bin/ln -sf ../../../../System/Cryptexes/OS/System/DriverKit/System/Library/dyld /mnt1/System/DriverKit/System/Library/dyld")
os.system("rm custom_26.1/seputil 2>/dev/null") os.system("rm custom_26.1/seputil.bak 2>/dev/null")
file_path = "/mnt1/usr/libexec/seputil.bak" if not check_remote_file_exists(file_path): print(f"Created backup {file_path}") remote_cmd("/bin/cp /mnt1/usr/libexec/seputil /mnt1/usr/libexec/seputil.bak")
os.system("tools/sshpass -p 'alpine' scp -q -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -P 2222 [email protected]:/mnt1/usr/libexec/seputil.bak ./custom_26.1") os.system("mv custom_26.1/seputil.bak custom_26.1/seputil")
fp = open("custom_26.1/seputil", "r+b") patch(0x1B3F1, "AA") fp.close()
os.system("tools/ldid_macosx_arm64 -S -M -Ksigncert.p12 -Icom.apple.seputil custom_26.1/seputil")
os.system("tools/sshpass -p 'alpine' scp -q -r -ostricthostkeychecking=false -ouserknownhostsfile=/dev/null -o StrictHostKeyChecking=no -P 2222 custom_26.1/seputil '[email protected]:/mnt1/usr/libexec/seputil'") remote_cmd("/bin/chmod 0755 /mnt1/usr/libexec/seputil")
os.system("rm custom_26.1/seputil 2>/dev/null")
remote_cmd("/sbin/mount_apfs -o rw /dev/disk1s3 /mnt3") remote_cmd("/bin/mv /mnt3/*.gl /mnt3/AA.gl")
... # ========= INSTALL AppleParavirtGPUMetalIOGPUFamily =========
os.system("tools/sshpass -p 'alpine' scp -q -r -ostricthostkeychecking=false -ouserknownhostsfile=/dev/null -o StrictHostKeyChecking=no -P 2222 jb/iosbinpack64.tar '[email protected]:/mnt1'")
remote_cmd("/usr/bin/tar --preserve-permissions --no-overwrite-dir -xvf /mnt1/iosbinpack64.tar -C /mnt1") remote_cmd("/bin/rm /mnt1/iosbinpack64.tar")
''' /iosbinpack64/bin/mkdir -p /var/dropbear /iosbinpack64/bin/cp /iosbinpack64/etc/profile /var/profile /iosbinpack64/bin/cp /iosbinpack64/etc/motd /var/motd '''
os.system("rm custom_26.1/launchd_cache_loader 2>/dev/null") os.system("rm custom_26.1/launchd_cache_loader.bak 2>/dev/null")
file_path = "/mnt1/usr/libexec/launchd_cache_loader.bak" if not check_remote_file_exists(file_path): print(f"Created backup {file_path}") remote_cmd("/bin/cp /mnt1/usr/libexec/launchd_cache_loader /mnt1/usr/libexec/launchd_cache_loader.bak")
os.system("tools/sshpass -p 'alpine' scp -q -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -P 2222 [email protected]:/mnt1/usr/libexec/launchd_cache_loader.bak ./custom_26.1") os.system("mv custom_26.1/launchd_cache_loader.bak custom_26.1/launchd_cache_loader")
fp = open("custom_26.1/launchd_cache_loader", "r+b") patch(0xB58, 0xd503201f) fp.close()
os.system("tools/ldid_macosx_arm64 -S -M -Ksigncert.p12 -Icom.apple.launchd_cache_loader custom_26.1/launchd_cache_loader")
os.system("tools/sshpass -p 'alpine' scp -q -r -ostricthostkeychecking=false -ouserknownhostsfile=/dev/null -o StrictHostKeyChecking=no -P 2222 custom_26.1/launchd_cache_loader '[email protected]:/mnt1/usr/libexec/launchd_cache_loader'") remote_cmd("/bin/chmod 0755 /mnt1/usr/libexec/launchd_cache_loader")
os.system("rm custom_26.1/launchd_cache_loader 2>/dev/null")
os.system("tools/sshpass -p 'alpine' scp -q -r -ostricthostkeychecking=false -ouserknownhostsfile=/dev/null -o StrictHostKeyChecking=no -P 2222 jb/LaunchDaemons/bash.plist '[email protected]:/mnt1/System/Library/LaunchDaemons'") os.system("tools/sshpass -p 'alpine' scp -q -r -ostricthostkeychecking=false -ouserknownhostsfile=/dev/null -o StrictHostKeyChecking=no -P 2222 jb/LaunchDaemons/dropbear.plist '[email protected]:/mnt1/System/Library/LaunchDaemons'") os.system("tools/sshpass -p 'alpine' scp -q -r -ostricthostkeychecking=false -ouserknownhostsfile=/dev/null -o StrictHostKeyChecking=no -P 2222 jb/LaunchDaemons/trollvnc.plist '[email protected]:/mnt1/System/Library/LaunchDaemons'") remote_cmd("/bin/chmod 0644 /mnt1/System/Library/LaunchDaemons/bash.plist") remote_cmd("/bin/chmod 0644 /mnt1/System/Library/LaunchDaemons/dropbear.plist") remote_cmd("/bin/chmod 0644 /mnt1/System/Library/LaunchDaemons/trollvnc.plist")
os.system("rm custom_26.1/launchd.plist 2>/dev/null") os.system("rm custom_26.1/launchd.plist.bak 2>/dev/null")
file_path = "/mnt1/System/Library/xpc/launchd.plist.bak" if not check_remote_file_exists(file_path): print(f"Created backup {file_path}") remote_cmd("/bin/cp /mnt1/System/Library/xpc/launchd.plist /mnt1/System/Library/xpc/launchd.plist.bak")
os.system("tools/sshpass -p 'alpine' scp -q -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -P 2222 [email protected]:/mnt1/System/Library/xpc/launchd.plist.bak ./custom_26.1") os.system("mv custom_26.1/launchd.plist.bak custom_26.1/launchd.plist")
os.system("plutil -convert xml1 custom_26.1/launchd.plist")
target_file = 'custom_26.1/launchd.plist' source_file = 'jb/LaunchDaemons/bash.plist' insert_key = '/System/Library/LaunchDaemons/bash.plist'
with open(target_file, 'rb') as ft, open(source_file, 'rb') as fs: target_data = plistlib.load(ft) source_data = plistlib.load(fs)
target_data.setdefault('LaunchDaemons', {})[insert_key] = source_data
with open(target_file, 'wb') as f: plistlib.dump(target_data, f, sort_keys=False)
source_file = 'jb/LaunchDaemons/dropbear.plist' insert_key = '/System/Library/LaunchDaemons/dropbear.plist'
with open(target_file, 'rb') as ft, open(source_file, 'rb') as fs: target_data = plistlib.load(ft) source_data = plistlib.load(fs)
target_data.setdefault('LaunchDaemons', {})[insert_key] = source_data
with open(target_file, 'wb') as f: plistlib.dump(target_data, f, sort_keys=False)
source_file = 'jb/LaunchDaemons/trollvnc.plist' insert_key = '/System/Library/LaunchDaemons/trollvnc.plist'
with open(target_file, 'rb') as ft, open(source_file, 'rb') as fs: target_data = plistlib.load(ft) source_data = plistlib.load(fs)
target_data.setdefault('LaunchDaemons', {})[insert_key] = source_data
with open(target_file, 'wb') as f: plistlib.dump(target_data, f, sort_keys=False)
os.system("tools/sshpass -p 'alpine' scp -q -r -ostricthostkeychecking=false -ouserknownhostsfile=/dev/null -o StrictHostKeyChecking=no -P 2222 custom_26.1/launchd.plist '[email protected]:/mnt1/System/Library/xpc'") remote_cmd("/bin/chmod 0644 /mnt1/System/Library/xpc/launchd.plist")
os.system("rm custom_26.1/launchd.plist 2>/dev/null")
... remote_cmd("/sbin/halt") ...
# Primo tentativo di avvio
Il processo di avvio dovrebbe funzionare correttamente ora, ma quando si tenta di proseguire oltre la schermata nera di configurazione, si verifica un respring e non si va oltre.


# Implementazione di Metal
Quando si verifica con un programma personalizzato chiamato MetalTest, mostra che Metal non è supportato.```python
#import <stdio.h>
#import <Metal/Metal.h>
#import <Foundation/Foundation.h>
int main(int argc, char *argv[], char *envp[]) {
id<MTLDevice> device = MTLCreateSystemDefaultDevice();
NSLog(@"device: %@", device);
if (device) {
NSLog(@"Metal Device Create Success: %@", [device name]);
} else {
NSLog(@"Metal Not Supported!");
}
return 0;
}
Normalmente, l'output avrebbe dovuto assomigliare al risultato qui sotto.```python
seo@seos-Virtual-Machine Desktop % sysctl kern.version
kern.version: Darwin Kernel Version 25.0.0: Mon Aug 25 21:17:21 PDT 2025; root:xnu-12377.1.9~3/RELEASE_ARM64_VMAPPLE
seo@seos-Virtual-Machine Desktop % ./MetalTest
2026-02-08 23:16:56.846 MetalTest[682:5810] device: <AppleParavirtDevice: 0x102c48fe0>
name = Apple Paravirtual device
2026-02-08 23:16:56.847 MetalTest[682:5810] Metal Device Create Success: Apple Paravirtual device
seo@seos-Virtual-Machine Desktop %
Controllando con ioreg -l, come puoi vedere, il kernel riconosceva effettivamente AppleParavirtGPU.

Quando si verifica su un iPad 7ª generazione con iOS 16.6.1, chiamare la funzione MTLCreateSystemDefaultDevice accede internamente al driver IOGPU attraverso una libreria specifica chiamata AGXMetalA10. Questa libreria AGXMetalA10 si trova in /System/Library/Extensions.
Qui mi è venuto un pensiero improvviso: non ci sarebbero anche librerie GPU/Metal utilizzate per l'iPhone virtuale?

Controllando lo stesso percorso nella macchina virtuale PCC, si scopre che lì esistono 7 file.
Ho preso /System/Library/Extensions/AppleParavirtGPUMetalIOGPUFamily.bundle usato in PCC e l'ho inserito direttamente nell'iPhone virtuale. (Ho usato il SSH Ramdisk per questo.)

Rieseguendo MetalTest, la funzione MTLCreateSystemDefaultDevice ora funziona correttamente.

Tuttavia, poiché un file dylib specifico non esiste nella dsc (dyld shared cache) del modello iPhone 16, ho dovuto eseguire separatamente reverse engineering e implementarlo dalla dsc del PCC.


Una volta implementato, ora lo schermo di configurazione appare con uno sfondo. Poiché non sono riuscito a implementare correttamente il pulsante Home, ho risolto con una soluzione temporanea controllando tramite iproxy/VNC.

È compatibile solo con Mac Apple Silicon, e i dispositivi/versioni confermati funzionanti sono i seguenti:
Prevedo che probabilmente funzionerà su qualsiasi target che supporti pccvre.

Fonte: https://security.apple.com/documentation/private-cloud-compute/vresetup
A differenza della versione Tahoe 26, l'interazione touch non è possibile utilizzando solo l'oggetto VZVirtualMachineView, quindi è stato necessario sovrascrivere le funzioni degli eventi del mouse.
// Display let graphics_config = VZMacGraphicsDeviceConfiguration() let displays_config = VZMacGraphicsDisplayConfiguration( widthInPixels: 1179, heightInPixels: 2556, pixelsPerInch: 460 ) graphics_config.displays.append(displays_config) configuration.graphicsDevices = [graphics_config] ...