[RoLUG] RSG: Buffer overflow paper update
Roccatello Eduard
rolug@lists.linux.it
Sat, 15 Feb 2003 23:10:20 +0100
--------------Boundary-00=_8XEDTBSM91X4ORKHRG6P
Content-Type: text/plain;
charset="iso-8859-1"
Content-Transfer-Encoding: quoted-printable
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
Questa =E8 la situazione del paper:
v Introduzione
v Definizione di Stack
v Gestione della memoria dei processi
v Analisi strutturale dello stack di un processo
IP Violazione dello stack (Buffer Overflow)=20
IP Shellcode e loro generazione
x Sfruttare la vulnerabilit=E0
Legenda:=20
v =3D testing (presente nell'allegato)
IP =3D in corso di scrittura (non presente dell'allegato)
x =3D da fare
Mi sto impegnando per farli + semplice possibile ma sono argomenti ostici=
da=20
trattare quindi non so cosa ne ho ricavato. Provate a dare un occhio e a=20
commentare in lista in modo da poter modificare le parti difficili.
Grazie a tutti per l'attenzione :-)
PS: Ferdi che ne dici di accennare RSG sul sito web di rolug? (lo so, ti=20
faccio sempre lavorare :-)
- --=20
Roccatello Eduard
RoLUG member @ http://rovigo.linux.it
Webmaster @ http://www.pcimprover.it
Look to the headers for my GnuPG key
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.2.1 (GNU/Linux)
iD8DBQE+TrrOjUY2i0dNbbARAr0tAJ4kpYRjUt0+4+EIBjXvoBI5xroEIQCfWGDt
xZwv/xb0212co+8HFKIk8Hw=3D
=3DTZZi
-----END PGP SIGNATURE-----
--------------Boundary-00=_8XEDTBSM91X4ORKHRG6P
Content-Type: text/plain;
charset="us-ascii";
name="bufferoverflow2"
Content-Transfer-Encoding: quoted-printable
Content-Disposition: attachment; filename="bufferoverflow2"
RoLUG Security Guide
Buffer Overflows
INDEX
- Introduzione
- Definizione di Stack
- Gestione della memoria dei processi
- Analisi strutturale dello stack di un processo
- Violazione dello stack (Buffer Overflow)
- Shellcode e loro generazione
- Sfruttare la vulnerabilit=E0
INTRODUZIONE
Una delle vulnerabilit=E0 pi=F9 diffuse nei programmi =E8 sicuramente il =
buffer
overflow, che detiene il primato insieme a format string bug.
Usata da tempo dagli hacker per verificare la sicurezza dei sistemi
informatici, =E8 stata illustrata pubblicamente da AlephOne nel numero 49
della famosissima rivista elettronica underground Phrack (raggiungibile
all'indirizzo http://www.phrack.org).
Consiste essenzialmente nell'esecuzione arbitraria di codice malizioso e
sfrutta il mancato controllo delle dimensioni delle zone di memoria (buff=
er)
sulle quali verranno scritte le variabili del programma.
DEFINIZIONE DI STACK
Lo stack =E8 una struttura dati astratta (ADT) molto utilizzata nelle
archittetture informatiche odierne.
Per struttura dati astratta si intende un modello di struttura in grado d=
i
immagazzinare i dati e di compiere operazioni sui dati inseriti.
Uno stack pu=F2 essere rappresentato come una pila di oggetti, vincolata =
dal
fatto che la rimozione e l'aggiunta degli elementi pu=F2 essere fatta sol=
o in
cima. Si pu=F2 parlare quindi di una struttura LIFO (Last In First Out), =
dove
l'ultimo elemento ad entrare nello stack =E8 anche il primo ad uscire.
I metodi principali definibili di uno stack sono 3: PUSH, POP e TOP (dove
per metodo si intende una funzione eseguibile dallo stack).
PUSH =E8 il metodo standard per l'aggiunta degli oggetti allo stack, POP =
serve
a leggere ed a togliere l'elemento in cima allo stack mentre TOP esegue
solamente la lettura dell'oggetto pi=F9 in alto nella pila senza levarlo =
dalla
stessa. Le prestazioni di uno stack sono ottimali; ogni operazione
effettuabile ha prestazioni asintotiche O(1), cio=E8 il numero di element=
i
contenuti non influenza minimamente le prestazioni ottenibili, che dipend=
ono
invece dal tipo di implementazione effettuato dagli sviluppatori.
La creazione di una struttura dati come lo stack deriva dalla necessit=E0=
di
avere un'architettura pi=F9 consona possibile ai linguaggi di programmazi=
one
ad alto livello (molto pi=F9 simili al linguaggio parlato che al linguagg=
io
adottato dalla macchina). Non =E8 difficile trovare implementazioni di st=
ack
nelle moderne apparecchiature. Lo stack si presta infatti agevolmente al=20
passaggio degli argomenti di una funzione e all'archiviazione di dati
sequenziali LIFO come la sospensione dei metodi in un programma ed =E8 la
struttura utilizzata dalle archittetture i386 per la gestione delle varia=
bili
in memoria durante l'esecuzione di un programma.
GESTIONE DELLA MEMORIA DEI PROCESSI NEI SISTEMI LINUX
Per rendersi conto di come funzioni l'exploit della vulnerabilit=E0
generata dai buffer overflow bisogna aver chiaro come i processi vengano
allocati nella memoria.
Ogni processo attivo possiede tre regioni di memoria con caratteristiche=20
differenti, generalmente individuate con i nomi "Text", "Data" e "Stack".
La regione "Text" =E8 fissata dal programma e contiene istruzioni e dati
raggiungibili in modalit=E0 di sola lettura. Un eventuale accesso in scri=
ttura
a questa zona produce un errore irreversibile che termina il programma.
La regione "Data" contiene dati sia inizializzati che non e indirizza anc=
he
le variabili statiche, imagazzinate in questa regione di memoria.
La dimensione di questa regione di memoria pu=F2 essere modificata da una
chiamata di sistema particolare e nel caso venga esaurito lo spazio di
memoria allocata per il processo, questo viene reimpostato dallo schedule=
r
con una dimensione di memoria pi=F9 grande della precendente.
Questa zona di memoria viene comunemente chiamata come zona DATA-BSS di u=
n
file eseguibile.
La terza regione di memoria allocata per ogni processo =E8 lo stack dove=20
vengono memorizzate le variabili non statiche del programma ed =E8 l'ogge=
tto
della vulnerabilit=E0 trattata in questo capitolo.
=09=09[---------------] Zona di memoria con
=09=09[ T E X T ] indirizzi bassi
=09=09[---------------]
=09=09[ D A T A ]
=09=09[---------------]
=09=09[ S T A C K ] Zona di memoria con
=09=09[---------------] indirizzi alti
ANALISI STRUTTURALE DELLO STACK DI UN PROCESSO
Abbiamo visto che ogni processo genera tre regioni di memoria, una delle
quali =E8 lo stack. Ma cos'=E8 contenuto esattamente nello stack?
Si tratta di una zona di memoria creata dinamicamente all'avvio del proce=
sso
e viene modificata durante dell'esecuzione del programma.
Per capire meglio com'=E8 strutturato lo stack di un processo, =E8 bene i=
ntrodurne
uno che, seppur molto semplice, permette una schematizzazione chiara dell=
a
zona di memoria "Stack":
// Start: code.c
void funzione(int i)
{=09char buffer[64];
}
void main(void)
{=09funzione(15);
}
// End: code.c
Salviamo questa piccola porzione di codice in un file (che io chiamer=F2 =
code.c)
e compiliamo con "gcc -o code code.c".
Eseguendo il programma in questione ci accorgeremo che il programma non
invia nulla allo standard output.
Proviamo ad analizzarlo in profondit=E0 con GDB per capire cosa fa questa
piccola porzione di codice:
bash-2.05a$ gdb code
GNU gdb 5.2
Copyright 2002 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you =
are
welcome to change it and/or distribute copies of it under certain conditi=
ons.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for detail=
s.
This GDB was configured as "i386-slackware-linux"...
(gdb) disassemble main
Dump of assembler code for function main:
0x80481c8 <main>: push %ebp
0x80481c9 <main+1>: mov %esp,%ebp
0x80481cb <main+3>: sub $0x8,%esp
0x80481ce <main+6>: add $0xfffffff4,%esp
0x80481d1 <main+9>: push $0xf
0x80481d3 <main+11>: call 0x80481c0 <funzione>
0x80481d8 <main+16>: add $0x10,%esp
0x80481db <main+19>: xor %eax,%eax
0x80481dd <main+21>: jmp 0x80481e0 <main+24>
0x80481df <main+23>: nop
0x80481e0 <main+24>: leave
0x80481e1 <main+25>: ret
0x80481e2 <main+26>: nop
=2E..altri NOP
0x80481ef <main+39>: nop
End of assembler dump.
Il programma in questione, come prima cosa, salva il frame pointer nello =
stack
e imposta come nuovo frame pointer, lo stack pointer.
Nel frame pointer =E8 contenuto l'indirizzo di memoria della funzione app=
ena
eseguita dal microprocessore.
Vengono successivamente sottratti 8 byts dallo stack pointer per creare l=
o
stack frame privato della funzione. All'indirizzo 0x80481d1 (<main+9>)
inizia la chiamata alla funzione.
Le funzioni vengono chiamate dai programmi tramite l'inserimento (push) d=
egli
argomenti nello stack, in ordine inverso (dall'ultimo al primo), e tramit=
e
la funzione "call indirizzo".
In questo caso viene fatto il push di 0xf (15 in decimale) nello stack e =
poi
viene chiamata la funzione all'indirizzo 0x80481c0.
Al ritorno della funzione viene aggiunto il valore esadecimale 10 allo st=
ack
pointer facendo "avanzare" il puntatore, viene azzerato il registro
accumulatore EAX (si vedano le propriet=E0 dello XOR, detto anche OR Escl=
usivo,
per ulteriori chiarimenti sull'istruzione) e viene eseguito un salto forz=
ato
all'istruzione "leave".
Entriamo nel profondo della funzione "funzione" e vediamo come vengono
allocate le variabili di un programma, probabilmente la parte pi=F9 inter=
essante
di un programma :-)
(gdb) disassemble funzione
Dump of assembler code for function funzione:
0x80481c0 <funzione>: push %ebp
0x80481c1 <funzione+1>: mov %esp,%ebp
0x80481c3 <funzione+3>: sub $0x48,%esp
0x80481c6 <funzione+6>: leave
0x80481c7 <funzione+7>: ret
End of assembler dump.
Dopo aver eseguito l'istruzione "call", la routine "main" viene sospesa e=
il
controllo passa alla routine "funzione".
Viene salvato il frame pointer nullo stack e viene copiato il valore dell=
o
stesso nello stack pointer.
Immediatamente dopo avviene l'allocazione delle variabili, in questo caso=
un
array (successione) di 64 char.
I computer basati su architettura Intel considerano gli array di char com=
e
serie di caratteri terminata da un carattere nullo (0x0 in esadecimale).
Per allocare una variabile il sistema sposta lo stack pointer di tanti by=
tes
quanti occorrono al programma cio=E8 della dimensione delle variabili rif=
erita
alle dword occupate. La parola binaria pi=F9 piccola considerata nei sist=
emi
Intel =E8 di 4 bytes, per cui la dimensione delle variabili sar=E0 riferi=
ta a 4
bytes. Ad esempio se ho un array di 5 char il sistema allocher=E0 8 bytes=
,
corrispondenti a 2 dword.
Molto probabilmente avrete gi=E0 fatto i conti e come credo vi sembrer=E0=
strano
che al posto di 0x40 troviate 0x48. Teoricamente il compilatore dovrebbe
allocare esattamente la dimensione di tutte le variabili dichiarate nella
funzione ma, generalmente, viene allocata pi=F9 memoria del necessario pe=
r
contenere eventuali overflow (fuoriuscite).
Ora che abbiamo capito come funzionano le chiamate alle funzioni possiamo
schematizzare lo stack del nostro programma in questo modo:
[--buffer--][--stack pointer--][--instruction pointer--][--i--]
Cima dello stack=09=09=09=09=09Fondo dello stack
Riassumendo, sul fondo dello stack troviamo gli argomenti passati alla
funzione, poi troviamo l'istruction pointer (detto anche RET) e lo stack
pointer. Sulla cima dello stack possiamo trovare la locazione fisica dell=
e
variabili elaborate dal nostro programma (in questo caso solo buffer).
--------------Boundary-00=_8XEDTBSM91X4ORKHRG6P--