- Apple silicon Mac의 Virtualization.framework와 vma2 환경을 이용해 iOS 15.0.2 iPhone XR 빌드를
PreBoard.app까지 부팅한 실험임 - 접근의 핵심은 macOS 12.0.1 부트 체인을 재사용하고 iOS 시스템 이미지,
mtree,root_hash,trustcache만 교체해 초기 부트 체인 수정 부담을 줄이는 것임 - 비공개
_setProductionModeEnabled(false)호출로 VM을 CPFM01상태로 낮추면, 공개 vma2 장치에 대해 TSS가 임의 펌웨어에 서명해 기존 vma2pwn 방식의 필요성이 줄어듦 - 실제 부팅에는 XNU 플랫폼 검사, system keybag, IOMFB 크기 검사, ramdisk,
launchd,mount, DYLD shared cache,lockdownd,mobileactivationd를 건드리는 커널·시스템 패치가 필요함 - 가장 큰 미해결 과제는 system keybag 호환성이며, 터치 입력도 Virtualization.framework의 비공개 API로 가능할지 아직 확인되지 않음
실험의 목표와 출발점
- Apple silicon 전환과 Mac Catalyst 이후 iOS와 macOS의 거리가 가까워졌고, macOS 가상화가 가능해지면서 iOS도 같은 계열의 부트 체인을 수정해 가상화할 수 있는지가 핵심 질문이 됨
- 이전 작업인 vma2pwn은 macOS 게스트 VM용으로 수정 가능한 vma2 macOS 부트 체인을 만드는 프로젝트였으며, 이번 실험의 기반이 됨
- 공개적으로 완성된 iOS 가상화/에뮬레이션 사례로는 Corellium의 virtual iPhone cloud 제품이 있음
- qemu-t8030, QEMU 기반 작업, Zhuowei Zhang의 글도 참고했으며, 특히 macOS의
IOSurfaceRoot와 iOS의IOCoreSurfaceRoot관계가 이후 커널 패치 탐색에 도움이 됨 - Zhuowei Zhang의 글은 GUI macOS 앱은 iOS에서 실행할 수 없지만 그래픽 iOS 앱은 macOS에서 실행할 수 있다고 봤고, 이 성질이 iOS 그래픽 시스템 상당 부분에도 적용됨
Virtualization.framework의 비공개 기능
- Apple의 Virtualization.framework에는 문서화되지 않은 private 함수
_setProductionModeEnabled(false)가 있음 - 이 호출과 VM 설정의 대응 기능은 VM을 Chip Fuse Mode인 CPFM
01로 낮춰, 가상 장치를 “secure”이면서 “non-production”인 상태로 구성함 - 물리 장치에서는 TSS가 CPFM
00또는01같은 non-production/non-secure 장치에 필요한 SHSH blob 서명을 거부함 - 공개 vma2 장치에 대해서는 TSS가 제공된 임의 펌웨어에 서명하므로, 수정 펌웨어 체인을 만들던 vma2pwn 방식의 필요성이 크게 줄어듦
iOS VM 구성 방식
- 가장 성공적이었던 접근은 macOS 12.0.1 부트 체인을 그대로 쓰고, 시스템 OS 이미지를 iOS 15.0.2 iPhone XR 빌드의 이미지와 관련 파일로 교체하는 방식임
- 교체 대상은 시스템 이미지,
mtree,root_hash,trustcache - 이 방식은 iOS 초기화 이전 단계의 부트 체인과 복구 ramdisk 수정 필요성을 대부분 우회함
- 교체 대상은 시스템 이미지,
- iPhone XR 빌드는 arm64e 지원과 낮은 해상도 가능성 때문에 선택됨
- 다른 arm64e 장치 구성에서도 성공 가능성이 있지만, vma2 커널은 일부 sysctl 키에 대해
"iPad8,6"을 반환하도록 하드코딩되어 있음 - arm64 빌드는 추가 문제와 바이너리 비호환성을 겪어 시도할 가치가 낮다고 판단됨
- VM 실행에는 Apple silicon VM 관리용 서드파티 앱 tart의 포크인 super-tart가 사용됨
- super-tart는 Virtualization.framework의 필요한 비공개 기능을 사용할 수 있게 함
_setProductionModeEnabled(false)설정 변경 등 일부 변경은 아직 모두 푸시되지 않음- private API를 쓰는 Virtualization.framework 도구는 SIP를 꺼야 하며, AMFI도 꺼야 할 수 있음
- 복원 도구로는 idevicerestore 포크가 사용됨
커널 패치
- vma2pwn에서 사용한 서명 검사 패치가 필요할 수 있지만, CPFM
01방식에서는 반드시 필요한지 확실하지 않음 - 서명 관련 패치는 vma2 커널에서 다음 함수들이 0을 반환하도록 만드는 형태임
_apfs_extract_root_hash_arm_authenticate_root_hash__img4_firmware_property_callback_is_root_hash_authentication_required_img4_firmware_evaluate
lookup_in_static_trust_cache는 1을 반환하도록 패치해야 함- iOS 바이너리는 시뮬레이터 플랫폼 바이너리가 아니기 때문에 초기에는
EXEC, [0xe] Binary with wrong platform로 종료됨- XNU의
PLATFORM_IOS검사 라인을 건너뛰도록 패치하면 해결됨 - vma2 커널에서는
B.NE를B로 바꾸는 방식으로 적용됨
- XNU의
- system keybag 비호환성 때문에
PreBoard.app이Setup.app대신 “Swipe up to upgrade.”를 표시함ipc_make_system_keybag에 두 가지 패치를 적용해 함수가 오류를 반환하지 않도록 강제하면PreBoard.app까지 도달 가능함- 이 한계는 아직 완전히 해결되지 않음
- iOS 시스템 프레임워크와 macOS 커널 사이의 IOMFB 구조체 크기 불일치는
CLCDTransaction size mismatch. Returning error 0x%X.문자열과 함께 커널 패닉을 일으킴IOMobileFramebufferUserClient::swap_submit의 크기 검사를 제거하면 패닉이 멈춤
시스템 파일 패치 준비
- 복구 ramdisk와 iOS 시스템 파일은 서명되어 있으므로 패치 후 재서명하지 않으면 실행 시 종료됨
- 수정 환경에서는 Procursus의
ldid사용이 제안됨- 설치 예:
brew install ldid-procursus - 재서명 예:
ldid_macosx_arm64 -S -M <binary> -S는 바이너리를 pseudo-sign하고,-M은 기존 entitlements를 보존함
- 설치 예:
- 많은 바이너리는 identity 검사를 하므로 재서명 전에 이름을 identity로 바꿨다가 다시 원래 이름으로 되돌려야 함
keybagd는codesign -d -v keybagd로Identifier=com.apple.keybagd를 확인함mv keybagd com.apple.keybagdldid_macosx_arm64 -S -M com.apple.keybagdmv com.apple.keybagd keybagd
- 자동으로 심볼이 붙지 않은 함수는 문자열 XRef를 찾고, 로깅 호출 참조를 통해 호출 함수를 추적하는 방식으로 찾을 수 있음
- 직렬 터미널에서 상호작용 가능한 셸을 얻기 위해 Bash와 LaunchDaemon을 포팅할 수 있음
- Procursus 같은 버전 호환 iOS jailbreak payload에서 관련 파일을 복사하는 방식이 사용됨
DMG와 ramdisk 수정
- 시스템이나 복구 ramdisk를 패치하려면 DMG 볼륨을 직접 수정해야 함
- iOS 15.0.2 예시에서는 IPSW에 포함된 iOS System 볼륨을 읽기/쓰기 가능한 형태로 변환함
hdiutil convert -format UDRW -o 018-66258-074-rw.dmg 018-66258-074.dmg- 마운트 후
sudo mount -uw /Volumes/Sky19A404.N104N841OS - 수정 후
hdiutil convert -format ULFO -o 018-66258-074.dmg 018-66258-074-rw.dmg - 이후
asr imagescan --source 018-66258-074.dmg
- iOS 부팅 전 복구 ramdisk의
/usr/local/bin/restored_external수정이 필요함- iOS 버전
restored_external은MKBKeyBagCreateSystem으로 system keybag을 만들려고 하지만 macOS 커널과 호환되지 않음 - 오류 검사를 제거하는 방식으로 우회함
ramrod_set_NVRAM_variable호출 조건을 패치해allow-root-hash-mismatch를 true, 즉1로 설정하고 root hash 인증을 건너뜀
- iOS 버전
- 복구 ramdisk의
/usr/sbin/asr도 수정해야 함- iSuns9의 asr64_patcher를 사용할 수 있음
"Image failed signature verification."문자열을 출력하는 함수를 찾고, 이를 참조하는 함수에서 ARMv8-ABL호출을"Image passed signature verification"경로로 가는B점프로 바꿈- iOS 15.0.2의
asr바이너리에서는 파일 오프셋0x27A18에b #0x7c를 적용함
iOS 시스템 볼륨 수정
- iOS 시스템 자체를 macOS 커널과 맞추려면, 시스템 볼륨에서 파일시스템 루트에 설치될 대부분의 파일을
/System/Library/Templates/Data로 옮겨야 함 - 시스템이 부팅되면 이 파일들은
/에 존재하게 됨 - 일반 루트의 빈 폴더는 실제로 비어 있어도 시스템 볼륨에 남아 있어야 할 수 있음
- 예시로
/Applications가 있음 - 이 부분은 충분히 테스트되지 않음
- 예시로
launchd와 keybagd 패치
- iOS 부팅 초기에
/sbin/launchd가 실행되며, 실패 없이 초기 부팅 프로세스를 시작하려면 패치가 필요함 - 첫 패치는 바이너리에 내장된 설정 plist에 적용됨
<key>SIGTERMTimeout</key>문자열을 찾고 약 172바이트 앞을 보면 해당 설정을 찾을 수 있음mount-phase-2,fips,tzinit,finish-demo-restore,fud,xpcroleaccountd,prng_seedctl,MSUEarlyBootTask섹션에<key>PerformAfterUserspaceReboot</key><true/>를 추가함data-protection섹션의RequireSuccess를<false/>로 바꿈
data-protection변경은/usr/libexec/init_data_protection이 VM에서 실행될 때 실패하기 때문에 필요함- 해당 파일은
/usr/libexec/seputil에 심볼릭 링크되어 있음
- 해당 파일은
- 내장 plist 변경은 기존 문자열 공간을 넘을 수 있으므로 XML minimizer로 압축한 XML을 붙여 넣고 남은 영역을 XML 친화적인 공백 문자로 덮어쓸 수 있음
launchd는/usr/libexec/keybagd도 초기화하지만, 이 바이너리는 앞선 커널 차이 때문에 실패함- 우회 방법 중 하나는 단순히 종료 코드 0으로 끝나는 실행 파일을 컴파일해
keybagd를 대체하는 것임 - fixkeybag 프로젝트도 검토됐지만, 해당 앱의 system keybag 생성 코드도
MKBKeyBagCreateSystem을 호출하므로 부팅된 iOS 상태에서도 실패함
- 우회 방법 중 하나는 단순히 종료 코드 0으로 끝나는 실행 파일을 컴파일해
launchd에는Userspace reboot changed system version: previous %s != current %s패닉 문자열을 유발하는 조건 분기TBZ를NOP처리하는 추가 패치가 필요함
mount, DYLD shared cache, 그래픽 계층 수정
- macOS 부트 체인과 macOS ramdisk로 복원 과정을 처리하기 때문에, iOS의
/sbin/mount는 생성된 APFS 볼륨을 제대로 다루지 못함 - 해결책은 macOS 시스템 볼륨의
mount바이너리를 가져와 Mach-O 메타데이터를 iOS에서 실행 가능하도록 수정한 뒤 교체하는 것임- 수동으로도 가능하며 macOS에 포함된
vtool도 사용할 수 있음
- 수동으로도 가능하며 macOS에 포함된
- 더 어려운 수정은 DYLD shared cache 패치임
- macOS의
IOSurfaceRoot와 iOS의IOCoreSurfaceRoot는 기본적으로 같은 드라이버지만 이름 차이 때문에 호환되지 않음 - iOS DYLD shared cache에는 더 긴 문자열인
"IOCoreSurfaceRoot"가 있으므로 이를"IOSurfaceRoot"로 바꾸고 남은 바이트를0x00으로 채움
- macOS의
- DYLD shared cache 분석과 추출에는 blacktop의 ipsw 도구가 사용됨
ipsw dyld split <dsc file>로 내장 dylib를 분리함/System/Library/Frameworks/IOSurface.framework/IOSurface바이너리에서__iosConnectInitalize함수의"IOCoreSurfaceRoot"참조를 찾음ipsw dyld a2o로 가상 주소를 파일 오프셋으로 변환함- 예시에서는
dyld_shared_cache_arm64e의0x28fde373오프셋을 패치함
- DYLD shared cache 수정 후 다른 위치의 cdhash가 맞지 않아 예외가 발생함
- Virtualization.framework 프런트엔드가 제공하는 GDB stub로 커널의
cs_validate_hash함수에 브레이크포인트를 걸어 전체 cdhash를 확인함 - iOS 15.0.2 예시에서는 파일 오프셋
0x5a9cffc0의 기존 바이트를 새 cdhash로 패치함
- Virtualization.framework 프런트엔드가 제공하는 GDB stub로 커널의
시스템 데몬과 활성화 패치
watchdogd는 VM에서 실행 중인지 확인하고 정상 종료하는 macOS 코드 경로가 없어 crash-loop를 일으키지만, 이 크래시는 해롭지 않은 것으로 취급됨backboardd에서는PreBoard.app을 유발할 수 있는 데이터 마이그레이션 관련 호출 패치를 실험했지만, Apple 로고에서 멈추는 것 외에 차이를 확인하지 못함lockdownd의get_device_type_internal_block_invoke함수에서는"ShouldHactivate"에 대한getMGInt호출을mov x0, #1로 패치해 개발 환경에서 일반 iOS 활성화 제한을 우회하는 hactivation을 강제함mobileactivationd도 hactivation을 허용하도록 패치할 수 있음shouldHactivate함수를 ARMv8-A 명령어mov x0, #0과ret로 바꿈
- vma2 device tree에 필요한 추가 수정은 독자가 직접 처리할 과제로 남음
- 일부 동작을 위해
chmod -R 777 /같은 비정상적인 조치가 필요할 수 있음
남은 한계와 터치 입력
- system keybag 문제를 넘어서려면 iOS 시스템과 커널에 존재하는 해당 구조를 더 이해하고 추가 패치를 만들어야 함
- 프로젝트에는 이미 최소 수백 시간이 들어갔고, 현재 공개된 진행 상태는
PreBoard.app까지 부팅하는 수준임 - public vma2 Mac 커널과 펌웨어를 통해 터치 기능이 동작하는지는 아직 확인되지 않음
- Virtualization.framework에는 터치 관련 비공개 API가 있음
_VZAppleTouchScreenConfiguration_VZUSBTouchScreenConfiguration_VZTouch_VZMultiTouchEvent
- 이 API들로 터치 이벤트를 보낼 수 있지만, 정확한 파라미터 사용법은 완전히 파악되지 않음
TouchPhaseenum은NSTouch.Phase와 같은 이름의 값을 구현하는 단순 enum임- 예제 코드는 때때로 예외를 만들 수 있음
- 좌표값이 VM이 기대하는 방식으로 매핑되는지, VM이 이를 처리할 수 있는지도 확인되지 않음
- 데모는 부팅 시퀀스를 보여주며, 중간의 약 30초 대기 구간은 잘려 있음