Experimento de eliminación de Stack Canary

Feb 27 2023
Inspirado por Walter Harold Bishop, en esta publicación intentaré encontrar una mejor manera de proteger la integridad de las direcciones de retorno en comparación con los canarios de pila. Tenga en cuenta que no creo que lo que tenía en mente sea la solución perfecta.

Inspirado por Walter Harold Bishop, en esta publicación intentaré encontrar una mejor manera de proteger la integridad de las direcciones de retorno en comparación con los canarios de pila. Tenga en cuenta que no creo que lo que tenía en mente sea la solución perfecta. Hay muchas cosas en las que aún no he pensado bien. De todos modos, puedes pensar en esto como "Intento #1".

No me gustan los canarios de pila porque en realidad no resuelven la causa raíz del problema. Incluso la página de Wikipedia lo pone en la perspectiva correcta. Permítanme citar:

Esta técnica puede aumentar en gran medida la dificultad de explotar un desbordamiento de búfer de pila.

“Aumenta la dificultad de explotación”. No soluciona el problema. Aumenta la dificultad de explotación.

Al momento de escribir esta publicación, el año es 2023. Todavía puede hacer lo siguiente:

#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[]) {
    char *payload =
        "AAAAAAAAAAAAAAAA"
        "AAAAAAAAAAAAAAAA"
        "AAAAAAAAAAAAAAAA"
        "AAAAAAAAAAAAAAAA"
        "AAAAAAAAAAAAAAAA"
        "AAAAAAAAAAAAAAAA"
        "AAAAAAAAAAAAAAAA"
        "AAAAAAAAAAAAAAAA";
    char buffer[32];
    memcpy(buffer, payload, (int)strlen(payload));
    printf("%s\n", buffer);
}

(lldb) target create "./test"
Current executable set to '/Users/zsoltimre/DEVEL/EXPERIMENT/Virtual-CPU/test' (arm64).
(lldb) r
Process 15890 launched: '/Users/zsoltimre/DEVEL/EXPERIMENT/Virtual-CPU/test' (arm64)
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Process 15890 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BREAKPOINT (code=1, subcode=0x19c4c4bb0)
    frame #0: 0x000000019c4c4bb0 libsystem_c.dylib`__chk_fail_overflow + 24
libsystem_c.dylib`:
->  0x19c4c4bb0 <+24>: brk    #0x1
libsystem_c.dylib`:
    0x19c4c4bb4 <+0>:  pacibsp 
    0x19c4c4bb8 <+4>:  stp    x29, x30, [sp, #-0x10]!
    0x19c4c4bbc <+8>:  mov    x29, sp
Target 0: (test) stopped.

Decidí investigar y experimentar con otra cosa. ¿Qué pasa si eliminamos las direcciones de retorno de la pila por completo? En un nivel alto, significaría que los atacantes no podrían alterar el flujo de ejecución ni bloquear el programa modificando la dirección de retorno. Admito que tal solución tiene un precio, pero resuelve el problema.

Entonces, mi idea y la prueba de concepto simplificada en pocas palabras:

  • Las direcciones de retorno se almacenan en un área de memoria dedicada (RSTACK) que se comporta como la pila. RSTACK es de solo lectura para el código de usuario. Solo la CPU puede enviar y sacar valores de RSTACK. En el PoC, esto se aplica mediante el uso de una matriz dedicada .
  • Hay un registro de enlace dedicado (Return Pointer / RP) que apunta a la parte superior de RSTACK. Este registro es de solo lectura para el código de usuario. Solo la CPU puede modificar su valor.
  • Las instrucciones CALL* y RET* de la CPU gestionan RSTACK.
  • Ninguna de las otras instrucciones puede escribir en RSTACK.
El estado inicial de la CPU

Veamos cómo se ve el estado de la CPU y la memoria justo después de la instrucción CALL.

Entonces, si dejamos que las instrucciones de la dirección 7 se ejecuten y corrompamos la pila al ejecutar la instrucción RET final, aterrizaremos exactamente donde queríamos, en la dirección 4. No importa lo que hagamos con la pila, siempre podemos regresar y continuar la ejecución.

En una aplicación de la vida real, es bastante probable que la corrupción de los valores en la pila provoque otros problemas (p. ej., división por cero o desreferencia de puntero nulo) que provocarían el bloqueo del programa. Por lo tanto, esta solución no aborda todos los problemas de denegación de servicio. Pero,

  • Evita perfectamente que un atacante controle el flujo de ejecución.
  • Los ataques de fuerza bruta contra el canario también se eliminan por completo porque ya no hay canarios.
  • Una razón menos para que un programa se cuelgue debido a la corrupción.

De todos modos, me divertí jugando y espero que te hayas divertido leyendo esta publicación. ¡Hasta la próxima!