LOAD_NAME / LOAD_CONST opcode OOB Read
Last updated
Last updated
Impara e pratica il hacking AWS:HackTricks Training AWS Red Team Expert (ARTE) Impara e pratica il hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)
Queste informazioni sono state prese da questo writeup.
Possiamo utilizzare la funzione OOB read nell'opcode LOAD_NAME / LOAD_CONST per ottenere alcuni simboli nella memoria. Ciò significa utilizzare trucchi come (a, b, c, ... centinaia di simboli ..., __getattribute__) if [] else [].__getattribute__(...)
per ottenere un simbolo (come il nome di una funzione) che desideri.
Poi basta creare il tuo exploit.
Il codice sorgente è piuttosto breve, contiene solo 4 righe!
Puoi inserire 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.
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 si verifica l'errore di segmentazione?
Iniziamo con un semplice esempio, [a, b, c]
potrebbe essere compilato nel seguente bytecode.
Ma cosa succede se il 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 da cui originariamente dovrebbe essere. Sì, questa è una "caratteristica" di lettura fuori limite.
Il concetto fondamentale per la soluzione è semplice. Alcuni opcodes in CPython, ad esempio LOAD_NAME
e LOAD_CONST
, sono vulnerabili (?) a letture OOB.
Essi recuperano un oggetto dall'indice oparg
dalla tupla consts
o names
(questo è ciò che co_consts
e co_names
sono chiamati dietro le quinte). Possiamo fare riferimento al seguente breve frammento su LOAD_CONST
per vedere cosa fa CPython quando elabora l'opcode LOAD_CONST
.
In questo modo possiamo utilizzare la funzione OOB per ottenere un "nome" da un offset di memoria arbitrario. Per assicurarci di quale nome si tratta e qual è il suo offset, continua a provare LOAD_NAME 0
, LOAD_NAME 1
... LOAD_NAME 99
... E potresti trovare qualcosa in circa oparg > 700. Puoi anche provare a usare gdb per dare un'occhiata al layout della memoria, ma non credo che sarebbe più facile?
Una volta recuperati quegli offset utili per nomi / consts, come facciamo a ottenere un nome / const da quell'offset e usarlo? Ecco un trucco per te:
Supponiamo di poter ottenere un nome __getattribute__
dall'offset 5 (LOAD_NAME 5
) con co_names=()
, quindi fai semplicemente le seguenti cose:
Nota che non è necessario chiamarlo
__getattribute__
, puoi chiamarlo con un nome più corto o più strano
Puoi capire il motivo semplicemente visualizzando il suo bytecode:
Nota che LOAD_ATTR
recupera anche il nome da co_names
. Python carica i nomi dallo stesso offset se il nome è lo stesso, quindi il secondo __getattribute__
è ancora caricato da offset=5. Utilizzando questa funzionalità possiamo usare 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 usato consts a causa del limite di lunghezza.
Prima ecco uno script per trovare quegli offset dei nomi.
E il seguente è per generare il vero exploit Python.
Fondamentalmente fa le seguenti cose, per quelle stringhe le otteniamo dal metodo __dir__
:
Impara e pratica il hacking AWS:HackTricks Training AWS Red Team Expert (ARTE) Impara e pratica il hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)