[FADB] 5. Sobreescritura directa de la dirección de retorno
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
0x804830f
0x8048313
0x8048316
0x8048318
0x804831b
0x804831e
0x8048324
0x8048327
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
Post a Comment
Comment, motherf*cker