Stack Canaries
StackGuard y StackShield
StackGuard inserta un valor especial conocido como canary antes del EIP (Extended Instruction Pointer), específicamente 0x000aff0d
(que representa nulo, nueva línea, EOF, retorno de carro) para protegerse contra desbordamientos de búfer. Sin embargo, funciones como recv()
, memcpy()
, read()
y bcopy()
siguen siendo vulnerables, y no protege el EBP (Base Pointer).
StackShield adopta un enfoque más sofisticado que StackGuard al mantener una Pila de Retorno Global, que almacena todas las direcciones de retorno (EIPs). Esta configuración garantiza que cualquier desbordamiento no cause daño, ya que permite comparar las direcciones de retorno almacenadas con las reales para detectar ocurrencias de desbordamiento. Además, StackShield puede verificar la dirección de retorno frente a un valor límite para detectar si el EIP apunta fuera del espacio de datos esperado. Sin embargo, esta protección puede ser eludida mediante técnicas como Return-to-libc, ROP (Programación Orientada a Retorno) o ret2ret, lo que indica que StackShield tampoco protege las variables locales.
Protector de Stack Smash (ProPolice) -fstack-protector
:
-fstack-protector
:Este mecanismo coloca un canary antes del EBP y reorganiza las variables locales para posicionar los búferes en direcciones de memoria más altas, evitando que sobrescriban otras variables. También copia de forma segura los argumentos pasados en la pila por encima de las variables locales y utiliza estas copias como argumentos. Sin embargo, no protege los arrays con menos de 8 elementos o los búferes dentro de una estructura de usuario.
El canary es un número aleatorio derivado de /dev/urandom
o un valor predeterminado de 0xff0a0000
. Se almacena en TLS (Thread Local Storage), lo que permite que espacios de memoria compartidos entre hilos tengan variables globales o estáticas específicas del hilo. Estas variables se copian inicialmente del proceso padre, y los procesos hijos pueden modificar sus datos sin afectar al padre o a los hermanos. Sin embargo, si se utiliza un fork()
sin crear un nuevo canary, todos los procesos (padre e hijos) comparten el mismo canary, volviéndolo vulnerable. En la arquitectura i386, el canary se almacena en gs:0x14
, y en x86_64, en fs:0x28
.
Esta protección local identifica funciones con búferes vulnerables a ataques e inyecta código al inicio de estas funciones para colocar el canary, y al final para verificar su integridad.
Cuando un servidor web utiliza fork()
, habilita un ataque de fuerza bruta para adivinar el byte del canary uno por uno. Sin embargo, usar execve()
después de fork()
sobrescribe el espacio de memoria, anulando el ataque. vfork()
permite que el proceso hijo se ejecute sin duplicación hasta que intenta escribir, momento en el que se crea una duplicación, ofreciendo un enfoque diferente para la creación de procesos y el manejo de memoria.
Longitudes
En binarios x64
, la cookie del canary es un qword de 0x8
bytes. Los primeros siete bytes son aleatorios y el último byte es un byte nulo.
En binarios x86
, la cookie del canary es un dword de 0x4
bytes. Los primeros tres bytes son aleatorios y el último byte es un byte nulo.
El byte menos significativo de ambos canaries es un byte nulo porque será el primero en la pila proveniente de direcciones más bajas y, por lo tanto, las funciones que leen cadenas se detendrán antes de leerlo.
Bypasses
Filtrar el canary y luego sobrescribirlo (por ejemplo, desbordamiento de búfer) con su propio valor.
Si el canary se bifurca en procesos hijos podría ser posible forzarlo un byte a la vez:
Si hay alguna fuga interesante o vulnerabilidad de lectura arbitraria en el binario, podría ser posible filtrarlo:
Sobrescribir punteros almacenados en la pila
La pila vulnerable a un desbordamiento de pila podría contener direcciones a cadenas o funciones que pueden ser sobrescritas para explotar la vulnerabilidad sin necesidad de alcanzar el canary de la pila. Verifica:
Pointer RedirectingModificar tanto el canary maestro como el del hilo
Un desbordamiento de búfer en una función enhebrada protegida con canary puede usarse para modificar el canary maestro del hilo. Como resultado, la mitigación es inútil porque la verificación se realiza con dos canaries que son iguales (aunque modificados).
Además, un desbordamiento de búfer en una función enhebrada protegida con canary podría usarse para modificar el canary maestro almacenado en el TLS. Esto se debe a que podría ser posible alcanzar la posición de memoria donde se almacena el TLS (y por lo tanto, el canary) a través de un bof en la pila de un hilo. Como resultado, la mitigación es inútil porque la verificación se realiza con dos canaries que son iguales (aunque modificados). Este ataque se realiza en el documento: http://7rocky.github.io/en/ctf/htb-challenges/pwn/robot-factory/#canaries-and-threads
Consulta también la presentación de https://www.slideshare.net/codeblue_jp/master-canary-forging-by-yuki-koike-code-blue-2015 que menciona que generalmente el TLS se almacena mediante mmap
y cuando se crea una pila de hilo también se genera mediante mmap
según esto, lo que podría permitir el desbordamiento como se muestra en el documento anterior.
Modificar la entrada GOT de
__stack_chk_fail
Si el binario tiene RELRO parcial, entonces puedes usar una escritura arbitraria para modificar la entrada GOT de __stack_chk_fail
y que sea una función ficticia que no bloquee el programa si el canary se modifica.
Este ataque se realiza en el documento: https://7rocky.github.io/en/ctf/other/securinets-ctf/scrambler/
Referencias
Last updated