- gala는 iPhone 4용 iOS 4 탈옥을 직접 구현하는 프로젝트이며, 1부는 오래된 기기에서 SecureROM 취약점을 이용해 초기 코드 실행을 얻는 과정에 집중함
- iOS 부팅은 SecureROM이 LLB 또는 iBSS를 검증하고, 이후 단계가 다음 단계를 검증하는 신뢰 체인으로 이어지며 SecureROM은 제조 후 업데이트로 교체할 수 없음
- limera1n은 A4 SoC의 SecureROM을 대상으로 DFU 모드에서 iBSS 전송을 기다리는 장치를 공격하며, USB 제어 메시지 퍼징에서 발견된 충돌이 힙 오버플로와 셸코드 실행으로 이어지는 것으로 보임
- pod2g의 SecureROM dumper를 참고해
0x84000000통신 영역과0xA1:2USB 읽기 요청을 활용하고, SecureROM 덤프와 디버그 값을 호스트로 가져오는 흐름을 재구현함 - Rust로 작성한 페이로드를 Mach-O의
__TEXT,__text에서 셸코드로 추출해 실행하는 파이프라인을 만들었지만, 정적 문자열이__const에 배치되면서 명령 포인터 상대 주소 지정과 어셈블리 배치가 필요해짐
iPhone 4 탈옥 프로젝트의 출발점
- gala는 iPhone 4용 iOS 4 탈옥을 만드는 프로젝트이며, 이 문서는 그중 1부인 “Gaining Entry”에 해당함
- 이전 iOS tweak 개발 경험은 Cydia 배포, SpringBoard 기능 변경, Objective-C 런타임 직접 사용, 폐쇄 소스 바이너리 리버스 엔지니어링으로 이어졌음
- 직접 탈옥을 작성하려는 목표는 탈옥 과정이 실제로 어떻게 작동하는지 이해하는 데 있음
- 이 작업은 p0sixninja와 axi0mX가 오픈소스로 공유한 지식에 크게 의존함
오래된 iPhone과 Boot ROM 취약점 선택
- 첫 단계는 eBay에서 iPhone 4와 iPhone 3GS를 구입하는 일이었음
- 최신 Xcode가 오래된 iOS 버전 타깃을 허용하지 않아, 2010년대 방식으로 앱을 빌드·설치하는 경로는 곧 막힘
- 오래된 Mac OS X와 Xcode를 VM에 설치하는 방안도 검토했지만 중단함
- Apple이 여전히 레거시 iOS 타깃 바이너리를 서명할지도 불명확했음
- 대안은 Boot ROM 취약점을 직접 노리는 방식임
- 오래된 툴체인이나 VM 없이 호스트 머신에서 USB로 장치와 상호작용하는 코드만으로 시도할 수 있음
- iPhone Wiki의
Vulnerabilities and Exploits섹션에서 limera1n 익스플로잇 코드를 확인함
SecureROM과 iOS 부팅 신뢰 체인
- Apple 용어의 SecureROM은 iOS 부팅 프로세스의 첫 단계이며, 다음 부팅 단계를 시작함
- SecureROM은 두 가지 구성 요소를 로드할 수 있음
- 정상 부팅 시 NOR의 디스크 파티션에서
Low Level Bootloader, 즉 LLB를 부팅함 - DFU 모드에서 USB로 컴퓨터에 연결되면
Restore iPhone과정에서iBoot Single Stage, 즉 iBSS 부트로더를 받을 수 있음
- 정상 부팅 시 NOR의 디스크 파티션에서
- SecureROM은 LLB 또는 iBSS가 Apple이 서명한 신뢰 가능한 이미지인지 확인함
- 이후 LLB와 iBSS도 자신이 로드하는 다음 단계를 검증해 신뢰 체인을 형성함
- SecureROM은 첫 단계라서 이전 단계의 검증을 받지 않으며, 제조 시 읽기 전용 메모리에 새겨짐
- 다른 단계는 iOS 업데이트로 교체할 수 있음
- 특정 SecureROM 버전의 취약점은 그 버전으로 제조된 장치에 영구적으로 남음
limera1n으로 DFU 장치에서 코드 실행 얻기
- limera1n은 geohot이 2010년에 공개하고 같은 이름의 탈옥 도구로 패키징한 SecureROM 익스플로잇임
- DFU 모드 장치가 USB를 통해 호스트에서 iBSS를 받기 위해 대기 중일 때 limera1n을 사용할 수 있음
- A4 SoC에 포함된 SecureROM이 취약하므로 iPhone 4가 적합한 대상이 됨
- limera1n의 정확한 동작 원리는 공개적으로 완전히 정리되어 있지 않음
- geohot은 왜 작동하는지 모른다고 말했음
- p0sixninja는 이론을 추측했음
- 충돌은 USB 제어 메시지 퍼징으로 발견됐고, 힙 오버플로로 이어지는 레이스 컨디션처럼 보이며 셸코드 주입과 실행을 가능하게 하는 것으로 보임
DFU 모드 장치에서 메모리 읽기
- pod2g의 SecureROM dumper는 limera1n 구현, 페이로드 예시, USB 기반 메모리 읽기 방식을 함께 보여주는 참고 구현이 됨
- SecureROM dumper는 SecureROM이 매핑된
0x0메모리를 USB 수신 영역으로 복사한 뒤, 호스트에서 USB 제어 메시지로 데이터를 읽음 - 이해한 흐름은 다음과 같음
- A4의 MMU는 SRAM의 시작을
0x84000000에 매핑함 - 호스트는
request type 0x21,request ID 1인 USB 제어 패킷으로 iBSS 이미지를 조각 단위로 보낼 수 있음 - 이 데이터는
0x84000000부터 SRAM에 복사되고, SecureROM은 다음 패킷 복사 위치를 추적하는 내부 카운터를 유지함 - 장치는
request type 0xA1,request ID 2제어 패킷에도 응답하며,0x84000000의 메모리 내용을 호스트로 보냄
- A4의 MMU는 SRAM의 시작을
- 이 읽기 기능은 장치에서 코드를 실행할 수 있을 때 특히 유용함
- 페이로드가 원하는 데이터를
0x84000000으로 복사함 - 호스트는
A1:2읽기 요청으로 해당 데이터를 가져올 수 있음
- 페이로드가 원하는 데이터를
- p0sixninja의 2013년 Hack In the Box Malaysia 발표 슬라이드에는 pod2g의 SecureROM dumper가 SHAtter 기반이라고 되어 있지만, 실제 유틸리티는 limera1n 구현을 사용함
- 자체 limera1n 구현으로 SecureROM 덤프에 성공함
0x84000000을 이용한 페이로드 디버깅
- 코드 실행을 얻은 뒤에는 셸코드가 어디에서 실행되는지, 스택 위치가 어디인지, 셸코드가 어떤 메모리를 덮어쓰는지 확인해야 했음
- SecureROM dumper의 읽기 흐름을 디버그 출력 용도로 재사용함
- 페이로드가 명령 포인터(instruction pointer)와 스택 포인터(stack pointer) 값을
0x84000000에 복사함 - 호스트 측 코드는 같은 방식으로 값을 다시 읽음
- 이 방식은 메모리 덤프를 통한 간이
print()역할을 함
- 페이로드가 명령 포인터(instruction pointer)와 스택 포인터(stack pointer) 값을
- 자동 스크립트는 익스플로잇 실행 후
0x84000000의 첫 몇 워드를 덤프해 출력 창에 표시함 - 확인된 값은 셸코드 실행 위치와 스택 위치를 보여줌
- 명령 포인터는
0x8402b048근처였음 - 스택 포인터는
0x8403bfa0였음 - 스택 포인터는 SecureROM이 설정한 일반 스택 영역 안에 있었고, 명령 포인터는 수신 이미지 영역 안에 있었음
- 명령 포인터는
Rust 페이로드와 Mach-O 셸코드 추출
- 어셈블리만으로 페이로드를 작성하는 대신 Rust 페이로드를 빌드해 셸코드로 변환하는 빌드 시스템을 구성함
- Rust는 2020년 초
armv7-apple-ios타깃 지원을 중단했지만,rustup으로 오래된 지원 툴체인으로 전환할 수 있음 - 고수준 언어로 컴파일하면 원시 머신 코드뿐 아니라 메타데이터, 가상 주소 공간 설정 정보, 심볼 테이블, 링커 정보가 포함된 바이너리가 생성됨
- 익스플로잇에는 메모리에 주입해 점프할 바이트만 필요하므로 Mach-O 전체가 아니라
__TEXT세그먼트의__text섹션만 필요함 - strongarm은 Mach-O 분석 라이브러리이며, 빌드 시스템에서 Mach-O를 파싱해
__TEXT,__text섹션을 파일로 추출하는 데 사용됨 - 추출된 파일의 바이트가 limera1n으로 장치에서 실행할 셸코드가 됨
링커와 정적 데이터 문제
- 일반 바이너리는 OS 인프라와
start또는_main같은 진입점 심볼 규약을 사용하지만, 이 셸코드 용도에는 그런 심볼이 필요 없음 - 링커는 기본적으로
_main또는start가 없으면 오류를 냄 -U _main,-U start,-static옵션을 조합해 dyld를 쓰지 않는 Mach-O 파일을 만들 수 있었음- strongarm은 처음에
LC_DYLD_INFO로드 명령이 없는 바이너리를 처리하지 못해 예외를 냈음- 해당 바이너리는 dyld를 사용하지 않아
LC_DYLD_INFO가 없음 - strongarm에 패치를 추가해 처리함
- 해당 바이너리는 dyld를 사용하지 않아
- Rust 코드에 정적 문자열을 추가하자 페이로드가 깨짐
- 컴파일된 정적 문자열은
__const에 배치됨 - 셸코드 추출 과정은
__TEXT,__text만 남기므로__const데이터는 메모리에 로드되지 않음 - 결과적으로 코드는 매핑되지 않은 주소에서 문자열을 읽으려 하며 크래시함
- 컴파일된 정적 문자열은
- 해결책은 정적 데이터를
__TEXT,__text안에 포함시키고, 안정적인 로드 주소를 가정하지 않도록 명령 포인터 상대 주소 지정을 사용하는 것임 - 현재 방식은 어셈블리에서 문자열을 정의하고 그 주소를 Rust 페이로드 진입점에 넘김
1부에서 완성한 실행 파이프라인
- 최종 파이프라인은 다음 순서로 동작함
- Rust 페이로드를 수정함
- 버튼을 눌러 페이로드를 컴파일함
- 바이너리에서 셸코드를 추출함
- 러너가 연결된 DFU iPhone에서 limera1n으로 페이로드를 실행함
- 러너가 통신 공간으로 쓰는
0x84000000에서 데이터를 자동으로 읽어 hexdump로 표시함
- 이 시점에서 장치에서 임의 코드를 실행할 수 있음
- 임의 코드 실행은 이론상 많은 작업을 가능하게 하지만, 장치가 실제로 흥미로운 일을 하게 만드는 작업은 별개의 단계로 남음
- 다음 단계는 Part 2: Bypassing the Bootchain로 이어짐