Trouvez et corrigez les vulnérabilités des fichiers binaires dans Linux - avec l'utilitaire checksec et le compilateur gcc



Image : Images de livres d'archives Internet. Modifié par Opensource.com. CC BY-SA 4.0



Après avoir compilé le même code source, nous pouvons nous retrouver avec des binaires différents. Cela dépend des drapeaux que nous passons entre les mains du compilateur. Certains de ces indicateurs vous permettent d'activer ou de désactiver un certain nombre de propriétés liées à la sécurité du binaire.



Certains d'entre eux sont activés ou désactivés par le compilateur par défaut. C'est ainsi que des vulnérabilités peuvent survenir dans les fichiers binaires dont nous ne sommes pas conscients.



Checksec est un utilitaire simple pour déterminer quelles propriétés ont été incluses au moment de la compilation. Dans cet article je vais vous dire :



  • comment utiliser l'utilitaire checksec pour trouver des vulnérabilités ;
  • comment utiliser le compilateur gcc pour corriger les vulnérabilités trouvées.


Installation de checksec



Pour Fedora OS et autres systèmes basés sur RPM :



$ sudo dnf install checksec
      
      





Pour les systèmes basés sur Debian, utilisez apt.



Démarrage rapide avec checksec



L'utilitaire checksec se compose d'un seul fichier de script, qui est cependant assez volumineux. Grâce à cette transparence, vous pouvez savoir quelles commandes système pour rechercher des vulnérabilités dans les binaires sont exécutées sous le capot :



$ file /usr/bin/checksec

/usr/bin/checksec: Bourne-Again shell script, ASCII text executable, with very long lines

$ wc -l /usr/bin/checksec

2111 /usr/bin/checksec
      
      





Exécutons checksec sur l'utilitaire de navigation dans les répertoires (ls) :



$ checksec --file=/usr/bin/ls

<strong>RELRO           STACK CANARY      NX            PIE             RPATH     RUNPATH      Symbols       FORTIFY Fortified       Fortifiable    FILE</strong>

Full RELRO      Canary found      NX enabled    PIE enabled     No RPATH   No RUNPATH   No Symbols        Yes   5       17              /usr/bin/ls

      
      





En exécutant la commande dans le terminal, vous recevrez un rapport sur les propriétés utiles de ce binaire et celles qu'il n'a pas.  



La première ligne est la tête du tableau, qui répertorie les différentes propriétés de sécurité - RELRO, STACK CANARY, NX, etc. La deuxième ligne montre les valeurs de ces propriétés pour le binaire utilitaire ls.



Bonjour binaire !



Je vais compiler un binaire à partir du code C le plus simple :



#include <stdio.h>

int main()

{

        printf(«Hello World\n»);

        return 0;

}

      
      





Veuillez noter que jusqu'à présent, je n'ai pas passé un seul indicateur au compilateur, à l'exception de -o (c'est hors de propos, mais indique simplement où sortir le résultat de la compilation):



$ gcc hello.c -o hello

$ file hello

hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=014b8966ba43e3ae47fab5acae051e208ec9074c, for GNU/Linux 3.2.0, not stripped

$ ./hello

Hello World
      
      





Maintenant, je vais exécuter l'utilitaire checksec pour mon binaire. Certaines propriétés sont différentes des propriétés



ls (     ):

$ checksec --file=./hello

<strong>RELRO           STACK CANARY      NX            PIE             RPATH     RUNPATH      Symbols         FORTIFY Fortified       Fortifiable     FILE</strong>

Partial RELRO   No canary found   NX enabled    No PIE          No RPATH   No RUNPATH   85) Symbols       No    0       0./hello

      
      





Checksec vous permet d'utiliser une variété de formats de sortie, que vous pouvez spécifier avec l'option --output. Je vais choisir le format JSON et rendre la sortie plus descriptive avec l'utilitaire jq :



$ checksec --file=./hello --output=json | jq

{

  «./hello»: {

    «relro»: «partial»,

    «canary»: «no»,

    «nx»: «yes»,

    «pie»: «no»,

    «rpath»: «no»,

    «runpath»: «no»,

    «symbols»: «yes»,

    «fortify_source»: «no»,

    «fortified»: «0»,

    «fortify-able»: «0»

  }

}

      
      





Analyse (checksec) et élimination (gcc) des vulnérabilités



Le fichier binaire créé ci-dessus possède plusieurs propriétés qui déterminent, disons, le degré de sa vulnérabilité. Je vais comparer les propriétés de ce fichier avec les propriétés du binaire ls (également répertoriées ci-dessus) et expliquer comment procéder à l'aide de l'utilitaire checksec. 



Pour chaque élément, je vais en outre vous montrer comment éliminer les vulnérabilités trouvées.



1. Symboles de débogage



Je vais commencer simple. Certains symboles sont inclus dans le binaire au moment de la compilation. Ces symboles sont utilisés dans le développement de logiciels : ils sont nécessaires pour le débogage et la correction des bogues.



Les symboles de débogage sont généralement supprimés de la version du binaire que les développeurs publient pour une utilisation générale. Cela n'affecte en rien le fonctionnement du programme. Ce nettoyage (indiqué par le mot strip ) est souvent effectué pour économiser de l'espace, car le fichier devient plus léger une fois les caractères supprimés. Et dans les logiciels propriétaires, ces caractères sont souvent supprimés également parce que les attaquants ont la possibilité de les lire au format binaire et de les utiliser à leurs propres fins.



Checksec montre que les symboles de débogage sont présents dans mon binaire, mais ils ne le sont pas dans le fichier ls. 



$ checksec --file=/bin/ls --output=json | jq | grep symbols

    «symbols»: «no»,

$ checksec --file=./hello --output=json | jq | grep symbols

    «symbols»: «yes»,
      
      







L'exécution de la commande file peut afficher la même chose. Les caractères ne sont pas supprimés.



$ file hello

hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=014b8966ba43e3ae47fab5acae051e208ec9074c, for GNU/Linux 3.2.0, <strong>not stripped</strong>
      
      





Comment fonctionne checksec



Exécutons cette commande avec l'option --debug :



$ checksec --debug --file=./hello

      
      





Étant donné que l'utilitaire checksec est un long script, vous pouvez utiliser les fonctions Bash pour l'examiner. Affichez les commandes que le script exécute pour mon fichier hello :



$ bash -x /usr/bin/checksec --file=./hello
      
      





Portez une attention particulière à echo_message - la sortie d'un message indiquant si le binaire contient des symboles de débogage :



+ readelf -W --symbols ./hello

+ grep -q '\.symtab'

+ echo_message '\033[31m96) Symbols\t\033[m  ' Symbols, ' symbols=«yes»' '«symbols»:«yes»,'
      
      





L'utilitaire checksec utilise la commande readelf avec l'indicateur spécial --symbols pour lire un fichier binaire. Il imprime tous les symboles de débogage dans le binaire. 



$ readelf -W --symbols ./hello
      
      





A partir du contenu de la section .symtab, vous pouvez connaître le nombre de symboles trouvés :



$ readelf -W --symbols ./hello | grep -i symtab
      
      





Comment supprimer les symboles de débogage après la compilation



L'utilitaire de bande nous y aidera.



$ gcc hello.c -o hello

$
 

$ file hello

hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=322037496cf6a2029dcdcf68649a4ebc63780138, for GNU/Linux 3.2.0, <strong>not stripped</strong>

$
 

$ strip hello

$
 

$ file hello

hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=322037496cf6a2029dcdcf68649a4ebc63780138, for GNU/Linux 3.2.0, <strong>stripped</strong>
      
      





Comment supprimer les symboles de débogage au moment de la compilation



Lors de la compilation, utilisez l'indicateur -s :



$ gcc -s hello.c -o hello

$

$ file hello

hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=247de82a8ad84e7d8f20751ce79ea9e0cf4bd263, for GNU/Linux 3.2.0, <strong>stripped</strong>

      
      





Vous pouvez également vérifier que les symboles ont été supprimés à l'aide de l'utilitaire checksec :



$ checksec --file=./hello --output=json | jq | grep symbols

    «symbols»: «no»,

      
      





2. Canari



Canary (informateurs) sont des valeurs "secrètes" qui sont stockées sur la pile entre le tampon et les données de contrôle. Ils sont utilisés pour se protéger contre les attaques par débordement de tampon : si ces valeurs sont modifiées, cela vaut la peine de tirer la sonnette d'alarme. Lorsqu'une application est lancée, sa propre pile est créée pour elle. Dans ce cas, il s'agit simplement d'une structure de données avec des opérations push et pop. Un attaquant pourrait préparer des données malveillantes et les écrire dans la pile. Dans ce cas, la mémoire tampon peut déborder et la pile peut être endommagée. À l'avenir, cela entraînera un plantage du programme. L'analyse des valeurs canary vous permet de comprendre rapidement qu'un piratage s'est produit et de prendre des mesures.



$ checksec --file=/bin/ls --output=json | jq | grep canary

    «canary»: «yes»,

$

$ checksec --file=./hello --output=json | jq | grep canary

    «canary»: «no»,

$

 ,    canary,  checksec   :

$ readelf -W -s ./hello | grep -E '__stack_chk_fail|__intel_security_cookie'
      
      





Activer Canari



Pour ce faire, lors de la compilation, nous utilisons le drapeau -stack-protector-all :



$ gcc -fstack-protector-all hello.c -o hello

$ checksec --file=./hello --output=json | jq | grep canary

    «canary»: «yes»,

      
      





Maintenant, checksec peut nous dire en toute conscience que le mécanisme Canary est activé :



$ readelf -W -s ./hello | grep -E '__stack_chk_fail|__intel_security_cookie'

     2: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __stack_chk_fail@GLIBC_2.4 (3)

    83: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __stack_chk_fail@@GLIBC_2.4

$

      
      





3. TARTE



La propriété PIE activée permet au code exécutable d'être placé arbitrairement en mémoire quelle que soit son adresse absolue :



PIE (Position Independent Executable) - code exécutable indépendant de la position. La capacité de prédire où et quelles zones de mémoire se trouvent dans l'espace d'adressage d'un processus fait le jeu des attaquants. Les programmes utilisateur sont chargés et exécutés à partir d'une adresse de mémoire virtuelle de processus prédéfinie à moins qu'ils ne soient compilés avec l'option PIE. L'utilisation de PIE permet au système d'exploitation de charger des sections de code exécutable dans des morceaux arbitraires de mémoire, ce qui le rend beaucoup plus difficile à déchiffrer.



$ checksec --file=/bin/ls --output=json | jq | grep pie

    «pie»: «yes»,

$ checksec --file=./hello --output=json | jq | grep pie

    «pie»: «no»,

      
      





Souvent, la propriété PIE n'est incluse que lors de la compilation des bibliothèques. Dans la sortie ci-dessous, hello est marqué comme exécutable LSB et le fichier libc de bibliothèque standard (.so) est marqué comme objet partagé LSB :



$ file hello

hello: ELF 64-bit <strong>LSB executable</strong>, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=014b8966ba43e3ae47fab5acae051e208ec9074c, for GNU/Linux 3.2.0, not stripped

$ file /lib64/libc-2.32.so

/lib64/libc-2.32.so: ELF 64-bit <strong>LSB shared object</strong>, x86-64, version 1 (GNU/Linux), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=4a7fb374097fb927fb93d35ef98ba89262d0c4a4, for GNU/Linux 3.2.0, not stripped

      
      





Checksec obtient ces informations comme ceci :



$ readelf -W -h ./hello | grep EXEC

  Type:                              EXEC (Executable file)

      
      





Si vous exécutez la même commande pour la bibliothèque, vous verrez DYN au lieu d'EXEC :



$ readelf -W -h /lib64/libc-2.32.so | grep DYN

  Type:                              DYN (Shared object file)

      
      





Activer PIE



Lors de la compilation du programme, vous devez spécifier les indicateurs suivants :



$ gcc -pie -fpie hello.c -o hello
      
      





Pour vous assurer que la propriété PIE est activée, exécutez la commande suivante :



$ checksec --file=./hello --output=json | jq | grep pie

    «pie»: «yes»,

$
      
      





Maintenant, notre fichier binaire (bonjour) changera son type d'EXEC à DYN :



$ file hello

hello: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=bb039adf2530d97e02f534a94f0f668cd540f940, for GNU/Linux 3.2.0, not stripped

$ readelf -W -h ./hello | grep DYN

  Type:                              DYN (Shared object file)

      
      





4. NX



Les outils du système d'exploitation et du processeur vous permettent de configurer de manière flexible les droits d'accès aux pages de mémoire virtuelle. En activant la propriété NX (No Execute), nous pouvons empêcher que les données soient interprétées comme des instructions du processeur. Souvent, dans les attaques par débordement de tampon, les attaquants poussent du code sur la pile, puis essaient de l'exécuter. Cependant, en empêchant le code de s'exécuter dans ces segments de mémoire, de telles attaques peuvent être évitées. En compilation normale avec gcc, cette propriété est activée par défaut :



$ checksec --file=/bin/ls --output=json | jq | grep nx

    «nx»: «yes»,

$ checksec --file=./hello --output=json | jq | grep nx

    «nx»: «yes»,

      
      





Checksec utilise à nouveau la commande readelf pour obtenir des informations sur la propriété NX. Dans ce cas, RW signifie que la pile est en lecture/écriture. Mais comme cette combinaison ne contient pas le caractère E, il y a une interdiction d'exécuter du code à partir de cette pile :



$ readelf -W -l ./hello | grep GNU_STACK

  GNU_STACK      0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW  0x10

      
      





Désactiver NX 



Il n'est pas recommandé de désactiver la propriété NX, mais vous pouvez le faire comme ceci :



$ gcc -z execstack hello.c -o hello

$ checksec --file=./hello --output=json | jq | grep nx

    «nx»: «no»,

      
      





Après la compilation, nous verrons que les permissions de la pile sont passées à RWE :



$ readelf -W -l ./hello | grep GNU_STACK

  GNU_STACK      0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RWE 0x10
      
      





5. RELRO



Dans les binaires liés dynamiquement, un GOT (Global Offset Table) spécial est utilisé pour appeler des fonctions à partir de bibliothèques. Cette table est référencée par les binaires ELF (Executable Linkable Format). Lorsque la protection RELRO (Relocation Read-Only) est activée, le GOT devient en lecture seule. Cela vous permet de vous protéger contre certains types d'attaques qui modifient les enregistrements de la table :



$ checksec --file=/bin/ls --output=json | jq | grep relro

    «relro»: «full»,

$ checksec --file=./hello --output=json | jq | grep relro

    «relro»: «partial»,

      
      





Dans ce cas, une seule des propriétés RELRO est activée, donc checksec renvoie la valeur "partielle". Checksec utilise la commande readelf pour afficher les paramètres. 



$ readelf -W -l ./hello | grep GNU_RELRO

  GNU_RELRO      0x002e10 0x0000000000403e10 0x0000000000403e10 0x0001f0 0x0001f0 R   0x1

$ readelf -W -d ./hello | grep BIND_NOW
      
      





Activer la protection complète (FULL RELRO)



Pour ce faire, lors de la compilation, vous devez utiliser les indicateurs appropriés :



$ gcc -Wl,-z,relro,-z,now hello.c -o hello

$ checksec --file=./hello --output=json | jq | grep relro

    «relro»: «full»,

      
      





Ça y est, maintenant notre binaire a reçu le titre honorifique de FULL RELRO :



$ readelf -W -l ./hello | grep GNU_RELRO

  GNU_RELRO      0x002dd0 0x0000000000403dd0 0x0000000000403dd0 0x000230 0x000230 R   0x1

$ readelf -W -d ./hello | grep BIND_NOW

 0x0000000000000018 (BIND_NOW)    
      
      



      

Autres fonctionnalités de checksec



Le thème de la sécurité peut être étudié à l'infini. Même en parlant du simple utilitaire checksec dans cet article, je ne peux pas tout couvrir. Cependant, je mentionnerai quelques autres possibilités intéressantes.



Vérification de plusieurs fichiers



Il n'est pas nécessaire d'exécuter une commande distincte pour chaque fichier. Vous pouvez exécuter une commande pour plusieurs binaires à la fois :



$ checksec --dir=/usr/bin
      
      





Vérification des processus



L'utilitaire checksec vous permet également d'analyser la sécurité des processus. La commande suivante affiche les propriétés de tous les programmes en cours d'exécution sur votre système (vous devez utiliser l'option --proc-all pour ce faire) : 



$ checksec --proc-all
      
      





Vous pouvez également sélectionner un processus à vérifier en spécifiant son nom :



$ checksec --proc=bash
      
      





Vérification du noyau



De même, vous pouvez analyser les vulnérabilités dans le noyau de votre système.



$ checksec --kernel
      
      





Prévenu est prévenu



Étudiez les propriétés de sécurité en détail et essayez de comprendre ce que chacune d'elles affecte exactement et quels types d'attaques elle peut empêcher. Checksec pour vous aider !






Les serveurs cloud de Macleod sont rapides et sécurisés.



Inscrivez-vous en utilisant le lien ci-dessus ou en cliquant sur la bannière et bénéficiez d'une remise de 10 % pour le premier mois de location d'un serveur de n'importe quelle configuration !






All Articles