27.05.2020 Cuando un programa se ejecuta, su memoria está organizada como: ------------- | Pila | Parte superior de la memoria |-------------| | | | | v | | | | | | | |-------------| | Texto | ------------- Parte baja de la memoria La pila se usa para el llamado de funciones y para guardar todas las variables automáticas (en C). gdb debugger lldb (macOS) gcc compilador desemsamblador Se podía explotar la misma forma de ejecución de un proceso. Es lo que vamos a ver en esta clase. $ uname -a Linux kali 5.5.0-kali2-amd64 #1 SMP Debian 5.5.17-1kali1 (2020-04-21) x86_64 GNU/Linux Probaremos con un procesador de 64 bits (programa Smash/tamanio.c) int 4 4 bytes long 8 8 bytes int * 8 8 puntero Usando 4 bytes para un entero, el número 1 sería: 0x00000001 Pero en los procesadores Intel los números se representan al revés: 0x01000000 Compilamos y debugeamos con gdb el programa: gcc -g -o prog1 pro1.c gdb (gdb) file prog1 (gdb) l (gdb) b 8 (gdb) run (gdb) p buffer1 $2 = "UUU\000" (gdb) p &va $3 = (int (*)[5]) 0x555555558030 <-Vean, los las direcciones bajas (gdb) p &buffer1 $4 = (char (*)[5]) 0x7fffffffe41b <-Está en la pila, direcciones altes (gdb) x/32x 0x7fffffffe400 0x7fffffffe400: 0x00000000 0x00000003 0x00000002 0x00000001 0x7fffffffe410: 0x55555160 0x00005555 0x55555040 0x00005555 0x7fffffffe420: 0xffffe430 0x00007fff 0x5555514d 0x00005555 0x7fffffffe430: 0x55555160 0x00005555 0xf7e1ce0b 0x00007fff 0x7fffffffe440: 0x00000000 0x00000000 0xffffe518 0x00007fff 0x7fffffffe450: 0x00300000 0x00000001 0x55555135 0x00005555 0x7fffffffe460: 0x00000000 0x00000000 0x268fbc3d 0x9c17f820 0x7fffffffe470: 0x55555040 0x00005555 0xffffe510 0x00007fff ^ ^ ^ ^ | | | | Los bytes 3 2 1 0 7 6 5 4 b a 9 8 f e d c El contenido de buffer1 es basura, porque no está inicializado y es "UUU\x00", y está en la dirección $4 = (char (*)[5]) 0x7fffffffe41b 0x7fffffffe410: 0x55555160 0x00005555 0x55555040 0x00005555 ^ ^ ^ ^ | | | | U \x00 U U Si se necesita más memoria se puede reservar con malloc() (y liberar con free()). Esta memoria lo da el sistema operativo en otro espacio que se llama "el montón" (heap). $ ./prog2 Segmentation fault Cuando se llama una función en C, se realiza lo siguiente: 1. Se salvan las variables de los argumentos de la función en la pila 2. Se guarda la dirección de regreso a la siguiente instrucción en la pila 3. Se guarda una copia de la dirección de la pila en la pila, para indicar donde empiezan las variables locales de la función. f( int a, int b, int c ) { char buffer[8]; } main( ) { f( 1, 2, 3 ); DIR: } push 3 push 2 push 1 push DIR push SP (Stack Pointer, indica la tapa de la pila) push buffer Entonces podríamos sobreescribir la dirección de retorno de la función y dirigirlo a donde nosotros quisiesemos. La sobreescritura sería aprovechando un agujero de seguridad al sobreesribir la variable buffer en la pila. $ gcc -o pointer pointer.c $ ./pointer 123 0x7ffe9658bf14 $ ./pointer 123 0x7ffd9a078584 $ ./pointer 123 0x7ffd759b71e4 - Ahora mismo los sistemas ejecutan en direcciones distintas los programas https://wiki.ubuntu.com/Security/Features Address Space Layout Randomisation (ASLR) cat /proc/sys/kernel/randomize_va_space **** El sistema ASLR hace más difícil un posible ataque, porque las direcciones están cambiando y no son fijas. **** En los sistemas con un procesador de 64 bits, las direcciones son de 8 bytes: 0x7fffffffe494 0x 00 00 7f ff ff ff e4 94 8 7 6 5 4 3 2 1 **** Existen ceros en las direcciones, esos ceros son el fin de cadena en C y ya no se pueden usar para sobreescribir la dirección de regreso **** Shellcode Los sistemas actuales también tienen protección en hardware para no ejecutar código en la pila. (checar Shell/shell.c) Sobreescritura de variables El compilador gcc tiene banderas para proteger los programas de esta vulnerabilidad. El gcc de macOS reorganiza las variables volátiles para evitar esta vulnerabilidad (checar el programa Overwrite/prog2.c)