hacktricks/binary-exploitation/heap/README.md

16 KiB
Raw Permalink Blame History

Σωρός

Βασικά για τον Σωρό

Ο σωρός είναι βασικά το μέρος όπου ένα πρόγραμμα μπορεί να αποθηκεύσει δεδομένα όταν ζητά δεδομένα καλώντας συναρτήσεις όπως malloc, calloc... Επιπλέον, όταν αυτή η μνήμη δεν χρειάζεται πλέον, γίνεται διαθέσιμη καλώντας τη συνάρτηση free.

Όπως φαίνεται, βρίσκεται αμέσως μετά από το σημείο όπου φορτώνεται το δυαδικό στη μνήμη (ελέγξτε την ενότητα [heap]):

Βασική Δέσμη Χονδρικής Εκχώρησης

Όταν ζητείται να αποθηκευτούν κάποια δεδομένα στο σωρό, κατανέμεται κάποιος χώρος του σωρού γι' αυτά. Αυτός ο χώρος θα ανήκει σε έναν κάδο και μόνο τα ζητηθέντα δεδομένα + ο χώρος των κεφαλίδων των κάδων + η ελάχιστη μετατόπιση μεγέθους κάδου θα είναι κρατημένα για το κομμάτι. Ο στόχος είναι να κρατηθεί όση μνήμη χρειάζεται χωρίς να γίνει περίπλοκο να βρεθεί πού βρίσκεται κάθε κομμάτι. Γι' αυτό, χρησιμοποιούνται πληροφορίες μεταδεδομένων κομματιών για να γνωρίζουν πού βρίσκεται κάθε χρησιμοποιούμενο/ελεύθερο κομμάτι.

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

  • Το πρόγραμμα ξεκινάει ζητώντας συγκεκριμένη ποσότητα μνήμης.
  • Αν στη λίστα των κομματιών υπάρχει κάποιο αρκετά μεγάλο για να καλύψει το αίτημα, θα χρησιμοποιηθεί.
  • Αυτό μπορεί ακόμη να σημαίνει ότι ένα μέρος του διαθέσιμου κομματιού θα χρησιμοποιηθεί για αυτό το αίτημα και το υπόλοιπο θα προστεθεί στη λίστα των κομματιών
  • Αν δεν υπάρχει κανένα διαθέσιμο κομμάτι στη λίστα αλλά υπάρχει ακόμη χώρος στην εκχωρημένη μνήμη του σωρού, ο διαχειριστής του σωρού δημιουργεί ένα νέο κομμάτι
  • Αν δεν υπάρχει αρκετός χώρος στο σωρό για να εκχωρηθεί το νέο κομμάτι, ο διαχειριστής του σωρού ζητά από τον πυρήνα να επεκτείνει τη μνήμη που έχει εκχωρηθεί στο σωρό και στη συνέχεια να χρησιμοποιήσει αυτή τη μνήμη για να δημιουργήσει το νέο κομμάτι
  • Αν αποτύχει τα πάντα, το malloc επιστρέφει null.

Σημειώστε ότι αν η ζητούμενη μνήμη ξεπεράσει ένα κατώτατο όριο, θα χρησιμοποιηθεί το mmap για να χαρτογραφήσει τη ζητούμενη μνήμη.

Αρένες

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

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

Η προεπιλεγμένη "κύρια" αρένα χειρίζεται τις λειτουργίες σωρού για μονονηματικές εφαρμογές. Όταν προστίθενται νέα νήματα, ο διαχειριστής του σωρού αναθέτει σε αυτά δευτερεύουσες αρένες για να μειώσει τον ανταγωνισμό. Πρώτα προσπαθεί να συνδέσει κάθε νέο νήμα σε μια αχρησιμοποίητη αρένα, δημιουργώντας νέες αν χρειαστεί, μέχρι ένα όριο 2 φορές του αριθμού των πυρήνων CPU για συστήματα 32-bit και 8 φορές για συστήματα 64-bit. Μόλις φτάσει το όριο, τα νήματα πρέπει να μοιράζονται αρένες, οδηγώντας σε πιθανό ανταγωνισμό.

Αντίθετα με την κύρια αρένα, η οποία επεκτείνεται χρησιμοποιώντας την κλήση συστήματος brk, οι δευτερεύουσες αρένες δημιουργούν "υποσωρούς" χρησιμοποιώντας mmap και mprotect για να προσομοιώσουν τη συμπεριφορά του σωρού, επιτρέποντας ευελιξία στη διαχείριση μνήμης για πολυνηματικές λειτουργίες.

Υποσωροί

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

  1. Αρχικός Σωρός έναντι Υποσωρών:
  • Ο αρχικός σωρός βρίσκεται αμέσως μετά το δυαδικό του προγράμματος στη μνήμη και επεκτείνεται χρησιμοποιώντας την κλήση συστήματος sbrk.
  • Οι υποσωροί, χρησιμοποιούμενοι από δευτερεύουσες αρένες, δημιουργούνται μέσω mmap, μιας κλήσης συστήματος που χαρτογραφεί μια συγκεκριμένη περιοχή μνήμης.
  1. Επιφύλαξη Μνήμης με mmap:
  • Όταν ο διαχειριστής του σωρού δημιουργεί έναν υποσωρό, επιφυλάσσει ένα μεγάλο τμήμα μνήμης μέσω του mmap. Αυτή η επιφύλαξη δεν εκχωρεί μνήμη αμέσως· απλώς καθορίζει μια περιοχή που άλλες διεργασίες του συστήματος ή εκχωρήσεις δεν πρέπει να χρησιμοποιούν.
  • Από προεπιλογή, το μέγεθος της επιφύλαξης για έναν υποσωρό είναι 1 MB για διεργασίες 32-bit και 64 MB για διεργασίες 64-bit.
  1. Σταδιακή Επέκταση με mprotect:
  • Η επιφυλαγμέ
// From https://heap-exploitation.dhavalkapil.com/diving_into_glibc_heap/malloc_state
struct malloc_state
{
/* Serialize access.  */
__libc_lock_define (, mutex);
/* Flags (formerly in max_fast).  */
int flags;

/* Fastbins */
mfastbinptr fastbinsY[NFASTBINS];
/* Base of the topmost chunk -- not otherwise kept in a bin */
mchunkptr top;
/* The remainder from the most recent split of a small request */
mchunkptr last_remainder;
/* Normal bins packed as described above */
mchunkptr bins[NBINS * 2 - 2];

/* Bitmap of bins */
unsigned int binmap[BINMAPSIZE];

/* Linked list */
struct malloc_state *next;
/* Linked list for free arenas.  Access to this field is serialized
by free_list_lock in arena.c.  */
struct malloc_state *next_free;
/* Number of threads attached to this arena.  0 if the arena is on
the free list.  Access to this field is serialized by
free_list_lock in arena.c.  */

INTERNAL_SIZE_T attached_threads;
/* Memory allocated from the system in this arena.  */
INTERNAL_SIZE_T system_mem;
INTERNAL_SIZE_T max_system_mem;
};

typedef struct malloc_state *mstate;

malloc_chunk

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

// From https://heap-exploitation.dhavalkapil.com/diving_into_glibc_heap/malloc_chunk
struct malloc_chunk {
INTERNAL_SIZE_T      mchunk_prev_size;  /* Size of previous chunk, if it is free. */
INTERNAL_SIZE_T      mchunk_size;       /* Size in bytes, including overhead. */
struct malloc_chunk* fd;                /* double links -- used only if this chunk is free. */
struct malloc_chunk* bk;
/* Only used for large blocks: pointer to next larger size.  */
struct malloc_chunk* fd_nextsize; /* double links -- used only if this chunk is free. */
struct malloc_chunk* bk_nextsize;
};

typedef struct malloc_chunk* mchunkptr;

Όπως σχολιάστηκε προηγουμένως, αυτά τα κομμάτια έχουν επίσης μερικά μεταδεδομένα, πολύ καλά αναπαριστώμενα σε αυτήν την εικόνα:

https://azeria-labs.com/wp-content/uploads/2019/03/chunk-allocated-CS.png

Τα μεταδεδομένα είναι συνήθως 0x08B, υποδεικνύοντας το μέγεθος του τρέχοντος κομματιού χρησιμοποιώντας τα τελευταία 3 bits για να υποδείξει:

  • A: Αν είναι 1 προέρχεται από έναν υπο-σωρό, αν είναι 0 βρίσκεται στην κύρια αρένα
  • M: Αν είναι 1, αυτό το κομμάτι είναι μέρος ενός χώρου που έχει εκχωρηθεί με το mmap και όχι μέρος ενός σωρού
  • P: Αν είναι 1, το προηγούμενο κομμάτι είναι σε χρήση

Στη συνέχεια, ο χώρος για τα δεδομένα του χρήστη, και τέλος 0x08B για να υποδείξει το μέγεθος του προηγούμενου κομματιού όταν το κομμάτι είναι διαθέσιμο (ή για να αποθηκεύσει τα δεδομένα του χρήστη όταν είναι εκχωρημένο).

Επιπλέον, όταν είναι διαθέσιμα, τα δεδομένα του χρήστη χρησιμοποιούνται επίσης για να περιέχουν κάποια δεδομένα:

  • Δείκτη προς το επόμενο κομμάτι
  • Δείκτη προς το προηγούμενο κομμάτι
  • Μέγεθος του επόμενου κομματιού στη λίστα
  • Μέγεθος του προηγούμενου κομματιού στη λίστα

https://azeria-labs.com/wp-content/uploads/2019/03/chunk-allocated-CS.png

{% hint style="info" %} Σημειώστε πώς η ταξινόμηση της λίστας με αυτόν τον τρόπο αποτρέπει την ανάγκη να υπάρχει ένας πίνακας όπου κάθε μεμονωμένο κομμάτι καταγράφεται. {% endhint %}

Γρήγορο Παράδειγμα Σωρού

Γρήγορο παράδειγμα σωρού από https://guyinatuxedo.github.io/25-heap/index.html αλλά σε arm64:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void main(void)
{
char *ptr;
ptr = malloc(0x10);
strcpy(ptr, "panda");
}

Ορίστε ένα σημείο διακοπής στο τέλος της κύριας συνάρτησης και ας ανακαλύψουμε πού αποθηκεύτηκε η πληροφορία:

Είναι δυνατόν να δούμε ότι η συμβολοσειρά "panda" αποθηκεύτηκε στη διεύθυνση 0xaaaaaaac12a0 (η οποία ήταν η διεύθυνση που δόθηκε ως απάντηση από την malloc μέσα στο x0). Ελέγχοντας 0x10 bytes πριν από αυτήν, είναι δυνατόν να δούμε ότι το 0x0 αντιπροσωπεύει ότι το προηγούμενο τμήμα δεν χρησιμοποιείται (μήκος 0) και ότι το μήκος αυτού του τμήματος είναι 0x21.

Τα επιπλέον κενά που κρατήθηκαν (0x21-0x10=0x11) προέρχονται από τα προστιθέμενα κεφαλίδες (0x10) και το 0x1 δεν σημαίνει ότι κρατήθηκαν 0x21B αλλά τα τελευταία 3 bits του μήκους της τρέχουσας κεφαλίδας έχουν κάποιες ειδικές σημασίες. Δεδομένου ότι το μήκος είναι πάντα σε ευθυγραμμισμένα 16-byte (σε μηχανές 64 bits), αυτά τα bits προφανώς δεν θα χρησιμοποιηθούν ποτέ από τον αριθμό του μήκους.

0x1:     Previous in Use     - Specifies that the chunk before it in memory is in use
0x2:     Is MMAPPED          - Specifies that the chunk was obtained with mmap()
0x4:     Non Main Arena      - Specifies that the chunk was obtained from outside of the main arena

Bins & Εκχωρήσεις/Απελευθερώσεις Μνήμης

Ελέγξτε ποια είναι τα bins και πώς οργανώνονται και πώς γίνεται η εκχώρηση και απελευθέρωση μνήμης σε:

{% content-ref url="bins-and-memory-allocations.md" %} bins-and-memory-allocations.md {% endcontent-ref %}

Έλεγχοι Ασφαλείας Συναρτήσεων Heap

Οι συναρτήσεις που σχετίζονται με το heap θα πραγματοποιήσουν ορισμένους ελέγχους πριν εκτελέσουν τις ενέργειές τους προκειμένου να βεβαιωθούν ότι το heap δεν έχει διαφθαρεί:

{% content-ref url="heap-functions-security-checks.md" %} heap-functions-security-checks.md {% endcontent-ref %}

Αναφορές