Identifier les propriétés de sécurité sur Linux à l’aide de checksec
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 :
- Les commandes sous-jacentes que checksec utilise pour trouver des informations sur les propriétés de sécurité
- 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
Contents
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) Symbolst 33[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 thefile
command, which now shows the output asstripped
:$ 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.