Perché i buffer overflow vengono eseguiti nella direzione in cui si trovano?

13

Sto seguendo il video del Security Tube qui .

Esamina il buffer overflow e menziona come la memoria viene eseguita dal più alto al più basso nello stack (almeno con la sua implementazione presumo). Quindi passiamo l'indirizzo di memoria di una funzione che non viene chiamata nel programma, in un buffer di 3 parole. Sovrascriviamo quel buffer con una stringa di 12 caratteri, e quindi l'indirizzo di memoria indietro . Quindi sembra qualcosa del genere:

printf "123456789abc\x32\x07\x45\xb4" | ./demo

L'indirizzo effettivo era (b4450732).

Perché è possibile visualizzare l'indirizzo di memoria all'indietro, ma alla fine dello stack? Se la memoria viene letta da alta a bassa, non dovrebbe essere indietro, ma all'inizio della stringa passiamo al programma? Ovviamente, questo non è il caso, dal momento che ha mostrato che funziona. Però; mi sembra che lo stack non verrebbe sovraccaricato e che i valori x\cba987654321 verrebbero eseguiti.

    
posta theCowardlyFrench 01.03.2015 - 03:01
fonte

2 risposte

16

Crash Course in Computer Architecture

Nelle architetture Intel x86 e x64 c'è qualcosa chiamato stack. Questo è essenzialmente il luogo in cui viene memorizzato tutto ciò che determina il percorso di esecuzione. I parametri delle funzioni, le variabili locali e gli indirizzi di ritorno sono tutti memorizzati nello stack. I registri della CPU tengono traccia di dove si trova il programma in esecuzione. È possibile inserire valori e indirizzi di memoria nello stack fino alla dimensione in bit dell'architettura.

Che cosa intendo con questo? Se si utilizza un sistema a 32 bit, ogni valore che si preme sullo stack sarà essenzialmente un unsigned int a 32 bit (4 byte). Se si utilizza un sistema a 64 bit, avrà una dimensione di 64 bit (8 byte). Di seguito è riportato un esempio [1] di ciò che lo stack sembra per una funzione:

uint32_t function(int a, int b, int c, int d, int e, int f, int g, int h);

L'esempiosopraèperx64.IregistriCPURDI,RSI,RDX,RCX,R8eR9memorizzanoiprimi6parametrieilrestovieneinseritonellostack.returnaddress,gehsarannovaloria8byte.gpotrebbeessereugualesoloa0x10,maquandolosipremenellostacksembrerà0x0000000000000010.

Dopocheiparametrisonostatiinseritinellostack,vieneeseguital'istruzionecall.Ciòspingeràl'indirizzodiritornonellostackepasseràallafunzioneperl'esecuzione.Poichélostackaumentaversolospaziodiindirizzamentobassoognivoltachesipremequalcosanellostack,siavvicinaazero.Nell'immaginesopravedraianchelevariabililocalixx,yyezz.Ognunodiquestisimuoveancheinbassoversolamemoriainferiore.Certo,renditicontocheilprogrammastasempremanipolandoil top dello stack .

Overflow dello stack

Diciamo che crei una variabile locale che è un buffer con un massimo di 12 byte. Qualcosa del genere, unsigned char buffer[12]; . Lo stack crea spazio per 12 byte di dati. Diciamo che riempiamo questo buffer con "012345678912" che assomiglierà a questo nello stack 1 :

High
...
32313938
37363534
33323130
...
Low

Poiché l'inizio del buffer sarà sempre verso la memoria più bassa [2] . Quindi, quando inizi a scrivere in un buffer, scrivi sempre da Basso a Alto. Se non si alloca spazio sufficiente e si scrivono più dati di quelli assegnati, si verifica un overflow del buffer.

Endianess

Ora vuoi sovrascrivere l'indirizzo di ritorno nello stack. Lo metti alla fine della tua stringa in modo che, mentre la copia scrive sulla memoria più alta, sovrascriva il tuo indirizzo di ritorno con l'indirizzo che vuoi lì invece 2 . Intel ti lancia un'altra sfera curva. I sistemi x86 e x64 sono Little Endian. Ciò significa che il byte meno significativo si trova nel più piccolo indirizzo.

Quindi vorrai il byte meno significativo dell'indirizzo ( 0x32 ) scritto nella memoria inferiore. Quindi scriverai l'indirizzo nella memoria all'indietro perché stai scrivendo dalla memoria bassa a quella alta. Quando si visualizza l'indirizzo da alto a basso (come mostra l'esempio sopra) l'indirizzo di memoria apparirà corretto. Tuttavia, quando si guarda dal basso verso l'alto, sarà indietro. L'aspetto importante da ricordare è che l'architettura richiede che l'LSB venga scritto per ridurre la memoria.

1 - Sto usando un sistema a 32 bit qui perché una larghezza di 4 byte è più facile da disegnare.
2 - EBP salvato, sto ignorando questo perché non è importante per la discussione. È molto importante ricordare, ma per il momento ignorare la sua esistenza.

    
risposta data 02.03.2015 - 15:46
fonte
15

Ci sono due cose qui:

  1. Su x86 e x86-64 (e sulla maggior parte dell'hardware), lo stack cresce dalla parte superiore della memoria verso il basso. Per questo motivo, i dati utilizzati da una funzione (ad esempio il buffer che stai traboccando) si verificano a un numero di indirizzo inferiore rispetto ai dati utilizzati nel chiamare la funzione (ad esempio l'indirizzo per tornare dopo che la funzione è stata eseguita, che sei cercando di traboccare in).

  2. Su x86 e x86-64 (e una varietà di altri componenti hardware), i valori multibyte come gli indirizzi sono memorizzati in ordine little-endian , es. "indietro" dal punto di vista di una persona che lo legge.

risposta data 01.03.2015 - 03:10
fonte

Leggi altre domande sui tag