iOS Exploiting
Physical use-after-free
이것은 https://alfiecg.uk/2024/09/24/Kernel-exploit.html의 게시물 요약이며, 이 기술을 사용한 exploit에 대한 추가 정보는 https://github.com/felix-pb/kfd에서 찾을 수 있습니다.
Memory management in XNU
iOS의 사용자 프로세스에 대한 가상 메모리 주소 공간은 0x0에서 0x8000000000까지입니다. 그러나 이러한 주소는 물리 메모리에 직접 매핑되지 않습니다. 대신, 커널은 페이지 테이블을 사용하여 가상 주소를 실제 물리 주소로 변환합니다.
Levels of Page Tables in iOS
페이지 테이블은 세 가지 수준으로 계층적으로 구성됩니다:
L1 페이지 테이블 (레벨 1):
여기의 각 항목은 큰 범위의 가상 메모리를 나타냅니다.
0x1000000000 바이트(또는 256 GB)의 가상 메모리를 포함합니다.
L2 페이지 테이블 (레벨 2):
여기의 항목은 더 작은 가상 메모리 영역을 나타내며, 구체적으로 0x2000000 바이트(32 MB)입니다.
L1 항목은 전체 영역을 매핑할 수 없는 경우 L2 테이블을 가리킬 수 있습니다.
L3 페이지 테이블 (레벨 3):
가장 세밀한 수준으로, 각 항목은 단일 4 KB 메모리 페이지를 매핑합니다.
L2 항목은 더 세밀한 제어가 필요할 경우 L3 테이블을 가리킬 수 있습니다.
Mapping Virtual to Physical Memory
직접 매핑 (블록 매핑):
페이지 테이블의 일부 항목은 가상 주소 범위를 연속적인 물리 주소 범위에 직접 매핑합니다(단축키와 같은 방식).
자식 페이지 테이블에 대한 포인터:
더 세밀한 제어가 필요할 경우, 한 수준의 항목(예: L1)은 다음 수준의 자식 페이지 테이블(예: L2)을 가리킬 수 있습니다.
Example: Mapping a Virtual Address
가상 주소 0x1000000000에 접근하려고 한다고 가정해 보겠습니다:
L1 테이블:
커널은 이 가상 주소에 해당하는 L1 페이지 테이블 항목을 확인합니다. 만약 L2 페이지 테이블에 대한 포인터가 있다면, 해당 L2 테이블로 이동합니다.
L2 테이블:
커널은 더 자세한 매핑을 위해 L2 페이지 테이블을 확인합니다. 만약 이 항목이 L3 페이지 테이블을 가리킨다면, 그곳으로 진행합니다.
L3 테이블:
커널은 최종 L3 항목을 조회하여 실제 메모리 페이지의 물리 주소를 가리킵니다.
Example of Address Mapping
물리 주소 0x800004000을 L2 테이블의 첫 번째 인덱스에 기록하면:
가상 주소 0x1000000000에서 0x1002000000까지는 물리 주소 0x800004000에서 0x802004000까지 매핑됩니다.
이는 L2 수준에서의 블록 매핑입니다.
대신, L2 항목이 L3 테이블을 가리키는 경우:
가상 주소 범위 0x1000000000 -> 0x1002000000의 각 4 KB 페이지는 L3 테이블의 개별 항목에 의해 매핑됩니다.
Physical use-after-free
물리적 use-after-free (UAF)는 다음과 같은 경우에 발생합니다:
프로세스가 읽기 및 쓰기 가능한 메모리를 할당합니다.
페이지 테이블이 이 메모리를 프로세스가 접근할 수 있는 특정 물리 주소에 매핑하도록 업데이트됩니다.
프로세스가 메모리를 해제(프리)합니다.
그러나 버그로 인해 커널이 페이지 테이블에서 매핑을 제거하는 것을 잊어버립니다, 비록 해당 물리 메모리를 프리로 표시하더라도.
커널은 이후 이 "해제된" 물리 메모리를 커널 데이터와 같은 다른 용도로 재할당할 수 있습니다.
매핑이 제거되지 않았기 때문에 프로세스는 여전히 이 물리 메모리에 읽기 및 쓰기를 할 수 있습니다.
이는 프로세스가 커널 메모리의 페이지에 접근할 수 있음을 의미하며, 이는 민감한 데이터나 구조를 포함할 수 있어 공격자가 커널 메모리를 조작할 수 있는 가능성을 제공합니다.
Exploitation Strategy: Heap Spray
공격자가 해제된 메모리에 어떤 특정 커널 페이지가 할당될지 제어할 수 없기 때문에, 그들은 힙 스프레이라는 기술을 사용합니다:
공격자는 커널 메모리에 많은 수의 IOSurface 객체를 생성합니다.
각 IOSurface 객체는 그 필드 중 하나에 매직 값을 포함하여 쉽게 식별할 수 있게 합니다.
그들은 해제된 페이지를 스캔하여 이러한 IOSurface 객체가 해제된 페이지에 위치했는지 확인합니다.
해제된 페이지에서 IOSurface 객체를 찾으면, 이를 사용하여 커널 메모리를 읽고 쓸 수 있습니다.
이와 관련된 더 많은 정보는 https://github.com/felix-pb/kfd/tree/main/writeups에서 확인할 수 있습니다.
Step-by-Step Heap Spray Process
IOSurface 객체 스프레이: 공격자는 특별한 식별자("매직 값")로 많은 IOSurface 객체를 생성합니다.
해제된 페이지 스캔: 그들은 어떤 객체가 해제된 페이지에 할당되었는지 확인합니다.
커널 메모리 읽기/쓰기: IOSurface 객체의 필드를 조작하여 커널 메모리에서 임의의 읽기 및 쓰기를 수행할 수 있는 능력을 얻습니다. 이를 통해:
한 필드를 사용하여 커널 메모리에서 32비트 값을 읽을 수 있습니다.
다른 필드를 사용하여 64비트 값을 쓸 수 있으며, 안정적인 커널 읽기/쓰기 원시를 달성합니다.
IOSURFACE_MAGIC 매직 값을 가진 IOSurface 객체를 생성하여 나중에 검색합니다:
IOSurface
객체를 하나의 해제된 물리 페이지에서 검색:
커널 읽기/쓰기를 IOSurface로 달성하기
커널 메모리에서 IOSurface 객체에 대한 제어를 달성한 후(사용자 공간에서 접근 가능한 해제된 물리 페이지에 매핑됨), 우리는 이를 임의의 커널 읽기 및 쓰기 작업에 사용할 수 있습니다.
IOSurface의 주요 필드
IOSurface 객체에는 두 가지 중요한 필드가 있습니다:
사용 카운트 포인터: 32비트 읽기를 허용합니다.
인덱스 타임스탬프 포인터: 64비트 쓰기를 허용합니다.
이 포인터를 덮어쓰면 커널 메모리의 임의 주소로 리디렉션하여 읽기/쓰기 기능을 활성화할 수 있습니다.
32비트 커널 읽기
읽기를 수행하려면:
사용 카운트 포인터를 목표 주소에서 0x14 바이트 오프셋을 뺀 주소로 덮어씁니다.
get_use_count
메서드를 사용하여 해당 주소의 값을 읽습니다.
64비트 커널 쓰기
쓰기 작업을 수행하려면:
인덱스된 타임스탬프 포인터를 대상 주소로 덮어씁니다.
set_indexed_timestamp
메서드를 사용하여 64비트 값을 씁니다.
Exploit Flow Recap
물리적 Use-After-Free 트리거: 해제된 페이지는 재사용 가능하다.
IOSurface 객체 스프레이: 커널 메모리에 고유한 "매직 값"을 가진 많은 IOSurface 객체를 할당한다.
접근 가능한 IOSurface 식별: 제어하는 해제된 페이지에서 IOSurface를 찾는다.
Use-After-Free 남용: IOSurface 객체의 포인터를 수정하여 IOSurface 메서드를 통해 임의의 커널 읽기/쓰기를 가능하게 한다.
이러한 원시 기능을 통해 익스플로잇은 커널 메모리에 대한 제어된 32비트 읽기 및 64비트 쓰기를 제공한다. 추가적인 탈옥 단계는 더 안정적인 읽기/쓰기 원시 기능을 포함할 수 있으며, 이는 추가 보호(예: 최신 arm64e 장치의 PPL)를 우회해야 할 수도 있다.
Last updated