Identifier les propriétés de sécurité sur Linux à l’aide de checksec


  • Français


  • La compilation du code source produit un binaire. Lors de la compilation, vous pouvez fournir des indicateurs au compilateur pour activer ou désactiver certaines propriétés sur le binaire. Certaines de ces propriétés sont pertinentes pour la sécurité.

    Checksec est un petit outil (et un script shell) astucieux qui, entre autres fonctions, identifie les propriétés de sécurité qui ont été intégrées dans un binaire lors de sa compilation. Un compilateur peut activer certaines de ces propriétés par défaut et vous devrez peut-être fournir des indicateurs spécifiques pour en activer d’autres.

    Cet article explique comment utiliser checksec pour identifier les propriétés de sécurité sur un binaire, notamment :

    1. Les commandes sous-jacentes que checksec utilise pour trouver des informations sur les propriétés de sécurité
    2. Comment activer les propriétés de sécurité à l’aide de GNU Compiler Collection (GCC) lors de la compilation d’un exemple de binaire

    Installer checksec

    Pour installer checksec sur Fedora et d’autres systèmes basés sur RPM, utilisez :

    $ sudo dnf install checksec

    Pour les distributions basées sur Debian, utilisez l’équivalent apt commander.

    Le script shell

    Checksec est un script shell à fichier unique, bien qu’il soit assez volumineux. Un avantage est que vous pouvez lire rapidement le script et comprendre toutes les commandes système en cours d’exécution pour trouver des informations sur les binaires ou les exécutables :

    $ 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

    Prenez checksec pour un lecteur avec un binaire que vous exécutez probablement quotidiennement : l’omniprésent ls commander. Le format de la commande est checksec --file= suivi du chemin absolu du ls binaire:

    $ checksec --file=/usr/bin/ls
    RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      Symbols         FORTIFY Fortified       Fortifiable     FILE
    Full RELRO      Canary found      NX enabled    PIE enabled     No RPATH   No RUNPATH   No Symbols        Yes   5       17              /usr/bin/ls

    Lorsque vous exécutez cela dans un terminal, vous voyez un codage couleur qui montre ce qui est bon et ce qui ne l’est probablement pas. Je dis “probablement” parce que même si quelque chose est en rouge, cela ne signifie pas nécessairement que les choses sont horribles – cela pourrait simplement signifier que les fournisseurs de distribution ont fait des compromis lors de la compilation des binaires.

    La première ligne fournit diverses propriétés de sécurité qui sont généralement disponibles pour les binaires, comme RELRO, STACK CANARY, NX, et ainsi de suite (j’explique en détail ci-dessous). La deuxième ligne montre l’état de ces propriétés pour le binaire donné (ls, dans ce cas). Par example, NX enabled signifie qu’une propriété est activée pour ce binaire.

    Un exemple de binaire

    Pour ce tutoriel, j’utiliserai le programme “hello world” suivant comme exemple de binaire.

    #include <stdio.h>

    int main()
    {
            printf("Hello Worldn");
            return 0;
    }
     

    Notez que je n’ai pas fourni gcc avec des drapeaux supplémentaires lors 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

    Exécutez le binaire via checksec. Certaines propriétés sont différentes de celles du ls commande ci-dessus (sur votre écran, celles-ci peuvent être affichées en rouge) :

    $ checksec --file=./hello
    RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      Symbols         FORTIFY Fortified       Fortifiable     FILE
    Partial RELRO   No canary found   NX enabled    No PIE          No RPATH   No RUNPATH   85) Symbols       No    0       0./hello
    $

    Modification du format de sortie

    Checksec autorise différents formats de sortie, que vous pouvez spécifier avec --output. Je vais choisir le format JSON et diriger la sortie vers le jq utilitaire pour une jolie impression.

    A suivre, assurez-vous d’avoir jq installée car ce didacticiel utilise ce format de sortie pour rechercher rapidement des propriétés spécifiques à partir de la sortie et du rapport yes ou alors no sur chaque:

    $ 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"
      }
    }

    Parcourir les propriétés de sécurité

    Le binaire ci-dessus inclut plusieurs propriétés de sécurité. Je vais comparer ce binaire avec le ls binaire ci-dessus pour examiner ce qui est activé et expliquer comment checksec a trouvé ces informations.

    1. Symboles

    Je vais commencer par le plus facile en premier. Lors de la compilation, certains symboles sont inclus dans le binaire, principalement pour le débogage. Ces symboles sont requis lorsque vous développez un logiciel et nécessitent plusieurs cycles pour le débogage et la réparation.

    Ces symboles sont généralement retirés (supprimés) du binaire final avant qu’il ne soit publié pour une utilisation générale. Cela n’affecte en rien l’exécution du binaire ; il fonctionnera comme avec les symboles. Le dépouillement est souvent fait pour économiser de l’espace, car le binaire est un peu plus léger une fois que les symboles ont été dépouillés. Dans les logiciels à code source fermé ou propriétaires, les symboles sont souvent supprimés car le fait d’avoir ces symboles dans un binaire permet de déduire assez facilement le fonctionnement interne du logiciel.

    Selon checksec, des symboles sont présents dans ce binaire, mais ils n’étaient pas dans le ls binaire. Vous pouvez également trouver ces informations en exécutant le file commande sur le programme—vous voyez not stripped dans la sortie vers la fin :

    $ checksec --file=/bin/ls --output=json | jq | grep symbols
        "symbols": "no",

    $ checksec --file=./hello --output=json | jq | grep symbols
        "symbols": "yes",

    $ 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

    Comment checksec a-t-il trouvé cette information ? Eh bien, c’est pratique --debug option pour afficher les fonctions exécutées. Par conséquent, l’exécution de la commande suivante devrait vous montrer quelles fonctions ont été exécutées dans le script shell :

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

    Dans ce tutoriel, je recherche les commandes sous-jacentes utilisées pour trouver ces informations. Comme il s’agit d’un script shell, vous pouvez toujours utiliser les fonctionnalités de Bash. Cette commande affichera chaque commande exécutée à partir du script shell :

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

    Si vous faites défiler la sortie, vous devriez voir un echo_message suivi de la catégorie de la propriété de sécurité. Voici ce que checksec rapporte pour savoir si le binaire contient des symboles :

    + lire moi-même -W --symboles ./Bonjour
    + grep -q '.symtab'
    + echo_message '33[31m96) Symbolst33[m  ' Symbols, ' symbols="yes"' '"symbols":"yes",'

    To simplify this, checksec utilizes the readelf utility to read the binary and provides a special --symbols flag that lists all symbols within the binary. Then it greps for a special value, .symtab, that provides a count of entries (symbols) it finds. You can try out the following commands on the test binary you compiled above:

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

    How to strip symbols

    You can strip symbols after compilation or during compilation.

    • Post compilation: After compilation, you can use the strip utility on the binary to remove the symbols. Confirm it worked using the file command, which now shows the output as stripped:

      $ 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, pour GNOU/Linux 3.2.0, non supprimé
      $
      $ déshabiller Bonjour
      $
      $ déposer Bonjour
      bonjour : elfe 64-bit LSB exécutable, x86-64, version 1 (SYSV), lié dynamiquement, interprète /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=322037496cf6a2029dcdcf68649a4ebc63780138, pour GNOU/Linux 3.2.0, supprimé
      $

    Comment supprimer les symboles lors de la compilation

    Au lieu de supprimer les symboles manuellement après la compilation, vous pouvez demander au compilateur de le faire pour vous en fournissant le -s argument:

    $ 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, stripped
    $

    Après avoir réexécuté checksec, vous pouvez voir que symbols sont montrés comme no:

    $ checksec --file=./hello --output=json | jq | grep symbols
        "symbols": "no",
    $

    2. Canari

    Les canaris sont des valeurs connues qui sont placées entre un tampon et des données de contrôle sur le empiler pour surveiller les débordements de buffer. Lorsqu’une application s’exécute, deux types de mémoire lui sont affectés. L’un d’eux est un empiler, qui est simplement une structure de données avec deux opérations : push, qui met les données dans la pile, et pop, qui supprime les données de la pile dans l’ordre inverse. Une entrée malveillante pourrait déborder ou corrompre la pile avec une entrée spécialement conçue et provoquer le plantage du programme :

    $ checksec --file=/bin/ls --output=json | jq | grep canary
        "canary": "yes",
    $
    $ checksec --file=./hello --output=json | jq | grep canary
        "canary": "no",
    $

    Comment checksec découvre-t-il si le binaire est activé avec un canari ? En utilisant la méthode ci-dessus, vous pouvez le réduire en exécutant la commande suivante dans le script shell :

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

    Activer Canari

    Pour se protéger contre ces cas, le compilateur fournit les -stack-protector-all flag, qui ajoute du code supplémentaire au binaire pour vérifier de tels débordements de tampon :

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

    $ checksec --file=./hello --output=json | jq | grep canary
        "canary": "yes",

    Checksec montre que la propriété est maintenant activée. Vous pouvez également le vérifier avec :

    $ 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

    PIE signifie exécutable indépendant de la position. Comme son nom l’indique, c’est du code qui est placé quelque part en mémoire pour exécution quelle que soit son adresse absolue :

    $ checksec --file=/bin/ls --output=json | jq | grep pie
        "pie": "yes",

    $ checksec --file=./hello --output=json | jq | grep pie
        "pie": "no",

    Souvent, PIE est activé uniquement pour les bibliothèques et non pour les programmes de ligne de commande autonomes. Dans la sortie ci-dessous, hello est montré comme LSB executable, tandis que le libc bibliothèque standard (.so) le fichier est marqué LSB shared object:

    $ 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

    $ file /lib64/libc-2.32.so
    /lib64/libc-2.32.so: ELF 64-bit LSB shared object, 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 essaie de trouver ces informations avec :

    $ readelf -W -h ./hello | grep EXEC
      Type:                              EXEC (Executable file)

    Si vous essayez la même commande sur une bibliothèque partagée au lieu de EXEC, vous verrez un DYN:

    $ readelf -W -h /lib64/libc-2.32.so | grep DYN
      Type:                              DYN (Shared object file)

    Activer PIE

    Pour activer PIE sur un programme de test, envoyez les arguments suivants au compilateur :

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

    Vous pouvez vérifier que PIE est activé à l’aide de checksec :

    $ checksec --file=./hello --output=json | jq | grep pie
        "pie": "yes",
    $

    Il devrait apparaître comme un exécutable PIE avec le type modifié de 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

    NX signifie “non exécutable”. Il est souvent activé au niveau du processeur, donc un système d’exploitation avec NX activé peut marquer certaines zones de mémoire comme non exécutables. Souvent, les exploits de débordement de tampon placent du code sur la pile, puis tentent de l’exécuter. Cependant, rendre cette zone inscriptible non exécutable peut empêcher de telles attaques. Cette propriété est activée par défaut lors de la compilation régulière en utilisant gcc:

    $ checksec --file=/bin/ls --output=json | jq | grep nx
        "nx": "yes",

    $ checksec --file=./hello --output=json | jq | grep nx
        "nx": "yes",

    Checksec détermine ces informations avec la commande ci-dessous. RW vers la fin signifie que la pile est lisible et inscriptible ; puisqu’il n’y a pas E, ce n’est pas exécutable :

    $ readelf -W -l ./hello | grep GNU_STACK
      GNU_STACK      0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW  0x10

    Désactiver NX à des fins de démonstration

    Ce n’est pas recommandé, mais vous pouvez désactiver NX lors de la compilation d’un programme en utilisant le -z execstack argument:

    $ gcc -z execstack hello.c -o hello

    $ checksec --file=./hello --output=json | jq | grep nx
        "nx": "no",

    A la compilation, la pile devient exécutable (RWE), qui permet à un code malveillant de s’exécuter :

    $ readelf -W -l ./hello | grep GNU_STACK
      GNU_STACK      0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RWE 0x10

    5. RELRO

    RELRO signifie Relocation Read-Only. Un binaire ELF (Executable Linkable Format) utilise une table de décalage global (GOT) pour résoudre les fonctions de manière dynamique. Lorsqu’elle est activée, cette propriété de sécurité rend le GOT dans le binaire en lecture seule, ce qui empêche certaines formes d’attaques de relocalisation :

    $ checksec --file=/bin/ls --output=json | jq | grep relro
        "relro": "full",

    $ checksec --file=./hello --output=json | jq | grep relro
        "relro": "partial",

    Checksec trouve ces informations en utilisant la commande ci-dessous. Ici, l’une des propriétés RELRO est activée ; par conséquent, le binaire affiche “partiel” lors de la vérification via checksec :

    $ readelf -W -l ./hello | grep GNU_RELRO
      GNU_RELRO      0x002e10 0x0000000000403e10 0x0000000000403e10 0x0001f0 0x0001f0 R   0x1

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

    Activer le RELRO complet

    Pour activer le RELRO complet, utilisez les arguments de ligne de commande suivants lors de la compilation avec gcc:

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

    $ checksec --file=./hello --output=json | jq | grep relro
        "relro": "full",

    Maintenant, la deuxième propriété est également activée, ce qui rend le programme RELRO complet :

    $ 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)          

    6. Fortifier

    Fortify est une autre propriété de sécurité, mais elle est hors de portée de cet article. Je vais partir en apprenant comment checksec vérifie fortify dans les binaires et comment il est activé avec gcc comme un exercice pour vous d’aborder.

    $ checksec --file=/bin/ls --output=json | jq  | grep -i forti
        "fortify_source": "yes",
        "fortified": "5",
        "fortify-able": "17"

    $ checksec --file=./hello --output=json | jq  | grep -i forti
        "fortify_source": "no",
        "fortified": "0",
        "fortify-able": "0"

    Autres fonctionnalités de checksec

    Le sujet de la sécurité est sans fin, et bien qu’il ne soit pas possible de tout couvrir ici, je veux mentionner quelques fonctionnalités supplémentaires du checksec commande qui font qu’il est agréable de travailler avec.

    Exécuter contre plusieurs binaires

    Vous n’êtes pas obligé de fournir chaque binaire à checksec individuellement. Au lieu de cela, vous pouvez fournir un chemin de répertoire où résident plusieurs binaires, et checksec les vérifiera tous pour vous en une seule fois :

    $ checksec --dir=/usr/bin

    Processus

    En plus des binaires, checksec fonctionne également sur les programmes pendant l’exécution. La commande suivante recherche les propriétés de sécurité de tous les programmes en cours d’exécution sur votre système. Vous pouvez utiliser --proc-all si vous voulez qu’il vérifie tous les processus en cours, ou vous pouvez sélectionner un processus spécifique en utilisant son nom :

    $ checksec --proc-all

    $ checksec --proc=bash

    Propriétés du noyau

    En plus des applications utilisateur de checksec décrites dans cet article, vous pouvez également l’utiliser pour vérifier les propriétés du noyau intégrées à votre système :

    $ checksec --kernel

    Essaie

    Checksec est un bon moyen de comprendre quelles propriétés de l’espace utilisateur et du noyau sont activées. Parcourez chaque propriété de sécurité en détail et essayez de comprendre les raisons de l’activation de chaque fonctionnalité et les types d’attaques qu’elle empêche.

    Source

    Houssen Moshinaly

    Pour contacter personnellement le taulier :

    Laisser un commentaire

    Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

    Copy code