La compilazione del codice sorgente produce un binario. Durante la compilazione, puoi fornire dei flag al compilatore per abilitare o disabilitare determinate proprietà sul file binario. Alcune di queste proprietà sono rilevanti per la sicurezza.
Checksec è un piccolo e ingegnoso strumento (e script di shell) che, tra le altre funzioni, identifica le proprietà di sicurezza che sono state integrate in un binario quando è stato compilato. Un compilatore potrebbe abilitare alcune di queste proprietà per impostazione predefinita e potresti dover fornire flag specifici per abilitarne altre.
Questo articolo spiega come utilizzare checksec per identificare le proprietà di sicurezza su un file binario, tra cui:
- I comandi sottostanti che checksec usa per trovare informazioni sulle proprietà di sicurezza
- Come abilitare le proprietà di sicurezza utilizzando GNU Compiler Collection (GCC) durante la compilazione di un file binario di esempio
Installa checksec
Per installare checksec su Fedora e altri sistemi basati su RPM, usa:
$ sudo dnf install checksec
Per le distribuzioni basate su Debian, utilizzare l'equivalente apt
comando.
Lo script della shell
Checksec è uno script di shell a file singolo, anche se piuttosto grande. Un vantaggio è che puoi leggere rapidamente lo script e comprendere tutti i comandi di sistema in esecuzione per trovare informazioni su binari o eseguibili:
$ 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
Prendi checksec per un disco con un binario che probabilmente esegui ogni giorno:l'onnipresente ls
comando. Il formato del comando è checksec --file=
seguito dal percorso assoluto di ls
binario:
$ 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
Quando lo esegui in un terminale, vedi la codifica a colori che mostra cosa è buono e cosa probabilmente non lo è. Dico "probabilmente" perché anche se qualcosa è in rosso, non significa necessariamente che le cose siano orribili, potrebbe semplicemente significare che i fornitori di distribuzioni hanno fatto dei compromessi durante la compilazione dei binari.
La prima riga fornisce varie proprietà di sicurezza che di solito sono disponibili per i binari, come RELRO
, STACK CANARY
, NX
, e così via (spiegherò in dettaglio di seguito). La seconda riga mostra lo stato di queste proprietà per il file binario specificato (ls
, in questo caso). Ad esempio, NX enabled
significa che alcune proprietà sono abilitate per questo binario.
Un binario di esempio
Per questo tutorial, userò il seguente programma "hello world" come binario di esempio.
#include <stdio.h>
int main()
{
printf("Hello World\n");
return 0;
}
Nota che non ho fornito gcc
con eventuali flag aggiuntivi durante la compilazione:
$ 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
Esegui il binario tramite checksec. Alcune delle proprietà sono diverse rispetto a ls
comando sopra (sul tuo schermo, questi potrebbero essere visualizzati in rosso):
$ 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
$
Modifica del formato di output
Checksec consente vari formati di output, che puoi specificare con --output
. Sceglierò il formato JSON e reindirizzare l'output a jq
utility per una bella stampa.
Per seguire, assicurati di avere jq
installato perché questo tutorial usa questo formato di output per cercare rapidamente proprietà specifiche dall'output e segnalare yes
o no
su ciascuno:
$ 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"
}
}
Esame delle proprietà di sicurezza
Maggiori informazioni sulla sicurezza
- La guida alla codifica difensiva
- Webinar:automazione della sicurezza del sistema e della conformità con un sistema operativo standard
- 10 livelli di sicurezza dei container Linux
- Libro da colorare SELinux
- Altri articoli sulla sicurezza
Il file binario sopra include diverse proprietà di sicurezza. Confronterò quel binario con ls
binario sopra per esaminare cosa è abilitato e spiegare come checksec ha trovato queste informazioni.
1. Simboli
Inizierò prima con quello facile. Durante la compilazione, alcuni simboli sono inclusi nel binario, principalmente per il debug. Questi simboli sono richiesti durante lo sviluppo di software e richiedono più cicli per il debug e la correzione delle cose.
Questi simboli vengono solitamente rimossi (rimossi) dal binario finale prima che venga rilasciato per uso generale. Ciò non influisce in alcun modo sull'esecuzione del binario; funzionerà proprio come farebbe con i simboli. Lo stripping viene spesso eseguito per risparmiare spazio, poiché il binario è un po' più leggero una volta che i simboli sono stati rimossi. Nel software closed-source o proprietario, i simboli vengono spesso rimossi perché avere questi simboli in un binario rende in qualche modo facile dedurre il funzionamento interno del software.
Secondo checksec, i simboli sono presenti in questo binario, ma non erano in ls
binario. Puoi anche trovare queste informazioni eseguendo il file
comando sul programma:vedi not stripped
nell'output verso la fine:
$ 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
Come ha fatto checksec a trovare queste informazioni? Bene, fornisce un pratico --debug
opzione per mostrare quali funzioni sono state eseguite. Pertanto, l'esecuzione del seguente comando dovrebbe mostrarti quali funzioni sono state eseguite all'interno dello script della shell:
$ checksec --debug --file=./hello
In questo tutorial, sto cercando i comandi sottostanti utilizzati per trovare queste informazioni. Poiché è uno script di shell, puoi sempre utilizzare le funzionalità di Bash. Questo comando produrrà tutti i comandi eseguiti dall'interno dello script della shell:
$ bash -x /usr/bin/checksec --file=./hello
Se scorri l'output, dovresti vedere un echo_message
seguito dalla categoria della proprietà di sicurezza. Ecco cosa riporta checksec sul fatto che il file binario contenga simboli:
+ readelf -W --symbols ./hello
+ grep -q '\.symtab'
+ echo_message '\033[31m96) Symbols\t\033[m ' Symbols, ' symbols="yes"' '"symbols":"yes",'
Per semplificare, checksec utilizza il readelf
utility per leggere il binario e fornisce uno speciale --symbols
flag che elenca tutti i simboli all'interno del binario. Quindi cerca un valore speciale, .symtab
, che fornisce un conteggio delle voci (simboli) che trova. Puoi provare i seguenti comandi sul binario di prova che hai compilato sopra:
$ readelf -W --symbols ./hello
$ readelf -W --symbols ./hello | grep -i symtab
Come rimuovere i simboli
Puoi rimuovere i simboli dopo la compilazione o durante la compilazione.
- Post compilazione: Dopo la compilazione, puoi utilizzare la
strip
utility sul binario per rimuovere i simboli. Conferma che ha funzionato usando ilfile
comando, che ora mostra l'output comestripped
:$ 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, not stripped
$
$ 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, stripped
$
Come rimuovere i simboli durante la compilazione
Invece di rimuovere manualmente i simboli dopo la compilazione, puoi chiedere al compilatore di farlo per te fornendo il -s
argomento:
$ 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
$
Dopo aver eseguito nuovamente checksec, puoi vedere quei symbols
sono mostrati come no
:
$ checksec --file=./hello --output=json | jq | grep symbols
"symbols": "no",
$
2. Canarie
I canarini sono valori noti che vengono inseriti tra un buffer e i dati di controllo sullo stack per monitorare gli overflow del buffer. Quando un'applicazione viene eseguita, le vengono assegnati due tipi di memoria. Uno di questi è uno stack , che è semplicemente una struttura dati con due operazioni:push
, che mette i dati nello stack e pop
, che rimuove i dati dallo stack in ordine inverso. L'input dannoso potrebbe sovraccaricare o danneggiare lo stack con input appositamente predisposti e causare l'arresto anomalo del programma:
$ checksec --file=/bin/ls --output=json | jq | grep canary
"canary": "yes",
$
$ checksec --file=./hello --output=json | jq | grep canary
"canary": "no",
$
In che modo checksec scopre se il binario è abilitato con un canary? Usando il metodo sopra, puoi restringere il campo eseguendo il seguente comando all'interno dello script della shell:
$ readelf -W -s ./hello | grep -E '__stack_chk_fail|__intel_security_cookie'
Abilita canary
Per proteggersi da questi casi, il compilatore fornisce il -stack-protector-all
flag, che aggiunge codice extra al file binario per verificare la presenza di tali overflow del buffer:
$ gcc -fstack-protector-all hello.c -o hello
$ checksec --file=./hello --output=json | jq | grep canary
"canary": "yes",
Checksec mostra che la proprietà è ora abilitata. Puoi anche verificarlo con:
$ 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. Torta
PIE sta per eseguibile indipendente dalla posizione. Come suggerisce il nome, è il codice che viene messo da qualche parte in memoria per l'esecuzione indipendentemente dal suo indirizzo assoluto:
$ checksec --file=/bin/ls --output=json | jq | grep pie
"pie": "yes",
$ checksec --file=./hello --output=json | jq | grep pie
"pie": "no",
Spesso, PIE è abilitato solo per le librerie e non per i programmi a riga di comando autonomi. Nell'output di seguito, hello
viene mostrato come LSB executable
, mentre la libc
libreria standard (.so
) il file è contrassegnato come 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 cerca di trovare queste informazioni con:
$ readelf -W -h ./hello | grep EXEC
Type: EXEC (Executable file)
Se provi lo stesso comando su una libreria condivisa invece di EXEC
, vedrai un DYN
:
$ readelf -W -h /lib64/libc-2.32.so | grep DYN
Type: DYN (Shared object file)
Abilita PIE
Per abilitare PIE su un programma di test, inviare i seguenti argomenti al compilatore:
$ gcc -pie -fpie hello.c -o hello
Puoi verificare che PIE sia abilitato usando checksec:
$ checksec --file=./hello --output=json | jq | grep pie
"pie": "yes",
$
Dovrebbe essere visualizzato come eseguibile PIE con il tipo modificato da EXEC
a 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 sta per "non eseguibile". È spesso abilitato a livello di CPU, quindi un sistema operativo con NX abilitato può contrassegnare determinate aree di memoria come non eseguibili. Spesso, gli exploit di buffer overflow mettono il codice nello stack e quindi tentano di eseguirlo. Tuttavia, rendere questa area scrivibile non eseguibile può prevenire tali attacchi. Questa proprietà è abilitata per impostazione predefinita durante la normale compilazione utilizzando gcc
:
$ checksec --file=/bin/ls --output=json | jq | grep nx
"nx": "yes",
$ checksec --file=./hello --output=json | jq | grep nx
"nx": "yes",
Checksec determina queste informazioni con il comando seguente. RW
verso la fine significa che lo stack è leggibile e scrivibile; poiché non esiste la E
, non è eseguibile:
$ readelf -W -l ./hello | grep GNU_STACK
GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW 0x10
Disabilita NX per scopi demo
Non è consigliato, ma puoi disabilitare NX
durante la compilazione di un programma utilizzando -z execstack
argomento:
$ gcc -z execstack hello.c -o hello
$ checksec --file=./hello --output=json | jq | grep nx
"nx": "no",
Dopo la compilazione, lo stack diventa eseguibile (RWE
), che consente l'esecuzione di codice dannoso:
$ readelf -W -l ./hello | grep GNU_STACK
GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RWE 0x10
5. RELRO
RELRO sta per Trasferimento di sola lettura. Un binario ELF (Executable Linkable Format) utilizza una Global Offset Table (GOT) per risolvere le funzioni in modo dinamico. Se abilitata, questa proprietà di sicurezza rende il GOT all'interno del binario di sola lettura, il che impedisce una qualche forma di attacchi di riposizionamento:
$ checksec --file=/bin/ls --output=json | jq | grep relro
"relro": "full",
$ checksec --file=./hello --output=json | jq | grep relro
"relro": "partial",
Checksec trova queste informazioni utilizzando il comando seguente. Qui è abilitata una delle proprietà RELRO; pertanto, il binario mostra "parziale" durante la verifica tramite checksec:
$ readelf -W -l ./hello | grep GNU_RELRO
GNU_RELRO 0x002e10 0x0000000000403e10 0x0000000000403e10 0x0001f0 0x0001f0 R 0x1
$ readelf -W -d ./hello | grep BIND_NOW
Abilita RELRO completo
Per abilitare RELRO completo, usa i seguenti argomenti della riga di comando durante la compilazione con gcc
:
$ gcc -Wl,-z,relro,-z,now hello.c -o hello
$ checksec --file=./hello --output=json | jq | grep relro
"relro": "full",
Ora, anche la seconda proprietà è abilitata, rendendo il programma completo 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)
6. Fortifica
Fortify è un'altra proprietà di sicurezza, ma non rientra nell'ambito di questo articolo. Lascerò l'apprendimento come checksec verifica fortificare nei binari e come è abilitato con gcc
come esercizio da affrontare.
$ 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"
Altre funzionalità di checksec
Il tema della sicurezza è infinito e, sebbene non sia possibile coprire tutto qui, voglio menzionare alcune altre funzionalità di checksec
comando con cui è un piacere lavorare.
Esegui su più binari
Non è necessario fornire ogni binario a checksec individualmente. Invece, puoi fornire un percorso di directory in cui risiedono più binari e checksec li verificherà tutti per te in una volta sola:
$ checksec --dir=/usr/bin
Processi
Oltre ai binari, checksec funziona anche sui programmi durante l'esecuzione. Il comando seguente trova le proprietà di sicurezza di tutti i programmi in esecuzione sul sistema. Puoi usare --proc-all
se vuoi che controlli tutti i processi in esecuzione, oppure puoi selezionare un processo specifico usando il suo nome:
$ checksec --proc-all
$ checksec --proc=bash
Proprietà del kernel
Oltre alle applicazioni userland di checksec descritte in questo articolo, puoi anche usarlo per controllare le proprietà del kernel integrate nel tuo sistema:
$ checksec --kernel
Fai una prova
Checksec è un buon modo per capire quali proprietà userland e kernel sono abilitate. Esamina in dettaglio ogni proprietà di sicurezza e cerca di capire i motivi per abilitare ciascuna funzionalità e i tipi di attacchi che previene.