Translated ['', 'src/binary-exploitation/ios-exploiting/ios-physical-uaf

This commit is contained in:
Translator 2025-09-29 08:59:49 +00:00
parent f8699c8f7f
commit b8f201f467

View File

@ -1,97 +1,97 @@
# iOS Physical Use-After-Free via IOSurface
# iOS Physical Use After Free via IOSurface
{{#include ../../banners/hacktricks-training.md}}
## Physical use-after-free
이 내용은 [https://alfiecg.uk/2024/09/24/Kernel-exploit.html](https://alfiecg.uk/2024/09/24/Kernel-exploit.html) 포스트의 요약입니다. 추가적인 익스플로잇 정보는 [https://github.com/felix-pb/kfd](https://github.com/felix-pb/kfd)에서 확인할 수 있습니다.
This is a summary from the post from [https://alfiecg.uk/2024/09/24/Kernel-exploit.html](https://alfiecg.uk/2024/09/24/Kernel-exploit.html) moreover further information about exploit using this technique can be found in [https://github.com/felix-pb/kfd](https://github.com/felix-pb/kfd)
### Memory management in XNU <a href="#memory-management-in-xnu" id="memory-management-in-xnu"></a>
iOS의 사용자 프로세스에 대한 virtual memory address space는 **0x0 부터 0x8000000000**까지입니다. 그러나 이 주소들이 물리 메모리에 직접 매핑되는 것은 아닙니다. 대신 kernel은 page tables를 사용해 virtual addresses를 실제 physical addresses로 변환합니다.
iOS의 사용자 프로세스**virtual memory address space**는 **0x0 to 0x8000000000** 범위를 가집니다. 하지만 이 주소들은 물리 메모리에 직접 대응하지 않습니다. 대신 **kernel**은 **page tables**를 사용해 가상 주소를 실제 **physical addresses**로 변환합니다.
#### Levels of Page Tables in iOS
Page tables는 계층적으로 세 단계로 구성됩니다:
페이지 테이블은 계층적으로 세 레벨로 구성됩니다:
1. **L1 Page Table (Level 1)**:
* 각 엔트리는 가상 메모리의 큰 범위를 나타냅니다.
* **0x1000000000 bytes**(256 GB)를 커버합니다.
* 여기의 각 엔트리는 넓은 범위의 가상 메모리를 나타냅니다.
* **0x1000000000 bytes** (또는 **256 GB**)의 가상 메모리를 커버합니다.
2. **L2 Page Table (Level 2)**:
* 여기의 각 엔트리는 더 작은 가상 메모리 영역인 **0x2000000 bytes**(32 MB)를 나타냅니다.
* L1 엔트리는 전체 영역을 직접 매핑하지 못할 경우 L2 테이블을 가리킬 수 있습니다.
* 이 레벨의 엔트리는 더 작은 영역, 구체적으로 **0x2000000 bytes** (32 MB)를 나타냅니다.
* L1 엔트리가 해당 영역 전체를 직접 매핑할 수 없으면 L2 테이블을 가리킬 수 있습니다.
3. **L3 Page Table (Level 3)**:
* 가장 세밀한 레벨로, 각 엔트리는 하나의 **4 KB** 메모리 페이지를 매핑합니다.
* L2 엔트리는 더 세밀한 제어가 필요하면 L3 테이블을 가리킬 수 있습니다.
* 가장 세밀한 레벨로, 각 엔트리는 단일 **4 KB** 메모리 페이지를 매핑합니다.
* 더 세밀한 제어가 필요하면 L2 엔트리가 L3 테이블을 가리킬 수 있습니다.
#### Mapping Virtual to Physical Memory
* **Direct Mapping (Block Mapping)**:
* 페이지 테이블의 일부 엔트리는 가상 주소 범위를 물리 주소의 연속 범위에 직접 매핑합니다(지름길과 유사).
* 페이지 테이블의 일부 엔트리는 가상 주소의 범위를 연속된 물리 주소 범위에 직접 **매핑**합니다(단축 경로처럼).
* **Pointer to Child Page Table**:
* 더 세밀한 제어가 필요하면 한 레벨의 엔트리(예: L1)가 다음 레벨의 child page table(L2)을 가리킬 수 있습니다.
* 더 세밀한 제어가 필요하면 한 레벨의 엔트리(예: L1)가 다음 레벨의 **child page table**를 가리킬 수 있습니다.
#### Example: Mapping a Virtual Address
예를 들어 가상 주소 **0x1000000000**에 접근하려고 면:
예를 들어 가상 주소 **0x1000000000**에 접근하려고 한다면:
1. **L1 Table**:
* 커널은 이 가상 주소에 해당하는 L1 page table 엔트리를 확인합니다. 해당 엔트리가 **L2 page table을 가리키면**, 그 L2 테이블로 이동합니다.
* 커널은 이 가상 주소에 해당하는 L1 페이지 테이블 엔트리를 확인합니다. 만약 그것이 **L2 page table**를 가리키면 L2 테이블로 이동합니다.
2. **L2 Table**:
* 커널은 더 상세한 매핑을 위해 L2 page table을 확인합니다. 이 엔트리가 **L3 page table을 가리키면**, 그곳으로 진행합니다.
* 커널은 더 상세한 매핑을 위해 L2 페이지 테이블을 확인합니다. 이 엔트리가 **L3 page table**를 가리키면 그곳으로 갑니다.
3. **L3 Table**:
* 커널은 최종 L3 엔트리를 조회하여 실제 메모리 페이지의 **physical address**를 얻습니다.
* 커널은 최종 L3 엔트리를 조회하여 실제 메모리 페이지의 **physical address**를 확인합니다.
#### Example of Address Mapping
만약 L2 테이블의 첫 인덱스에 물리 주소 **0x800004000**을 쓴다면:
만약 L2 테이블의 첫 인덱스에 물리 주소 **0x800004000**을 면:
* 가상 주소 **0x1000000000** ~ **0x1002000000**은 물리 주소 **0x800004000** ~ **0x802004000**에 매핑됩니다.
* 이는 L2 레벨의 **block mapping**입니다.
* 가상 주소 **0x1000000000**부터 **0x1002000000**까지는 물리 주소 **0x800004000**부터 **0x802004000**까지에 매핑됩니다.
* 이것은 L2 레벨에서의 **block mapping**입니다.
L2 엔트리가 L3 테이블을 가리키면:
안으로, L2 엔트리가 L3 테이블을 가리키면:
* 가상 주소 범위 **0x1000000000 -> 0x1002000000**의 각 4 KB 페이지는 L3 테이블의 개별 엔트리 매핑됩니다.
* 가상 주소 범위 **0x1000000000 -> 0x1002000000**의 각 4 KB 페이지는 L3 테이블의 개별 엔트리에 의해 매핑됩니다.
### Physical use-after-free
physical use-after-free (UAF)는 다음과 같은 경우 발생합니다:
A **physical use-after-free** (UAF)는 다음과 같은 상황에서 발생합니다:
1. 프로세스가 읽기/쓰기 가능한 메모리를 할당합니다.
2. page tables가 이 메모리를 프로세스가 접근할 수 있는 특정 physical address에 매핑하도록 업데이트됩니다.
3. 프로세스가 해당 메모리를 해제(free)합니다.
4. 그러나 버그로 인해 커널이 page tables에서 매핑을 제거하는 것을 잊어버리고, 해당 physical 메모리는 free로 표시됩니다.
5. 커널이 이 "해제된" physical 메모리를 다른 용도(예: kernel 데이터)로 재할당할 수 있습니다.
6. 매핑이 제거되지 않았기 때문에 프로세스는 여전히 이 physical 메모리를 읽고 쓸 수 있습니다.
1. 프로세스가 메모리를 **readable and writable**로 **할당**합니다.
2. 해당 메모리에 대해 프로세스가 접근할 수 있도록 **page tables**가 특정 물리 주소로 이 메모리를 매핑하도록 업데이트됩니다.
3. 프로세스가 메모리를 **할당 해제(free)** 합니다.
4. 하지만 **버그**로 인해 커널은 해당 매핑을 페이지 테이블에서 **제거하는 것을 잊어버리며**, 실제로는 해당 물리 메모리를 자유(프리) 상태로 표시합니다.
5. 커널은 이후 이 “프리된” 물리 메모리를 다른 용도로 **재할당**할 수 있습니다(예: **kernel data**).
6. 매핑이 제거되지 않았기 때문에 프로세스는 여전히 이 물리 메모리를 **읽고 쓸 수** 있습니다.
이로 인해 프로세스는 kernel 메모리 페이지에 접근할 수 있게 되고, 민감한 데이터나 구조체를 포함할 수 있는 해당 메모리를 조작해 kernel 메모리를 조작할 수 있습니다.
결과적으로 프로세스는 **커널 메모리 페이지**에 접근할 수 있게 되며, 여기에는 민감한 데이터나 구조체가 포함될 수 있어 공격자가 **커널 메모리를 조작**할 수 있게 됩니다.
### IOSurface Heap Spray
공격자는 어떤 특정한 kernel 페이지가 해제된 메모리에 할당될지 제어할 수 없기 때문에, heap spray라는 기법을 사용합니다:
공격자는 어떤 특정 커널 페이지가 프리된 메모리에 할당될지 제어할 수 없으므로, **heap spray** 기법을 사용합니다:
1. 공격자는 kernel 메모리에 많은 수의 IOSurface 객체를 생성합니다.
2. 각 IOSurface 객체는 식별이 용이하도록 필드 중 하나에 magic value를 포함합니다.
3. 공격자는 해제된 페이지를 스캔하여 이 IOSurface 객체들이 해제된 페이지에 들어갔는지 확인합니다.
4. 해제된 페이지에서 IOSurface 객체를 발견하면 이를 이용해 kernel 메모리를 읽고 쓸 수 있습니다.
1. 공격자는 커널 메모리에 많은 수의 IOSurface 객체를 **생성**합니다.
2. 각 IOSurface 객체는 식별이 쉬운 **magic value**를 특정 필드에 포함합니다.
3. 공격자는 프리된 페이지를 **스캔**하여 이러한 IOSurface 객체 중 일부가 프리된 페이지에 할당되었는지 확인합니다.
4. 프리된 페이지에서 IOSurface 객체를 찾으면 이를 이용해 **커널 메모리를 읽고 쓰는** 작업을 수행할 수 있습니다.
자세한 내용은 [https://github.com/felix-pb/kfd/tree/main/writeups](https://github.com/felix-pb/kfd/tree/main/writeups)에서 확인하세요.
> [!TIP]
> iOS 16+ (A12+) 디바이스는 physical UAF 기법의 효용을 크게 떨어뜨리는 하드웨어 완화책들을 도입했다는 점을 유의하세요.
> PPL은 code signing, entitlements, 및 민감한 kernel 데이터와 연관된 페이지에 대해 엄격한 MMU 보호를 적용하므로, 페이지가 재사용되더라도 userland나 손상된 kernel 코드에서 PPL로 보호된 페이지에 대한 쓰기는 차단됩니다.
> Secure Page Table Monitor (SPTM)는 PPL을 확장하여 page table 업데이트 자체를 강화합니다. 이는 특권 있는 kernel 코드조차 보안 검증을 거치지 않고 해제된 페이지를 은밀히 재매핑하거나 매핑을 조작하지 못하도록 보장합니다.
> KTRR (Kernel Text Read-Only Region)은 부팅 후 커널 코드 섹션을 읽기 전용으로 고정합니다. 이는 runtime에서 커널 코드를 수정하는 것을 방지하여 physical UAF 익스플로잇이 자주 의존하는 주요 공격 벡터를 차단합니다.
> 또한 IOSurface 할당은 예측하기 어려워졌고 user-accessible 영역으로 매핑하기 더 힘들어져서 "magic value 스캔" 기법의 신뢰성이 크게 떨어졌습니다. 그리고 IOSurface는 이제 entitlements 및 sandbox 제한으로 보호됩니다.
> iOS 16+ (A12+) 장치는 PPL 또는 SPTM 같은 하드웨어 완화책을 도입하여 physical UAF 기법의 실효성을 크게 낮춥니다.
> PPL은 코드 서명, entitlements 및 민감한 커널 데이터와 관련된 페이지에 대해 엄격한 MMU 보호를 적용하므로, 페이지가 재사용되더라도 userland 또는 손상된 커널 코드의 쓰기는 PPL로 보호되는 페이지에 대해 차단됩니다.
> Secure Page Table Monitor (SPTM)는 PPL을 확장하여 페이지 테이블 업데이트 자체를 강화합니다. 이는 권한 있는 커널 코드조차도 안전 검증 없이 프리된 페이지를 은밀히 리맵하거나 매핑을 조작하지 못하도록 보장합니다.
> KTRR (Kernel Text Read-Only Region)은 부팅 후 커널 코드 섹션을 읽기 전용으로 고정합니다. 이것은 physical UAF 공격이 흔히 의존하는 주요 공격 벡터를 차단합니다.
> 또한 `IOSurface` 할당은 예측하기 어렵고 유저 접근 가능한 영역으로 매핑하기가 더 어려워져, “magic value 스캔” 트릭의 신뢰도가 떨어집니다. 그리고 `IOSurface`는 이제 entitlements와 sandbox 제한으로 보호됩니다.
### Step-by-Step Heap Spray Process
1. **Spray IOSurface Objects**: 공격자는 특수 식별자("magic value")를 가진 많은 IOSurface 객체를 생성합니다.
2. **Scan Freed Pages**: 해제된 페이지들 중 어떤 객체가 그 위에 할당되었는지를 확인합니다.
3. **Read/Write Kernel Memory**: IOSurface 객체의 필드를 조작하여 **arbitrary reads and writes**를 수행할 수 있게 됩니다. 이를 통해 다음을 수행할 수 있습니다:
* 한 필드를 사용해 kernel 메모리의 임의의 32-bit 값을 **읽습니다**.
* 다른 필드를 사용해 64-bit 값을 **씁니다**, 이를 통해 안정적인 **kernel read/write primitive**를 얻습니다.
2. **Scan Freed Pages**: 프리된 페이지 중 그 객체들이 할당되었는지 확인합니다.
3. **Read/Write Kernel Memory**: IOSurface 객체의 필드를 조작함으로써 **arbitrary reads and writes**를 수행할 수 있습니다. 이를 통해:
* 한 필드를 이용해 커널 메모리의 **임의의 32-bit 값**을 읽습니다.
* 다른 필드를 이용해 **64-bit 값**을 쓰며, 안정적인 **kernel read/write primitive**를 확보합니다.
Generate IOSurface objects with the magic value IOSURFACE\_MAGIC to later search for:
```c
@ -148,25 +148,25 @@ free(surfaceIDs);
return 0;
}
```
### IOSurface로 커널 읽기/쓰기 달성
### IOSurface로 Kernel Read/Write 달성
커널 메모리의 IOSurface 객체(사용자 공간에서 접근 가능한 해제된 물리 페이지에 매핑됨)를 제어하게 되면, 이를 이용해 **임의의 커널 읽기 및 쓰기 작업**을 수행할 수 있다.
IOSurface 객체가 kernel memory에 있고 (userspace에서 접근 가능한 freed physical page에 mapped된 상태로) 제어권을 얻으면, 이를 이용해 **arbitrary kernel read and write operations**를 수행할 수 있다.
**Key Fields in IOSurface**
IOSurface 객체에는 두 개의 중요한 필드가 있다:
IOSurface 객체에는 두 가지 중요한 필드가 있다:
1. **Use Count Pointer**: **32-bit read**를 허용한다.
2. **Indexed Timestamp Pointer**: **64-bit write**를 허용한다.
1. **Use Count Pointer**: **32-bit read**가 가능하다.
2. **Indexed Timestamp Pointer**: **64-bit write**가 가능하다.
이 포인터들을 덮어써서 커널 메모리의 임의 주소로 리다이렉트하면, 읽기/쓰기 기능을 사용할 수 있다.
이 포인터들을 덮어쓰면 kernel memory의 임의 주소로 리다이렉트하여 read/write 기능을 사용할 수 있다.
#### 32-Bit Kernel Read
읽기를 수행하려면:
1. 대상 주소에서 0x14 바이트 오프셋을 뺀 위치를 가리키도록 **use count pointer**를 덮어쓴다.
2. `get_use_count` 메서드를 사용해 해당 주소의 값을 읽어온다.
1. **use count pointer**를 대상 주소에서 0x14-byte offset을 뺀 위치를 가리키도록 덮어쓴다.
2. `get_use_count` 메서드를 사용해 해당 주소의 값을 읽다.
```c
uint32_t get_use_count(io_connect_t client, uint32_t surfaceID) {
uint64_t args[1] = {surfaceID};
@ -184,12 +184,12 @@ iosurface_set_use_count_pointer(info.object, orig);
return value;
}
```
#### 64비트 커널 쓰기
#### 64-Bit Kernel Write
쓰기 수행하려면:
쓰기 수행:
1. 대상 주소로 **인덱스된 타임스탬프 포인터**를 덮어씁니다.
2. 64비트 값을 쓰기 위해 `set_indexed_timestamp` 메서드를 사용합니다.
1. 대상 주소로 **indexed timestamp pointer**를 덮어쓴다.
2. `set_indexed_timestamp` 메서드를 사용해 64비트 값을 쓴다.
```c
void set_indexed_timestamp(io_connect_t client, uint32_t surfaceID, uint64_t value) {
uint64_t args[3] = {surfaceID, 0, value};
@ -205,11 +205,11 @@ iosurface_set_indexed_timestamp_pointer(info.object, orig);
```
#### Exploit 흐름 요약
1. **Trigger Physical Use-After-Free**: 해제된 페이지가 재사용 가능해집니다.
2. **Spray IOSurface Objects**: kernel 메모리에 고유한 "magic value"를 가진 많은 IOSurface 객체를 할당합니다.
3. **Identify Accessible IOSurface**: 제어 중인 해제된 페이지에 있는 IOSurface를 찾습니다.
4. **Abuse Use-After-Free**: IOSurface 객체의 포인터를 수정하여 IOSurface 메서드를 통해 임의의 **kernel read/write**를 가능하게 합니다.
1. **Trigger Physical Use-After-Free**: 해제된 페이지는 재사용을 위해 이용 가능해진다.
2. **Spray IOSurface Objects**: 커널 메모리에 고유한 "magic value"를 가진 다수의 IOSurface 객체를 할당한다.
3. **Identify Accessible IOSurface**: 제어하는 해제된 페이지에서 IOSurface를 찾아낸다.
4. **Abuse Use-After-Free**: IOSurface 객체의 포인터를 수정하여 IOSurface 메서드를 통해 임의의 **kernel read/write**를 가능하게 다.
이러한 primitives로 익스플로잇은 kernel 메모리에 대한 제어된 **32-bit reads** 및 **64-bit writes**를 제공합니다. 추가적인 jailbreak 단계는 더 안정적인 read/write primitives를 필요로 할 수 있으며, 이는 추가적인 보호(예: 최신 arm64e 장치의 PPL)를 우회해야 할 수 있습니다.
이러한 primitives로 exploit는 커널 메모리에 대해 제어된 **32-bit reads**와 **64-bit writes**를 제공한다. 추가적인 jailbreak 단계는 더 안정적인 read/write primitives를 포함할 수 있으며, 이는 추가 보호 장치(예: 최신 arm64e 기기의 PPL)를 우회해야 할 수 있다.
{{#include ../../banners/hacktricks-training.md}}