Das Kompilieren des Quellcodes erzeugt eine Binärdatei. Während der Kompilierung können Sie dem Compiler Flags bereitstellen, um bestimmte Eigenschaften der Binärdatei zu aktivieren oder zu deaktivieren. Einige dieser Eigenschaften sind sicherheitsrelevant.
Checksec ist ein raffiniertes kleines Tool (und Shell-Skript), das neben anderen Funktionen die Sicherheitseigenschaften identifiziert, die bei der Kompilierung in eine Binärdatei eingebaut wurden. Ein Compiler aktiviert möglicherweise einige dieser Eigenschaften standardmäßig, und Sie müssen möglicherweise bestimmte Flags bereitstellen, um andere zu aktivieren.
In diesem Artikel wird erläutert, wie Sie mithilfe von Checksec die Sicherheitseigenschaften einer Binärdatei identifizieren, einschließlich:
- Die zugrunde liegenden Befehle, die checksec verwendet, um Informationen zu den Sicherheitseigenschaften zu finden
- So aktivieren Sie Sicherheitseigenschaften mithilfe der GNU Compiler Collection (GCC) beim Kompilieren einer Beispiel-Binärdatei
Checksec installieren
Um checksec auf Fedora und anderen RPM-basierten Systemen zu installieren, verwenden Sie:
$ sudo dnf install checksec
Verwenden Sie für Debian-basierte Distributionen das Äquivalent apt
Befehl.
Das Shell-Skript
Checksec ist ein Ein-Datei-Shell-Skript, wenn auch ein ziemlich großes. Ein Vorteil ist, dass Sie das Skript schnell durchlesen und alle ausgeführten Systembefehle verstehen können, um Informationen zu Binärdateien oder ausführbaren Dateien zu finden:
$ 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
Nehmen Sie checksec für ein Laufwerk mit einer Binärdatei, die Sie wahrscheinlich täglich ausführen: die allgegenwärtige ls
Befehl. Das Format des Befehls ist checksec --file=
gefolgt vom absoluten Pfad des ls
binär:
$ 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
Wenn Sie dies in einem Terminal ausführen, sehen Sie eine Farbcodierung, die anzeigt, was gut ist und was wahrscheinlich nicht. Ich sage “wahrscheinlich”, denn selbst wenn etwas rot ist, bedeutet dies nicht unbedingt, dass die Dinge schrecklich sind – es könnte nur bedeuten, dass die Distributionsanbieter beim Kompilieren der Binärdateien einige Kompromisse eingegangen sind.
Die erste Zeile bietet verschiedene Sicherheitseigenschaften, die normalerweise für Binärdateien verfügbar sind, wie z RELRO
, STACK CANARY
, NX
, und so weiter (das erkläre ich weiter unten). Die zweite Zeile zeigt den Status dieser Eigenschaften für die angegebene Binärdatei (ls
, in diesem Fall). Zum Beispiel, NX enabled
bedeutet, dass eine Eigenschaft für diese Binärdatei aktiviert ist.
Eine Beispiel-Binärdatei
Für dieses Tutorial verwende ich das folgende “hello world”-Programm als Beispiel-Binärdatei.
#include <stdio.h>
int main()
{
printf("Hello Worldn");
return 0;
}
Beachten Sie, dass ich nicht bereitgestellt habe gcc
mit eventuellen zusätzlichen Flags während der Kompilierung:
$ 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
Führen Sie die Binärdatei durch checksec aus. Einige der Eigenschaften sind anders als bei den ls
Befehl oben (auf Ihrem Bildschirm können diese in Rot angezeigt werden):
$ 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
$
Ausgabeformat ändern
Checksec erlaubt verschiedene Ausgabeformate, die Sie mit angeben können --output
. Ich wähle das JSON-Format und leite die Ausgabe an die jq
Dienstprogramm für hübsches Drucken.
Um mitzumachen, stellen Sie sicher, dass Sie jq
installiert, weil dieses Tutorial dieses Ausgabeformat verwendet, um schnell nach bestimmten Eigenschaften aus der Ausgabe und dem Bericht zu suchen yes
oder no
auf jeder:
$ 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"
}
}
Durch die Sicherheitseigenschaften gehen
Die obige Binärdatei enthält mehrere Sicherheitseigenschaften. Ich vergleiche diese Binärdatei mit der ls
binär oben, um zu untersuchen, was aktiviert ist, und zu erklären, wie checksec diese Informationen gefunden hat.
1. Symbole
Ich fange zuerst mit dem einfachen an. Während der Kompilierung werden bestimmte Symbole in die Binärdatei aufgenommen, hauptsächlich zum Debuggen. Diese Symbole werden bei der Entwicklung von Software benötigt und erfordern mehrere Zyklen zum Debuggen und Beheben von Problemen.
Diese Symbole werden normalerweise aus der endgültigen Binärdatei entfernt (entfernt), bevor sie für den allgemeinen Gebrauch freigegeben wird. Dies hat keinerlei Einfluss auf die Ausführung der Binärdatei; es läuft genauso wie mit den Symbolen. Das Strippen wird oft durchgeführt, um Platz zu sparen, da die Binärdatei nach dem Strippen der Symbole etwas heller ist. In Closed-Source- oder proprietärer Software werden Symbole oft entfernt, da diese Symbole in einer Binärdatei es etwas einfacher machen, auf das Innenleben der Software zu schließen.
Laut checksec sind in dieser Binärdatei Symbole vorhanden, die jedoch nicht in der ls
binär. Sie können diese Informationen auch finden, indem Sie die file
Befehl im Programm – Siehst du not stripped
in der Ausgabe gegen Ende:
$ 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
Wie hat checksec diese Informationen gefunden? Nun, es bietet ein praktisches --debug
Option, um anzuzeigen, welche Funktionen ausgeführt wurden. Daher sollte Ihnen die Ausführung des folgenden Befehls zeigen, welche Funktionen innerhalb des Shell-Skripts ausgeführt wurden:
$ checksec --debug --file=./hello
In diesem Tutorial suche ich nach den zugrunde liegenden Befehlen, die zum Auffinden dieser Informationen verwendet werden. Da es sich um ein Shell-Skript handelt, können Sie jederzeit die Bash-Funktionen nutzen. Dieser Befehl gibt jeden Befehl aus, der innerhalb des Shell-Skripts ausgeführt wurde:
$ bash -x /usr/bin/checksec --file=./hello
Wenn Sie durch die Ausgabe scrollen, sollten Sie ein . sehen echo_message
gefolgt von der Kategorie der Sicherheitseigenschaft. Folgendes meldet checksec darüber, ob die Binärdatei Symbole enthält:
+ wiederelfen -W --symbole ./Hallo
+ 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, zum GNU/Linux 3.2.0, nicht abgespeckt
$
$ Streifen Hallo
$
$ Datei Hallo
Hallo: ELF 64-bit LSB ausführbar, x86-64, Ausführung 1 (SYSV), dynamisch verknüpft, Interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=322037496cf6a2029dcdcf68649a4ebc63780138, zum GNU/Linux 3.2.0, abgespeckt
$
So entfernen Sie Symbole während der Kompilierung
Anstatt Symbole nach der Kompilierung manuell zu entfernen, können Sie den Compiler bitten, dies für Sie zu tun, indem Sie die -s
Streit:
$ 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
$
Nachdem Sie checksec erneut ausgeführt haben, können Sie das sehen symbols
werden angezeigt als no
:
$ checksec --file=./hello --output=json | jq | grep symbols
"symbols": "no",
$
2. Kanarienvogel
Kanarienvögel sind bekannte Werte, die zwischen einem Puffer und Kontrolldaten auf dem Stapel Pufferüberläufe zu überwachen. Wenn eine Anwendung ausgeführt wird, werden ihr zwei Arten von Speicher zugewiesen. Einer von ihnen ist ein Stapel, die einfach eine Datenstruktur mit zwei Operationen ist: push
, die Daten auf den Stack legt, und pop
, die Daten in umgekehrter Reihenfolge vom Stack entfernt. Böswillige Eingaben können den Stack mit speziell gestalteten Eingaben überlaufen oder beschädigen und zum Absturz des Programms führen:
$ checksec --file=/bin/ls --output=json | jq | grep canary
"canary": "yes",
$
$ checksec --file=./hello --output=json | jq | grep canary
"canary": "no",
$
Wie findet checksec heraus, ob die Binärdatei mit einem Canary aktiviert ist? Mit der obigen Methode können Sie es eingrenzen, indem Sie den folgenden Befehl innerhalb des Shell-Skripts ausführen:
$ readelf -W -s ./hello | grep -E '__stack_chk_fail|__intel_security_cookie'
Kanarienvogel aktivieren
Um sich vor diesen Fällen zu schützen, bietet der Compiler die -stack-protector-all
Flag, das der Binärdatei zusätzlichen Code hinzufügt, um auf solche Pufferüberläufe zu prüfen:
$ gcc -fstack-protector-all hello.c -o hello
$ checksec --file=./hello --output=json | jq | grep canary
"canary": "yes",
Checksec zeigt an, dass die Eigenschaft jetzt aktiviert ist. Sie können dies auch überprüfen mit:
$ 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. TORTE
PIE steht für positionsunabhängige ausführbare Datei. Wie der Name schon sagt, handelt es sich um Code, der unabhängig von seiner absoluten Adresse zur Ausführung irgendwo im Speicher abgelegt wird:
$ checksec --file=/bin/ls --output=json | jq | grep pie
"pie": "yes",
$ checksec --file=./hello --output=json | jq | grep pie
"pie": "no",
PIE ist oft nur für Bibliotheken und nicht für eigenständige Befehlszeilenprogramme aktiviert. In der Ausgabe unten, hello
wird angezeigt als LSB executable
, während die libc
Standardbibliothek (.so
) Datei ist markiert 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 versucht diese Informationen zu finden mit:
$ readelf -W -h ./hello | grep EXEC
Type: EXEC (Executable file)
Wenn Sie denselben Befehl in einer gemeinsam genutzten Bibliothek versuchen, anstatt EXEC
, du wirst sehen DYN
:
$ readelf -W -h /lib64/libc-2.32.so | grep DYN
Type: DYN (Shared object file)
PIE aktivieren
Um PIE in einem Testprogramm zu aktivieren, senden Sie die folgenden Argumente an den Compiler:
$ gcc -pie -fpie hello.c -o hello
Sie können überprüfen, ob PIE mit checksec aktiviert ist:
$ checksec --file=./hello --output=json | jq | grep pie
"pie": "yes",
$
Es sollte als ausführbare PIE-Datei mit dem Typ angezeigt werden, der von geändert wurde EXEC
zu 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 steht für „nicht ausführbar“. Es wird oft auf CPU-Ebene aktiviert, sodass ein Betriebssystem mit aktiviertem NX bestimmte Speicherbereiche als nicht ausführbar markieren kann. Häufig legen Buffer-Overflow-Exploits Code auf den Stack und versuchen dann, ihn auszuführen. Wenn Sie diesen beschreibbaren Bereich jedoch nicht ausführbar machen, können solche Angriffe verhindert werden. Diese Eigenschaft ist während der regulären Kompilierung standardmäßig aktiviert mit gcc
:
$ checksec --file=/bin/ls --output=json | jq | grep nx
"nx": "yes",
$ checksec --file=./hello --output=json | jq | grep nx
"nx": "yes",
Checksec ermittelt diese Informationen mit dem folgenden Befehl. RW
gegen Ende bedeutet, dass der Stapel lesbar und beschreibbar ist; da es keine gibt E
, es ist nicht ausführbar:
$ readelf -W -l ./hello | grep GNU_STACK
GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW 0x10
Deaktivieren Sie NX für Demozwecke
Es wird nicht empfohlen, aber Sie können es deaktivieren NX
beim Kompilieren eines Programms mit dem -z execstack
Streit:
$ gcc -z execstack hello.c -o hello
$ checksec --file=./hello --output=json | jq | grep nx
"nx": "no",
Beim Kompilieren wird der Stack ausführbar (RWE
), wodurch bösartiger Code ausgeführt werden kann:
$ readelf -W -l ./hello | grep GNU_STACK
GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RWE 0x10
5. RELRO
RELRO steht für Relocation Read-Only. Eine Binärdatei im Executable Linkable Format (ELF) verwendet eine Global Offset Table (GOT), um Funktionen dynamisch aufzulösen. Wenn diese Sicherheitseigenschaft aktiviert ist, macht diese Sicherheitseigenschaft das GOT innerhalb der Binärdatei schreibgeschützt, was irgendeine Form von Relocation-Angriffen verhindert:
$ checksec --file=/bin/ls --output=json | jq | grep relro
"relro": "full",
$ checksec --file=./hello --output=json | jq | grep relro
"relro": "partial",
Checksec findet diese Informationen mithilfe des folgenden Befehls. Hier ist eine der RELRO-Eigenschaften aktiviert; daher zeigt die Binärdatei “partiell” an, wenn sie über checksec verifiziert wird:
$ readelf -W -l ./hello | grep GNU_RELRO
GNU_RELRO 0x002e10 0x0000000000403e10 0x0000000000403e10 0x0001f0 0x0001f0 R 0x1
$ readelf -W -d ./hello | grep BIND_NOW
Vollständiges RELRO aktivieren
Um vollständiges RELRO zu aktivieren, verwenden Sie die folgenden Befehlszeilenargumente beim Kompilieren mit gcc
:
$ gcc -Wl,-z,relro,-z,now hello.c -o hello
$ checksec --file=./hello --output=json | jq | grep relro
"relro": "full",
Jetzt ist auch die zweite Eigenschaft aktiviert, wodurch das Programm vollständig RELRO wird:
$ 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. Stärken
Fortify ist eine weitere Sicherheitseigenschaft, die jedoch in diesem Artikel nicht behandelt wird. Ich werde lernen, wie Checksec Fortify in Binärdateien überprüft und wie es mit aktiviert wird gcc
als Übung für Sie anzugehen.
$ 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"
Andere Checksec-Funktionen
Das Thema Sicherheit ist endlos, und obwohl es hier nicht möglich ist, alles abzudecken, möchte ich noch ein paar weitere Funktionen des checksec
Befehle, mit denen die Arbeit zu einem Vergnügen wird.
Gegen mehrere Binärdateien ausführen
Sie müssen nicht jede Binärdatei einzeln für checksec bereitstellen. Stattdessen können Sie einen Verzeichnispfad angeben, in dem sich mehrere Binärdateien befinden, und checksec überprüft alle auf einmal für Sie:
$ checksec --dir=/usr/bin
Prozesse
Neben Binärdateien arbeitet checksec auch an Programmen während der Ausführung. Der folgende Befehl findet die Sicherheitseigenschaften aller laufenden Programme auf Ihrem System. Sie können verwenden --proc-all
Wenn Sie möchten, dass alle laufenden Prozesse überprüft werden, oder Sie können einen bestimmten Prozess mithilfe seines Namens auswählen:
$ checksec --proc-all
$ checksec --proc=bash
Kernel-Eigenschaften
Zusätzlich zu den in diesem Artikel beschriebenen Userland-Anwendungen von checksec können Sie damit auch die in Ihr System integrierten Kernel-Eigenschaften überprüfen:
$ checksec --kernel
Versuche es
Checksec ist eine gute Möglichkeit zu verstehen, welche Userland- und Kernel-Eigenschaften aktiviert sind. Gehen Sie jede Sicherheitseigenschaft im Detail durch und versuchen Sie, die Gründe für die Aktivierung der einzelnen Funktionen und die Arten von Angriffen zu verstehen, die sie verhindern.