Expérience d'élimination de Stack Canary

Feb 27 2023
Inspiré par Walter Harold Bishop, dans cet article, je vais essayer de trouver un meilleur moyen de protéger l'intégrité des adresses de retour par rapport aux stack canaries. Veuillez noter que je ne crois pas que ce que j'avais en tête soit la solution parfaite.

Inspiré par Walter Harold Bishop, dans cet article, je vais essayer de trouver un meilleur moyen de protéger l'intégrité des adresses de retour par rapport aux stack canaries. Veuillez noter que je ne crois pas que ce que j'avais en tête soit la solution parfaite. Il y a beaucoup de choses auxquelles je n'ai pas encore bien réfléchi. Quoi qu'il en soit, vous pouvez considérer cela comme une «tentative n ° 1».

Je n'aime pas les stack canaries car ils ne résolvent pas réellement la cause première du problème. Même la page Wikipedia le met dans la bonne perspective. Permettez-moi de citer :

Cette technique peut augmenter considérablement la difficulté d'exploiter un débordement de tampon de pile

"Augmenter la difficulté d'exploitation". Cela ne résout pas le problème. Cela augmente la difficulté d'exploitation.

Au moment de la rédaction de cet article, l'année est 2023. Vous pouvez toujours faire ce qui suit :

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

J'ai décidé d'enquêter et d'expérimenter autre chose. Et si nous supprimions complètement les adresses de retour de la pile ? À un niveau élevé, cela signifierait que les attaquants ne pourraient pas modifier le flux d'exécution ou planter le programme en modifiant l'adresse de retour. J'admets qu'une telle solution a un prix, mais elle résout le problème.

Donc, mon idée et la preuve de concept trop simplifiée en un mot :

  • Les adresses de retour sont stockées dans une zone mémoire dédiée (RSTACK) qui se comporte comme la pile. RSTACK est en lecture seule pour le code utilisateur. Seul le CPU peut pousser et sortir des valeurs de RSTACK. Dans le PoC, cela est appliqué en utilisant un tableau dédié .
  • Il existe un registre de liaison dédié (Return Pointer / RP) qui pointe vers le haut de RSTACK. Ce registre est en lecture seule pour le code utilisateur. Seul le CPU peut modifier sa valeur.
  • Les instructions CALL* et RET* du CPU gèrent RSTACK.
  • Aucune des autres instructions ne peut écrire dans RSTACK.
L'état initial du CPU

Voyons à quoi ressemblent l'état du CPU et la mémoire juste après l'instruction CALL.

Ensuite, si nous laissons les instructions de l'adresse 7 s'exécuter et corrompre la pile lors de l'exécution de l'instruction RET finale, nous atterrirons exactement où nous voulions, à l'adresse 4. Peu importe ce que nous faisons avec la pile, nous pouvons toujours revenir et poursuivre l'exécution.

Dans une application réelle, il est fort probable que la corruption des valeurs sur la pile causerait d'autres problèmes (par exemple, la division par zéro ou le déréférencement du pointeur nul) qui conduiraient au plantage du programme. Par conséquent, cette solution ne résout pas tous les problèmes de déni de service. Mais,

  • Il empêche parfaitement un attaquant de contrôler le flux d'exécution.
  • Les attaques par force brute contre le canari sont également complètement éliminées car il n'y a plus de canaris.
  • Une raison de moins pour qu'un programme plante à cause de la corruption.

Quoi qu'il en soit, je me suis amusé à jouer et j'espère que vous vous êtes amusé à lire ce post. À la prochaine!