
ios-sweet는 Xiaomi Redmi Note 10 Pro에서 Apple의 iOS 16을 구동하는 실험 프로젝트다. XNU 커널과 iPhone X용 iOS 16.7.16 사용자 공간을 이식했으며, 현재 잠금 화면과 홈 화면, 설정 등을 사용할 수 있지만 일상적으로 쓸 수 있는 운영체제는 아니다.
이 프로젝트는 Apple의 오픈소스 XNU 커널(xnu-8796, iOS 16 세대)을 Xiaomi Redmi Note 10 Pro(sweet, Qualcomm SM7150 / Snapdragon 732G)에 이식한다. 그 위에서 수정하지 않은 iPhone X용 iOS 16.7.16(20H392) 사용자 공간을 실행한다. 여기에는 launchd, 시스템 데몬, backboardd, SpringBoard와 기본 앱이 포함된다.
2026년 10월 18일 기준으로 기기는 iOS 홈 화면까지 부팅된다. 잠금을 해제하고 설정을 열 수 있으며, 제어 센터와 알림 센터를 내리고 터치스크린과 하드웨어 버튼을 사용할 수 있다. 그래픽은 GPU 드라이버 없이 전부 CPU로 렌더링한다.
이는 실험적인 초기 구동(bring-up)이며, 사용할 수 있는 휴대전화 운영체제가 아니다. 많은 기능이 빠져 있거나 스텁으로 대체돼 있고, Apple의 보안 모델도 대부분 꺼져 있다. 설치 과정에서 userdata 파티션을 사용하므로 Android 사용자 데이터가 지워진다.
작동하는 기능과 한계
| 영역 | 상태 |
|---|---|
| 부팅 | 기본 Xiaomi 부트로더에서 fastboot boot로 shim을 실행한 뒤 XNU를 구동한다. 부팅 파티션에는 아무것도 플래시하지 않는다. |
| CPU | PSCI를 통해 8개 코어 중 Cortex-A55 코어 6개를 사용한다. 16K 페이지, GICv3 인터럽트와 타이머를 지원한다. |
| 메모리 | DDR 상위 뱅크에서 약 3.95 GiB를 사용한다. |
| 저장장치 | 자체 UFS 호스트 드라이버를 사용한다. userdata 안의 저널링 HFS+ 볼륨이 iOS 루트이며 읽기와 쓰기가 가능하다. |
| iOS 사용자 공간 | 실제 iPhone X의 16.7.16 루트 파일 시스템과 공유 캐시를 사용하며 launchd와 시스템 데몬이 실행된다. |
| 디스플레이 | 커널의 IOSurface와 IOMobileFramebuffer(AppleCLCD) 서비스를 사용한다. CoreAnimation이 소프트웨어로 렌더링한 뒤 프레임을 부트로더의 프레임버퍼에 복사한다. iOS에는 1125×2436 iPhone X 패널로 표시된다. |
| 그래픽 인터페이스 | backboardd, SpringBoard, 잠금 화면, 홈 화면, 제어 센터, 알림 센터와 설정이 작동한다. |
| 터치 | I²C(GENI)로 연결된 Goodix GT9896을 사용한다. RPMh를 통해 전원 레일을 켜고 HID 디지타이저 이벤트를 전달한다. |
| 버튼 | 전원과 볼륨 위·아래 버튼을 SPMI의 PMIC를 통해 HID 소비자 키로 처리한다. 잠금과 깨우기가 작동한다. |
| 배터리 | IOPMPowerSource 서비스가 정적인 100% 잔량과 충전 아이콘을 표시한다. 실제 연료 게이지 값 읽기는 준비 중이다. |
| 기기 식별 | 홈 버튼이 없고 제스처 UI를 쓰는 iPhone X(iPhone10,3 / D22AP)로 식별된다. |
| 활성화 | 기기 트리의 보안 상태를 기준으로 mobileactivationd의 기본 “Hactivation” 경로를 선택한다. Apple 서버는 사용하지 않는다. |
아직 앱에서 빠져나오는 방법이 없다. 홈 제스처와 AssistiveTouch의 홈 기능은 작동하지 않는다. Wi-Fi, Bluetooth, 셀룰러, 오디오, 카메라, 센서, GPS, SEP / Face ID도 지원하지 않는다. 관련 데몬은 시작한 뒤 실패하거나 계속 재시작한다.
GPU는 없다. 모든 화면을 CPU로 그려 초당 약 20~30프레임을 낸다. Cortex-A76 코어 두 개는 아직 사용하지 않는다. 시스템 볼륨에 들어 있는 앱만 있어 앱 수가 적고, App Store와 Apple ID도 없다. 설정의 일부 페이지는 비어 있다. Apple 로고 화면에서 부팅하는 데 약 4분이 걸린다.
보안 기능도 꺼져 있다. 코드 서명(AMFI)과 샌드박스 정책이 허용적으로 설정됐고, AppleKeyStore는 스텁이며 데이터 보호 기능도 없다. 개인 정보를 기기에 저장하지 말아야 한다.
부팅 구조와 로그
부팅 흐름은 다음과 같다.
ABL (stock Xiaomi bootloader, unlocked) -> fastboot boot shim-boot.img (temporary; nothing is flashed to boot) -> shim: own MMU setup, reads the live FDT, loads the payload from the ramdisk -> XNU Mach-O + generated Apple Device Tree (ADT) + boot-args -> XNU -> UFS driver -> HFS+ root in the userdata partition -> dyld + shared cache -> launchd -> daemons -> backboardd + SpringBoard ```
기판에서 디버그 UART가 SPI로 연결돼 있어 시리얼 포트는 없다. 커널 콘솔은 ABL이 `0x9c000000`에 남겨 둔 프레임버퍼에 표시한다. `userdata` 끝에는 4 MiB 크기의 영구 로그 링이 저장된다. 화면을 촬영하지 않아도 OrangeFox 복구 환경에서 `tools/sweet-log-pull.sh`로 로그를 읽을 수 있다.
로그에는 역추적 정보가 포함된 충돌 보고서(`CRASH name ...`), `backboardd`와 SpringBoard의 IOKit 추적, 드라이버의 `SWEET ...` 메시지가 들어간다. `tools/sweet-crash.py`는 공유 캐시를 기준으로 충돌 로그의 심볼을 해석한다.
## 직접 부팅할 때 필요한 조건
필요한 기기는 부트로더가 잠금 해제된 Redmi Note 10 Pro(`sweet`, 글로벌 또는 인도 버전, SM7150)다. Linux PC와 커널 빌드용 GitHub 계정도 필요하다. 아래 절차는 프로젝트의 스크린샷에 사용한 경로이며, 다른 변형 모델이나 펌웨어에서는 변경이 필요할 수 있다. `userdata`의 내용은 모두 사라지지만 부트로더, 복구, 모뎀, `persist` 파티션은 건드리지 않는다.
### 백업
OrangeFox 또는 adb 루트 권한이 있는 복구 환경을 임시로 부팅하고, 최소한 `persist`, `modemst1`, `modemst2`, `fsg`, `fsc`, `boot`, `dtbo`, `vbmeta`를 가져온다. 이 파티션에는 기기별 보정 데이터가 들어 있다. 백업을 Git에 올리지 말아야 한다. `.gitignore`는 이미 `backup/`, `dtb/`, `recovery/`를 제외한다.
shim에는 기본 DTB가 필요하다. 기기의 기본 `boot.img`를 다음과 같이 푼다.
```bash
python3 tools/unpack_bootv2.py backup/boot.img dtb/boot_unpacked # -> dtb/boot_unpacked/dtb
커널 빌드
커널은 blacktop/darwin-xnu-build를 사용해 GitHub의 macOS 러너에서 빌드한다. 워크플로는 xnu-8796을 가져와 xnu/patches/를 순서대로 적용하고, 필요한 고정 버전의 오픈소스 libpthread, HFS, BearSSL 소스도 준비한다. 저장소를 포크한 뒤 실행한다. 공개 저장소는 macOS 실행 시간을 무료로 사용할 수 있다.
gh workflow run xnu.yml -R YOU/ios-sweet
gh run download RUN_ID -R YOU/ios-sweet -n xnu-kernel -D xnu/out
CI 실행 시간을 쓰기 전에 패치를 로컬에서 확인하려면 tools/xnu-cc.py가 이전 CI 로그의 컴파일 명령을 재생해 개별 파일을 빌드한다.
iOS 루트 이미지 빌드
Apple에서 iPhone X(iPhone10,3)용 iOS 16.7.16(20H392) IPSW를 내려받는다. Linux에서 apfs-fuse, hfsprogs와 sudo 권한을 사용해 다음을 실행한다.
# OS, SystemOS and AppOS disk images from the IPSW (see BuildManifest.plist for their names)
tools/mkiosroot.sh OS.dmg SystemOS.dmg AppOS.dmg ios16-root.img 16G
sudo fsck.hfsplus -fy ios16-root.img
sudo mount -t hfsplus -o loop,force ios16-root.img /mnt/iosroot
sudo python3 tools/sweet-root-tweaks.py /mnt/iosroot
sudo umount /mnt/iosroot
img2simg ios16-root.img ios16-root.simg
mkiosroot.sh는 OS 볼륨과 두 cryptex를 하나의 대소문자 구분 저널링 HFS+ 볼륨으로 복사한다. 데몬이 요구하는 dyld 캐시 링크, 일반 시스템 키백, 로그 디렉터리도 추가한다. sweet-root-tweaks.py는 초기 구동에 필요한 변경을 적용한다. 설정 절차를 건너뛰고 활성화 상태를 미리 넣으며, 베이스밴드 데몬을 제거하고 느린 앱을 종료시키는 감시 기능과 충돌을 반복하는 미디어 경로를 비활성화하는 등의 작업을 한다. 스크립트 헤더에서 각 변경 사항을 설명한다.
기기 트리 생성기는 실제 iPhone X 기기 트리의 /product를 복사한다. MobileGestalt에 필요하기 때문이다. IPSW 전체를 내려받지 않고 한 번만 추출한다.
python3 tools/remotezip.py IPSW_URL DeviceTree.d22ap.im4p research/ipsw-x/dt
ipsw img4 im4p extract research/ipsw-x/dt/Firmware/all_flash/DeviceTree.d22ap.im4p -o research/ipsw-x/d22ap.adt
부팅 이미지 빌드
aarch64-linux-gnu-gcc와 mkbootimg가 필요하다.
python3 tools/mkadt.py shim/out/sweet.adt --highmem
python3 tools/mkpayload.py xnu/out/kernel.development.sweet shim/out/sweet.adt shim/out/payload.bin \
--cmdline 'debug=0xc serial=3 cpus=6 keepsyms=1 -enable_kprintf_spam -noprogress rd=ufsroot0 unentitled_ios_sim_launch=1 ignition_level=1 wdt_test=no_user_checkin ufsrw=1 rootrw=1 launchd_unsecure_cache=1 wdt=-1 sweetfb_noconsole=1 sweetfb_hz=30'
shim/build.sh ../shim/out/payload.bin # -> shim/out/shim-boot.img
일부 부팅 인수의 의미는 다음과 같다.
| 부팅 인수 | 효과 |
|---|---|
rd=ufsroot0 ufsrw=1 rootrw=1 | userdata 파티션을 쓰기 가능한 루트로 사용한다. |
cpus=6 | A55 코어 6개를 시작한다. 안전한 대체 설정은 cpus=1이다. |
launchd_unsecure_cache=1 | AMFI 없이 launchd가 작업 캐시를 받아들이게 한다. |
wdt=-1 wdt_test=no_user_checkin | 하드웨어 감시 기능을 끈다. |
sweetfb_noconsole=1 | GUI 위에 커널 텍스트를 그리지 않는다. 텍스트는 영구 로그에 남고 커널 패닉은 화면에 표시된다. |
sweetfb_hz=30 | CPU 복사 방식의 패널 주사율을 설정한다. |
sweettouch=0, sweetkeys=0 | 터치 드라이버 또는 버튼 드라이버를 끈다. |
루트와 부팅 이미지 기록
fastboot -S 512M flash userdata ios16-root.simg # ~7 minutes; do not unplug
# Xiaomi's fastboot often hangs after a large flash: re-enter fastboot with Power + Volume down
fastboot boot shim/out/shim-boot.img
처음 부팅할 때 Apple 로고 화면에서 약 4~5분이 걸린 뒤 잠금 화면이 나타난다. 위로 쓸어 올려 잠금을 해제한다. fastboot로 돌아가려면 전원 버튼과 볼륨 아래 버튼을 길게 누른다.
커널 로그 읽기
fastboot boot recovery/recovery.img # OrangeFox, temporary
tools/sweet-log-pull.sh logs/boot.txt # 4 MiB console ring from the end of userdata
adb reboot bootloader
QEMU와 저장소 구성
tools/qemu-sweet.py는 테스트용 RAM 디스크 루트를 사용해 같은 커널을 QEMU virt에서 부팅한다. 커널만 변경할 때 유용하며 자세한 내용은 NOTES.md에 있다.
저장소의 주요 경로와 역할은 다음과 같다.
| 경로 | 역할 |
|---|---|
shim/ | C와 어셈블리로 만든 베어메탈 부팅 shim. Android boot.img v2 형식으로 묶는다. |
xnu/patches/ | CI가 순서대로 적용하는 XNU 패치와 libpthread, HFS, BearSSL 준비 스크립트다. |
.github/workflows/xnu.yml | 커널 CI 빌드 워크플로다. |
tools/mkadt.py | sweet의 MMIO 배치와 iPhone X 식별 정보를 바탕으로 Apple Device Tree를 생성한다. |
tools/mkpayload.py | 커널, ADT, 부팅 인수를 shim 페이로드에 묶는다. |
tools/mkiosroot.sh | IPSW 이미지에서 HFS+ iOS 루트를 만든다. |
tools/sweet-root-tweaks.py | 설정 건너뛰기, 활성화, 데몬 변경 등 루트 초기 구동 작업을 적용한다. |
tools/sweet-log-pull.sh, sweet-log.py | 복구 환경에서 영구 커널 로그를 읽는다. |
tools/sweet-crash.py | 콘솔 충돌 보고서의 심볼을 해석한다. |
tools/xnu-cc.py | CI 로그를 이용해 개별 파일을 로컬에서 컴파일 확인한다. |
tools/qemu-sweet.py | 프레임버퍼 텍스트 디코딩을 포함한 QEMU 테스트 부팅 도구다. |
tools/adt.py, remotezip.py, unpack_bootv2.py, sparsediff.py | 보조 도구다. |
NOTES.md | 모든 부팅과 충돌, 결정 사항, 하드웨어 정보를 담은 상세 엔지니어링 기록이다. |
파티션 백업, 실제 기기 트리, 복구 이미지, Apple 펌웨어와 루트 이미지는 Git에 포함하지 않는다. 이 자료에는 기기 일련번호와 보정 데이터, 또는 Apple의 저작권이 적용되는 파일이 들어 있다.
커널 패치
| 패치 | 내용 |
|---|---|
| 0001–0009 | 툴체인 호환성, SWEET 머신 설정, PAC를 사용하지 않는 arm64, Apple 내부 구성 요소의 스텁 |
| 0010–0018 | 초기 프레임버퍼 콘솔, physmap 수정, corecrypto 없는 PRNG, 기기 트리 플랫폼 전문가, 타이머 PPI, 부팅 CPU 드라이버 |
| 0019–0020 | Apple 오픈소스 libpthread 커널 제공자, BearSSL AES |
| 0021–0027 | RAM 디스크 루트, mockfs, SHA-256, 최소 AMFI, Apple 오픈소스 HFS+, MD5 |
| 0028–0034 | UFS 호스트 드라이버와 루트 장치, 읽기·쓰기 루트, 4 GiB 초과 DMA |
| 0035–0045 | 자격(entitlements), bindfs, AppleKeyStore 스텁, IOWatchdog, 콘솔 충돌 보고서 |
| 0046 | IOSurface(IOCoreSurfaceRoot)와 IOMobileFramebuffer(AppleCLCD) 서비스 |
| 0047–0055 | 진단 기능: 영구 로그, /dev/null tee, IOKit 추적, 충돌 시 힙 문자열과 사용자 스택 샘플러, 허용적 샌드박스 정책, HFS 이름 변경 교체, AppleKeyStore 선택자 |
| 0056–0057 | 주사율을 제한한 패널 복사, 외부 식별자 확인 기능 없음 |
| 0058 | HID 이벤트 서비스. iOS 16.7 사용자 공간에 맞춘 Apple IOHIDFamily-1915.140.3 사용자 클라이언트와 이벤트 큐 |
| 0059 | iPhone X 식별 정보(hw.product / hw.target)를 기기 트리에서 가져온다. |
| 0060 | SPMI를 통한 전원·볼륨 키와 GENI I²C를 통한 터치 |
| 0061 | 디스플레이를 1125×2436으로 표시 |
| 0062 | 터치 전원 레일용 RPMh 레귤레이터 설정, 배터리 전원 소스 |
| 0063 | GENI FIFO 패킹 수정과 Goodix GT9896 프로토콜 |
| 0064 | UIKit의 CoreAnimation 작업 간격인 PRIV_WORK_INTERVAL 권한 부여 |
| 0065 | PSCI CPU_ON을 통한 보조 A55 코어 SMP 지원 |
| 0066–0067 | 오류 영역 충돌 정보, 콘솔 음소거 |
| 0068 | 이미 매핑된 표면에 대한 IOSurface 조회 동작 |
| 0069 | 영구 로그를 묶음 단위로 기록 |
주요 하드웨어 정보
- RAM 페이로드는 상위 뱅크
0x100000000+0xfcc00000을 사용한다. 약 3.95 GiB이며 Qualcomm의 보호 영역 위에 있다. - 보안 확장 기능이 있는 GICv3를 사용한다. 타이머는 Apple 실리콘처럼 FIQ가 아니라 Group 1 IRQ(가상 타이머 INTID 19)를 거친다.
- PSCI는
smc를 통해 사용한다. 타이머는 19.2 MHz이며, 포인터 인증이 없는 ARMv8.2를 지원한다. MPIDR은 코어 번호에 Aff1을 사용한다. - 패널은 1080×2400 DSC 비디오 모드다. ABL에서 이어지는 연속 스플래시를 사용하며 별도의 화면 갱신 신호가 필요하지 않다.
- 터치 장치는 QUP SE7(
0xa84000)의 I²C0x5d에 연결된 Goodix GT9896이다. RPMh를 통해 PM6150L LDO11에서 전원을 공급받는다. - 전원과 볼륨 아래 버튼은 PM6150 PON 블록에 연결되고, 볼륨 위 버튼은 PM6150L GPIO2(SPMI SID 4)에 연결된다.
자세한 내용은 NOTES.md에 있다.
크레딧
이 프로젝트는 Apple의 오픈소스 공개물(xnu, IOHIDFamily, libpthread, hfs), BearSSL, blacktop/darwin-xnu-build, ipsw, 하드웨어 정보를 제공한 Qualcomm과 Xiaomi의 공개 Linux 커널 소스, OrangeFox Recovery를 활용했다. iOS는 Apple 소유이며 이 저장소에서 배포하지 않는다.
댓글 (0)
로그인하면 이 기사에 내 생각을 남길 수 있어요