[FADB] 5. Sobreescritura directa de la dirección de retorno

FUNDAMENTOS DE LOS ATAQUES POR DESBORDAMIENTO DE BUFFER

Como acabamos de ver, tanto los parámetros como la dirección de retorno y las variables locales se guardan en la pila. Sabiendo esto podríamos escribir código que sobreescriba a propósito la dirección de retorno, haciéndo saltar al programa a cualquier dirección que especifiquemos cuando ejecute la instrucción RET.

El siguiente código en C, muy simple, sobreescribe la dirección de retorno de f() con 0x06666666:

void f()
{
char z[4];
int *p;
p = (int *)z;
p += 1;
*p = 0x06666666;
}

void main()
{
f();
}

Si lo guardamos como prueba1.c y lo compilamos con gcc -g prueba1.c -o prueba1 y lo ejecutamos obtendremos el error Segmentation fault. Esto es porque el proceso recibe una señal SIGSEGV (SIGnal SEGment Violation) por parte del sistema operativo al intentar acceder a la dirección 0x06666666, a la que no tiene derecho a acceder y que hemos puesto como dirección de retorno de f( ).

[bash]$ gdb -q prueba1
(gdb) run
Starting program: /home/m0s/Programacion/C%2fC++/prueba1
Program received signal SIGSEGV, Segmentation fault.
0x06666666 in ?? ()
(gdb) info registers eip
eip 0x6666666 0x6666666
(gdb)

¿Cómo funciona? main( ) simplemente llama a la función f( ). En f( ) declaramos primero un array de 4 caracteres z, y un puntero p (32 bits + 32 bits = 4+4 bytes reservados en la pila) a int (entero, 4 bytes). Vayamos a echarle un vistazo a f( ) con gdb.

(gdb) disas f
Dump of assembler code for function f:
0x804830c : sub $0x8,%esp
0x804830f : lea 0x4(%esp,1),%eax
0x8048313 : mov %eax,(%esp,1)
0x8048316 : mov %esp,%eax
0x8048318 : addl $0x4,(%eax)
0x804831b : mov (%esp,1),%eax
0x804831e : movl $0x6666666,(%eax)
0x8048324 : add $0x8,%esp
0x8048327 : ret
End of assembler dump.
(gdb) break f
Breakpoint 1 at 0x804830c: file prueba1.c, line 2.
(gdb) info registers esp
esp 0xbffff7fc 0xbffff7fc
(gdb) x 0xbffff7fc
0xbffff7fc : 0x08048337
(gdb)

Primero marcamos un punto de interrupción en la entrada de f( ). Vemos que al entrar en f( ), ESP = 0xBFFFF7FC. Sabemos que justo en ese momento ESP apunta a la dirección de retorno, guardada en la pila, que como vemos es 0x08048337.

SUB 0x8,ESP reserva espacio (8 bytes) para z y p y deja así ESP apuntando a p.

LEA 0x4(ESP,1),EAX y MOV EAX,(ESP,1) corresponden a la línea p = (int *)z, que como sabemos hace que p apunte al mismo sitio que z: LEA 0x4(ESP,1),EAX carga la dirección de z (ESP+4) en EAX y MOV EAX,(ESP,1) pone el contenido de EAX (la dirección de z) en la dirección apuntada por ESP (p).

MOV ESP,EAX y ADDL 0x4,(EAX) corresponden a p += 1: MOV ESP,EAX mueve la dirección en ESP (que apunta a p) a EAX, y ADDL 0x4,(EAX) suma 4 al contenido de EAX (suma 4 a p). ¿Por qué sumamos 1 en C y 4 en ensamblador? Porque C es un lenguaje de alto nivel y como hemos declarado int *p y el tipo de datos int ocupa 4 bytes, C mueve el puntero 4 bytes, supuestamente para apuntar al siguiente dato int.

Al mover el puntero p 4 bytes, nos encontramos con que ahora apunta a la famosa dirección de retorno, que como hemos dicho es la siguiente en la pila. *p = 0x06666666 escribe el número 0x06666666 en la dirección apuntada por p, es decir, sobreescribe la dirección de retorno con este valor. A esta instrucción en C le corresponden las instrucciones MOV (ESP,1),EAX, que pone el contenido de ESP (dirección de p) en EAX, y MOVL 0x6666666,(EAX), que carga 0x6666666 en el espacio apuntado por EAX (o apuntado por p).

Finalmente, ADD 0x8,ESP es la instrucción contraria a la inicial SUB 0x8,ESP, es decir, desasigna el espacio reservado a las variables locales z y p, devolviendo así la dirección original a ESP.

Volviendo al depurador, nos habíamos quedado justo en el principio de f( ), en la dirección 0x804830C:

(gdb) info registers eip esp eax
eip 0x804830c 0x804830c
esp 0xbffff7fc 0xbffff7fc
eax 0x0 0
(gdb) nexti [SUB 0x8, ESP]
6 p = (int *)z;
(gdb) info registers esp
esp 0xbffff7f4 0xbffff7f4
(gdb) nexti [LEA 0x4(ESP,1), EAX]
0x08048313 6 p = (int *)z;
(gdb) info registers esp eax
esp 0xbffff7f4 0xbffff7f4
eax 0xbffff7f8 -1073743880
(gdb) nexti [MOV EAX, (ESP,1)]
7 p += 1;
(gdb) info registers esp eax
esp 0xbffff7f4 0xbffff7f4
eax 0xbffff7f8 -1073743880
(gdb) x 0xbffff7f4
0xbffff7f4: 0xbffff7f8
(gdb) nexti [MOV ESP,EAX]
0x08048318 7 p += 1;
(gdb) info registers esp eax
esp 0xbffff7f4 0xbffff7f4
eax 0xbffff7f4 -1073743884
(gdb) nexti [ADDL 0x4, (EAX)]
8 *p = 0x06666666;
(gdb) x 0xbffff7f4
0xbffff7f4: 0xbffff7fc
(gdb) nexti [MOV (ESP,1), EAX]
0x0804831e 8 *p = 0x06666666;
(gdb) info registers esp eax
esp 0xbffff7f4 0xbffff7f4
eax 0xbffff7fc -1073743876
(gdb) x 0xbffff7f4
0xbffff7f4: 0xbffff7fc
(gdb) nexti [MOVL 0x6666666, (EAX)]
9 }
(gdb) info registers esp eax
esp 0xbffff7f4 0xbffff7f4
eax 0xbffff7fc -1073743876
(gdb) x 0xbffff7fc
0xbffff7fc: 0x06666666
(gdb) nexti [ADD 0x8, ESP]
0x08048327 9 }
(gdb) info registers esp eax
esp 0xbffff7fc 0xbffff7fc
eax 0xbffff7fc -1073743876
(gdb) nexti [RET]
Program received signal SIGSEGV, Segmentation fault.
0x06666666 in ?? ()
(gdb) info registers eip
eip 0x6666666 0x6666666
(gdb)

Este ejemplo es totalmente benigno ya que saltamos a una dirección fija prohibida para nuestro programa. Pero esto no tiene por qué ser así como veremos más adelante.

Comments

Popular Posts