Mémoire - protection : SSP    Enregistrer au format PDF

Stack Smashing Protection

SSP, pour Stack Smashing Protection, est une protection introduite par GCC depuis sa version 4.1.


par Tosh , S3cur3D

Cette protection permet de grandement limiter les débordements sur la pile de plusieurs manières :

 En plaçant un "Cookie" (Généralement une valeur aléatoire ou semi-aléatoire) entre les variables locales et le saved-ebp et le saved-eip des fonctions à risques.
 En recopiant les arguments des fonctions sur la pile.
 En réorganisant les variables locales des fonctions.

L’utilisation de cookies ou de canaries n’est pas une protection matérielle ou du système d’exploitation comme précedemment, mais une protection au niveau logiciel. En réalité, dès la compilation, les préludes et prologues des fonctions sont modifiés. Au tout début de l’exécution, le canary est placé dans le segment data et initialisé. C’est un entier aléatoire, de la taille d’un registre. Inutile de dire qu’il est initialisé avec des valeurs fortement aléatoires (quoique des travaux ont montré que l’on pouvait fortement diminuer l’entropie des cookies). Quoiqu’il en soit, à chaque début de fonction, le cookie est placé soit entre le Saved Frame Pointer (valeur enregistrée de l’ebp) et l’adresse de retour, soit après la sauvegarde du contexte (donc plus haut que les variables locales, mais plus bas que le SFP, l’adresse de retour et les éventuels registres sauvegardés à l’entrée de la procédure). Au contraire, à la fin d’une fonction, avant le leave/ret, la valeur du cookie est vérifiée et le programme se termine si la comparaison échoue.
Inutile de dire que lorsque un overflow intervient, la valeur du cookie n’est plus égale à celle spécifiée dans le segment data, puisqu’il est a priori très difficile de deviner le cookie. Ceci dit, ces protections peuvent être détournées de plusieurs façons selon l’implémentation : par exploitation de format strings permettant de passer outre l’écrasement du cookie, par exploitations type off-by-one (lorsque on peut écraser le SFP, on est capable de bouger la prochaine frame plus bas dans la pile ou dans la GOT, afin de pouvoir forcer la prochaine adresse de retour qui sera popée) ou encore par overflows dans le heap ou le segment data (écrasement du cookie).
Ceci dit, contrairement à Windows, sous Linux ces exploitations sont très différentes selon le contexte, je ne ferais donc pas de généralités et partirait du principe que le programme n’est pas compilé avec ce genre de protections. Certaines distributions, comme Ubuntu, incluent par défaut cette option dans gcc. Il faut compiler avec l’argument -fno-stack-protector pour ne pas l’inclure.

Pour illustrer la manière dont est mise en oeuvre SSP, prenons un exemple :

  1. #include <string.h>
  2. #include <stdio.h>
  3.  
  4.  
  5. void foo(char *arg) {
  6.   int i = 42;
  7.  
  8.   char buff[20];
  9.   strcpy(buff, arg);
  10.   printf("%d\n", i);
  11. }
  12.  
  13. int main(int argc, char **argv) {
  14.  
  15.   if(argc > 1)
  16.     foo(argv[1]);
  17.  
  18.   return 0;
  19. }

Télécharger

Compilons sans activer SSP (option -fno-stack-protector) et désassemblons la fonction foo :

  1. 08048430 <foo>:
  2.  8048430:       55                      push   ebp
  3.  8048431:       89 e5                   mov    ebp,esp
  4.  8048433:       83 ec 38                sub    esp,0x38
  5.  8048436:       c7 45 f4 2a 00 00 00    mov    DWORD PTR [ebp-0xc],0x2a
  6.  804843d:       8b 45 08                mov    eax,DWORD PTR [ebp+0x8]
  7.  8048440:       89 44 24 04             mov    DWORD PTR [esp+0x4],eax
  8.  8048444:       8d 45 e0                lea    eax,[ebp-0x20]
  9.  8048447:       89 04 24                mov    DWORD PTR [esp],eax
  10.  804844a:       e8 b1 fe ff ff          call   8048300 <strcpy@plt>
  11.  804844f:       8b 45 f4                mov    eax,DWORD PTR [ebp-0xc]
  12.  8048452:       89 44 24 04             mov    DWORD PTR [esp+0x4],eax
  13.  8048456:       c7 04 24 20 85 04 08    mov    DWORD PTR [esp],0x8048520
  14.  804845d:       e8 8e fe ff ff          call   80482f0 <printf@plt>
  15.  8048462:       c9                      leave
  16.  8048463:       c3                      ret

Télécharger

Maintenant, compilons avec SSP (option -fstack-protector-all), et comparons les deux désassemblage :

  1. 08048480 <foo>:
  2.  8048480:       55                      push   ebp
  3.  8048481:       89 e5                   mov    ebp,esp
  4.  8048483:       83 ec 38                sub    esp,0x38
  5.  8048486:       8b 45 08                mov    eax,DWORD PTR [ebp+0x8]
  6.  8048489:       89 45 d4                mov    DWORD PTR [ebp-0x2c],eax
  7.  804848c:       65 a1 14 00 00 00       mov    eax,gs:0x14
  8.  8048492:       89 45 f4                mov    DWORD PTR [ebp-0xc],eax
  9.  8048495:       31 c0                   xor    eax,eax
  10.  8048497:       c7 45 dc 2a 00 00 00    mov    DWORD PTR [ebp-0x24],0x2a
  11.  804849e:       8b 45 d4                mov    eax,DWORD PTR [ebp-0x2c]
  12.  80484a1:       89 44 24 04             mov    DWORD PTR [esp+0x4],eax
  13.  80484a5:       8d 45 e0                lea    eax,[ebp-0x20]
  14.  80484a8:       89 04 24                mov    DWORD PTR [esp],eax
  15.  80484ab:       e8 a0 fe ff ff          call   8048350 <strcpy@plt>
  16.  80484b0:       8b 45 dc                mov    eax,DWORD PTR [ebp-0x24]
  17.  80484b3:       89 44 24 04             mov    DWORD PTR [esp+0x4],eax
  18.  80484b7:       c7 04 24 c0 85 04 08    mov    DWORD PTR [esp],0x80485c0
  19.  80484be:       e8 6d fe ff ff          call   8048330 <printf@plt>
  20.  80484c3:       8b 45 f4                mov    eax,DWORD PTR [ebp-0xc]
  21.  80484c6:       65 33 05 14 00 00 00    xor    eax,DWORD PTR gs:0x14
  22.  80484cd:       74 05                   je     80484d4 <foo+0x54>
  23.  80484cf:       e8 6c fe ff ff          call   8048340 <__stack_chk_fail@plt>
  24.  80484d4:       c9                      leave
  25.  80484d5:       c3                      ret
  26.  

Télécharger

1/ Aux adresses 8048486 et 8048489 de la version SSP, on remarque que l’argument de la fonction est recopié sur la pile, avant le buffer. En cas de débordement de celui-ci, nous ne pourrons donc pas écraser l’argument de la fonction.

2/ Aux adresses 804848c et 8048492, il s’agit du cookie (valeur contenue dans gs:0x14, qui est généralement une valeur aléatoire) qui est placé sur la pile, entre les variables locales et le saved-ebp et saved-eip : en cas de débordement, le cookie est écrasé et ne correspond plus alors à la valeur présente dans gs:0x14.

À la sortie de la fonction (adresse 80484c6), le cookie est comparé avec la valeur de gs:0x14 : si la valeur a changée (débordement), le programme se termine (fonction __stack_chk_fail) en affichant un message d’erreur.

3/ On remarque que dans la version sans SSP, la variable i est placée à ebp-0xc, et le buffer à ebp-0x20. Le buffer est donc placé avant la variable, et un débordement écrasera la variable i.

Dans la version SSP, la variable i est placée à ebp-0x24 alors que le buffer est situé à ebp-0x20 : un débordement ne pourra pas écraser la variable i.

Voilà pour le petit aperçu de SSP, il s’agit d’une protection très efficace pour contrer les débordement sur la pile. En revanche elle s’avère inutile dans de nombreux cas : débordements dans un autre segment, cas où on contrôle l’indice d’un buffer, processus forké, débordement dans une structure...

Documentations publiées dans cette rubrique