LOAD_NAME / LOAD_CONST opcode OOB Read
Last updated
Last updated
Impara e pratica l'hacking su AWS:HackTricks Training AWS Red Team Expert (ARTE) Impara e pratica l'hacking su GCP: HackTricks Training GCP Red Team Expert (GRTE)
Questa informazione è stata presa da questo articolo.
Possiamo utilizzare la funzionalità di lettura OOB nell'opcode LOAD_NAME / LOAD_CONST per ottenere alcuni simboli in memoria. Ciò significa utilizzare trucchi come (a, b, c, ... centinaia di simboli ..., __getattribute__) if [] else [].__getattribute__(...)
per ottenere un simbolo (come ad esempio il nome di una funzione) desiderato.
Poi basta creare il proprio exploit.
Il codice sorgente è piuttosto breve, contiene solo 4 righe!
Puoi inserire del codice Python arbitrario e verrà compilato in un oggetto codice Python. Tuttavia, co_consts
e co_names
di quell'oggetto codice verranno sostituiti con una tupla vuota prima di valutare quell'oggetto codice.
Quindi, in questo modo, tutte le espressioni che contengono costanti (ad es. numeri, stringhe, ecc.) o nomi (ad es. variabili, funzioni) potrebbero causare un errore di segmentazione alla fine.
Come avviene il segmentation fault?
Iniziamo con un esempio semplice, [a, b, c]
potrebbe essere compilato nel bytecode seguente.
Ma cosa succede se co_names
diventa una tupla vuota? L'opcode LOAD_NAME 2
viene comunque eseguito e cerca di leggere il valore da quell'indirizzo di memoria inizialmente previsto. Sì, questa è una "caratteristica" di lettura out-of-bound.
Il concetto principale per la soluzione è semplice. Alcuni opcode in CPython come LOAD_NAME
e LOAD_CONST
sono vulnerabili (?) alla lettura OOB.
Recuperano un oggetto dall'indice oparg
dalla tupla consts
o names
(che è come sono chiamati sotto il cofano co_consts
e co_names
). Possiamo fare riferimento al breve snippet seguente su LOAD_CONST
per vedere cosa fa CPython quando elabora l'opcode LOAD_CONST
.
In questo modo possiamo utilizzare la funzionalità OOB per ottenere un "nome" da un offset di memoria arbitrario. Per assicurarci di quale nome si tratti e qual è il suo offset, basta provare LOAD_NAME 0
, LOAD_NAME 1
... LOAD_NAME 99
... E potresti trovare qualcosa intorno a oparg > 700. Puoi anche provare ad utilizzare gdb per dare un'occhiata alla struttura della memoria ovviamente, ma non penso che sarebbe più facile?
Una volta che abbiamo recuperato quegli utili offset per i nomi / costanti, come possiamo ottenere un nome / costante da quell'offset e utilizzarlo? Ecco un trucco per te:
Supponiamo di poter ottenere un nome __getattribute__
dall'offset 5 (LOAD_NAME 5
) con co_names=()
, allora basta fare le seguenti operazioni:
Notare che non è necessario chiamarlo
__getattribute__
, puoi chiamarlo con un nome più corto o strano
Puoi capire il motivo semplicemente visualizzando il bytecode:
Si noti che LOAD_ATTR
recupera anche il nome da co_names
. Python carica i nomi dalla stessa posizione se il nome è lo stesso, quindi il secondo __getattribute__
viene comunque caricato dalla posizione=5. Utilizzando questa caratteristica possiamo utilizzare un nome arbitrario una volta che il nome è in memoria nelle vicinanze.
Per generare numeri dovrebbe essere banale:
0: not [[]]
1: not []
2: (not []) + (not [])
...
Non ho utilizzato costanti a causa del limite di lunghezza.
Ecco prima uno script per trovare quelle posizioni dei nomi.
E il seguente è per generare l'effettivo exploit Python.
Si limita fondamentalmente a fare le seguenti cose, per le stringhe che otteniamo dal metodo __dir__
:
Impara e pratica l'hacking su AWS: HackTricks Training AWS Red Team Expert (ARTE) Impara e pratica l'hacking su GCP: HackTricks Training GCP Red Team Expert (GRTE)