Skip to content

Commit

Permalink
Merge pull request #224 from petervas/hardening_checks
Browse files Browse the repository at this point in the history
Test suite for all supported checksec file and process hardening checks
  • Loading branch information
slimm609 authored Jul 22, 2023
2 parents 90e1d28 + d9fe36e commit fea00ef
Show file tree
Hide file tree
Showing 13 changed files with 549 additions and 59 deletions.
2 changes: 1 addition & 1 deletion Dockerfile.photon
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ RUN tdnf upgrade -y && tdnf remove toybox -y && \
make autoconf automake gcc ncurses-devel sed tar texinfo procps-ng grep \
findutils gzip file which libxml2 python3 python3-pip jq && \
pip3 install --upgrade pip && pip3 install setuptools && \
pip3 install demjson
pip3 install demjson3

COPY . /root
WORKDIR /root
1 change: 1 addition & 0 deletions Dockerfile.ubuntu
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ RUN apt-get update && apt-get -y -q upgrade && DEBIAN_FRONTEND=noninteractive ap
bc bison flex build-essential ccache git \
libncurses-dev libssl-dev u-boot-tools wget \
xz-utils vim xfce4 libxml2-utils python-demjson jq \
gcc-multilib clang \
&& apt-get clean

COPY . /root
Expand Down
51 changes: 22 additions & 29 deletions checksec
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ command_exists() {
type "${1}" > /dev/null 2>&1
}

for command in cat awk sed sysctl objdump uname mktemp openssl grep stat file find sort head ps readlink basename id which xargs ldd; do
for command in cat awk sed sysctl objdump uname mktemp openssl grep stat file find sort head ps readlink basename id which xargs ldd tr; do
if ! (command_exists ${command}); then
echo >&2 -e "\e[31mWARNING: '${command}' not found! It's required for most checks.\e[0m"
commandsmissing=true
Expand Down Expand Up @@ -501,19 +501,18 @@ chk_proc() {
exit 1
fi
cd /proc || exit
if (isString "${CHK_PROC}"); then
IFS=" " read -r -a fpids <<< "$(pgrep -d ' ' "${CHK_PROC}")"
elif (isNumeric "${CHK_PROC}"); then
fpids=("${CHK_PROC}")
else
printf "\033[31mError: Please provide a valid process name or pid.\033[m\n\n"
exit 1
fi

# assume a process name was given
IFS=" " read -r -a fpids <<< "$(pgrep -d ' ' "${CHK_PROC}")"
# if nothing was found check if it is a PID
if [[ ${#fpids} -eq 0 ]]; then
printf "\033[31mError: No process with the given name or pid found.\033[m\n\n"
exit 1
if (isNumeric "${CHK_PROC}") && [[ -n "$(ps -p "${CHK_PROC}" -o pid=)" ]]; then
fpids=("${CHK_PROC}")
else
printf "\033[31mError: Please provide a valid process name or pid.\033[m\n\n"
exit 1
fi
fi

echo_message "* System-wide ASLR" '' '' ''
aslrcheck
echo_message "* Does the CPU support NX: " '' '' ''
Expand Down Expand Up @@ -699,8 +698,8 @@ debug_report() {
# check file(s)
filecheck() {
# check for RELRO support
if [[ $(${readelf} -l "${1}") =~ "no program headers" ]]; then
echo_message '\033[31mN/A \033[m ' 'N/A,' '<file relro="n/a"' " \"${1}\": { \"relro\":\"n/a\","
if [[ $(${readelf} -l "${1}" 2> /dev/null) =~ "no program headers" ]]; then
echo_message '\033[32mN/A \033[m ' 'N/A,' '<file relro="n/a"' " \"${1}\": { \"relro\":\"n/a\","
elif ${readelf} -l "${1}" 2> /dev/null | grep -q 'GNU_RELRO'; then
if ${readelf} -d "${1}" 2> /dev/null | grep -q 'BIND_NOW' || ! ${readelf} -l "${1}" 2> /dev/null | grep -q '\.got\.plt'; then
echo_message '\033[32mFull RELRO \033[m ' 'Full RELRO,' '<file relro="full"' " \"${1}\": { \"relro\":\"full\","
Expand All @@ -720,10 +719,10 @@ filecheck() {

# check for NX support
# shellcheck disable=SC2126
if [[ $(${readelf} -l "${1}") =~ "no program headers" ]]; then
echo_message '\033[31mN/A \033[m ' 'N/A,' ' nx="n/a"' '"nx":"n/a",'
if [[ $(${readelf} -l "${1}" 2> /dev/null) =~ "no program headers" ]]; then
echo_message '\033[32mN/A \033[m ' 'N/A,' ' nx="n/a"' '"nx":"n/a",'
elif ${readelf} -l "${1}" 2> /dev/null | grep -q 'GNU_STACK'; then
if [[ $(${s_readelf} -l "${1}" 2> /dev/null | grep -A 1 'GNU_STACK' | sed 'N;s/\n//g' | grep -Eo "0x[0-9a-f]{16}" | grep -v 0x0000000000000000 | wc -l) -gt 0 ]]; then
if [[ $(${readelf} -l "${1}" 2> /dev/null | grep 'GNU_STACK' | grep -oP '(?<=0x).*(?=RW )' | grep -o . | sort -u | tr -d '\n') != " 0x" ]]; then
echo_message '\033[31mNX disabled\033[m ' 'NX disabled,' ' nx="no"' '"nx":"no",'
else
echo_message '\033[32mNX enabled \033[m ' 'NX enabled,' ' nx="yes"' '"nx":"yes",'
Expand Down Expand Up @@ -778,7 +777,7 @@ filecheck() {
# check for rpath / run path
# search for a line that matches RPATH and extract the colon-separated path list within brackets
# example input: "0x000000000000000f (RPATH) Library rpath: [/lib/systemd:/lib/apparmor]"
if [[ $(${readelf} -d "${1}") =~ "no dynamic section" ]]; then
if [[ $(${readelf} -d "${1}" 2> /dev/null) =~ "no dynamic section" ]]; then
echo_message '\033[32mN/A \033[m ' 'N/A,' ' rpath="n/a"' '"rpath":"n/a",'
else
IFS=: read -r -a rpath_array <<< "$(${readelf} -d "${1}" 2> /dev/null | awk -F'[][]' '/RPATH/ {print $2}')"
Expand All @@ -794,7 +793,7 @@ filecheck() {
fi

# search for a line that matches RUNPATH and extract the colon-separated path list within brackets
if [[ $(${readelf} -d "${1}") =~ "no dynamic section" ]]; then
if [[ $(${readelf} -d "${1}" 2> /dev/null) =~ "no dynamic section" ]]; then
echo_message '\033[32mN/A \033[m ' 'N/A,' ' runpath="n/a"' '"runpath":"n/a",'
else
IFS=: read -r -a runpath_array <<< "$(${readelf} -d "${1}" 2> /dev/null | awk -F'[][]' '/RUNPATH/ {print $2}')"
Expand Down Expand Up @@ -949,11 +948,6 @@ isNumeric() {
echo "$@" | grep -q -v "[^0-9]"
}

# check if input is a string
isString() {
echo "$@" | grep -q -v "[^ A-Z_a-z]"
}

# help
help() {
echo "Usage: checksec [--format={cli,csv,xml,json}] [OPTION]"
Expand Down Expand Up @@ -1502,8 +1496,8 @@ proccheck() {
fi

# check for stack canary support
if ${readelf} -W -s "${1}/exe" 2> /dev/null | grep -q 'Symbol table'; then
if ${readelf} -W -s "${1}/exe" 2> /dev/null | grep " UND " | grep -Eq '__stack_chk_fail|__stack_chk_guard|__intel_security_cookie'; then
if ${readelf} -s "${1}/exe" 2> /dev/null | grep -q 'Symbol table'; then
if ${readelf} -s "${1}/exe" 2> /dev/null | grep " UND " | grep -Eq '__stack_chk_fail|__stack_chk_guard|__intel_security_cookie'; then
echo_message '\033[32mCanary found \033[m ' 'Canary found,' ' canary="yes"' '"canary":"yes",'
else
echo_message '\033[31mNo canary found \033[m ' 'No Canary found,' ' canary="no"' '"canary":"no",'
Expand Down Expand Up @@ -1570,10 +1564,10 @@ proccheck() {
echo_message '\033[31mPaX disabled\033[m ' 'Pax disabled,' ' pax="no"' '"pax":"no",'
fi
# fallback check for NX support
elif [[ $(${s_readelf} -l "${1}/exe" 2> /dev/null | grep -A 1 'GNU_STACK' | sed 'N;s/\n//g' | grep -Eo "0x[0-9a-f]{16}" | grep -v 0x0000000000000000 | wc -l) -gt 0 ]]; then
elif [[ $(${readelf} -l "${1}/exe" 2> /dev/null | grep 'GNU_STACK' | grep -oP '(?<=0x).*(?=RW )' | grep -o . | sort -u | tr -d '\n') != " 0x" ]]; then
echo_message '\033[31mNX disabled\033[m ' 'NX disabled,' ' nx="no"' '"nx":"no",'
else
echo_message '\033[32mNX enabled \033[m ' 'NX enabled,' ' pax="yes"' '"nx":"yes",'
echo_message '\033[32mNX enabled \033[m ' 'NX enabled,' ' nx="yes"' '"nx":"yes",'
fi

# check for PIE support
Expand Down Expand Up @@ -1651,7 +1645,6 @@ upgrade() {
rm -f "${TMP_FILE}" "${SIG_FILE}" "${PUBKEY_FILE}" > /dev/null 2>&1
exit 1
fi
exit 0
}

# Version compare
Expand Down
2 changes: 1 addition & 1 deletion src/core.sh
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ command_exists() {
type "${1}" > /dev/null 2>&1
}

for command in cat awk sed sysctl objdump uname mktemp openssl grep stat file find sort head ps readlink basename id which xargs ldd; do
for command in cat awk sed sysctl objdump uname mktemp openssl grep stat file find sort head ps readlink basename id which xargs ldd tr; do
if ! (command_exists ${command}); then
echo >&2 -e "\e[31mWARNING: '${command}' not found! It's required for most checks.\e[0m"
commandsmissing=true
Expand Down
21 changes: 10 additions & 11 deletions src/functions/chk_proc.sh
Original file line number Diff line number Diff line change
Expand Up @@ -59,19 +59,18 @@ chk_proc() {
exit 1
fi
cd /proc || exit
if (isString "${CHK_PROC}"); then
IFS=" " read -r -a fpids <<< "$(pgrep -d ' ' "${CHK_PROC}")"
elif (isNumeric "${CHK_PROC}"); then
fpids=("${CHK_PROC}")
else
printf "\033[31mError: Please provide a valid process name or pid.\033[m\n\n"
exit 1
fi

# assume a process name was given
IFS=" " read -r -a fpids <<< "$(pgrep -d ' ' "${CHK_PROC}")"
# if nothing was found check if it is a PID
if [[ ${#fpids} -eq 0 ]]; then
printf "\033[31mError: No process with the given name or pid found.\033[m\n\n"
exit 1
if (isNumeric "${CHK_PROC}") && [[ -n "$(ps -p "${CHK_PROC}" -o pid=)" ]]; then
fpids=("${CHK_PROC}")
else
printf "\033[31mError: Please provide a valid process name or pid.\033[m\n\n"
exit 1
fi
fi

echo_message "* System-wide ASLR" '' '' ''
aslrcheck
echo_message "* Does the CPU support NX: " '' '' ''
Expand Down
14 changes: 7 additions & 7 deletions src/functions/filecheck.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
# check file(s)
filecheck() {
# check for RELRO support
if [[ $(${readelf} -l "${1}") =~ "no program headers" ]]; then
echo_message '\033[31mN/A \033[m ' 'N/A,' '<file relro="n/a"' " \"${1}\": { \"relro\":\"n/a\","
if [[ $(${readelf} -l "${1}" 2> /dev/null) =~ "no program headers" ]]; then
echo_message '\033[32mN/A \033[m ' 'N/A,' '<file relro="n/a"' " \"${1}\": { \"relro\":\"n/a\","
elif ${readelf} -l "${1}" 2> /dev/null | grep -q 'GNU_RELRO'; then
if ${readelf} -d "${1}" 2> /dev/null | grep -q 'BIND_NOW' || ! ${readelf} -l "${1}" 2> /dev/null | grep -q '\.got\.plt'; then
echo_message '\033[32mFull RELRO \033[m ' 'Full RELRO,' '<file relro="full"' " \"${1}\": { \"relro\":\"full\","
Expand All @@ -26,10 +26,10 @@ filecheck() {

# check for NX support
# shellcheck disable=SC2126
if [[ $(${readelf} -l "${1}") =~ "no program headers" ]]; then
echo_message '\033[31mN/A \033[m ' 'N/A,' ' nx="n/a"' '"nx":"n/a",'
if [[ $(${readelf} -l "${1}" 2> /dev/null) =~ "no program headers" ]]; then
echo_message '\033[32mN/A \033[m ' 'N/A,' ' nx="n/a"' '"nx":"n/a",'
elif ${readelf} -l "${1}" 2> /dev/null | grep -q 'GNU_STACK'; then
if [[ $(${s_readelf} -l "${1}" 2> /dev/null | grep -A 1 'GNU_STACK' | sed 'N;s/\n//g' | grep -Eo "0x[0-9a-f]{16}" | grep -v 0x0000000000000000 | wc -l) -gt 0 ]]; then
if [[ $(${readelf} -l "${1}" 2> /dev/null | grep 'GNU_STACK' | grep -oP '(?<=0x).*(?=RW )' | grep -o . | sort -u | tr -d '\n') != " 0x" ]]; then
echo_message '\033[31mNX disabled\033[m ' 'NX disabled,' ' nx="no"' '"nx":"no",'
else
echo_message '\033[32mNX enabled \033[m ' 'NX enabled,' ' nx="yes"' '"nx":"yes",'
Expand Down Expand Up @@ -84,7 +84,7 @@ filecheck() {
# check for rpath / run path
# search for a line that matches RPATH and extract the colon-separated path list within brackets
# example input: "0x000000000000000f (RPATH) Library rpath: [/lib/systemd:/lib/apparmor]"
if [[ $(${readelf} -d "${1}") =~ "no dynamic section" ]]; then
if [[ $(${readelf} -d "${1}" 2> /dev/null) =~ "no dynamic section" ]]; then
echo_message '\033[32mN/A \033[m ' 'N/A,' ' rpath="n/a"' '"rpath":"n/a",'
else
IFS=: read -r -a rpath_array <<< "$(${readelf} -d "${1}" 2> /dev/null | awk -F'[][]' '/RPATH/ {print $2}')"
Expand All @@ -100,7 +100,7 @@ filecheck() {
fi

# search for a line that matches RUNPATH and extract the colon-separated path list within brackets
if [[ $(${readelf} -d "${1}") =~ "no dynamic section" ]]; then
if [[ $(${readelf} -d "${1}" 2> /dev/null) =~ "no dynamic section" ]]; then
echo_message '\033[32mN/A \033[m ' 'N/A,' ' runpath="n/a"' '"runpath":"n/a",'
else
IFS=: read -r -a runpath_array <<< "$(${readelf} -d "${1}" 2> /dev/null | awk -F'[][]' '/RUNPATH/ {print $2}')"
Expand Down
5 changes: 0 additions & 5 deletions src/functions/general_functions.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,3 @@ echo_message() {
isNumeric() {
echo "$@" | grep -q -v "[^0-9]"
}

# check if input is a string
isString() {
echo "$@" | grep -q -v "[^ A-Z_a-z]"
}
8 changes: 4 additions & 4 deletions src/functions/proccheck.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ proccheck() {
fi

# check for stack canary support
if ${readelf} -W -s "${1}/exe" 2> /dev/null | grep -q 'Symbol table'; then
if ${readelf} -W -s "${1}/exe" 2> /dev/null | grep " UND " | grep -Eq '__stack_chk_fail|__stack_chk_guard|__intel_security_cookie'; then
if ${readelf} -s "${1}/exe" 2> /dev/null | grep -q 'Symbol table'; then
if ${readelf} -s "${1}/exe" 2> /dev/null | grep " UND " | grep -Eq '__stack_chk_fail|__stack_chk_guard|__intel_security_cookie'; then
echo_message '\033[32mCanary found \033[m ' 'Canary found,' ' canary="yes"' '"canary":"yes",'
else
echo_message '\033[31mNo canary found \033[m ' 'No Canary found,' ' canary="no"' '"canary":"no",'
Expand Down Expand Up @@ -89,10 +89,10 @@ proccheck() {
echo_message '\033[31mPaX disabled\033[m ' 'Pax disabled,' ' pax="no"' '"pax":"no",'
fi
# fallback check for NX support
elif [[ $(${s_readelf} -l "${1}/exe" 2> /dev/null | grep -A 1 'GNU_STACK' | sed 'N;s/\n//g' | grep -Eo "0x[0-9a-f]{16}" | grep -v 0x0000000000000000 | wc -l) -gt 0 ]]; then
elif [[ $(${readelf} -l "${1}/exe" 2> /dev/null | grep 'GNU_STACK' | grep -oP '(?<=0x).*(?=RW )' | grep -o . | sort -u | tr -d '\n') != " 0x" ]]; then
echo_message '\033[31mNX disabled\033[m ' 'NX disabled,' ' nx="no"' '"nx":"no",'
else
echo_message '\033[32mNX enabled \033[m ' 'NX enabled,' ' pax="yes"' '"nx":"yes",'
echo_message '\033[32mNX enabled \033[m ' 'NX enabled,' ' nx="yes"' '"nx":"yes",'
fi

# check for PIE support
Expand Down
1 change: 0 additions & 1 deletion src/functions/upgrade.sh
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ upgrade() {
rm -f "${TMP_FILE}" "${SIG_FILE}" "${PUBKEY_FILE}" > /dev/null 2>&1
exit 1
fi
exit 0
}

# Version compare
Expand Down
45 changes: 45 additions & 0 deletions tests/binaries/build_binaries.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#!/bin/bash

# All hardening features on (except for CFI and SafeStack)
gcc -o all test.c -w -D_FORTIFY_SOURCE=2 -fstack-protector-strong -fpie -O2 -z relro -z now -z noexecstack -pie -s
# Partial RELRO
gcc -o partial test.c -w -D_FORTIFY_SOURCE=2 -fstack-protector-strong -fpie -O2 -z relro -z lazy -z noexecstack -s
# RPATH
gcc -o rpath test.c -w -D_FORTIFY_SOURCE=2 -fstack-protector-strong -fpie -O2 -z relro -z now -z noexecstack -pie -s -Wl,-rpath,./ -Wl,--disable-new-dtags
# RUNPATH
gcc -o runpath test.c -w -D_FORTIFY_SOURCE=2 -fstack-protector-strong -fpie -O2 -z relro -z now -z noexecstack -pie -s -Wl,-rpath,./ -Wl,--enable-new-dtags
# no hardening features
gcc -o none test.c -w -D_FORTIFY_SOURCE=0 -fno-stack-protector -no-pie -O2 -z norelro -z lazy -z execstack
# REL (PIE)
gcc -c test.c -o rel.o
# DSO (PIE)
gcc -shared -fPIC -o dso.so test.c -w -D_FORTIFY_SOURCE=2 -fstack-protector-strong -O2 -z relro -z now -z noexecstack -s
# CFI and SafeStack
clang -o cfi test.c -w -flto -fsanitize=cfi -fvisibility=default
clang -o sstack test.c -w -fsanitize=safe-stack
# clang instead of gcc
clang -o all_cl test.c -w -D_FORTIFY_SOURCE=2 -fstack-protector-strong -fpie -O2 -z relro -z now -z noexecstack -pie -s
clang -o partial_cl test.c -w -D_FORTIFY_SOURCE=2 -fstack-protector-strong -fpie -O2 -z relro -z lazy -z noexecstack -s
clang -o rpath_cl test.c -w -D_FORTIFY_SOURCE=2 -fstack-protector-strong -fpie -O2 -z relro -z now -z noexecstack -pie -s -Wl,-rpath,./ -Wl,--disable-new-dtags
clang -o runpath_cl test.c -w -D_FORTIFY_SOURCE=2 -fstack-protector-strong -fpie -O2 -z relro -z now -z noexecstack -pie -s -Wl,-rpath,./ -Wl,--enable-new-dtags
clang -o none_cl test.c -w -D_FORTIFY_SOURCE=0 -fno-stack-protector -no-pie -O2 -z norelro -z lazy -z execstack
clang -c test.c -o rel_cl.o
clang -shared -fPIC -o dso_cl.so test.c -w -D_FORTIFY_SOURCE=2 -fstack-protector-strong -O2 -z relro -z now -z noexecstack -s

# 32-bit (you might need 'sudo apt install gcc-multilib')
gcc -m32 -o all32 test.c -w -D_FORTIFY_SOURCE=2 -fstack-protector-strong -fpie -O2 -z relro -z now -z noexecstack -pie -s
gcc -m32 -o partial32 test.c -w -D_FORTIFY_SOURCE=2 -fstack-protector-strong -fpie -O2 -z relro -z lazy -z noexecstack -s
gcc -m32 -o rpath32 test.c -w -D_FORTIFY_SOURCE=2 -fstack-protector-strong -fpie -O2 -z relro -z now -z noexecstack -pie -s -Wl,-rpath,./ -Wl,--disable-new-dtags
gcc -m32 -o runpath32 test.c -w -D_FORTIFY_SOURCE=2 -fstack-protector-strong -fpie -O2 -z relro -z now -z noexecstack -pie -s -Wl,-rpath,./ -Wl,--enable-new-dtags
gcc -m32 -o none32 test.c -w -D_FORTIFY_SOURCE=0 -fno-stack-protector -no-pie -O2 -z norelro -z lazy -z execstack
gcc -m32 -c test.c -o rel32.o
gcc -m32 -shared -fPIC -o dso32.so test.c -w -D_FORTIFY_SOURCE=2 -fstack-protector-strong -O2 -z relro -z now -z noexecstack -s
clang -m32 -o cfi32 test.c -w -flto -fsanitize=cfi -fvisibility=default
clang -m32 -o sstack32 test.c -w -fsanitize=safe-stack
clang -m32 -o all_cl32 test.c -w -D_FORTIFY_SOURCE=2 -fstack-protector-strong -fpie -O2 -z relro -z now -z noexecstack -pie -s
clang -m32 -o partial_cl32 test.c -w -D_FORTIFY_SOURCE=2 -fstack-protector-strong -fpie -O2 -z relro -z lazy -z noexecstack -s
clang -m32 -o rpath_cl32 test.c -w -D_FORTIFY_SOURCE=2 -fstack-protector-strong -fpie -O2 -z relro -z now -z noexecstack -pie -s -Wl,-rpath,./ -Wl,--disable-new-dtags
clang -m32 -o runpath_cl32 test.c -w -D_FORTIFY_SOURCE=2 -fstack-protector-strong -fpie -O2 -z relro -z now -z noexecstack -pie -s -Wl,-rpath,./ -Wl,--enable-new-dtags
clang -m32 -o none_cl32 test.c -w -D_FORTIFY_SOURCE=0 -fno-stack-protector -no-pie -O2 -z norelro -z lazy -z execstack
clang -m32 -c test.c -o rel_cl32.o
clang -m32 -shared -fPIC -o dso_cl32.so test.c -w -D_FORTIFY_SOURCE=2 -fstack-protector-strong -O2 -z relro -z now -z noexecstack -s
20 changes: 20 additions & 0 deletions tests/binaries/test.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#include <stdio.h>
#include <string.h>
#include <unistd.h>

int false__stack_chk_fail(int a) { return a; }

int main(int argc, char** argv) {
char buf[16];
int (*op)(int) = false__stack_chk_fail;

if (argc>1)
strcpy(buf,argv[1]);
else
strcpy(buf,"test");

printf("%s,%d\n", buf, op(42));

sleep(2);
return 0;
}
Loading

0 comments on commit fea00ef

Please sign in to comment.