LOAD_NAME / LOAD_CONST opcode OOB Read
Last updated
Last updated
Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE) Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Αυτές οι πληροφορίες ελήφθησαν από αυτήν την ανάρτηση.
Μπορούμε να χρησιμοποιήσουμε τη δυνατότητα OOB read στο LOAD_NAME / LOAD_CONST opcode για να αποκτήσουμε κάποιο σύμβολο στη μνήμη. Αυτό σημαίνει ότι χρησιμοποιούμε κόλπα όπως (a, b, c, ... εκατοντάδες σύμβολα ..., __getattribute__) if [] else [].__getattribute__(...)
για να αποκτήσουμε ένα σύμβολο (όπως το όνομα μιας συνάρτησης) που θέλουμε.
Στη συνέχεια, απλά κατασκευάζετε την εκμετάλλευσή σας.
Ο πηγαίος κώδικας είναι αρκετά σύντομος, περιέχει μόνο 4 γραμμές!
Μπορείτε να εισάγετε αυθαίρεο Python κώδικα, και θα μετατραπεί σε ένα Python code object. Ωστόσο, το co_consts
και το co_names
αυτού του code object θα αντικατασταθούν με ένα κενό tuple πριν την εκτέλεση αυτού του code object.
Έτσι, με αυτόν τον τρόπο, όλες οι εκφράσεις που περιέχουν σταθερές (π.χ. αριθμούς, συμβολοσειρές κ.λπ.) ή ονόματα (π.χ. μεταβλητές, συναρτήσεις) μπορεί να προκαλέσουν σφάλμα τμηματοποίησης στο τέλος.
Πώς συμβαίνει το segfault;
Ας ξεκινήσουμε με ένα απλό παράδειγμα, [a, b, c]
θα μπορούσε να μετατραπεί στον παρακάτω bytecode.
Αλλά τι γίνεται αν το co_names
γίνει κενό tuple; Ο opcode LOAD_NAME 2
εκτελείται ακόμα και προσπαθεί να διαβάσει την τιμή από τη μνήμη που αρχικά θα έπρεπε να είναι. Ναι, αυτό είναι ένα χαρακτηριστικό "out-of-bound read".
Η βασική έννοια για τη λύση είναι απλή. Ορισμένοι opcodes στην CPython, για παράδειγμα LOAD_NAME
και LOAD_CONST
, είναι ευάλωτοι (?) σε OOB read.
Ανακτούν ένα αντικείμενο από τον δείκτη oparg
από το tuple consts
ή names
(αυτό είναι που ονομάζονται co_consts
και co_names
κάτω από την επιφάνεια). Μπορούμε να αναφερθούμε στο παρακάτω σύντομο απόσπασμα σχετικά με το LOAD_CONST
για να δούμε τι κάνει η CPython όταν επεξεργάζεται τον opcode LOAD_CONST
.
Με αυτόν τον τρόπο μπορούμε να χρησιμοποιήσουμε τη δυνατότητα OOB για να αποκτήσουμε ένα "όνομα" από αυθαίρετη διεύθυνση μνήμης. Για να βεβαιωθούμε ποιο όνομα έχει και ποια είναι η διεύθυνσή του, απλώς συνεχίστε να δοκιμάζετε LOAD_NAME 0
, LOAD_NAME 1
... LOAD_NAME 99
... Και θα μπορούσατε να βρείτε κάτι σε περίπου oparg > 700. Μπορείτε επίσης να προσπαθήσετε να χρησιμοποιήσετε το gdb για να ρίξετε μια ματιά στη διάταξη μνήμης φυσικά, αλλά δεν νομίζω ότι θα ήταν πιο εύκολο;
Μόλις ανακτήσουμε αυτές τις χρήσιμες διευθύνσεις για ονόματα / σταθερές, πώς ακριβώς αποκτούμε ένα όνομα / σταθερά από αυτή τη διεύθυνση και το χρησιμοποιούμε; Εδώ είναι ένα κόλπο για εσάς:
Ας υποθέσουμε ότι μπορούμε να αποκτήσουμε ένα όνομα __getattribute__
από τη διεύθυνση 5 (LOAD_NAME 5
) με co_names=()
, τότε απλώς κάντε τα εξής:
Παρατηρήστε ότι δεν είναι απαραίτητο να το ονομάσετε ως
__getattribute__
, μπορείτε να το ονομάσετε με κάτι πιο σύντομο ή πιο περίεργο
Μπορείτε να κατανοήσετε τον λόγο πίσω από αυτό απλά βλέποντας τον bytecode του:
Σημειώστε ότι το LOAD_ATTR
ανακτά επίσης το όνομα από το co_names
. Η Python φορτώνει ονόματα από την ίδια θέση αν το όνομα είναι το ίδιο, οπότε το δεύτερο __getattribute__
φορτώνεται ακόμα από offset=5. Χρησιμοποιώντας αυτή τη δυνατότητα, μπορούμε να χρησιμοποιήσουμε οποιοδήποτε όνομα μόλις το όνομα είναι στη μνήμη κοντά.
Για τη δημιουργία αριθμών θα πρέπει να είναι απλό:
0: όχι [[]]
1: όχι []
2: (όχι []) + (όχι [])
...
Δεν χρησιμοποίησα consts λόγω του περιορισμού μήκους.
Πρώτα εδώ είναι ένα σενάριο για να βρούμε αυτά τα offsets των ονομάτων.
Και το παρακάτω είναι για τη δημιουργία του πραγματικού εκμεταλλευτή Python.
Βασικά εκτελεί τα εξής πράγματα, για αυτές τις συμβολοσειρές τις αποκτούμε από τη μέθοδο __dir__
:
Μάθετε & εξασκηθείτε στο AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE) Μάθετε & εξασκηθείτε στο GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)