SMALLOC HOWTO - OR - HOW TO ALLOC MEMORY INTO SECURITY



SOMMAIRE

    1. Introduction

    2. La mémoire

    3. Heap Overflow

    4. Smalloc()

    5. Smalloc Functions

    6. Conclusion

    7. TODO

    8. Greets



1. Introduction

Ce texte est là pour expliquer le fonctionnement de smalloc(). Cette fonction effectue le même travail que la fonction de la glibC malloc() mis à part que smalloc() est bien entendu plus lente. En effet le coup de la sécurité à un prix, celui de la rapidité. De plus, malloc() (glibC) utilise des algorithmes performants en alliant différentes techniques. Cela est compliqué à mettre en oeuvre et n'a pas encore été étudié. Pour toute erreur, suggestion, conseil, vous pouvez m'adresser un mail à: nostrobo@klc-research.com

A savoir que cette fonction est l'aboutissement d'une légère étude qui sera peut être approfondie ultérieurement. Ceci n'est qu'une ébauche, mais tout de même intéressante.

Il est également bon de savoir que le code a été écrit et testé sur plateforme i386 (32 bits), plus particulièrement sur le système d'exploitation OpenBSD 2.9.



2. La mémoire

Pour l'explication du fonctionnement de la gestion de la mémoire, veuillez vous référez à d'autres textes complets, abordant ce sujet. En effet ce domaine est extrêmement vague et dépasse largement le cadre de cet article.

Seront tout de même présentes des explications sur la mémoire et le fonctionnement de malloc(). Néanmoins, seul les différences entre une version basique de malloc() et smalloc() seront présentes. En effet, par exemple le fonctionnement de break et des fonctions brk(), sbrk() ne seront expliquées.

Pour résumé voici comment sont disposées les zones mémoire dans votre RAM:

                                ____________________
                               |                    |
                               |       STACK        |
                               |____________________|
                               |                    |
                               |                    |
                               |                    |
                               |                    |
                               |        HEAP        |
                               |                    |
                               |                    |
                               |                    |
                               |____________________|
                               |                    |
                               |        DATA        |
                               |____________________|


Voyons la chose simplement. Cela ne vous empêche pas d'aller vous documenter sur le domaine.

DATA : Contient le texte, le code éxecutable ainsi que des données accessible en lecture seule.
STACK : Zone mémoire temporaire utilisée par le kernel pour éxecuté des instructions.
HEAP : Le Heap est une immense zone mémoire vide. Celle ci est utilisée par les programmes pour stocker des données pour une durée indéterminée. C'est dans cette zone que malloc() alloue des blocs de mémoire aux processus.

Le but de malloc() est donc d'allouer des blocs de mémoire et de retourner leurs adresses aux processus. De plus, celui ci doit garder en mémoire des informations sur chacun des blocs qu'il a eut à gérer. De ce fait, lorsque malloc() est appellé, il doit retourner l'adresse d'un bloc de mémoire de n octets. Mais celui ci va en réalité réservé n + sizeof(identifier). En effet malloc() réserve plus de mémoire et stocke des informations relative au bloc courant au début de celui ci. Puis il retourne au processus l'adresse du bloc de mémoire vide correspondant, c'est à dire: addr + sizeof(identifier).

Les nouveaux malloc() utilisent des techniques particulièrent nous n'en parlerons donc pas.
Les anciens, en revanche, se contentaient d'inclure à chaque bloc alloués une structure décrivant le bloc, comme expliqué précédemment. Dans sa version la plus simple cette structure n'est composée que de la taille du bloc. Pour une zone libre, cette structure doit également contenir l'adresse de la prochaine zone libre.

Voici donc comment un bloc de mémoire allouée est représenté: zone réservée + zone allouée au processus:

                                ____________________
                               |                    |
                               |       STACK        |
                               |____________________|
                               |                    |
                               |                    |
                               |                    |             |---------------| - structure de données reservée à malloc()
                               |                    |             |size = ?? bytes|
                               |                    |             |               |
                               |    ___________     |             |---------------| - bloc de mémoire allouée (addr)
                               |   |___________|----|-----------> |               |   malloc() retourne cette adresse
                               |                    |             |               |
                               |____________________|             | process data  |
                               |                    |             |               |
                               |        DATA        |             |               |
                               |____________________|             |---------------|


Ainsi lorsque le processus appelle la fonction free() avec l'adresse du buffer (addr), free() retrouve les informations sur la zone mémoire (addr -= sizeof(identifier)) et peut ainsi libérer l'espace mémoire et faire le nécéssaire pour pouvoir réallouer cette zone ultérieurement.

Nous n'irons pas plus loin dans l'explication du fonctionnement de malloc: gestion des blocs libres, réutilisation des blocs libres etc..
Peu nous importe la méthode, seul l'aspet sécurité est abordé car nous savons très bien que nous ne pourrons rivaliser en vitesse avec un malloc() moderne.



3. Heap Overflow

Même chose, je vous conseille de vous référez à des textes traitant ce type d'attaques.

Un Heap Overflow est une attaque, comme son nom l'indique, se déroulant dans le Heap (Tas).

Comme expliqué auparavant, le Heap est constitué de zones mémoires, allouées ou libres, gérées par la fonction malloc().

Imaginons deux buffers, un sensible contenant des données importantes, et l'autre banal mais auquel l'attaquant a accès:

                                ____________________
                               |                    |
                               |       STACK        |
                               |____________________|
                               |                    |
                               |                    |       1 = buffer dans lequel nous pouvons écrire et même au dela (faille).
                               |                    |       2 = buffer contenant des données importantes.
                               |                    |
                               |                    |
                               | ______  __________ |
                               ||___1__||_____2____||
                               |                    |
                               |____________________|
                               |                    |
                               |        DATA        |
                               |____________________|


Le problème est donc que nous pouvons écrire au dela du buffer 1. En toute logique nous pouvons imaginer que si nous écrivons au dela de notre zone réservée au buffer 1, nous écrirons sur les données du buffer 2.
C'est exactement ce qu'il va se produire. Cette attaque se nomme Heap Overflow. Elle consiste tout simplement à écraser des données auxquelles nous ne devrions normalement pas accéder.

La faiblesse de malloc() réside donc de le fait que rien n'empêche un utilisateur d'écrire au delà de ses limites (si il existe une faille bien entendu). C'est un réel problème.



4. Smalloc()

Voyons désormais les principales différences entre malloc() et smalloc().

Sachez tout d'abord que malloc() doit tenir certains engagements, si je puis dire:
- allouer la mémoire en réquisitionnant le minimum de mémoire pour ses sauvegardes.
- être le plus rapide possible: c'est à dire allouer, réallouer mais également gérer les zones libres le mieux possible.

Pour ce qui est de la rapidité, smalloc() est très logiquement en dessous de malloc().

Un ancien malloc(), basic, utilise des structures de données d'une taille de 4 octets pour les blocs de mémoire allouée et de 8 octets pour les blocs de mémoire libre (sur 32 bits):

typedef struct s_block                            (4 bytes)
{
  unsigned int size;
}              t_block;

typedef struct s_freeblock                        (8 bytes)
{
  int size;
  struct s_freeblock *next;
}              t_freeblock;

La fonction smalloc() doit sauvegarder plus de données en mémoire pour pouvoir contrôler celle ci. Voici les structures de qu'elle utilise:

typedef struct s_secureblock                      (8 bytes)
{
  unsigned int size;
  short checksum;
  short control;
}              t_secureblock;

typedef struct s_securefreeblock                   (12 bytes)
{
  int size;
  struct s_securefreeblock *next;
  short checksum;
  short control;
}              t_securefreblock;

L'utilisation de short permet de maintenir la mémoire utilisée par smalloc() à 8 octets pour chaque bloc ce qui reste tout à fait raisonnable.

CHECKSUM : De type short, 'checksum' est une variable contenant un nombre aléatoire généré par smalloc(). Celui ci est sauvegardé dans la structure actuelle mais pas seulement. CONTROL : De type short, 'control' est une variable qui contient la même valeur que contient la variable 'checksum' du bloc suivant.

En effet, smalloc() génére des nombres aléatoires qu'il sauvegarde dans les nouveaux blocs de mémoire créés. L'astuce consiste à sauvegarder cette même valeur dans le bloc précédent.

Ainsi le Heap Overflow est toujours possible mais ne passera pas inaperçu en cas de contrôle.

En effet, pour écrire par dessus le buffer suivant sans être repéré, l'attaquant doit réussir à réécrire la structure de données de smalloc().

Cela est tout à fait possible dans le cas d'un malloc() ordinaire puisque celui ci ne sauvegarde que la taille du bloc. Or l'attaquant, possédant les sources ou un désassembleur, peut aisément connaitre cette taille et ainsi reproduire la structure.
Il ne lui suffit donc plus qu'à overwrité cette structure pour qu'elle reste inchangée puis de réécrire les données sensible du second buffer comme l'attaquant le désire.

Avec smalloc() en mode: SECURE_MODE_ENABLE, cela n'est pas possible.

En effet voyons un exemple avec 3 buffers:

                  BUFFER 1                                 BUFFER 2                                   BUFFER 3

...][size = 12, checksum = 20, control = 165][size = 32, checksum = 165, control = 1234][size = 128, checksum = 1234, control = 0]

                                                       - - - - - - - - - >

Dans cette configuration, en imaginant que l'attaquant à accès au buffer 2, il doit réécrire la structure du buffer 3 pour pouvoir y modifier quelconque information:

struct s_secureblock
{
  size = 128;
  checksum = 1234;     /* valeur générée aléatoirement */
  control = 0;
};

Comme pour une structure d'un malloc() classique, la variable 'size' est déterminable, néanmois, les structures de données de smalloc() sont constituées d'un 'checksum' qu'il n'est pas possible à deviner.

C'est ainsi que l'attaquant peut toujours overwrité le buffer 3, mais si le programme fait ensuite une vérification de la mémoire, celui ci se rendra compte que le buffer 3 à été attaqué, étant donné que le 'control' du buffer 2 et le 'checksum' du buffer 3 sont différents.

Dans le cas où l'attaquant a accès au buffer 1 et qu'il veut modifier des informations du buffer 3.

Il peut vouloir modifier la structure de données du buffer 2 puis celle du buffer 3 pour les faire concorder. De cette manière, une vérification du buffer 3 (check_block()) renverra une réponse positive, signifiant que le buffer n'a pas été attaqué puisque le 'control' du buffer 2 et le 'checksum' du buffer 3 concordent.

Néanmoins, si le processus lance ensuite une vérification complète de la mémoire (check_mem()), l'attaque sera démasquée.

En effet le programme constatera que le 'control' du buffer 1 ne concorde pas avec le 'checksum' du buffer 2.

A noter, que pour accroitre encore la sécurité, un mode nommé SECURE_CONTROL_ENABLE, lance une vérification de lé mémoire à chaque appel à la fonction smalloc() ou sfree().

Ce mode est encore plus lent mais accroit réellement la sécurité, bien que non assurée encore une fois.

En effet si l'attaquant modifie le buffer de données sensible et que celui ci fait effectuer la tâche qu'il désire au programme vulnérable, la détection du Heap Overflow ne servira à rien puisque le mal sera déjà fait.



5. Smalloc Functions

FONCTIONS DE GESTION MEMOIRE:


void *smalloc(unsigned int size);

    smalloc() résèrve un espace mémoire d'une taille de size octets. La valeur renvoyée correspond à l'adresse de cette zone.

    Retourne -1 en cas d'erreur.

int sfree(char *addr);
    sfree() libère la mémoire, précédement allouée via la fonction smalloc(), située à l'adresse addr.

    Retourne 0 en cas de succès, -1 en cas d'erreur.


FONCTIONS DE CONTROLE MEMOIRE:


int check_block(char *addr);
    check_block() vérifie l'intégrité du bloc mémoire (libre ou alloué) située à l'adresse addr.

    En cas d'erreur ou si le bloc est défectueux, la fonction retourne -1; 0 dans le cas contraire.

int check_mem(void);
    check_mem() vérifie l'intégrité de toutes les zones mémoires (libres ou allouées) gérées par smalloc().

    Retourne 0 si aucune erreur n'a été trouvée, -1 dans le cas contraire ou en cas d'erreur.


FONCTIONS D'AFFICHAGE MEMOIRE:


void show_alloc_mem(void);
    show_alloc_mem() affiche, sur la sortie standard, des informations sur chaque bloc de mémoire allouée gérés par smalloc().
    L'affichage est formaté de la sorte:

         .::[ Show Alloc Mem Function ]::.
         break : 0x15100
         0x15020 - 0x15040 : 32 bytes
         0x15040 - 0x15080 : 64 bytes
         ...
         ...
    

void show_free_mem(void);
    show_free_mem() effectue la même tâche que show_alloc_mem() mis à part que ce sont cette fois les blocs de mémoire libre qui sont affichés.
    L'affichage est formaté de la sorte.
         .::[ Show Free Mem Function ]::.
         break : 0x15100
         0x15000 - 0x15020 : 32 bytes
         0x15080 - 0x15100 : 128 bytes
         ...
         ...
    


6. Conclusion

D'un point de vue réaliste, il est clair que smalloc() n'apporte pas une solution de sécurité fiable à la détection de Heap Overflow.

Même en incluant les deux modes sécurisés de smalloc(): SECURE_MODE_ENABLE et CONTROL_MODE_ENABLE, celle ci ne peut détecter un Heap Overflow si l'action néfaste est executée aussitôt après l'overwriting.

Le meilleur moyen serait de lancer une vérification du bloc, juste après une opération destinée à écrire des données en mémoire, ou bien mieux à vérifier toute la mémoire. Bien entendu cela serait extrêmement lent.

En conclusion, le meilleur moyen reste de programmer clairement en prévoyant les attaques possibles et ainsi les éviter.



7. TODO

Comme je l'ai dis précédement, ceci n'est qu'un code de démonstration, très peu optimisé.
Suivant vos suggestions, propositions et la motivation de l'auteur, smalloc() peut voir le jour sous de nouvelles versions plus performantes et surtout plus complètes.

Voyons ce que pourrait rajouter l'auteur dans un futur proche:

   - gestion des erreurs comme malloc() le fait.
   - développement de srealloc(): fonction qui réalloue un espace mémoire d'une nouvelle taille.
   - meilleure gestion des blocs libre en allouant par exemple de nouveaux petits blocs dans de grands blocs libres etc..
   - gestion de la mémoire comme le font les malloc() modernes: utilisation de mmap() etc..
   - gestion du fichier de configuration 'smalloc.conf'.
   - ainsi que tout ce qui n'a pas encore été étudié.
   - ...


8. Greets

klc-research [www.klc-research.com]
RedKod [www.redkod.com]


thanks to SubK [www.subk.org]