Image: Internet Archive Book Images. Modified by Opensource.com. CC BY-SA 4.0
After compiling the same source code, we may end up with different binaries. It depends on which flags we pass into the hands of the compiler. Some of these flags allow you to enable or disable a number of security-related properties of the binary.
Some of them are enabled or disabled by the compiler by default. This is how vulnerabilities can arise in binary files that we are not aware of.
Checksec is a simple utility for determining which properties were included at compile time. In this article I will tell you:
- how to use the checksec utility to find vulnerabilities;
- how to use the gcc compiler to fix the vulnerabilities found.
Installing checksec
For Fedora OS and other RPM-based systems:
$ sudo dnf install checksec
For Debian based systems use apt.
Quick start with checksec
The checksec utility consists of a single script file, which, however, is quite large. Thanks to this transparency, you can find out which system commands to search for vulnerabilities in binaries are executed under the hood:
$ 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
Let's run checksec on the directory browsing utility (ls):
$ checksec --file=/usr/bin/ls <strong>RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE</strong> Full RELRO Canary found NX enabled PIE enabled No RPATH No RUNPATH No Symbols Yes 5 17 /usr/bin/ls
By executing the command in the terminal, you will receive a report on what useful properties this binary has and what it does not.
The first line is the head of the table, which lists the various security properties - RELRO, STACK CANARY, NX, and so on. The second line shows the values of these properties for the ls utility binary.
Hello binary!
I will compile a binary from the simplest C code:
#include <stdio.h>
int main()
{
printf(«Hello World\n»);
return 0;
}
Please note that so far I have not passed a single flag to the compiler, with the exception of -o (it is beside the point, it just tells where to output the compilation result):
$ 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
Now I will run the checksec utility for my binary. Some properties are different from properties
ls ( ): $ checksec --file=./hello <strong>RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE</strong> Partial RELRO No canary found NX enabled No PIE No RPATH No RUNPATH 85) Symbols No 0 0./hello
Checksec allows you to use a variety of output formats, which you can specify with the --output option. I'll choose the JSON format and make the output more descriptive with the jq utility :
$ 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» } }
Analysis (checksec) and elimination (gcc) of vulnerabilities
The binary file created above has several properties that determine, let's say, the degree of its vulnerability. I will compare the properties of this file with the properties of the ls binary (also listed above) and explain how to do this using the checksec utility.
For each item, I will additionally show you how to eliminate the vulnerabilities found.
1. Debug symbols
I'll start simple. Certain symbols are included in the binary at compile time. These symbols are used in software development: they are needed for debugging and bug fixing.
Debug symbols are usually removed from the version of the binary that the developers release for general use. This does not affect the operation of the program in any way. This cleanup (denoted by the word strip ) is often done to save space, as the file becomes lighter after the characters are removed. And in proprietary software, these characters are often removed also because attackers have the ability to read them in binary format and use them for their own purposes.
Checksec shows that debug symbols are present in my binary, but they are not in ls.
$ checksec --file=/bin/ls --output=json | jq | grep symbols «symbols»: «no», $ checksec --file=./hello --output=json | jq | grep symbols «symbols»: «yes»,
Running the file command can show the same thing. Characters are not stripped.
$ 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, <strong>not stripped</strong>
How checksec works
Let's run this command with the --debug option:
$ checksec --debug --file=./hello
Since the checksec utility is one long script, you can use Bash functions to examine it. Let's display the commands that the script runs for my hello file:
$ bash -x /usr/bin/checksec --file=./hello
Pay special attention to echo_message - the output of a message about whether the binary contains debug symbols:
+ readelf -W --symbols ./hello
+ grep -q '\.symtab'
+ echo_message '\033[31m96) Symbols\t\033[m ' Symbols, ' symbols=«yes»' '«symbols»:«yes»,'
The checksec utility uses the readelf command with the special flag --symbols to read a binary file. It prints out all the debug symbols in the binary.
$ readelf -W --symbols ./hello
From the contents of the .symtab section, you can find out the number of symbols found:
$ readelf -W --symbols ./hello | grep -i symtab
How to remove debug symbols after compilation
The strip utility will help us with this.
$ 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, <strong>not stripped</strong>
$
$ 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, <strong>stripped</strong>
How to remove debug symbols at compile time
When compiling, use the -s flag:
$ 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, <strong>stripped</strong>
You can also verify that the symbols have been removed using the checksec utility:
$ checksec --file=./hello --output=json | jq | grep symbols «symbols»: «no»,
2. Canary
Canary (informants) are "secret" values that are stored on the stack between the buffer and control data. They are used to protect against buffer overflow attacks: if these values are changed, then it is worth sounding the alarm. When an application is launched, its own stack is created for it. In this case, it's just a data structure with push and pop operations. An attacker could prepare malicious data and write it onto the stack. In this case, the buffer may overflow and the stack may be damaged. In the future, this will lead to a crash of the program. Analysis of the canary values allows you to quickly understand that a hack has occurred and take action.
$ checksec --file=/bin/ls --output=json | jq | grep canary
«canary»: «yes»,
$
$ checksec --file=./hello --output=json | jq | grep canary
«canary»: «no»,
$
, canary, checksec :
$ readelf -W -s ./hello | grep -E '__stack_chk_fail|__intel_security_cookie'
Turn on canary
To do this, when compiling, we use the -stack-protector-all flag:
$ gcc -fstack-protector-all hello.c -o hello $ checksec --file=./hello --output=json | jq | grep canary «canary»: «yes»,
Now checksec can tell us with a clear conscience that the canary mechanism is on:
$ 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. PIE
The enabled PIE property allows executable code to be arbitrarily placed in memory regardless of its absolute address:
PIE (Position Independent Executable) - positionally independent executable code. The ability to predict where and what areas of memory are in the address space of a process plays into the hands of attackers. User programs are loaded and executed from a predefined process virtual memory address unless compiled with the PIE option. Using PIE allows the operating system to load sections of executable code into arbitrary chunks of memory, making it much more difficult to crack.
$ checksec --file=/bin/ls --output=json | jq | grep pie «pie»: «yes», $ checksec --file=./hello --output=json | jq | grep pie «pie»: «no»,
Often the PIE property is only included when compiling libraries. In the output below, hello is marked as LSB executable and the standard library libc (.so) file is marked as LSB shared object:
$ file hello
hello: ELF 64-bit <strong>LSB executable</strong>, 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 <strong>LSB shared object</strong>, 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 obtains this information as follows:
$ readelf -W -h ./hello | grep EXEC Type: EXEC (Executable file)
If you run the same command for the library, you will see DYN instead of EXEC:
$ readelf -W -h /lib64/libc-2.32.so | grep DYN Type: DYN (Shared object file)
Turn on PIE
When compiling the program, you need to specify the following flags:
$ gcc -pie -fpie hello.c -o hello
To make sure that the PIE property is enabled, run the following command:
$ checksec --file=./hello --output=json | jq | grep pie «pie»: «yes», $
Now our binary file (hello) will change its type from EXEC to 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
Operating system and processor tools allow you to flexibly configure access rights to virtual memory pages. By enabling the NX (No Execute) property, we can prevent data from being interpreted as processor instructions. Often, in buffer overflow attacks, attackers push code onto the stack and then try to execute it. However, by preventing the execution of code in these memory segments, such attacks can be prevented. In normal compilation using gcc, this property is enabled by default:
$ checksec --file=/bin/ls --output=json | jq | grep nx «nx»: «yes», $ checksec --file=./hello --output=json | jq | grep nx «nx»: «yes»,
Checksec again uses the readelf command to get information about the NX property. In this case, RW means the stack is read / write. But since this combination does not contain the E character, there is a prohibition on executing code from this stack:
$ readelf -W -l ./hello | grep GNU_STACK GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW 0x10
Disable NX
It is not recommended to disable the NX property, but you can do it like this:
$ gcc -z execstack hello.c -o hello $ checksec --file=./hello --output=json | jq | grep nx «nx»: «no»,
After compilation, we will see that the stack permissions have changed to RWE:
$ readelf -W -l ./hello | grep GNU_STACK GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RWE 0x10
5. RELRO
In dynamically linked binaries, a special GOT (Global Offset Table) is used to call functions from libraries. This table is referenced by ELF (Executable Linkable Format) binaries. When RELRO (Relocation Read-Only) protection is enabled, the GOT becomes read-only. This allows you to protect against some types of attacks that modify table records:
$ checksec --file=/bin/ls --output=json | jq | grep relro «relro»: «full», $ checksec --file=./hello --output=json | jq | grep relro «relro»: «partial»,
In this case, only one of the RELRO properties is enabled, so checksec outputs the value "partial". Checksec uses the readelf command to display the settings.
$ readelf -W -l ./hello | grep GNU_RELRO GNU_RELRO 0x002e10 0x0000000000403e10 0x0000000000403e10 0x0001f0 0x0001f0 R 0x1 $ readelf -W -d ./hello | grep BIND_NOW
Turn on full protection (FULL RELRO)
To do this, when compiling, you need to use the appropriate flags:
$ gcc -Wl,-z,relro,-z,now hello.c -o hello $ checksec --file=./hello --output=json | jq | grep relro «relro»: «full»,
That's it, now our binary has received the honorary title of FULL 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)
Other checksec features
The topic of security can be studied endlessly. Even talking about the simple checksec utility in this article, I cannot cover everything. However, I will mention a few more interesting possibilities.
Checking multiple files
There is no need to run a separate command for each file. You can run one command for several binaries at once:
$ checksec --dir=/usr/bin
Checking processes
The checksec utility also allows you to analyze process security. The following command displays the properties of all running programs on your system (you need to use the --proc-all option to do this):
$ checksec --proc-all
You can also select one process to check by specifying its name:
$ checksec --proc=bash
Kernel check
Similarly, you can analyze vulnerabilities in the kernel of your system.
$ checksec --kernel
Forewarned is forearmed
Study the security properties in detail and try to understand what exactly each of them affects and what types of attacks it can prevent. Checksec to help you!
Cloud servers from Macleod are fast and secure.
Register using the link above or by clicking on the banner and get a 10% discount for the first month of renting a server of any configuration!