Files
emba/modules/S07_bootloader_check.sh
2026-06-25 10:01:52 +02:00

435 lines
16 KiB
Bash
Executable File

#!/bin/bash -p
# EMBA - EMBEDDED LINUX ANALYZER
#
# Copyright 2020-2023 Siemens AG
# Copyright 2020-2026 Siemens Energy AG
#
# EMBA comes with ABSOLUTELY NO WARRANTY. This is free software, and you are
# welcome to redistribute it under the terms of the GNU General Public License.
# See LICENSE file for usage of this software.
#
# EMBA is licensed under GPLv3
# SPDX-License-Identifier: GPL-3.0-only
#
# Author(s): Michael Messner, Pascal Eckmann
# Description: Scans for device tree blobs, bootloader and startup files and checks for the default runlevel.
# This module is based on source code from lynis: https://raw.githubusercontent.com/CISOfy/lynis/master/include/tests_boot_services
S07_bootloader_check() {
module_log_init "${FUNCNAME[0]}"
module_title "Check bootloader and system startup"
pre_module_reporter "${FUNCNAME[0]}"
export STARTUP_FINDS=0
export INITTAB_V=()
check_dtb
check_bootloader
find_boot_files
find_runlevel
module_end_log "${FUNCNAME[0]}" "${STARTUP_FINDS}"
}
check_dtb() {
sub_module_title "Scan for device tree blobs"
local lDTB_ARR=()
local lDTB_FILE=""
# readarray -t lDTB_ARR < <( find "${FIRMWARE_PATH}" "${EXCL_FIND[@]}" -xdev -iname "*.dtb" -print0|xargs -r -0 -P 16 -I % sh -c 'md5sum "%" 2>/dev/null' | sort -u -k1,1 | cut -d\ -f3 || true)
mapfile -t lDTB_ARR < <(grep "\\.dtb;" "${P99_CSV_LOG}" | cut -d ';' -f2 | sort -u || true)
if [[ ${#lDTB_ARR[@]} -gt 0 ]]; then
print_output "[+] Device tree blobs found:"
for lDTB_FILE in "${lDTB_ARR[@]}"; do
print_output "$(indent "$(orange "${lDTB_FILE}")")"
write_link "${LOG_PATH_MODULE}""/""$(basename "${lDTB_FILE}" .dtb)""-DUMP.txt"
write_log "$(fdtdump "${lDTB_FILE}" 2>/dev/null || true)" "${LOG_PATH_MODULE}""/""$(basename "${lDTB_FILE}" .dtb)""-DUMP.txt" "g"
((STARTUP_FINDS += 1))
write_csv_log "bootloader status" "device tree blobs" "found"
done
print_ln
else
print_output "[-] No device tree blobs found"
write_csv_log "bootloader status" "device tree blobs" "not_found"
fi
}
check_bootloader() {
sub_module_title "Scan for bootloader"
local lBOOTLOADER=0
local lCHECK=0
# Syslinux
local lSYSLINUX_PATHS_ARR=()
local lSYSLINUX_FILE=""
# mapfile -t lSYSLINUX_PATHS_ARR < <(find "${FIRMWARE_PATH}" -xdev -type f -iwholename "/boot/syslinux/syslinux.cfg" || true)
mapfile -t lSYSLINUX_PATHS_ARR < <(grep "/boot/syslinux/syslinux.cfg;" "${P99_CSV_LOG}" | cut -d ';' -f2 | sort -u || true)
for lSYSLINUX_FILE in "${lSYSLINUX_PATHS_ARR[@]}"; do
if [[ -f "${lSYSLINUX_FILE}" ]]; then
lCHECK=1
print_output "[+] Found Syslinux config: ""$(print_path "${lSYSLINUX_FILE}")"
lBOOTLOADER="Syslinux"
((STARTUP_FINDS += 1))
write_csv_log "bootloader status" "Syslinux configuration file" "found"
fi
done
if [[ ${lCHECK} -eq 0 ]]; then
print_output "[-] No Syslinux configuration file found"
write_csv_log "bootloader status" "Syslinux configuration file" "not_found"
fi
# Grub
lCHECK=0
local lGRUB_PATHS_ARR=()
local lGRUB_FILE=""
local lGRUB=""
# mapfile -t lGRUB_PATHS_ARR < <(find "${FIRMWARE_PATH}" -xdev -type f -iwholename "/boot/grub/grub.conf" || true)
mapfile -t lGRUB_PATHS_ARR < <(grep "/boot/grub/grub.conf;" "${P99_CSV_LOG}" | cut -d ';' -f2 | sort -u || true)
for lGRUB_FILE in "${lGRUB_PATHS_ARR[@]}"; do
if [[ -f "${lGRUB_FILE}" ]]; then
lCHECK=1
print_output "[+] Found Grub config: ""$(print_path "${lGRUB_FILE}")"
lGRUB="${lGRUB_FILE}"
lBOOTLOADER="Grub"
((STARTUP_FINDS += 1))
write_csv_log "bootloader status" "Grub configuration file" "found"
fi
done
# mapfile -t lGRUB_PATHS_ARR < <(find "${FIRMWARE_PATH}" -xdev -type f -iwholename "/boot/grub/menu.lst" || true)
mapfile -t lGRUB_PATHS_ARR < <(grep "/boot/grub/menu.lst;" "${P99_CSV_LOG}" | cut -d ';' -f2 | sort -u || true)
for lGRUB_FILE in "${lGRUB_PATHS_ARR[@]}"; do
if [[ -f "${lGRUB_FILE}" ]]; then
lCHECK=1
print_output "[+] Found Grub config: ""$(print_path "${lGRUB_FILE}")"
lGRUB="${lGRUB_FILE}"
lBOOTLOADER="Grub"
((STARTUP_FINDS += 1))
write_csv_log "bootloader status" "Grub configuration file" "found"
fi
done
if [[ ${lCHECK} -eq 0 ]]; then
print_output "[-] No Grub configuration file found"
write_csv_log "bootloader status" "Grub configuration file" "not_found"
fi
# Grub2
lCHECK=0
# mapfile -t lGRUB_PATHS_ARR < <(find "${FIRMWARE_PATH}" -xdev -type f -iwholename "/boot/grub/grub.cfg" || true)
mapfile -t lGRUB_PATHS_ARR < <(grep "/boot/grub/grub.cfg;" "${P99_CSV_LOG}" | cut -d ';' -f2 | sort -u || true)
for lGRUB_FILE in "${lGRUB_PATHS_ARR[@]}"; do
if [[ -f "${lGRUB_FILE}" ]]; then
lCHECK=1
print_output "[+] Found Grub2 config: ""$(print_path "${lGRUB_FILE}")"
lGRUB="${lGRUB_FILE}"
lBOOTLOADER="Grub2"
((STARTUP_FINDS += 1))
fi
done
# mapfile -t lGRUB_PATHS_ARR < <(find "${FIRMWARE_PATH}" -xdev -type f -iwholename "/boot/grub/grub.conf" || true)
mapfile -t lGRUB_PATHS_ARR < <(grep "/boot/grub/grub.conf;" "${P99_CSV_LOG}" | cut -d ';' -f2 | sort -u || true)
for lGRUB_FILE in "${lGRUB_PATHS_ARR[@]}"; do
if [[ -f "${lGRUB_FILE}" ]]; then
lCHECK=1
print_output "[+] Found Grub2 config: ""$(print_path "${lGRUB_FILE}")"
lGRUB="${lGRUB_FILE}"
lBOOTLOADER="Grub2"
((STARTUP_FINDS += 1))
write_csv_log "bootloader status" "Grub configuration file" "found"
fi
done
if [[ ${lCHECK} -eq 0 ]]; then
print_output "[-] No Grub configuration file found"
write_csv_log "bootloader status" "Grub configuration file" "not_found"
fi
# Grub configuration
local lFIND=""
local lFIND2=""
local lFIND3=""
local lFIND4=""
local lFIND5=""
local lFOUND=0
if [[ -n "${lGRUB:-}" ]]; then
print_output "[*] Check Grub config: ""$(print_path "${lGRUB}")"
lFIND=$(grep 'password --md5' "${lGRUB}" | grep -v '^#' || true)
lFIND2=$(grep 'password --encrypted' "${lGRUB}" | grep -v '^#' || true)
lFIND3=$(grep 'set superusers' "${lGRUB}" | grep -v '^#' || true)
lFIND4=$(grep 'password_pbkdf2' "${lGRUB}" | grep -v '^#' || true)
lFIND5=$(grep 'grub.pbkdf2' "${lGRUB}" | grep -v '^#' || true)
# GRUB1: Password should be set (MD5 or SHA1)
if [[ -n "${lFIND}" ]] || [[ -n "${lFIND2}" ]]; then
lFOUND=1
# GRUB2: Superusers AND password should be defined
elif [[ -n "${lFIND3}" ]]; then
if [[ -n "${lFIND4}" ]] || [[ -n "${lFIND5}" ]]; then
lFOUND=1
((STARTUP_FINDS += 1))
fi
fi
if [[ ${lFOUND} -eq 1 ]]; then
print_output "[+] GRUB has password protection"
write_csv_log "bootloader status" "Grub configuration check" "found"
else
print_output "[-] No hashed password line in GRUB boot file"
write_csv_log "bootloader status" "Grub configuration check" "not_found"
fi
else
print_output "[-] No Grub configuration check"
write_csv_log "bootloader status" "Grub configuration check" "found"
fi
# FreeBSD or DragonFly
lCHECK=0
local lBOOT1_ARR=()
local lBOOT2_ARR=()
local lBOOTL_ARR=()
# mapfile -t lBOOT1_ARR < <(find "${FIRMWARE_PATH}" -xdev -type f -iwholename "/boot/boot1" || true)
mapfile -t lBOOT1_ARR < <(grep "/boot/boot1;" "${P99_CSV_LOG}" | cut -d ';' -f2 | sort -u || true)
mapfile -t lBOOT2_ARR < <(grep "/boot/boot2;" "${P99_CSV_LOG}" | cut -d ';' -f2 | sort -u || true)
mapfile -t lBOOTL_ARR < <(grep "/boot/loader;" "${P99_CSV_LOG}" | cut -d ';' -f2 | sort -u || true)
# mapfile -t lBOOT2_ARR < <(find "${FIRMWARE_PATH}" -xdev -type f -iwholename "/boot/boot2" || true)
# mapfile -t lBOOTL_ARR < <(find "${FIRMWARE_PATH}" -xdev -type f -iwholename "/boot/loader" || true)
local lB1=""
local lB2=""
local lBL=""
for lB1 in "${lBOOT1_ARR[@]}"; do
for lB2 in "${lBOOT2_ARR[@]}"; do
for lBL in "${lBOOTL_ARR[@]}"; do
if [[ -f "${lB1}" ]] && [[ -f "${lB2}" ]] && [[ -f "${lBL}" ]]; then
lCHECK=1
print_output "[+] Found ""$(print_path "${lB1}")"", ""$(print_path "${lB2}")"" and ""$(print_path "${lBL}")"" (FreeBSD or DragonFly)"
lBOOTLOADER="FreeBSD / DragonFly"
((STARTUP_FINDS += 1))
write_csv_log "bootloader status" "FreeBSD or DragonFly bootloader files" "found"
fi
done
done
done
if [[ ${lCHECK} -eq 0 ]]; then
print_output "[-] No FreeBSD or DragonFly bootloader files found"
write_csv_log "bootloader status" "FreeBSD or DragonFly bootloader files" "not_found"
fi
# LILO=""
lCHECK=0
local lLILO_PATH_ARR=()
local lLILO_FILE=""
mapfile -t lLILO_PATH_ARR < <(mod_path "/ETC_PATHS/lilo.conf")
for lLILO_FILE in "${lLILO_PATH_ARR[@]}"; do
if [[ -f "${lLILO_FILE}" ]]; then
lCHECK=1
print_output "[+] Found lilo.conf: ""$(print_path "${lLILO_FILE}")"" (LILO)"
lFIND=$(grep 'password[[:space:]]?=' "${lLILO_FILE}" | grep -v "^#" || true)
write_csv_log "bootloader status" "LILO configuration file" "found"
if [[ -z "${lFIND}" ]]; then
print_output "[+] LILO has password protection"
((STARTUP_FINDS += 1))
fi
lBOOTLOADER="LILO"
fi
done
if [[ ${lCHECK} -eq 0 ]]; then
print_output "[-] No LILO configuration file found"
write_csv_log "bootloader status" "LILO configuration file" "not_found"
fi
# SILO
lCHECK=0
local lSILO_PATH_ARR=()
local lSILO_FILE=""
mapfile -t lSILO_PATH_ARR < <(mod_path "/ETC_PATHS/silo.conf")
for lSILO_FILE in "${lSILO_PATH_ARR[@]}"; do
if [[ -f "${lSILO_FILE}" ]]; then
lCHECK=1
print_output "[+] Found silo.conf: ""$(print_path "${lSILO_FILE}")"" (SILO)"
lBOOTLOADER="SILO"
((STARTUP_FINDS += 1))
write_csv_log "bootloader status" "SILO configuration file" "found"
fi
done
if [[ ${lCHECK} -eq 0 ]]; then
print_output "[-] No SILO configuration file found"
write_csv_log "bootloader status" "SILO configuration file" "not_found"
fi
# YABOOT
lCHECK=0
local lYABOOT_PATH_ARR=()
local lYABOOT_FILE=""
mapfile -t lYABOOT_PATH_ARR < <(mod_path "/ETC_PATHS/yaboot.conf")
for lYABOOT_FILE in "${lYABOOT_PATH_ARR[@]}"; do
if [[ -f "${lYABOOT_FILE}" ]]; then
lCHECK=1
print_output "[+] Found yaboot.conf: ""$(print_path "${lYABOOT_FILE}")"" (YABOOT)"
lBOOTLOADER="Yaboot"
((STARTUP_FINDS += 1))
write_csv_log "bootloader status" "YABOOT configuration file" "found"
fi
done
if [[ ${lCHECK} -eq 0 ]]; then
print_output "[-] No YABOOT configuration file found"
write_csv_log "bootloader status" "YABOOT configuration fileot_found"
fi
# OpenBSD
lCHECK=0
local lOBSD_PATH1_ARR=()
local lOBSD_PATH2_ARR=()
local lOBSD_FILE1=""
local lOBSD_FILE2=""
# mapfile -t lOBSD_PATH1_ARR < <(find "${FIRMWARE_PATH}" -xdev -type f -iwholename "/usr/mdec/biosboot" || true)
mapfile -t lOBSD_PATH1_ARR < <(grep "/usr/mdec/biosboot;" "${P99_CSV_LOG}" | cut -d ';' -f2 | sort -u || true)
# mapfile -t lOBSD_PATH2_ARR < <(find "${FIRMWARE_PATH}" -xdev -type f -iwholename "/boot" || true)
mapfile -t lOBSD_PATH2_ARR < <(grep "/boot;" "${P99_CSV_LOG}" | cut -d ';' -f2 | sort -u || true)
for lOBSD_FILE1 in "${lOBSD_PATH1_ARR[@]}"; do
for lOBSD_FILE2 in "${lOBSD_PATH2_ARR[@]}"; do
if [[ -f "${lOBSD_FILE1}" ]] && [[ -f "${lOBSD_FILE2}" ]]; then
lCHECK=1
print_output "[+] Found first and second stage bootstrap in ""$(print_path "${lOBSD_FILE1}")"" and ""$(print_path "${lOBSD_FILE2}")"" (OpenBSD)"
lBOOTLOADER="OpenBSD"
((STARTUP_FINDS += 1))
write_csv_log "bootloader status" "OpenBSD/bootstrap files" "found"
fi
done
done
if [[ ${lCHECK} -eq 0 ]]; then
print_output "[-] No OpenBSD/bootstrap files found"
write_csv_log "bootloader status" "OpenBSD/bootstrap files" "not_found"
fi
# OpenBSD boot configuration
lCHECK=0
local lOPENBSD_PATH_ARR=()
local lOPENBSD_PATH2_ARR=()
local lOPENBSD=""
local lOPENBSD2=""
mapfile -t lOPENBSD_PATH_ARR < <(mod_path "/ETC_PATHS/boot.conf")
for lOPENBSD in "${lOPENBSD_PATH_ARR[@]}"; do
if [[ -f "${lOPENBSD}" ]]; then
lCHECK=1
print_output "[+] Found ""$(print_path "${lOPENBSD}")"" (OpenBSD)"
write_csv_log "bootloader status" "OpenBSD configuration file" "found"
lFIND=$(grep '^boot' "${lOPENBSD}" || true)
if [[ -z "${lFIND}" ]]; then
print_output "[+] System can be booted into single user mode without password"
((STARTUP_FINDS += 1))
fi
mapfile -t lOPENBSD_PATH2_ARR < <(mod_path "/ETC_PATHS/rc.conf")
for lOPENBSD2 in "${lOPENBSD_PATH2_ARR[@]}"; do
if [[ -e "${lOPENBSD2}" ]]; then
lFIND=$(grep -v -i '^#|none' "${lOPENBSD2}" | grep-i '_enable.*(yes|on|1)' || true | sort | awk -F= '{ print $1 }' | sed 's/_enable//')
print_output "[+] Found OpenBSD boot services ""$(print_path "${lOPENBSD2}")"
if [[ -z "${lFIND}" ]]; then
print_output "$(indent "$(orange "${lFIND}")")"
((STARTUP_FINDS += 1))
fi
fi
done
fi
done
if [[ ${lCHECK} -eq 0 ]]; then
print_output "[-] No OpenBSD configuration file found"
write_csv_log "bootloader status" "OpenBSD configuration file" "not_found"
fi
# U-Boot quick check on firmware file
lCHECK=0
if [[ "${UBOOT_IMAGE}" -eq 1 ]]; then
lCHECK=1
print_output "[+] Found uboot image: ""$(print_path "${FIRMWARE_PATH}")"" (U-BOOT)"
lBOOTLOADER="U-Boot"
((STARTUP_FINDS += 1))
write_csv_log "bootloader status" "U-Boot image" "found"
fi
if [[ ${lCHECK} -eq 0 ]]; then
print_output "[-] No U-Boot image found"
write_csv_log "bootloader status" "U-Boot image" "not_found"
fi
if [[ -z "${lBOOTLOADER:-}" ]]; then
print_output "[-] No bootloader found"
write_csv_log "bootloader status" "bootloader" "not_found"
fi
}
find_boot_files() {
sub_module_title "Scan for startup files"
local lBOOT_FILES_ARR=()
local lLINE=""
mapfile -t lBOOT_FILES_ARR < <(config_find "${CONFIG_DIR}""/boot_files.cfg")
if [[ "${lBOOT_FILES_ARR[0]-}" == "C_N_F" ]]; then
print_output "[!] Config not found"
elif [[ "${#lBOOT_FILES_ARR[@]}" -ne 0 ]]; then
print_output "[+] Found ""${#lBOOT_FILES_ARR[@]}"" startup files:"
write_csv_log "startup files count" "${#lBOOT_FILES_ARR[@]}" ""
for lLINE in "${lBOOT_FILES_ARR[@]}"; do
print_output "$(indent "$(orange "$(print_path "${lLINE}")")")"
write_csv_log "startup file" "${lLINE}" ""
if [[ "$(basename "${lLINE}")" == "inittab" ]]; then
INITTAB_V=("${INITTAB_V[@]}" "${lLINE}")
fi
((STARTUP_FINDS += 1))
done
else
print_output "[-] No startup files found"
write_csv_log "startup files count" "0" ""
fi
}
find_runlevel() {
sub_module_title "Check default run level"
local lSYSTEMD_PATH_ARR=()
local lSYSTEMD_P=""
local lDEFAULT_TARGET_PATH=""
local lFIND=""
local lINIT_TAB_F=""
mapfile -t lSYSTEMD_PATH_ARR < <(mod_path "/ETC_PATHS/systemd")
for lSYSTEMD_P in "${lSYSTEMD_PATH_ARR[@]}"; do
if [[ -d "${lSYSTEMD_P}" ]]; then
print_output "[*] Check runlevel in systemd directory: ""$(print_path "${lSYSTEMD_P}")"
lDEFAULT_TARGET_PATH="${lSYSTEMD_P}""/system/default.target"
if [[ -L "${lDEFAULT_TARGET_PATH}" ]]; then
lFIND="$(read -r "${lDEFAULT_TARGET_PATH}"'' | grep "runlevel" || true)"
if [[ -z "${lFIND}" ]]; then
print_output "[+] systemd run level information:"
print_output "$(indent "${lFIND}")"
((STARTUP_FINDS += 1))
else
print_output "[-] No run level in ""$(print_path "${lDEFAULT_TARGET_PATH}")"" found"
fi
else
print_output "[-] ""$(print_path "${lDEFAULT_TARGET_PATH}")"" not found"
fi
fi
done
if [[ -v INITTAB_V[@] ]]; then
if [[ ${#INITTAB_V[@]} -gt 0 ]]; then
for lINIT_TAB_F in "${INITTAB_V[@]}"; do
print_output "[*] Check runlevel in ""$(print_path "${lINIT_TAB_F}")"
lFIND=$(awk -F: '/^id/ { print $2; }' "${lINIT_TAB_F}" | head -n 1)
if [[ -z "${lFIND}" ]]; then
print_output "[-] No default run level ""$(print_path "${lINIT_TAB_F}")"" found"
else
print_output "[+] Found default run level: ""$(orange "${lFIND}")"
((STARTUP_FINDS += 1))
fi
done
else
print_output "[-] No default run level found"
fi
else
print_output "[-] No default run level found"
fi
}