Part 5 — HardeningMeter
Link to HardeningMeter.
HardeningMeter is a Python-based open-source tool developed as a pivotal part of my extensive research into security mechanisms within ELF files and the Linux kernel. While many turn to the widely used checksec tool for such assessments, my experience revealed its limitations — lack of accuracy, insufficient documentation, and an absence of coverage for specific ELF types and linking processes.
As I delved deeper into the intricacies of security mechanisms in ELF files, binary file representations, and the nuances of different ELF types and linking processes, I identified a critical gap in checksec’s functionality. It failed to flag security mechanisms that were irrelevant for certain ELF types and linking processes. Additionally, it lacked coverage for some compiler and kernel-level security mechanisms I deemed crucial, with a lack of updates for recent releases.
Motivated to address these gaps, I created HardeningMeter. This tool not only rectifies the shortcomings of existing tools but also ensures broader coverage, accuracy, and documentation clarity. Written in Python, with future plans for cross-platform support, HardeningMeter is designed to cater to the cybersecurity community’s diverse needs.
This comprehensive documentation serves as your guide to understanding the implementation of each security mechanism covered by HardeningMeter. Your comments and requests are invaluable, and I’m committed to incorporating them to make HardeningMeter an indispensable asset for the cybersecurity community. Feel free to reach out with your feedback, and let’s collectively shape the tool the community truly needs.
Exmaple of HardeningMeter’s output:
Binaries
States
HardeningMeter’s output is consisted of 3 different states:
(X) — this state indicates that the binary hardening mechanism is disabled.
(V) — this state indicates that the binary hardening mechanism is enabled.
(-) — this state indicates that the binary hardening mechanism is not relevant in this particular case. Usually depends on the type of the ELF file.
Check ELFs
HardeningMeter can retrieve a directory or a list of files to be scanned. It loops over the files and directory recursively, looking only for files of type ELF with the file command.
It checks if the f’{file}: ELF’ is included in the output of the file command.
Readelf
HardeningMeter uses the readelf command to check the binary hardening mechanisms. Since the readelf command has a limitation that only outputs lines of less than 80 characters, we add the -W flag to each execution of the readelf command, which allows an output width greater than 80 characters.
File Type
The creation of the file type field consists of 3 steps.
First, HardeningMeter distinguishes between the different ELF binary files. ELFs have different file types that identify their behavior, including: Relocatables, Executables, Dynamic Shared Objects, Dynamic Position Independent Executables. In addition, binary files can be linked statically or dynamically. HardeningMeter first checks the file type in the program header section:
readelf -h <ELF file> | grep -i Type
There is a dictionary containing all ELF types in order to check if the type of the readelf file contains one of the following dictionary keys:
ELF_TYPES = {‘DYN (Position-Independent Executable file)’: ‘PIE’, ‘REL (Relocatable file)’: ‘REL’, ‘DYN (Shared object file)’: ‘SO’, ‘EXEC (Executable file)’: ‘Exec’}
If this is the case, the appropriate value is specified as the file type.
HardeningMeter also checks if the file is a Go binary (Golang language). Go binaries are usually different because their compilation process is different, and we have found that they usually lack binary hardening mechanisms. We believe that the reason for this is that people lack knowledge when it comes to compiling Go binaries.
Then it separates relocatable ELFs from the others because relocatable files do not go through the final linking process like other types of ELF binaries.
For other files, it adds a check of the linking status — statically or dynamically using the file command. If the file contains a ‘statically linked’ string, the file type starts with ‘Static’, otherwise it starts with ‘Dynamic’.
PIE/PIC
HardeningMeter checks if the file is compiled with Position Independent binary hardening mechanism. We exclude relocatable files and statically linked files since they can not be position independent.
Then if the file type field is DYN (Position-Independent Executable file) or DYN (Shared object file) then the code was compiled with PIE/PIC.
RELRO
In order to know whether the code was compiled with RELRO or not, HardeningMeter searches for the GNU_RELRO string in the program header.
readelf -l <ELF file> | grep -i GNU_RELRO
If the string exists then the ELF file was compiled with RELRO enabled.
Not Exec Stack
In order to know if the file was compiled with execute code from the stack HardeningMeter checks the GNU_STACK permissions in the program headers:
readelf -l <ELF file>
If the permissions are RWE then the stack is executable, if it only has RW then the stack is not executable (not stack exec).
BIND NOW
In order to know whether the code was compiled with BIND NOW or not, HardeningMeter checks if the(FLAGS)and BIND_NOW strings are in the dynamic section.
readelf -d <ELF file>
If the strings exist then the ELF file was compiled with BIND NOW enabled.
Stack Protector
HardeningMeter checks if the file was compiled with stack protector by searching for the ‘__stack_chk_fail’ function which is called to perform the program’s error-handling mechanism when buffer overflow is detected, in the dynamic symbol table:
readelf -s –dyn-syms <ELF file> | grep -i __stack_chk_fail
It’s important to note that the compiler’s decision to add stack canaries can vary based on optimization levels, compiler settings, and specific code patterns. Therefore, the presence of __stack_chk_fail in the compiled code is not guaranteed to be present in every code compiled with stack canaries.
Fortify
When compiling a code using Fortify Source the compiler uses a protected version of functions that adds checks to potentially targeted functions.
In order to identify the targeted functions bucket, HardeningMeter searches for the symbols of the libc.so.6 file. It tries to find the location of the libc.so.6 file via the ‘ldd’ command which displays a list of shared libraries that the specified file depends on.
If it finds the libc.so.6 file in the list, it uses the path displayed in the output.
However, in cases like relocatable files and statically linked files that are not linked, it can not find the libc.so.6 via the ‘ldd’ function, so it searches in strategic locations in the filesystem, using the following command:
find /lib /usr/lib /lib64 /usr/lib64 -name “libc.so.6"
After finding the libc.so.6, it searches for the potentially targeted functions in the dynamic symbol table:
readelf -s — dyn-syms <ELF file> | grep -i _chk
Of course, HardeningMeter does not include the ‘__stack_chk_fail’ function because this function performs the program’s error-handling mechanism when buffer overflow is detected.
After building the list of the functions that have the _chk string, it creates another list of the same functions but without the _chk string in order to identify the functions that could be fortified but did not.
It then moves to find the functions in the binary file, using the dynamic symbol table HardeningMeter identifies functions that are potentially targeted functions or already fortified.
readelf -s — dyn-syms <ELF file>
Lastly, the final output is built by counting the fortified functions out of the potentially targeted functions and accordingly mark if fortify is enabled or not if at least one of the potentially targeted functions is fortified.
Control Flow Enforcement (CET)
In order to check if the ELF is compiled with control flow enforcement, HardeningMeter uses the notes of the ELF.
Shadow Stack
It checks if there is. “SHSTK” string in the notes:
readelf -n <ELF file>
If the strings exist then the ELF file was compiled with Shadow Stack enabled.
Indirect Branch Tracking (IBT)
It checks if there is. “IBT” string in the notes:
readelf -n <ELF file>
If the strings exist then the ELF file was compiled with IBT enabled.
ASAN
ASAN is an external check that is performed only when the user enables external checks.
When an ELF file is compiled with ASAN, there is a library that contains the asan string and other functions starting with __asan string in the dynamic symbol table. In order to identify if ASAN is enabled, HardeningMeter does not search for the asan string in the dynamic section since this section is missing in both relocatable files and statically linked files, it searches for a function that starts with __asan string in the dynamic symbol table.
readelf -s — dyn-syms <ELF file>
If it finds at least one function, then it determines that the file was compiled with ASAN enabled.
System
ASLR
HardeningMeter checks the ASLR status according to the following file:
/proc/sys/kernel/randomize_va_space
It determines ASLR status according to the following:
If the file contains 0 then the ASLR status is Disabled.
If the file contains 1 then the ASLR status is Partial Enabled.
If the file contains any other number, then the ASLR status is Full Enabled.
NX bit
HardeningMeter checks the NX bit status by reading the dmesg.
It determines the NX bit state according to the value presented after the following line:
NX (Execute Disable) protection
SMEP and SMAP
HardeningMeter checks if SMEP and SMAP are enabled by reading the “/proc/cpuinfo”.
If it finds the “smep” string in the file, it determines that Linux has SMEP enabled.
If it finds the “smap” string in the file, it determines that Linux has SMAP enabled.
KASLR
HardeningMeter checks if KASLR is enabled by reading the “/boot/config-$(uname -r)”.
BASE
If it finds that there is a line that starts with the following string “CONFIG_RANDOMIZE_BASE=y”, it determines that Linux has KASLR BASE enabled.
Memory
If it finds that there is a line that starts with the following string “CONFIG_RANDOMIZE_MEMORY=y”, it determines that Linux has KASLR MEMORY enabled.
KSTACK
If it finds that there is a line that starts with the following string “CONFIG_RANDOMIZE_KSTACK_OFFSET=y”, it determines that Linux has KASLR KSTACK enabled.
KSTACK Default
If it finds that there is a line that starts with the following string “CONFIG_RANDOMIZE_KSTACK_OFFSET_DEFAULT=y”, it determines that Linux has KASLR KSTACK Default enabled.
IBT
HardeningMeter checks if IBT is enabled by reading the “/boot/config-$(uname -r)”.
If it finds that there is a line that starts with the following string “CONFIG_X86_KERNEL_IBT=y”, it determines that Linux has IBT enabled.
PTI
HardeningMeter checks if PTI is enabled by reading the “/proc/cpuinfo”.
If it finds the “pti” string in the file, it determines that Linux has PTI enabled.
HardeningMeter VS. Checksec
HardeningMeter distinguishes itself from the checksec tool in several important aspects:
Language and Integration: While checksec is a bash tool, HardeningMeter is developed in Python. This choice offers numerous benefits, with one of the main advantages being easy integration into various environments, ease of extensibility and code readability.
Cross-Platform Support: Unlike checksec, which is tailored exclusively for Linux systems, HardeningMeter is purposefully designed to support multiple operating systems. It comes with built-in structures to accommodate various platforms beyond Linux.
Accurate Differentiation of Binary Files: Checksec lacks the capability to distinguish between different types of binaries, including statically linked binaries, relocatable binaries, and shared objects, which can lead to misleading results. In contrast, HardeningMeter’s comprehensive design ensures accurate and reliable results, providing users with precise information about the security hardening of each binary type
Security Mechanisms Coverage: HardeningMeter goes beyond checksec by covering security mechanisms that checksec does not. It includes checks for recent security mechanisms at the compiler level and some other security mechanisms at the kernel level.
Documentation and Transparent Research: In contrast to checksec, HardeningMeter is extensively documented, offering clear insights into its research and development. Users can access transparent information about the tool’s methodology and underlying research, promoting a deeper understanding of binary hardening principles.
Personalized Recommendations: One of the significant features HardeningMeter offers is the ability for users to set the output to receive tailored recommendations on which binary files require heightened attention and monitoring. This unique aspect allows users to focus their security efforts effectively, addressing potential security issues with precision.
By combining Python’s versatility, cross-platform support, accurate analysis of binary files, and transparent research, HardeningMeter emerges as a powerful and user-friendly tool, suitable for a wide range of security applications and environments. Its commitment to documentation ensures that users have the necessary resources to make informed decisions and take proactive steps towards securing their systems effectively.