LOAD_NAME / LOAD_CONST opcode OOB Read
Αυτές οι πληροφορίες προήχθησαν από αυτήν την ανάλυση.
TL;DR
Μπορούμε να χρησιμοποιήσουμε το χαρακτηριστικό OOB read στον κώδικα LOAD_NAME / LOAD_CONST opcode για να ανακτήσουμε κάποιο σύμβολο από τη μνήμη. Αυτό σημαίνει χρήση κόλπου όπως (a, b, c, ... εκατοντάδες σύμβολα ..., __getattribute__) if [] else [].__getattribute__(...)
για να ανακτήσετε ένα σύμβολο (όπως το όνομα μιας συνάρτησης) που θέλετε.
Στη συνέχεια απλά δημιουργήστε την εκμετάλλευσή σας.
Επισκόπηση
Ο πηγαίος κώδικας είναι αρκετά σύντομος, περιέχει μόνο 4 γραμμές!
Ανάγνωση εκτός ορίων
Πώς συμβαίνει το σφάλμα σεγμεντέισον;
Ας ξεκινήσουμε με ένα απλό παράδειγμα, [a, b, c]
μπορεί να μεταγλωττιστεί στον ακόλουθο κώδικα bytecode.
Αλλά τι συμβαίνει αν τα co_names
γίνουν ένα κενό tuple; Το LOAD_NAME 2
opcode εξακολουθεί να εκτελείται και προσπαθεί να διαβάσει την τιμή από αυτήν τη διεύθυνση μνήμης που αρχικά έπρεπε να είναι. Ναι, αυτό είναι ένα χαρακτηριστικό εκτός ορίων ανάγνωσης.
Το βασικό συναίσθημα για τη λύση είναι απλό. Κάποια opcodes στο CPython, για παράδειγμα το LOAD_NAME
και το LOAD_CONST
, είναι ευάλωτα (?) στην OOB ανάγνωση.
Ανακτούν ένα αντικείμενο από το δείκτη oparg
από το tuple consts
ή names
(αυτό που ονομάζεται co_consts
και co_names
κάτω από το καπάκι). Μπορούμε να αναφερθούμε στο ακόλουθο σύντομο απόσπασμα σχετικά με το LOAD_CONST
για να δούμε τι κάνει το CPython όταν επεξεργάζεται το LOAD_CONST
opcode.
Με αυτόν τον τρόπο μπορούμε να χρησιμοποιήσουμε το χαρακτηριστικό OOB για να λάβουμε ένα "όνομα" από αυθαίρετη μνήμη. Για να βεβαιωθούμε για το όνομα που έχει και το offset του, απλά συνεχίστε να δοκιμάζετε LOAD_NAME 0
, LOAD_NAME 1
... LOAD_NAME 99
... Και θα μπορούσατε να βρείτε κάτι γύρω από oparg > 700. Μπορείτε επίσης να δοκιμάσετε να χρησιμοποιήσετε το gdb για να ρίξετε μια ματιά στη διάταξη της μνήμης φυσικά, αλλά δεν νομίζω ότι θα ήταν πιο εύκολο;
Δημιουργία του Εκμεταλλευτή
Αφού ανακτήσουμε αυτά τα χρήσιμα offsets για ονόματα / σταθερές, πώς μπορούμε να λάβουμε ένα όνομα / σταθερά από αυτό το offset και να το χρησιμοποιήσουμε; Εδώ υπάρχει ένα κόλπο για εσάς:
Ας υποθέσουμε ότι μπορούμε να λάβουμε ένα όνομα __getattribute__
από το offset 5 (LOAD_NAME 5
) με co_names=()
, τότε απλά κάντε τα ακόλουθα:
Σημείωσε ότι δεν είναι απαραίτητο να το ονομάσεις
__getattribute__
, μπορείς να το ονομάσεις κάτι πιο σύντομο ή πιο παράξενο
Μπορείς να καταλάβεις τον λόγο απλά βλέποντας το bytecode του:
Σημειώστε ότι το LOAD_ATTR
ανακτά επίσης το όνομα από το co_names
. Το Python φορτώνει ονόματα από την ίδια θέση εάν το όνομα είναι το ίδιο, έτσι το δεύτερο __getattribute__
φορτώνεται ακόμα από τη θέση offset=5. Χρησιμοποιώντας αυτό το χαρακτηριστικό μπορούμε να χρησιμοποιήσουμε το όνομα που θέλουμε μια φορά που το όνομα βρίσκεται στη μνήμη κοντά.
Για τη δημιουργία αριθμών θα πρέπει να είναι ασήμαντο:
0: not [[]]
1: not []
2: (not []) + (not [])
...
Σενάριο Εκμετάλλευσης
Δεν χρησιμοποίησα σταθερές λόγω του όριου μήκους.
Πρώτα εδώ υπάρχει ένα σενάριο για εμάς για να βρούμε αυτές τις θέσεις των ονομάτων.
Και το παρακάτω είναι για τη δημιουργία του πραγματικού εκμεταλλεύματος Python.
Βασικά κάνει τα ακόλουθα πράγματα, για εκείνες τις συμβολοσειρές που τις λαμβάνουμε από τη μέθοδο __dir__
:
Last updated