2P by GN⁺ | ★ favorite | 댓글 1개
  • 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을 CPFM 01 상태로 낮추면, 공개 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.NEB로 바꾸는 방식으로 적용됨
  • system keybag 비호환성 때문에 PreBoard.appSetup.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로 바꿨다가 다시 원래 이름으로 되돌려야 함
    • keybagdcodesign -d -v keybagdIdentifier=com.apple.keybagd를 확인함
    • mv keybagd com.apple.keybagd
    • ldid_macosx_arm64 -S -M com.apple.keybagd
    • mv 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_externalMKBKeyBagCreateSystem으로 system keybag을 만들려고 하지만 macOS 커널과 호환되지 않음
    • 오류 검사를 제거하는 방식으로 우회함
    • ramrod_set_NVRAM_variable 호출 조건을 패치해 allow-root-hash-mismatch를 true, 즉 1로 설정하고 root hash 인증을 건너뜀
  • 복구 ramdisk의 /usr/sbin/asr도 수정해야 함
    • iSuns9의 asr64_patcher를 사용할 수 있음
    • "Image failed signature verification." 문자열을 출력하는 함수를 찾고, 이를 참조하는 함수에서 ARMv8-A BL 호출을 "Image passed signature verification" 경로로 가는 B 점프로 바꿈
    • iOS 15.0.2의 asr 바이너리에서는 파일 오프셋 0x27A18b #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 상태에서도 실패함
  • launchd에는 Userspace reboot changed system version: previous %s != current %s 패닉 문자열을 유발하는 조건 분기 TBZNOP 처리하는 추가 패치가 필요함

mount, DYLD shared cache, 그래픽 계층 수정

  • macOS 부트 체인과 macOS ramdisk로 복원 과정을 처리하기 때문에, iOS의 /sbin/mount는 생성된 APFS 볼륨을 제대로 다루지 못함
  • 해결책은 macOS 시스템 볼륨의 mount 바이너리를 가져와 Mach-O 메타데이터를 iOS에서 실행 가능하도록 수정한 뒤 교체하는 것임
    • 수동으로도 가능하며 macOS에 포함된 vtool도 사용할 수 있음
  • 더 어려운 수정은 DYLD shared cache 패치임
    • macOS의 IOSurfaceRoot와 iOS의 IOCoreSurfaceRoot는 기본적으로 같은 드라이버지만 이름 차이 때문에 호환되지 않음
    • iOS DYLD shared cache에는 더 긴 문자열인 "IOCoreSurfaceRoot"가 있으므로 이를 "IOSurfaceRoot"로 바꾸고 남은 바이트를 0x00으로 채움
  • 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_arm64e0x28fde373 오프셋을 패치함
  • DYLD shared cache 수정 후 다른 위치의 cdhash가 맞지 않아 예외가 발생함
    • Virtualization.framework 프런트엔드가 제공하는 GDB stub로 커널의 cs_validate_hash 함수에 브레이크포인트를 걸어 전체 cdhash를 확인함
    • iOS 15.0.2 예시에서는 파일 오프셋 0x5a9cffc0의 기존 바이트를 새 cdhash로 패치함

시스템 데몬과 활성화 패치

  • watchdogd는 VM에서 실행 중인지 확인하고 정상 종료하는 macOS 코드 경로가 없어 crash-loop를 일으키지만, 이 크래시는 해롭지 않은 것으로 취급됨
  • backboardd에서는 PreBoard.app을 유발할 수 있는 데이터 마이그레이션 관련 호출 패치를 실험했지만, Apple 로고에서 멈추는 것 외에 차이를 확인하지 못함
  • lockdowndget_device_type_internal_block_invoke 함수에서는 "ShouldHactivate"에 대한 getMGInt 호출을 mov x0, #1로 패치해 개발 환경에서 일반 iOS 활성화 제한을 우회하는 hactivation을 강제함
  • mobileactivationd도 hactivation을 허용하도록 패치할 수 있음
    • shouldHactivate 함수를 ARMv8-A 명령어 mov x0, #0ret로 바꿈
  • vma2 device tree에 필요한 추가 수정은 독자가 직접 처리할 과제로 남음
  • 일부 동작을 위해 chmod -R 777 / 같은 비정상적인 조치가 필요할 수 있음

남은 한계와 터치 입력

  • system keybag 문제를 넘어서려면 iOS 시스템과 커널에 존재하는 해당 구조를 더 이해하고 추가 패치를 만들어야 함
  • 프로젝트에는 이미 최소 수백 시간이 들어갔고, 현재 공개된 진행 상태는 PreBoard.app까지 부팅하는 수준임
  • public vma2 Mac 커널과 펌웨어를 통해 터치 기능이 동작하는지는 아직 확인되지 않음
  • Virtualization.framework에는 터치 관련 비공개 API가 있음
    • _VZAppleTouchScreenConfiguration
    • _VZUSBTouchScreenConfiguration
    • _VZTouch
    • _VZMultiTouchEvent
  • 이 API들로 터치 이벤트를 보낼 수 있지만, 정확한 파라미터 사용법은 완전히 파악되지 않음
    • TouchPhase enum은 NSTouch.Phase와 같은 이름의 값을 구현하는 단순 enum임
    • 예제 코드는 때때로 예외를 만들 수 있음
    • 좌표값이 VM이 기대하는 방식으로 매핑되는지, VM이 이를 처리할 수 있는지도 확인되지 않음
  • 데모는 부팅 시퀀스를 보여주며, 중간의 약 30초 대기 구간은 잘려 있음

댓글과 토론

Hacker News 의견들
  • Corellium은 법적 분쟁에서 이겨서 보안 연구용 iOS 클라우드 VM을 임대할 수 있게 됐음 https://hn.algolia.com/?query=corellium
    iOS를 Apple Silicon MacBook에서 가상화할 수 있다면 상용 iOS 가상화 서비스 수요가 줄어들 수 있음
    개인은 월 $400, 기업은 연 $60,000 수준임 https://support.corellium.com/subscriptions/pricing

    • 맙소사, 시간당 $4~$8인데 누가 이런 VM에 돈을 내는지 궁금함
  • 좋음. 다음에는 iPad에 macOS를 설치하는 방법도 찾아내서, Apple이 만들어주길 바라던 그 컴퓨터를 드디어 쓸 수 있게 해주면 좋겠음

    • Windows XP부터 시작할 수 있음
      https://www.theverge.com/2024/7/22/24200536/windows-xp-ipad-...
    • 선행 연구 부분에 따르면, [Zhuowei Zhang]은 GUI macOS 앱은 iOS에서 실행될 수 없지만, 그래픽 iOS 앱은 macOS에서 실행될 수 있다고 결론냈음
      Mac Catalyst도 예상대로 한 방향으로만 동작하는 듯함
    • 정말 1000번 공감함. 지난 몇 주 동안 딱 이 주제, 즉 개발용 운영체제가 올라가는 태블릿을 찾고 있었음
      크기만 보면 iPad Mini가 좋은데 iPadOS는 쓸모가 없고, 지금은 Surface Go를 보고 있음. 다만 약간 큼
      MacOS가 들어간 제품은 없다는 걸 알지만, Win11이 되는 작은 태블릿 추천이 있으면 알려주면 좋겠음. 필요하면 중국에서 주문할 생각도 있음
    • iPad Pro는 M4가 들어간 가장 빠른 기기
    • 위아래가 뒤집힌 돌출부와 분리형 키보드가 달린 MacBook Air라고 부르면 그걸로 충분한 건가?
  • 작성자의 GitHub 프로필을 보니 갓 컴퓨터공학 졸업생인 것 같은데, 정말 인상적인 작업임

    • Apple에서 채용 제안이 곧 오지 않을까 싶음
  • Apple이 Simulator를 Emulator로 만들지 않은 이유는 사람들이 iOS 내부 기반층을 파헤치는 걸 원하지 않아서라는 느낌이 듦

    • 애초에 Simulator였고 Emulator가 아니었던 또 다른 이유는, 당시 iOS 또는 iPhone OS의 많은 구성요소가 기존 Mac OS X 라이브러리의 포크였기 때문일 수도 있음
    • 개발자들은 아직 Intel Mac도 쓰고 있고, 거기서는 ARM iOS를 가상화할 수 없음
    • iPhone, Mac, iPad가 모두 arm64, 그것도 Apple Silicon 기반이 된 지금은 iOS와 macOS의 부트로더가 얼마나 다른지 정말 궁금함
      부트로더를 지나고 나면, 특히 Apple이 하드웨어까지 통제하는 상황에서 굳이 두 운영체제와 많은 차이를 계속 유지할 이유가 있는지 의문임
  • qemu-t8030을 만든 사람이 SpringBoard를 실행하는 데 성공했지만 https://mastodon.social/@ntrung03/109712247237110967, 코드는 공개하지 않았음
    그 진전이 이번 작업과 합쳐질 수 있으면 훌륭할 듯함

  • 과거 댓글: https://news.ycombinator.com/item?id=40219423

  • 관련 글: https://worthdoingbadly.com/hv/
    탈옥된 iPhone 12 / iOS 14.1에서 하드웨어 가속 가상 머신을 다룸

  • 약간 곁가지지만, 누가 ARM macOS를 x86-64에서 가상화한 적이 있는지 궁금함

    • 불가능함. 일반적으로 “가상화”는 하드웨어 가상화를 통해 운영체제를 실행한다는 뜻이고, 호스트 CPU가 코드를 네이티브로 실행하면서 모든 입출력을 하이퍼바이저로 넘기는 방식임
      그래서 호스트 시스템과 같은 CPU 아키텍처용으로 빌드된 운영체제만 가상화할 수 있음
      ARM 소프트웨어를 x86에서 실행하거나 그 반대처럼 나머지 경우에는 에뮬레이션을 써야 하고, 이는 코드를 해석하거나 동적으로 재컴파일하는 방식임
      정의상 어떤 것도 다른 어떤 것 위에서 에뮬레이션할 수는 있음. 최근에는 최초의 마이크로프로세서인 Intel 4004에서 MIPS용 Linux를 부팅한 사례도 있지만, 성능이 문제가 될 수 있음
    • QEMU에서 일반 ARM을 가상화해 보면 Raspberry Pi 성능에도 못 미친다는 걸 알 수 있음. 최근 버전에는 기본으로 들어 있을 것임
      Mn CPU를 가상화하는 건 더 쓸모가 적을 듯함
    • Hackintosh 프로젝트를 살펴보면 좋음
  • Apple은 이미 Xcode에 iOS Simulator를 제공하는데, 이 프로젝트가 Apple 제공 도구보다 나은 점이 무엇인지 궁금함

    • Simulator는 실제 iOS나 앱의 iOS 빌드를 실행하는 게 아님. Simulator에서 앱을 실행하면 앱은 현재 Mac의 네이티브 명령어 집합으로 컴파일되고, 기대되는 iOS 동작을 흉내 내거나 일부는 껍데기만 제공하는 Mac 프레임워크와 라이브러리에 링크되어 실행됨
      예를 들어 App Store에서 iOS 바이너리를 가져와 iOS Simulator에서 그대로 실행할 수 없고, 특히 Intel Mac에서는 더더욱 안 됨
      Simulator는 완전한 iOS를 실행하지 않기 때문에 실제 iOS 내부가 어떻게 동작하는지 조사하고 배울 수도 없음. Simulator의 프레임워크를 충분히 깊게 파고들면 결국 macOS로 돌아오게 됨
      반면 에뮬레이터는 실제 기기와 동일한 전체 iOS 빌드를 실행하는 방식임. 이론상 어떤 iOS 바이너리든 수정 없이 실행하고 실제 운영체제가 어떻게 동작하는지 조사할 수 있음
      Wine으로 앱을 실행하는 것과 Windows VM에서 앱을 실행하는 것의 차이와 비슷함. 다만 Simulator의 경우에는 Windows 앱을 실행하기 전에 Wine 환경에 맞춰 별도로 재컴파일하고 링크해야 하는 상황에 더 가까움
      Windows 내부를 연구하고 싶다면 Wine 실행만으로 배울 수 있는 건 많지 않지만, Windows VM을 조사하면 훨씬 많은 걸 배울 수 있음
  • 클릭팜들에게는 이른 크리스마스 선물이 되겠음