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 astucieux (et un script shell) 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
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
commande.
Le script shell
Checksec est un script shell à fichier unique, bien qu'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 :script shell Bourne-Again, exécutable en texte ASCII, avec de très longues lignes
$ 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
commande. Le format de la commande est checksec --file=
suivi du chemin absolu du ls
binaire :
$ checksec --file =/ usr / bin / ls
Stack Relro Stack NX Pie RPATH Symboles FORTIFY FORDIFIABLE FORTIFIABLE FORTIFIABLE FORTIFIABLE
TRANSFORME COMPLET NX activé Non RPATH Aucun Symbole Non Symboles Oui 5 17 /usr/bin/ls
Lorsque vous l'exécutez dans un terminal, vous voyez un code couleur indiquant 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 peut 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 le statut de ces propriétés pour le binaire donné (ls
, dans ce cas). Par exemple, NX enabled
signifie qu'une propriété est activée pour ce binaire.
Un exemple binaire
Pour ce tutoriel, j'utiliserai le programme "hello world" suivant comme exemple binaire.
#include
int main()
{
printf("Hello World\n");
return 0;
}
Notez que je n'ai pas fourni gcc
avec tous les 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), dynamiquement lié, interpréteur / lib64/ld-linux-x86-64.so.2, BuildID[sha1]=014b8966ba43e3ae47fab5acae051e208ec9074c, pour GNU/Linux 3.2.0, non supprimé
$ ./hello
Hello WorldExécutez le binaire via checksec. Certaines propriétés sont différentes de celles avec
ls
commande ci-dessus (sur votre écran, celles-ci peuvent être affichées en rouge) :$ CheckSec --File =. / Hello
Relro Stack Canary Nx Pie RPATH Symboles RunPath FORTIFY FORTIFIABLE FORTIFIABLE FORTIFIABLE
NO PARTIE FONCTIONNAIRE NX activé Non Pie Pas de RPATH No RunPath 85) Symboles No 0 0./bonjour
Modifier le 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 lejq
utilitaire pour de jolies impressions.Pour suivre, assurez-vous d'avoir
jq
installé car ce didacticiel utilise ce format de sortie pour rechercher rapidement des propriétés spécifiques à partir de la sortie et signaleryes
ouno
sur chaque :$ checksec --file=./hello --output=json | jq
{
"./hello":{
"relro":"partial",
"canary":"no",
"nx":" oui",
"pie":"non",
"rpath":"non",
"runpath":"non",
"symbols":"oui" ,
"fortify_source":"no",
"fortified":"0",
"fortify-able":"0"
}
}Parcourir les propriétés de sécurité
En savoir plus sur la sécurité
- Le guide de codage défensif
- Webinaire :Automatiser la sécurité et la conformité du système avec un système d'exploitation standard
- 10 couches de sécurité des conteneurs Linux
- Livre de coloriage SELinux
- Plus d'articles sur la 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é cette information.
1. Symboles
Je vais d'abord commencer par le plus facile. Lors de la compilation, certains symboles sont inclus dans le binaire, principalement pour le débogage. Ces symboles sont nécessaires lorsque vous développez un logiciel et nécessitent plusieurs cycles pour déboguer et réparer les choses.
Ces symboles sont généralement supprimé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 il le ferait avec les symboles. La suppression est souvent effectuée pour économiser de l'espace, car le binaire est un peu plus léger une fois que les symboles ont été supprimés. Dans les logiciels à source fermée 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, les 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 | symboles grep
"symbols":"no",
$ checksec --file=./hello --output=json | jq | symboles grep
"symbols":"yes",
$ file hello
hello:ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamiquement lié , interpréteur /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=014b8966ba43e3ae47fab5acae051e208ec9074c, pour GNU/Linux 3.2.0, non supprimé
Comment checksec a-t-il trouvé cette information ? Eh bien, il fournit un --debug
pratique option pour montrer quelles fonctions ont été exécutées. Par conséquent, l'exécution de la commande suivante devrait vous montrer quelles fonctions s'exécutent dans le script shell :
$ checksec --debug --file=./hello
Dans ce didacticiel, 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 toutes les commandes exécutées à 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 indique si le binaire contient des symboles :
+ readelf -W --symbols ./hello
+ grep -q '\.symtab'
+ echo_message '\033[31m96) Symboles\t\033[m ' Symboles, ' symboles ="oui"' '"symboles":"oui",'
Pour simplifier cela, checksec utilise le readelf
utilitaire pour lire le binaire et fournit un --symbols
spécial drapeau qui répertorie tous les symboles dans le binaire. Ensuite, il recherche une valeur spéciale, .symtab
, qui fournit un nombre d'entrées (symboles) qu'il trouve. Vous pouvez essayer les commandes suivantes sur le binaire de test que vous avez compilé ci-dessus :
$ readelf -W --symbols ./hello
$ readelf -W --symbols ./hello | grep -i symtab
Comment supprimer les symboles
Vous pouvez supprimer les symboles après la compilation ou pendant la compilation.
- Post-compilation : Après compilation, vous pouvez utiliser le
strip
utilitaire sur le binaire pour supprimer les symboles. Confirmez que cela a fonctionné en utilisant lefile
commande, qui affiche maintenant la sortie commestripped
:$ gcc bonjour.c -o bonjour
$
$ fichier bonjour
bonjour :exécutable LSB ELF 64 bits, x86-64, version 1 (SYSV) , lié dynamiquement, interpréteur /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=322037496cf6a2029dcdcf68649a4ebc63780138, pour GNU/Linux 3.2.0, non supprimé
$
$ strip hello
$
$ fichier bonjour
bonjour :exécutable LSB 64 bits ELF, x86-64, version 1 (SYSV), lié dynamiquement, interpréteur /lib64/ld-linux-x86-64.so .2, BuildID[sha1]=322037496cf6a2029dcdcf68649a4ebc63780138, pour GNU/Linux 3.2.0, supprimé
$
Comment supprimer les symboles lors de la compilation
Au lieu de supprimer manuellement les symboles 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), dynamiquement lié , interpréteur /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=247de82a8ad84e7d8f20751ce79ea9e0cf4bd263, pour GNU/Linux 3.2.0, supprimé
$
Après avoir réexécuté checksec, vous pouvez voir que symbols
sont affichés comme no
:
$ checksec --file=./hello --output=json | jq | symboles grep
"symbols":"non",
$
2. Canari
Les canaris sont des valeurs connues qui sont placées entre un tampon et des données de contrôle sur la pile pour surveiller les débordements de tampon. Lorsqu'une application s'exécute, deux types de mémoire lui sont affectés. L'un d'eux est une pile , qui est simplement une structure de données avec deux opérations :push
, qui place les données sur 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 canari
"canari":"non",
$
Comment checksec trouve-t-il si le binaire est activé avec un canari ? En utilisant la méthode ci-dessus, vous pouvez l'affiner 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 le -stack-protector-all
flag, qui ajoute du code supplémentaire au binaire pour vérifier ces débordements de tampon :
$ gcc -fstack-protector-all hello.c -o hello
$ checksec --file=./hello --output=json | jq | grep canari
"canari":"oui",
Checksec montre que la propriété est maintenant activée. Vous pouvez également le vérifier avec :
$ readelf -W -s ./bonjour | Grep -e '__stack_chk_fail | __intel_security_cookie'
2:0000000000000000 0 Func global par défaut und __stack_chk_fail@glibc_2.4 (3)
83:0000000000000000 0 Func global par défaut 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":"non",
Souvent, PIE n'est activé que pour les bibliothèques et non pour les programmes de ligne de commande autonomes. Dans la sortie ci-dessous, hello
est affiché comme LSB executable
, tandis que la libc
bibliothèque standard (.so
) le fichier est marqué LSB shared object
:
$ fichier bonjour
bonjour :exécutable LSB 64 bits ELF, x86-64, version 1 (SYSV), lié dynamiquement, interpréteur /lib64/ld-linux-x86-64.so.2, BuildID[ sha1]=014b8966ba43e3ae47fab5acae051e208ec9074c, pour GNU/Linux 3.2.0, non supprimé
$ fichier /lib64/libc-2.32.so
/lib64/libc-2.32.so :ELF 64 bits Objet partagé LSB, x86-64, version 1 (GNU/Linux), lié dynamiquement, interpréteur /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=4a7fb374097fb927fb93d35ef98ba89262d0c4a4, pour GNU/Linux 3.2.0, non dépouillé
Checksec essaie de trouver ces informations avec :
$ readelf -W -h ./bonjour | grep EXEC
Tapez : EXEC (fichier exécutable)
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
Tapez : DYN (fichier objet partagé)
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é en utilisant checksec :
$ checksec --file=./hello --output=json | jq | grep pie
"pie":"oui",
$
Il devrait apparaître comme un exécutable PIE avec le type changé de EXEC
à DYN
:
$ fichier bonjour
bonjour :ELF 64 bits LSB pie exécutable, x86-64, version 1 (SYSV), lié dynamiquement, interpréteur /lib64/ld-linux-x86-64.so.2, BuildID [sha1]=bb039adf2530d97e02f534a94f0f668cd540f940, pour GNU/Linux 3.2.0, non supprimé
$ readelf -W -h ./hello | grep DYN
Tapez : DYN (fichier objet partagé)
4. NX
NX signifie "non exécutable". Il est souvent activé au niveau du processeur, de sorte qu'un système d'exploitation avec NX activé peut marquer certaines zones de la 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 à l'aide de gcc
:
$ checksec --file=/bin/ls --output=json | jq | grep nx
"nx":"yes",
$ checksec --file=./hello --output=json | jq | grep nx
"nx":"oui",
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 de E
, il n'est pas exécutable :
$ readelf -W -l ./bonjour | 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 -z execstack
argument :
$ gcc -z execstack hello.c -o hello
$ checksec --file=./hello --output=json | jq | grep nx
"nx":"non",
A la compilation, la pile devient exécutable (RWE
), ce qui permet à un code malveillant de s'exécuter :
$ readelf -W -l ./bonjour | 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 globale (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 "partial" lors de la vérification via checksec :
$ readelf -W -l ./bonjour | 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,maintenant hello.c -o hello
$ checksec --file=./hello --output=json | jq | grep relro
"relro":"complet",
Maintenant, la deuxième propriété est également activée, rendant le programme complet RELRO :
$ readelf -W -l ./bonjour | 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 sort du cadre de cet article. Je vais apprendre comment checksec vérifie fortifier dans les binaires et comment il est activé avec gcc
comme un exercice à 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 avec laquelle il est agréable de travailler.
Exécuter sur plusieurs binaires
Vous n'avez pas à fournir chaque binaire à checksec individuellement. Au lieu de cela, vous pouvez fournir un chemin de répertoire où résident plusieurs fichiers 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 d'exécution, 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
Essayez-le
Checksec est un bon moyen de comprendre quelles propriétés de l'espace utilisateur et du noyau sont activées. Passez en revue 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.