mirror of
https://github.com/e-m-b-a/emba.git
synced 2026-10-12 02:13:21 +00:00
885 lines
38 KiB
Bash
Executable File
885 lines
38 KiB
Bash
Executable File
#!/bin/bash -p
|
|
|
|
# EMBA - EMBEDDED LINUX ANALYZER
|
|
#
|
|
# Copyright 2026-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, Google Gemini AI
|
|
|
|
S130_binary_map_builder() {
|
|
module_log_init "${FUNCNAME[0]}"
|
|
module_title "Binary dependency map builder"
|
|
local lNEG_LOG=0
|
|
|
|
if [[ "${EMBA_MAP_GENERATOR:-0}" -eq 0 ]]; then
|
|
module_end_log "${FUNCNAME[0]}" 0
|
|
return
|
|
fi
|
|
|
|
module_wait "S115_usermode_emulator"
|
|
|
|
# default behavior:
|
|
local lSVG_BUILD_TIMEOUT=0
|
|
lSVG_BUILD_TIMEOUT="$(convert_timeformat "${SVG_BUILD_TIMEOUT:-0s}")"
|
|
# if we have a AI_MIN_RUNTIME set and this is bigger then our current S16_MIN_RUNTIME we adjust S16 to the AI runtime
|
|
if [[ "${AI_OPTION}" -gt 0 ]]; then
|
|
AI_MIN_RUNTIME="$(convert_timeformat "${AI_MIN_RUNTIME:-0s}")"
|
|
if [[ "${lSVG_BUILD_TIMEOUT}" -lt "${AI_MIN_RUNTIME}" ]]; then
|
|
print_output "[*] S130 - setting minimum runtime to AI_MIN_RUNTIME - ${AI_MIN_RUNTIME} seconds"
|
|
lSVG_BUILD_TIMEOUT="${AI_MIN_RUNTIME}"
|
|
# start timer for track the runtime of the module
|
|
export SECONDS=0
|
|
fi
|
|
fi
|
|
|
|
# needed if we start the module standalone via the helper script
|
|
[[ ! -d "${LOG_PATH_MODULE}" ]] && mkdir -p "${LOG_PATH_MODULE}"
|
|
|
|
load_default_environment
|
|
setup_environment
|
|
build_dot
|
|
|
|
if [[ -f "${DOT_FILE}" ]]; then
|
|
build_svg "${lSVG_BUILD_TIMEOUT}" "${SECONDS}"
|
|
# as soon as we have a dot file generated we build the HTML report file for this module
|
|
lNEG_LOG=1
|
|
fi
|
|
|
|
if [[ -f "${SVG_FILE}" ]]; then
|
|
build_html
|
|
lNEG_LOG=1
|
|
fi
|
|
|
|
module_end_log "${FUNCNAME[0]}" "${lNEG_LOG}"
|
|
|
|
# we need to adjust the html linking after the html report was created:
|
|
if [[ "${HTML}" -eq 1 ]]; then
|
|
local lS130_HTML_PAGE=""
|
|
lS130_HTML_PAGE=$(find "${HTML_PATH}/s130_binary_map_builder" -type f -name "EMBA-dependency-map.html" | head -1)
|
|
# lS130_HTML_PAGE=$(echo ${lS130_HTML_PAGE} | sed "s#${LOG_DIR}/html-report/##")
|
|
# needed the path without the html directory for later replacement
|
|
lS130_HTML_PAGE="${lS130_HTML_PAGE#"${HTML_PATH}"/}"
|
|
if [[ -f "${HTML_PATH}/s130_binary_map_builder.html" ]]; then
|
|
# now we can add the link around our svg image
|
|
sed -i "/img class.*EMBA-dependency-map.svg.*/i <a href=./${lS130_HTML_PAGE}>" "${HTML_PATH}/s130_binary_map_builder.html"
|
|
sed -i '/img class.*EMBA-dependency-map.svg.*/a <\/a>' "${HTML_PATH}/s130_binary_map_builder.html"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
load_default_environment() {
|
|
# Limit the number of processed files to 1000 for handling firmware images
|
|
# For testing this should be limited to something lower
|
|
[[ -z "${MAX_MAP_FILES}" ]] && export MAX_MAP_FILES=1000
|
|
|
|
# hard-coded dependency blacklist - is fp prone and overwhelming output
|
|
# add entries if you do not want to see them
|
|
export DEP_BLACKLIST_ARR=("echo" "test" "free" "event" "full" "port")
|
|
local lBL_COMMAND=""
|
|
print_output "[*] Blacklisted the following commands:"
|
|
for lBL_COMMAND in "${DEP_BLACKLIST_ARR[@]}"; do
|
|
print_output "$(indent "- ${lBL_COMMAND}")"
|
|
done
|
|
|
|
# Remove detection mechanism you do not want to check for
|
|
# FUZZY-STR is very powerful but also fp prone
|
|
# DETECTION_MECHANISMS_ARR=("FUZZY-STR" "STRICT-STR" "LDD-LIB" "QEMU-USER" "QEMU-SYS" "OBJDUMP-LIB")
|
|
export DETECTION_MECHANISMS_ARR=("FUZZY-STR" "STRICT-STR" "QEMU-USER" "QEMU-SYS" "OBJDUMP-LIB")
|
|
|
|
# in case we have not EMBA s115 module results we can remove these checks
|
|
if ! [[ -d "${S115_LOG_DIR}" ]]; then
|
|
print_ln ""
|
|
print_output "[-] No s115 qemu check possible - missing s115 EMBA log directory"
|
|
DETECTION_MECHANISMS_ARR=("${DETECTION_MECHANISMS_ARR[@]/QEMU-USER/}")
|
|
fi
|
|
|
|
# system emulation checks are only possible in standalone run via the helper script
|
|
# During EMBA run the system emulation results are not available
|
|
if ! [[ -f "${L10_CSV_LOG}" ]]; then
|
|
print_ln ""
|
|
print_output "[-] No system emulation checks possible - missing L10 EMBA log directory (future extension)"
|
|
DETECTION_MECHANISMS_ARR=("${DETECTION_MECHANISMS_ARR[@]/QEMU-SYS/}")
|
|
fi
|
|
|
|
local lDET_MECHANISM=""
|
|
print_ln ""
|
|
print_output "[*] Using the following detection mechanisms:"
|
|
for lDET_MECHANISM in "${DETECTION_MECHANISMS_ARR[@]}"; do
|
|
[[ -z "${lDET_MECHANISM}" ]] && continue
|
|
print_output "$(indent "- ${lDET_MECHANISM}")"
|
|
done
|
|
|
|
print_ln ""
|
|
# for threading
|
|
if [[ -z "${MAX_MAP_JOBS}" ]]; then
|
|
export MAX_MAP_JOBS=""
|
|
MAX_MAP_JOBS="$(nproc)"
|
|
fi
|
|
|
|
# Define temporary and output filenames.
|
|
export HTML_FILE="${LOG_PATH_MODULE}/EMBA-dependency-map.html"
|
|
export BASE_HTML_TEMPLATE="${HELP_DIR}/firmware_map_base.html"
|
|
|
|
export DOT_FILE="${LOG_PATH_MODULE}/EMBA-dependency-map.dot"
|
|
export DOT_FILE_tmp_dir="${LOG_PATH_MODULE}/tmp_dot"
|
|
rm -rf "${DOT_FILE_tmp_dir}"
|
|
mkdir "${DOT_FILE_tmp_dir}"
|
|
|
|
# External assets - downloaded once for offline capability.
|
|
export JS_LIB="${EXT_DIR}/svg-pan-zoom.min.js"
|
|
export LOGO_FILE="${HELP_DIR}/emba.svg"
|
|
|
|
export COLOR_BIN="#1a3a5f" # Deep Blue for Executables
|
|
export COLOR_LNK="#D5AAAA"
|
|
|
|
export DEPENDENCY_MAP_LOG="${LOG_PATH_MODULE}/dependency_map_details.log"
|
|
}
|
|
|
|
setup_environment() {
|
|
if [[ -f "${LOGO_FILE}" ]]; then
|
|
cp "${LOGO_FILE}" "${LOG_PATH_MODULE}" || print_error "[-] No EMBA logo found"
|
|
elif [[ -f "/tmp/${LOGO_FILE}" ]]; then
|
|
# if we start it from external script without an EMBA directory we have the logo
|
|
# already loaded to /tmp
|
|
cp "/tmp/${LOGO_FILE}" "${LOG_PATH_MODULE}" || print_error "[-] No EMBA logo found"
|
|
fi
|
|
if [[ -f "${JS_LIB}" ]]; then
|
|
cp "${JS_LIB}" "${LOG_PATH_MODULE}" || print_error "[-] No JS lib ${JS_LIB} found"
|
|
elif [[ -f "/tmp/${JS_LIB}" ]]; then
|
|
cp "/tmp/${JS_LIB}" "${LOG_PATH_MODULE}" || print_error "[-] No JS lib ${JS_LIB} found"
|
|
fi
|
|
|
|
# Get files for processing - if P99_CSV_LOG available we can use this
|
|
export ALL_EXEC_FILES_ARR=()
|
|
if [[ -f "${P99_CSV_LOG}" ]]; then
|
|
mapfile -t ALL_EXEC_FILES_ARR < <(cut -d ';' -f2 "${P99_CSV_LOG}" | grep -v "\.raw$\|\.uncompressed$")
|
|
else
|
|
mapfile -t ALL_EXEC_FILES_ARR < <(find "${FIRMWARE_PATH}" -type f ! \( -name "*.uncompressed" -o -name "*.raw" \) 2>/dev/null | sort -u)
|
|
fi
|
|
if [[ "${#ALL_EXEC_FILES_ARR[@]}" -gt "${MAX_MAP_FILES}" ]]; then
|
|
print_output "[*] INFO: A huge number of files (${#ALL_EXEC_FILES_ARR[@]} -gt ${MAX_MAP_FILES}) detected ... limit the module to a maximum of ${MAX_MAP_FILES} executables only"
|
|
if [[ -f "${P99_CSV_LOG}" ]]; then
|
|
mapfile -t ALL_EXEC_FILES_ARR < <(grep "ELF\|executable\|script" "${P99_CSV_LOG}" | cut -d ';' -f2 | grep -v "\.raw" | head -n "${MAX_MAP_FILES}" || true)
|
|
else
|
|
# quick and dirty backup solution to not handle all the executable files
|
|
mapfile -t ALL_EXEC_FILES_ARR < <(find "${FIRMWARE_PATH}" -type f -executable ! -name "*.raw" 2>/dev/null | sort -u | head -n "${MAX_MAP_FILES}" || true)
|
|
fi
|
|
fi
|
|
print_ln ""
|
|
print_output "[+] Testing ${ORANGE}${#ALL_EXEC_FILES_ARR[@]}${GREEN} files/executables in ${ORANGE}${FIRMWARE_PATH}${NC}"
|
|
print_ln ""
|
|
}
|
|
|
|
# system emulation checks are only possible in standalone run via the helper script
|
|
# During EMBA run the system emulation results are not available
|
|
system_emulator_init_runner() {
|
|
local lSYS_EMU_ENTRY=""
|
|
if [[ -f "${LOG_DIR}"/emulator_online_results.log ]]; then
|
|
lSYS_EMU_ENTRY=$(grep "ICMP ok\|TCP ok" "${LOG_DIR}"/emulator_online_results.log | sort -k 7 -t ';' | tail -1 || true)
|
|
else
|
|
print_output "[-] Identified NO system emulation details"
|
|
return
|
|
fi
|
|
print_output "[*] Identified the following system emulation details"
|
|
print_output " -> ${lSYS_EMU_ENTRY}"
|
|
|
|
lEMU_PATH=$(cut -d ';' -f11 <<<"${lSYS_EMU_ENTRY}") # field 11
|
|
lEMU_PATH=${LOG_DIR}"/l10_system_emulation/${lEMU_PATH}"
|
|
if [[ ! -d "${lEMU_PATH}" ]]; then
|
|
print_output "[-] No system emulation results available ... no checks performed"
|
|
return
|
|
fi
|
|
|
|
print_output "[*] Adjusting emulation runner script ..."
|
|
cp -r "${lEMU_PATH}" "${LOG_PATH_MODULE}/emulation_engine" || {
|
|
print_error "[-] S130 - Adjusting emulation runner script failed"
|
|
return
|
|
}
|
|
sed -i 's/firmadyne.syscall=[0-9]*/firmadyne.syscall=32/' "${LOG_PATH_MODULE}"/emulation_engine/run.sh || {
|
|
print_error "[-] S130 - Adjusting emulation runner script failed"
|
|
return
|
|
}
|
|
sed -i 's#file:./qemu.serial.log#file:./qemu.map.log#' "${LOG_PATH_MODULE}"/emulation_engine/run.sh || {
|
|
print_error "[-] S130 - Adjusting emulation runner script failed"
|
|
return
|
|
}
|
|
|
|
print_output "[*] Starting emulation engine ... running for 360 seconds"
|
|
local lHOME_DIR=""
|
|
lHOME_DIR=$(pwd)
|
|
cd "${LOG_PATH_MODULE}"/emulation_engine || {
|
|
print_error "[-] S130 - Emulation run failed"
|
|
return
|
|
}
|
|
|
|
if [[ ${EUID} -eq 0 ]]; then
|
|
timeout 360s ./run.sh
|
|
else
|
|
sudo timeout 360s ./run.sh
|
|
fi
|
|
|
|
# usually we were running into timeout and need to stop the interface now
|
|
local lTAP_INTERFACE=""
|
|
# extract the last field -> the tap interface
|
|
lTAP_INTERFACE=$(grep "ip route flush" ./run.sh || true)
|
|
lTAP_INTERFACE=${lTAP_INTERFACE//*\ /}
|
|
if [[ -n "${lTAP_INTERFACE}" ]]; then
|
|
if [[ ${EUID} -eq 0 ]]; then
|
|
ip route flush dev "${lTAP_INTERFACE}"
|
|
ip link set "${lTAP_INTERFACE}" down
|
|
# ip link delete "${lTAP_INTERFACE}"
|
|
ip tuntap del dev "${lTAP_INTERFACE}" mode tap
|
|
else
|
|
sudo ip route flush dev "${lTAP_INTERFACE}"
|
|
sudo ip link set "${lTAP_INTERFACE}" down
|
|
# sudo ip link delete "${lTAP_INTERFACE}"
|
|
sudo ip tuntap del dev "${lTAP_INTERFACE}" mode tap
|
|
fi
|
|
fi
|
|
|
|
cd "${lHOME_DIR}" || {
|
|
print_error "[-] S130 - Emulation run failed"
|
|
return
|
|
}
|
|
|
|
grep -a ANALYZE "${LOG_PATH_MODULE}"/emulation_engine/qemu.map.log | sed -e 's/.*PID: //' | awk '{print $2,$3}' | sort -u | sed 's/^(//' | sed 's/)]: / -> /' >>"${LOG_PATH_MODULE}"/system_emulation_results.log
|
|
# we should have something like "UPDATELEASES.sh -> /usr/sbin/phpsh"
|
|
print_ln ""
|
|
print_output "[+] Identified $(wc -l 2>/dev/null <"${LOG_PATH_MODULE}"/system_emulation_results.log) calls via system emulation"
|
|
print_ln ""
|
|
}
|
|
|
|
# Function to identify binary capabilities via syscall analysis (x86, ARM, MIPS)
|
|
get_capabilities() {
|
|
local lELF_FILE="${1}"
|
|
local lARCH_INFO="${2}"
|
|
local lCAPABILITIES=""
|
|
local lRAW_DISASM=""
|
|
|
|
# x86-64
|
|
if [[ "${lARCH_INFO}" =~ "x86-64" ]]; then
|
|
lRAW_DISASM=$("${OBJDUMP}" -d "${lELF_FILE}" 2>/dev/null | grep -B 3 "syscall" | grep -oE "mov\s+\\\$0x[0-9a-f]+,%eax" | cut -d"$" -f2 | cut -d"," -f1 || true)
|
|
[[ "${lRAW_DISASM}" =~ (0x3b|0x39) ]] && lCAPABILITIES+="EXEC "
|
|
[[ "${lRAW_DISASM}" =~ (0x29|0x2a|0x2b) ]] && lCAPABILITIES+="NET "
|
|
[[ "${lRAW_DISASM}" =~ (0x2|0x0|0x1) ]] && lCAPABILITIES+="FILE "
|
|
|
|
# x86 (32-bit)
|
|
elif [[ "${lARCH_INFO}" =~ "Intel 80386" || "${lARCH_INFO}" =~ "x86" ]]; then
|
|
lRAW_DISASM=$("${OBJDUMP}" -d "${lELF_FILE}" 2>/dev/null | grep -B 3 "int\s+\$0x80" | grep -oE "mov\s+\\\$0x[0-9a-f]+,%eax" | cut -d"$" -f2 | cut -d"," -f1 || true)
|
|
[[ "${lRAW_DISASM}" =~ (0xb|0x2) ]] && lCAPABILITIES+="EXEC "
|
|
[[ "${lRAW_DISASM}" =~ (0x66|0x167) ]] && lCAPABILITIES+="NET "
|
|
[[ "${lRAW_DISASM}" =~ (0x5|0x3|0x4) ]] && lCAPABILITIES+="FILE "
|
|
|
|
# ARM / AArch64
|
|
elif [[ "${lARCH_INFO}" =~ "ARM" ]]; then
|
|
lRAW_DISASM=$("${OBJDUMP}" -d "${lELF_FILE}" 2>/dev/null | grep -B 3 "svc\s+#0" || true)
|
|
[[ "${lRAW_DISASM}" =~ (r7|x8) ]] && {
|
|
[[ "${lRAW_DISASM}" =~ (0xb|0xdd|0x1) ]] && lCAPABILITIES+="EXEC "
|
|
[[ "${lRAW_DISASM}" =~ (0x119|0xc6|0xc7) ]] && lCAPABILITIES+="NET "
|
|
[[ "${lRAW_DISASM}" =~ (0x5|0x3|0x4) ]] && lCAPABILITIES+="FILE "
|
|
}
|
|
|
|
# MIPS / MIPS64
|
|
elif [[ "${lARCH_INFO}" =~ "MIPS" ]]; then
|
|
if [[ "${lARCH_INFO}" =~ "64" ]]; then
|
|
lRAW_DISASM=$("${OBJDUMP}" -d "${lELF_FILE}" 2>/dev/null | grep -B 3 "syscall" | grep -oE "li\s+v0,[0-9]+" || true)
|
|
[[ "${lRAW_DISASM}" =~ (5057|5056) ]] && lCAPABILITIES+="EXEC "
|
|
[[ "${lRAW_DISASM}" =~ (5040|5041) ]] && lCAPABILITIES+="NET "
|
|
[[ "${lRAW_DISASM}" =~ (5002|5000|5001) ]] && lCAPABILITIES+="FILE "
|
|
else
|
|
lRAW_DISASM=$("${OBJDUMP}" -d "${lELF_FILE}" 2>/dev/null | grep -B 3 "syscall" | grep -oE "li\s+v0,[0-9]+" || true)
|
|
[[ "${lRAW_DISASM}" =~ (4011|4002) ]] && lCAPABILITIES+="EXEC "
|
|
[[ "${lRAW_DISASM}" =~ (4183|4170) ]] && lCAPABILITIES+="NET "
|
|
[[ "${lRAW_DISASM}" =~ (4005|4003|4004) ]] && lCAPABILITIES+="FILE "
|
|
fi
|
|
|
|
# RISC-V (rv32/rv64)
|
|
elif [[ "${lARCH_INFO}" =~ "RISC-V" ]]; then
|
|
# RISC-V: ecall, Register a7 (x17)
|
|
lRAW_DISASM=$("${OBJDUMP}" -d "${lELF_FILE}" 2>/dev/null | grep -B 3 "ecall" | grep -oE "li\s+a7,[0-9]+" || true)
|
|
[[ "${lRAW_DISASM}" =~ (221|220) ]] && lCAPABILITIES+="EXEC " # execve=221, clone=220
|
|
[[ "${lRAW_DISASM}" =~ (198|203) ]] && lCAPABILITIES+="NET " # socket=198, connect=203
|
|
[[ "${lRAW_DISASM}" =~ (56|63|64) ]] && lCAPABILITIES+="FILE " # openat=56, read=63, write=64
|
|
|
|
# PowerPC
|
|
elif [[ "${lARCH_INFO}" =~ "PowerPC" ]]; then
|
|
# PPC: sc, Register r0
|
|
lRAW_DISASM=$("${OBJDUMP}" -d "${lELF_FILE}" 2>/dev/null | grep -B 3 "sc" | grep -oE "li\s+r0,[0-9]+" || true)
|
|
[[ "${lRAW_DISASM}" =~ (11|2) ]] && lCAPABILITIES+="EXEC " # execve=11, fork=2
|
|
[[ "${lRAW_DISASM}" =~ (102|359) ]] && lCAPABILITIES+="NET " # socketcall=102, socket=359
|
|
[[ "${lRAW_DISASM}" =~ (5|3|4) ]] && lCAPABILITIES+="FILE " # open=5, read=3, write=4
|
|
|
|
# Nios II
|
|
elif [[ "${lARCH_INFO}" =~ "Altera Nios II" || "${lARCH_INFO}" =~ "Nios II" ]]; then
|
|
lRAW_DISASM=$("${OBJDUMP}" -d "${lELF_FILE}" 2>/dev/null | grep -B 3 "trap" | grep -oE "movi\s+r2,[0-9]+" || true)
|
|
[[ "${lRAW_DISASM}" =~ (11|2) ]] && lCAPABILITIES+="EXEC "
|
|
[[ "${lRAW_DISASM}" =~ (97|98|99) ]] && lCAPABILITIES+="NET "
|
|
[[ "${lRAW_DISASM}" =~ (5|3|4) ]] && lCAPABILITIES+="FILE "
|
|
fi
|
|
|
|
echo "${lCAPABILITIES:-NONE}"
|
|
}
|
|
|
|
binary_sec_scan() {
|
|
local lELF_FILE="${1:-}"
|
|
local lBIN_SEC_FLAGS=""
|
|
local lFILE_INFO=""
|
|
local lREADELF_l=""
|
|
local lREADELF_h=""
|
|
local lSYMBOLS=""
|
|
|
|
# Check for Static Linking (Security Indicator)
|
|
lFILE_INFO=$(file -b "${lELF_FILE}" 2>/dev/null)
|
|
if [[ "${lFILE_INFO}" =~ "statically linked" ]]; then
|
|
lBIN_SEC_FLAGS+="STATIC "
|
|
fi
|
|
|
|
lREADELF_l=$(readelf -lW "${lELF_FILE}" 2>/dev/null)
|
|
lREADELF_h=$(readelf -h "${lELF_FILE}" 2>/dev/null)
|
|
lSYMBOLS=$(readelf -s "${lELF_FILE}" 2>/dev/null)
|
|
|
|
# NX (No-Execute) check
|
|
[[ "${lREADELF_l}" =~ "GNU_STACK" ]] && lBIN_SEC_FLAGS+="NX " || lBIN_SEC_FLAGS+="!NX "
|
|
# PIE (Position Independent Executable) check
|
|
[[ "${lREADELF_h}" =~ "DYN" ]] && lBIN_SEC_FLAGS+="PIE " || lBIN_SEC_FLAGS+="!PIE "
|
|
# Stack Canary check
|
|
[[ "${lSYMBOLS}" =~ "__stack_chk_fail" ]] && lBIN_SEC_FLAGS+="CANARY " || lBIN_SEC_FLAGS+="!CANARY "
|
|
echo "${lBIN_SEC_FLAGS}"
|
|
}
|
|
|
|
fuzzy_string_dependency_checker() {
|
|
local lFILE_TO_CHECK="${1:-}"
|
|
local lSAFE_NAME="${2:-}"
|
|
local lDOT_FILE_tmp_FILE="${3:-}"
|
|
|
|
local STRING_DEPS_SRC_ARR=()
|
|
local lSTR_DEP=""
|
|
|
|
# lets check for fuzzy string dependencies - we check for a minimum of 4 character strings, remove commends and entries with slashes
|
|
# as they are already handled from the strict_string_dependency_checker
|
|
mapfile -t STRING_DEPS_SRC_ARR < <(strings -n 4 "${lFILE_TO_CHECK}" | sed -r 's/^[[:space:]]*#.*$//' | tr " " "\n" | grep -v '/' |
|
|
tr -d '[:blank:]' | sed -E 's/([.,;!])\1+//g' | grep -E "^[a-zA-Z0-9._-]+$" | grep -E '.{4,}' | sort -u || true)
|
|
# print_output "[*] Testing ${#STRING_DEPS_SRC_ARR[@]} strings from source ${lFILE_TO_CHECK}"
|
|
# check for all identified strings - if they match a file in the filesystem
|
|
for lSTR_DEP in "${STRING_DEPS_SRC_ARR[@]}"; do
|
|
search_parse_log_helper "${lSTR_DEP}" "FUZZY-STR" "${lFILE_TO_CHECK}" "${lDOT_FILE_tmp_FILE}" "${lSAFE_NAME}"
|
|
done
|
|
}
|
|
|
|
strict_string_dependency_checker() {
|
|
local lFILE_TO_CHECK="${1:-}"
|
|
local lSAFE_NAME="${2:-}"
|
|
local lDOT_FILE_tmp_FILE="${3:-}"
|
|
|
|
local STRING_DEPS_SRC_ARR=()
|
|
local lSTR_DEP=""
|
|
|
|
# lets check for fuzzy string dependencies - we check for a minimum of 4 character strings, remove commends and /dev/ entries
|
|
# we split every entry on spaces and on ':'
|
|
# additionally we check for a slash / as path indicator
|
|
mapfile -t STRING_DEPS_SRC_ARR < <(strings -n 4 "${lFILE_TO_CHECK}" | sed -r 's/^[[:space:]]*#.*$//' | tr " " "\n" | tr ':' '\n' | grep -v '/dev/' |
|
|
grep '/' | grep -E "^[a-zA-Z0-9./_-]+$" | tr -d '[:blank:]' | grep -E '.{4,}' | sort -u || true)
|
|
# print_output "[*] Testing ${#STRING_DEPS_SRC_ARR[@]} strings from source ${lFILE_TO_CHECK}"
|
|
# check for all identified strings - if they match a file in the filesystem
|
|
for lSTR_DEP in "${STRING_DEPS_SRC_ARR[@]}"; do
|
|
search_parse_log_helper "${lSTR_DEP}" "STRICT-STR" "${lFILE_TO_CHECK}" "${lDOT_FILE_tmp_FILE}" "${lSAFE_NAME}"
|
|
done
|
|
}
|
|
|
|
get_arch() {
|
|
local lFILE_OUTPUT="${1:-}"
|
|
|
|
local lARCH=""
|
|
|
|
if [[ "${lFILE_OUTPUT}" == *"ELF"* ]]; then
|
|
# e.g. ELF 32-bit LSB shared object, MIPS, MIPS32 version 1 (SYSV)
|
|
# -> we need the part after the ','
|
|
lARCH=$(cut -d ',' -f2-3 <<<"${lFILE_OUTPUT}") # fields 2-3
|
|
else
|
|
# e.g. HTML document, ASCII text, with CRLF line terminators
|
|
# -> we need the part in front of the ','
|
|
lARCH=${lFILE_OUTPUT/,*/}
|
|
fi
|
|
echo "${lARCH//\"/\\\"}"
|
|
}
|
|
|
|
search_parse_log_helper() {
|
|
local lDEPENDENCY="${1:-}"
|
|
local lMARKER="${2:-}"
|
|
local lFILE_TO_CHECK="${3:-}"
|
|
local lDOT_FILE_tmp_FILE="${4:-}"
|
|
local lSAFE_NAME="${5:-}"
|
|
|
|
local lDEPENDENCY_TARGET_ARR=()
|
|
local lDEPENDENCY_TARGET=""
|
|
local lDEPNAME=""
|
|
local lFILE_PATH=""
|
|
local lSAFE_DEP_NAME=""
|
|
local lFILE_SIZE=""
|
|
local lBIN_SEC_FLAGS=""
|
|
local lFILE_ARCH=""
|
|
local lFILE_BIN_DATA=""
|
|
local lCAPABILITIES=""
|
|
local lFILENAME=""
|
|
lFILENAME=$(basename "${lFILE_TO_CHECK}")
|
|
|
|
[[ -z "${lDEPENDENCY}" ]] && return
|
|
if ! [[ "${lDEPENDENCY}" =~ ^[a-zA-Z0-9./_~'-']+$ ]]; then
|
|
return
|
|
fi
|
|
# remove paths like "/////////" or "asdf////bla"
|
|
if [[ "${lDEPENDENCY}" == *".."* || "${lDEPENDENCY}" == *",,"* || "${lDEPENDENCY}" == *"//"* || "${lDEPENDENCY}" == *";;"* ]]; then
|
|
return
|
|
fi
|
|
# as we always search for paths we add a / to the search term
|
|
if [[ "${lDEPENDENCY:0:1}" != "/" ]]; then
|
|
lDEPENDENCY="/${lDEPENDENCY}"
|
|
fi
|
|
# print_output "[*] Testing ${lDEPENDENCY} from ${lFILE_TO_CHECK} against ${FIRMWARE_PATH} - marker ${lMARKER}"
|
|
#
|
|
if [[ -f "${P99_CSV_LOG}" ]]; then
|
|
mapfile -t lDEPENDENCY_TARGET_ARR < <(cut -d ';' -f2 "${P99_CSV_LOG}" | grep "${lDEPENDENCY%\/}$" || true)
|
|
else
|
|
mapfile -t lDEPENDENCY_TARGET_ARR < <(find "${FIRMWARE_PATH}" -wholename "*${lDEPENDENCY%\/}" || true)
|
|
fi
|
|
|
|
if [[ "${#lDEPENDENCY_TARGET_ARR[@]}" -gt 0 ]]; then
|
|
print_output "[*] ${lMARKER}: Testing ${#lDEPENDENCY_TARGET_ARR[@]} possible targets from source ${lFILE_TO_CHECK} / dependency: ${lDEPENDENCY}" "${DEPENDENCY_MAP_LOG}" "" 0
|
|
for lDEPENDENCY_TARGET in "${lDEPENDENCY_TARGET_ARR[@]}"; do
|
|
# print_output "[*] Testing dependency ${lDEPENDENCY_TARGET} from source ${lFILE_TO_CHECK}"
|
|
[[ -d "${lDEPENDENCY_TARGET}" ]] && continue
|
|
lBIN_SEC_FLAGS="NONE"
|
|
lFILE_ARCH="NA"
|
|
lCAPABILITIES="NA"
|
|
|
|
# echo "lDEPENDENCY_TARGET: ${lDEPENDENCY_TARGET}"
|
|
lDEPNAME=$(basename "${lDEPENDENCY_TARGET}")
|
|
if printf '%s\0' "${DEP_BLACKLIST_ARR[@]}" | grep -Fxqz -- "${lDEPNAME}"; then
|
|
continue
|
|
fi
|
|
[[ "${lDEPNAME}" == "${lFILENAME}" ]] && continue
|
|
lFILE_PATH=$(realpath "${lDEPENDENCY_TARGET}" 2>/dev/null || echo "${lDEPENDENCY_TARGET}")
|
|
lSAFE_DEP_NAME="${lDEPNAME//\"/\\\"}"
|
|
lFILE_SIZE=$(stat -c%s "${lDEPENDENCY_TARGET}" 2>/dev/null | numfmt --to=iec || echo "0")
|
|
lFILE_BIN_DATA=$(file -b "${lDEPENDENCY_TARGET}" 2>/dev/null)
|
|
lFILE_ARCH=$(get_arch "${lFILE_BIN_DATA}")
|
|
lCOLOR=$(get_file_color "${lFILE_BIN_DATA}")
|
|
|
|
if [[ "${lFILE_BIN_DATA}" == *"ELF"* ]]; then
|
|
lBIN_SEC_FLAGS="$(binary_sec_scan "${lDEPENDENCY_TARGET}")"
|
|
lCAPABILITIES=$(get_capabilities "${lFILE_TO_CHECK}" "${lFILE_ARCH}")
|
|
fi
|
|
# print_output "[!] Found ${lFILE_ARCH} - ${lDEPENDENCY_TARGET}"
|
|
print_output "[*] ${lMARKER}: Found possible dependency ${ORANGE}${lDEPNAME}${NC} in ${ORANGE}${lSAFE_NAME}${NC}" "${DEPENDENCY_MAP_LOG}" "" 0
|
|
if grep -q " \"${lSAFE_NAME}\" -> \"${lSAFE_DEP_NAME}\";" "${lDOT_FILE_tmp_FILE}" 2>/dev/null; then
|
|
print_output "[*] ${lMARKER}: The dependency (${lSAFE_NAME} -> ${lSAFE_DEP_NAME}) is already available ..." "${DEPENDENCY_MAP_LOG}" "" 0
|
|
# the following is a future extension to also add the new marker as additional source
|
|
# Currently this is not used:
|
|
# lURL="${lFILE_SIZE}|${lFILE_ARCH}|${lBIN_SEC_FLAGS}|${lFILE_PATH}|${lCAPABILITIES}"
|
|
# write_entry_with_marker_check "${lURL}" "${lSAFE_DEP_NAME}" "${lMARKER}" "${lCOLOR}" "${lDOT_FILE_tmp_FILE}"
|
|
else
|
|
print_output "[*] ${lMARKER}: Creating new dependency ${ORANGE}${lSAFE_DEP_NAME}${NC} for ${ORANGE}${lSAFE_NAME}${NC}" "${DEPENDENCY_MAP_LOG}" "" 0
|
|
echo " \"${lSAFE_NAME}\" -> \"${lSAFE_DEP_NAME}\";" >>"${lDOT_FILE_tmp_FILE}"
|
|
fi
|
|
done
|
|
fi
|
|
}
|
|
|
|
write_entry_with_marker_check() {
|
|
local lURL="${1:-}"
|
|
local lSAFE_NAME="${2:-}"
|
|
local lMARKER="${3:-}"
|
|
local lCOLOR="${4:-}"
|
|
local lDOT_FILE_tmp_FILE="${5:-}"
|
|
|
|
# if we find an entry we need to update our source markers
|
|
local lCURRENT_ENTRY_ARR=()
|
|
local lCURRENT_ENTRY=""
|
|
local lCURRENT_SOURCES=""
|
|
if ! [[ -f "${lDOT_FILE_tmp_FILE}" ]]; then
|
|
touch "${lDOT_FILE_tmp_FILE}"
|
|
echo " \"${lSAFE_NAME}\" [shape=box, fillcolor=\"${lCOLOR}\", fontcolor=\"white\", URL=\"${lURL}|${lMARKER}\"];" >>"${lDOT_FILE_tmp_FILE}"
|
|
return
|
|
fi
|
|
mapfile -t lCURRENT_ENTRY_ARR < <(grep " \"${lSAFE_NAME}\" \[shape=.*, fillcolor=\".*\", URL=\".*" "${lDOT_FILE_tmp_FILE}" || true)
|
|
if [[ "${#lCURRENT_ENTRY_ARR[@]}" -gt 0 ]]; then
|
|
for lCURRENT_ENTRY in "${lCURRENT_ENTRY_ARR[@]}"; do
|
|
lCURRENT_SOURCES=$(echo "${lCURRENT_ENTRY}" | grep -o "URL=.*" | cut -d '|' -f6 | cut -d '"' -f1 || true)
|
|
# print_output "[*] lCURRENT_ENTRY: ${lCURRENT_ENTRY}" "no_log"
|
|
# print_output "[*] lCURRENT_SOURCES: ${lCURRENT_SOURCES}" "no_log"
|
|
local lUPDATED_SOURCES="${lCURRENT_SOURCES//NA/}"
|
|
if [[ "${lCURRENT_SOURCES}" != *"${lMARKER}"* ]]; then
|
|
local lUPDATED_SOURCES+=" ${lMARKER}"
|
|
# print_output "[*] lUPDATED_SOURCES: ${lUPDATED_SOURCES}" "no_log"
|
|
# missing sed command to replace the current marker field with the updated sources
|
|
local lNEW_ENTRY=""
|
|
# lNEW_ENTRY=$(echo "${lCURRENT_ENTRY}" | sed "s#|${lCURRENT_SOURCES}\"#|${lUPDATED_SOURCES}\"#")
|
|
lNEW_ENTRY="${lCURRENT_ENTRY//\|${lCURRENT_SOURCES}\"/\|${lUPDATED_SOURCES}\"}"
|
|
# print_output "[*] lNEW_ENTRY: ${lNEW_ENTRY}" "no_log"
|
|
# awk -v s="$lCURRENT_ENTRY" -v r="$lNEW_ENTRY" 's != "" { while ( (i = index($0, s)) > 0 ) { $0 = substr($0, 1, i-1) r substr($0, i + length(s)) } } 1' "${lDOT_FILE_tmp_FILE}"
|
|
awk -v s="${lCURRENT_ENTRY}" -v r="${lNEW_ENTRY}" '
|
|
BEGIN {
|
|
l = length(s)
|
|
}
|
|
{
|
|
out = ""
|
|
while (i = index($0, s)) {
|
|
out = out substr($0, 1, i-1) r
|
|
$0 = substr($0, i + l)
|
|
}
|
|
print out $0
|
|
}' "${lDOT_FILE_tmp_FILE}" >"${lDOT_FILE_tmp_FILE}.tmp" && mv "${lDOT_FILE_tmp_FILE}.tmp" "${lDOT_FILE_tmp_FILE}"
|
|
fi
|
|
done
|
|
fi
|
|
}
|
|
|
|
objdump_dependency_checker() {
|
|
local lFILE_TO_CHECK="${1:-}"
|
|
local lSAFE_NAME="${2:-}"
|
|
local lDOT_FILE_tmp_FILE="${3:-}"
|
|
|
|
local lOBJDUMP_DEPENDENCY_ARR=""
|
|
local lOBJDUMP_DEP=""
|
|
|
|
# Extract dynamic dependencies via objdump.
|
|
mapfile -t lOBJDUMP_DEPENDENCY_ARR < <(objdump -p "${lFILE_TO_CHECK}" 2>/dev/null | grep "NEEDED" | awk '{print $2}' | sort -u || true)
|
|
for lOBJDUMP_DEP in "${lOBJDUMP_DEPENDENCY_ARR[@]}"; do
|
|
# print_output "[*] Testing OBJDUMP-LIB for $lFILE_TO_CHECK - dependency $lOBJDUMP_DEP"
|
|
search_parse_log_helper "${lOBJDUMP_DEP}" "OBJDUMP-LIB" "${lFILE_TO_CHECK}" "${lDOT_FILE_tmp_FILE}" "${lSAFE_NAME}"
|
|
done
|
|
}
|
|
|
|
ldd_dependency_checker() {
|
|
local lFILE_TO_CHECK="${1:-}"
|
|
local lSAFE_NAME="${2:-}"
|
|
local lDOT_FILE_tmp_FILE="${3:-}"
|
|
|
|
local lLDD_DEPENDENCY_ARR=""
|
|
local lLDD_DEP=""
|
|
|
|
# Extract dynamic dependencies via ldd.
|
|
mapfile -t lLDD_DEPENDENCY_ARR < <(ldd "${lFILE_TO_CHECK}" 2>/dev/null | grep -o '/lib[^ ]*' | sort -u || true)
|
|
for lLDD_DEP in "${lLDD_DEPENDENCY_ARR[@]}"; do
|
|
# print_output "[*] Testing LDD-LIB for $lFILE_TO_CHECK - dependency $lLDD_DEP"
|
|
search_parse_log_helper "${lLDD_DEP}" "LDD-LIB" "${lFILE_TO_CHECK}" "${lDOT_FILE_tmp_FILE}" "${lSAFE_NAME}"
|
|
done
|
|
}
|
|
|
|
s115_emulation_dependency_checker() {
|
|
local lFILE_TO_CHECK="${1:-}"
|
|
local lSAFE_NAME="${2:-}"
|
|
local lDOT_FILE_tmp_FILE="${3:-}"
|
|
|
|
local lQEMU_STRACER_FILES_ARR=()
|
|
local lQEMU_STRACER_FILE=""
|
|
|
|
mapfile -t lQEMU_STRACER_FILES_ARR < <(find "${S115_LOG_DIR}" -name "stracer_*${lSAFE_NAME}*")
|
|
|
|
for lQEMU_STRACER_FILE in "${lQEMU_STRACER_FILES_ARR[@]}"; do
|
|
local lSTRACER_DEP_ARR=()
|
|
local lSTRACER_DEP=""
|
|
# extract all the open calls from the qemu logs:
|
|
# grep -a "open\|stat\|bind\|chmod\|link\|write" | grep "/" | tr '"' '\n' | grep "^/" | sort -u
|
|
mapfile -t lSTRACER_DEP_ARR < <(grep -a 'open\|stat\|bind\|chmod\|link\|write' "${lQEMU_STRACER_FILE}" | grep -v "proc" | tr '"' '\n' | grep "^/" | sort -u || true)
|
|
for lSTRACER_DEP in "${lSTRACER_DEP_ARR[@]}"; do
|
|
search_parse_log_helper "${lSTRACER_DEP}" "QEMU-USER" "${lFILE_TO_CHECK}" "${lDOT_FILE_tmp_FILE}" "${lSAFE_NAME}"
|
|
done
|
|
done
|
|
}
|
|
|
|
qemu_system_emulation_dependency_checker() {
|
|
local lFILE_TO_CHECK="${1:-}"
|
|
local lSAFE_NAME="${2:-}"
|
|
local lDOT_FILE_tmp_FILE="${3:-}"
|
|
|
|
local lQEMU_DEPENDENCY_ARR=""
|
|
local lQEMU_DEP=""
|
|
|
|
mapfile -t lQEMU_DEPENDENCY_ARR < <(grep "${lSAFE_NAME}" "${LOG_PATH_MODULE}"/system_emulation_results.log | awk '{print $3}' | sort -u || true)
|
|
for lQEMU_DEP in "${lQEMU_DEPENDENCY_ARR[@]}"; do
|
|
# print_output "[*] Testing system emulation dependency for $lFILE_TO_CHECK - dependency $lQEMU_DEP"
|
|
search_parse_log_helper "${lQEMU_DEP}" "QEMU-SYS" "${lFILE_TO_CHECK}" "${lDOT_FILE_tmp_FILE}" "${lSAFE_NAME}"
|
|
done
|
|
}
|
|
|
|
get_file_color() {
|
|
local lFILE_BIN_DATA="${1:-}"
|
|
|
|
if [[ "${lFILE_BIN_DATA}" == *"perl"* ]]; then
|
|
lCOLOR="#b4a7d6" # some purple style
|
|
elif [[ "${lFILE_BIN_DATA}" == *"lua"* ]]; then
|
|
lCOLOR="#d5a6bd" # some light pink style
|
|
elif [[ "${lFILE_BIN_DATA}" == *"PHP"* ]]; then
|
|
lCOLOR="#f6b26b" # some light orange style
|
|
elif [[ "${lFILE_BIN_DATA}" == *"Python"* ]]; then
|
|
lCOLOR="#e69138" # some orange style
|
|
elif [[ "${lFILE_BIN_DATA}" == *"shell script"* ]]; then
|
|
lCOLOR="#93c47d" # light green
|
|
elif [[ "${lFILE_BIN_DATA}" == *"shared object"* ]]; then
|
|
lCOLOR="#3d85c6" # light dark blue
|
|
elif [[ "${lFILE_BIN_DATA}" == *"ELF"* ]]; then
|
|
lCOLOR="#1a3a5f" # dark blue
|
|
elif [[ "${lFILE_BIN_DATA}" == *"Squashfs filesystem"* ]]; then
|
|
lCOLOR="#b45f06" # dark orange
|
|
elif [[ "${lFILE_BIN_DATA}" == *"HTML document"* ]]; then
|
|
lCOLOR="#c27ba0" # light pink
|
|
elif [[ "${lFILE_BIN_DATA}" == *"JavaScript"* ]]; then
|
|
lCOLOR="#d5a6bd" # light pink
|
|
elif [[ "${lFILE_BIN_DATA}" == *"ASCII text"* ]]; then
|
|
lCOLOR="#ffd966" # orange
|
|
elif [[ "${lFILE_BIN_DATA}" == *"data"* ]]; then
|
|
lCOLOR="#8fce00" # green
|
|
else
|
|
lCOLOR="#f9cb9c" # very light orange
|
|
fi
|
|
echo "${lCOLOR}"
|
|
}
|
|
|
|
main_processing_thread_helper() {
|
|
local lFILE_TO_CHECK="${1:-}"
|
|
|
|
# Extract metadata and sanitize names to prevent DOT injection.
|
|
local lFILENAME=""
|
|
lFILENAME=$(basename "${lFILE_TO_CHECK}")
|
|
local lSAFE_NAME=""
|
|
lSAFE_NAME="${lFILENAME//\"/\\\"}"
|
|
# ensure we escape stuff like [
|
|
lSAFE_NAME=$(printf "%q\n" "${lSAFE_NAME}")
|
|
local lFILE_PATH=""
|
|
lFILE_PATH=$(realpath "${lFILE_TO_CHECK}" 2>/dev/null)
|
|
|
|
local lMD5_SUM_FILE=""
|
|
lMD5_SUM_FILE=$(md5sum "${lFILE_TO_CHECK}" | awk '{print $1}')
|
|
local lDOT_FILE_tmp_FILE="${DOT_FILE_tmp_dir}/tmp_dot_${lMD5_SUM_FILE}.dot"
|
|
# print_output "[*] Using tmp log file ${lDOT_FILE_tmp_FILE} for ${lFILE_TO_CHECK}" "no_log"
|
|
|
|
local lBIN_SEC_FLAGS="NA"
|
|
local lFILE_ARCH="NA"
|
|
local lCAPABILITIES="NA"
|
|
|
|
# Get file size and architecture.
|
|
local lFILE_SIZE=""
|
|
lFILE_SIZE=$(stat -c%s "${lFILE_TO_CHECK}" 2>/dev/null | numfmt --to=iec || echo 0)
|
|
local lFILE_BIN_DATA=""
|
|
lFILE_BIN_DATA=$(file -b "${lFILE_TO_CHECK}" 2>/dev/null)
|
|
local lFILE_ARCH=""
|
|
lFILE_ARCH=$(get_arch "${lFILE_BIN_DATA}")
|
|
local lCOLOR=""
|
|
lCOLOR=$(get_file_color "${lFILE_BIN_DATA}")
|
|
|
|
if [[ "${lFILE_BIN_DATA}" == *"ELF"* ]]; then
|
|
# BINARY SECURITY SCAN
|
|
lBIN_SEC_FLAGS="$(binary_sec_scan "${lFILE_TO_CHECK}")"
|
|
|
|
# CAPABILITY DISCOVERY
|
|
lCAPABILITIES=$(get_capabilities "${lFILE_TO_CHECK}" "${lFILE_ARCH}")
|
|
fi
|
|
# print_output "[*] Testing details $lSAFE_NAME - arch: $lFILE_ARCH - $lBIN_SEC_FLAGS - $lCAPABILITIES"
|
|
|
|
# Store data in the URL attribute for JavaScript retrieval.
|
|
# Format: Size | Arch | SecFlags | FullPath | Capabilities | Source
|
|
# echo " \"${lSAFE_NAME}\" [shape=box, fillcolor=\"${COLOR_BIN}\", fontcolor=\"white\", URL=\"${lFILE_SIZE}|${lFILE_ARCH}|${lBIN_SEC_FLAGS}|${lFILE_PATH}|${lCAPABILITIES}|main-binary\"];" > "${lDOT_FILE_tmp_FILE}"
|
|
local lMARKER="main-binary"
|
|
local lURL="${lFILE_SIZE}|${lFILE_ARCH}|${lBIN_SEC_FLAGS}|${lFILE_PATH}|${lCAPABILITIES}"
|
|
write_entry_with_marker_check "${lURL}" "${lSAFE_NAME}" "${lMARKER}" "${lCOLOR}" "${lDOT_FILE_tmp_FILE}"
|
|
|
|
# following we can find calls to all our dependency checking modules
|
|
if printf '%s\0' "${DETECTION_MECHANISMS_ARR[@]}" | grep -Fxqz -- "LDD-LIB"; then
|
|
ldd_dependency_checker "${lFILE_TO_CHECK}" "${lSAFE_NAME}" "${lDOT_FILE_tmp_FILE}"
|
|
fi
|
|
if printf '%s\0' "${DETECTION_MECHANISMS_ARR[@]}" | grep -Fxqz -- "OBJDUMP-LIB"; then
|
|
objdump_dependency_checker "${lFILE_TO_CHECK}" "${lSAFE_NAME}" "${lDOT_FILE_tmp_FILE}"
|
|
fi
|
|
if printf '%s\0' "${DETECTION_MECHANISMS_ARR[@]}" | grep -Fxqz -- "QEMU-USER"; then
|
|
s115_emulation_dependency_checker "${lFILE_TO_CHECK}" "${lSAFE_NAME}" "${lDOT_FILE_tmp_FILE}"
|
|
fi
|
|
if printf '%s\0' "${DETECTION_MECHANISMS_ARR[@]}" | grep -Fxqz -- "QEMU-SYS"; then
|
|
qemu_system_emulation_dependency_checker "${lFILE_TO_CHECK}" "${lSAFE_NAME}" "${lDOT_FILE_tmp_FILE}"
|
|
fi
|
|
if printf '%s\0' "${DETECTION_MECHANISMS_ARR[@]}" | grep -Fxqz -- "STRICT-STR"; then
|
|
if [[ "${lFILE_BIN_DATA}" != *"Zip archive data"* ]]; then
|
|
strict_string_dependency_checker "${lFILE_TO_CHECK}" "${lSAFE_NAME}" "${lDOT_FILE_tmp_FILE}"
|
|
fi
|
|
fi
|
|
if printf '%s\0' "${DETECTION_MECHANISMS_ARR[@]}" | grep -Fxqz -- "FUZZY-STR"; then
|
|
if [[ "${lFILE_BIN_DATA}" != *"Zip archive data"* ]]; then
|
|
fuzzy_string_dependency_checker "${lFILE_TO_CHECK}" "${lSAFE_NAME}" "${lDOT_FILE_tmp_FILE}"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
build_dot() {
|
|
# GENERATE GRAPHVIZ DOT DATA
|
|
# We use orthogonal splines and specific node separation for a clean dashboard look.
|
|
print_output "[*] Dependency map analysis for ${FIRMWARE_PATH}" "" "${DEPENDENCY_MAP_LOG}"
|
|
|
|
# preparation step - prepare and run the system emulation
|
|
if printf '%s\0' "${DETECTION_MECHANISMS_ARR[@]}" | grep -Fxqz -- "QEMU-SYS"; then
|
|
if [[ $(wc -l 2>/dev/null <"${LOG_PATH_MODULE}"/system_emulation_results.log) -lt 100 ]]; then
|
|
system_emulator_init_runner
|
|
fi
|
|
fi
|
|
|
|
# generate the header in our final dot file:
|
|
# { cmd1; cmd2; } >> file
|
|
{
|
|
echo "digraph EMBA_Map {"
|
|
echo " rankdir=LR;"
|
|
echo " concentrate=true;"
|
|
echo " splines=ortho;"
|
|
echo " nodesep=0.2;"
|
|
echo " ranksep=2.0;"
|
|
echo " node [fontsize=10, fontname=\"Arial\", style=filled];"
|
|
} >>"${DOT_FILE}"
|
|
|
|
local lFILE_CNT=0
|
|
local lFILE_TO_CHECK=""
|
|
for lFILE_TO_CHECK in "${ALL_EXEC_FILES_ARR[@]}"; do
|
|
lFILE_CNT=$((lFILE_CNT + 1))
|
|
print_output "[*] Testing file ${ORANGE}${lFILE_CNT} / ${#ALL_EXEC_FILES_ARR[@]}${NC} - ${lFILE_TO_CHECK}" "${DEPENDENCY_MAP_LOG}" "" 0
|
|
|
|
if [[ "${lFILE_CNT}" -gt "${MAX_MAP_FILES}" ]]; then
|
|
break
|
|
fi
|
|
|
|
# which files should not be handled
|
|
[[ "${lFILE_TO_CHECK}" == *"/decompressed.bin" ]] && continue
|
|
[[ "${lFILE_TO_CHECK}" == *".raw" ]] && continue
|
|
|
|
main_processing_thread_helper "${lFILE_TO_CHECK}" &
|
|
while (($(jobs -r | wc -l) >= MAX_MAP_JOBS)); do
|
|
wait -n
|
|
done
|
|
done
|
|
|
|
# handle links
|
|
local lFS_LINKS_ARR=()
|
|
local lLINK_TO_CHECK=""
|
|
mapfile -t lFS_LINKS_ARR < <(find "${FIRMWARE_PATH}" -type l)
|
|
|
|
for lLINK_TO_CHECK in "${lFS_LINKS_ARR[@]}"; do
|
|
print_output "[*] Testing link ${ORANGE}${lFILE_CNT} / $((${#ALL_EXEC_FILES_ARR[@]} + ${#lFS_LINKS_ARR[@]}))${NC} - ${lLINK_TO_CHECK}" "${DEPENDENCY_MAP_LOG}" "" 0
|
|
lLNK_TARGET=$(readlink "${lLINK_TO_CHECK}" 2>/dev/null || echo "${lLINK_TO_CHECK}")
|
|
# if the link targets something like ../../asdf -> we check for asdf
|
|
while [[ "${lLNK_TARGET}" == "../"* ]]; do
|
|
lLNK_TARGET="${lLNK_TARGET#..\/}"
|
|
done
|
|
local lLNK_TARGET_FILES_ARR=()
|
|
mapfile -t lLNK_TARGET_FILES_ARR < <(find "${FIRMWARE_PATH}" -type f -wholename "*${lLNK_TARGET%\/}")
|
|
local LNK_TARGET_FILE=""
|
|
for LNK_TARGET_FILE in "${lLNK_TARGET_FILES_ARR[@]}"; do
|
|
lMD5_SUM_FILE=""
|
|
lMD5_SUM_FILE=$(md5sum "${LNK_TARGET_FILE}" | awk '{print $1}')
|
|
lDOT_FILE_tmp_FILE="${DOT_FILE_tmp_dir}/tmp_dot_${lMD5_SUM_FILE}.dot"
|
|
print_output "[*] Using tmp log file ${lDOT_FILE_tmp_FILE} for link ${lLINK_TO_CHECK}" "${DEPENDENCY_MAP_LOG}" "" 0
|
|
if [[ -f ${lDOT_FILE_tmp_FILE} ]]; then
|
|
print_output "[*] dot file for ${lMD5_SUM_FILE} - link: ${lLINK_TO_CHECK} - target file: ${LNK_TARGET_FILE} already available" "${DEPENDENCY_MAP_LOG}" "" 0
|
|
else
|
|
touch "${lDOT_FILE_tmp_FILE}"
|
|
fi
|
|
lDEPNAME=$(basename "${lLINK_TO_CHECK}")
|
|
local lLNK_TARGET_NAME=""
|
|
lLNK_TARGET_NAME=$(basename "${LNK_TARGET_FILE}")
|
|
lURL="NA|LNK to ${lLNK_TARGET_NAME}|NA|${lLINK_TO_CHECK}|NA"
|
|
lMARKER="lnk_detection"
|
|
echo " \"${lDEPNAME}\" [shape=ellipse, fillcolor=\"${COLOR_LNK}\", fontcolor=\"white\", URL=\"${lURL}|${lMARKER}\"];" >>"${lDOT_FILE_tmp_FILE}"
|
|
echo " \"${lDEPNAME}\" -> \"${lLNK_TARGET_NAME}\";" >>"${lDOT_FILE_tmp_FILE}"
|
|
done
|
|
if [[ "${lFILE_CNT}" -gt "${MAX_MAP_FILES}" ]]; then
|
|
break
|
|
fi
|
|
lFILE_CNT=$((lFILE_CNT + 1))
|
|
done
|
|
|
|
# wait for all jobs finished
|
|
print_output "[*] Waiting for all map building jobs ..." "no_log"
|
|
wait
|
|
|
|
# lets put all dot files together to the final dot
|
|
print_output "[*] Processing tmp dot files - building the final map ..." "${DEPENDENCY_MAP_LOG}" "" 0
|
|
mapfile -t lDOT_FILE_tmp_ALL_FILES < <(find "${DOT_FILE_tmp_dir}" -type f)
|
|
for lDOT_FILE_tmp in "${lDOT_FILE_tmp_ALL_FILES[@]}"; do
|
|
print_output "[*] Processing dot file ${lDOT_FILE_tmp}" "${DEPENDENCY_MAP_LOG}" "" 0
|
|
cat "${lDOT_FILE_tmp}" >>"${DOT_FILE}"
|
|
done
|
|
|
|
echo "}" >>"${DOT_FILE}"
|
|
}
|
|
|
|
build_svg() {
|
|
local lSVG_BUILD_TIMEOUT="${1:-}"
|
|
export SECONDS="${2:-}"
|
|
|
|
sub_module_title "SVG dependency map"
|
|
|
|
# Adjust the max runtime for neato with the current runtime on AI enabled runs
|
|
# On non AI runs we are using the defined/default SVG_BUILD_TIMEOUT
|
|
if [[ "${AI_OPTION}" -gt 0 ]]; then
|
|
lSVG_BUILD_TIMEOUT=$((lSVG_BUILD_TIMEOUT - SECONDS))
|
|
fi
|
|
|
|
# minimum Neato runtime of 60 seconds
|
|
if [[ "${lSVG_BUILD_TIMEOUT}" -lt 60 ]]; then
|
|
print_error "[*] Out of time for building binary map with neato"
|
|
return
|
|
fi
|
|
|
|
print_output "[*] Generating Neato map with overlap prevention - runtime max ${lSVG_BUILD_TIMEOUT} seconds"
|
|
timeout --preserve-status --signal SIGINT "${lSVG_BUILD_TIMEOUT}s" neato -Goverlap=false -Gsep=+20 -Tsvg "${DOT_FILE}" -o "${SVG_FILE}" || print_output "[-] WARNING: Neato SVG generation failed"
|
|
|
|
# probably some future extension:
|
|
# in case the neato generator failed
|
|
# if [[ ! -f "${SVG_FILE}" ]]; then
|
|
# print_output "[*] Dot map with overlap prevention" "" "${SVG_FILE//\.svg/_dot.svg}"
|
|
# timeout --preserve-status --signal SIGINT "${SVG_BUILD_TIMEOUT}" dot -Goverlap=false -Gsep=+20 -Tsvg "${DOT_FILE}" -o "${SVG_FILE//\.svg/_dot.svg}" || print_output "[-] WARNING: Dot SVG generation failed"
|
|
# fi
|
|
|
|
if [[ -f "${SVG_FILE}" ]]; then
|
|
print_output "[*] Neato map with overlap prevention" "" "${SVG_FILE}"
|
|
# Modify SVG header to include an ID for the pan-zoom library.
|
|
sed -i 's|<svg [^>]*>|<svg id="map-svg" style="width:100%;height:100%;">|' "${SVG_FILE}"
|
|
# Optional: Falls Graphviz hartgecodete width/height reinschreibt, diese löschen
|
|
sed -i 's/width="[^"]*" height="[^"]*"//i' "${SVG_FILE}"
|
|
fi
|
|
if [[ -f "${SVG_FILE//\.svg/_dot.svg}" ]]; then
|
|
# Modify SVG header to include an ID for the pan-zoom library.
|
|
sed -i 's|<svg [^>]*>|<svg id="map-svg" style="width:100%;height:100%;">|' "${SVG_FILE//\.svg/_dot.svg}"
|
|
# Optional: Falls Graphviz hartgecodete width/height reinschreibt, diese löschen
|
|
sed -i 's/width="[^"]*" height="[^"]*"//i' "${SVG_FILE//\.svg/_dot.svg}"
|
|
fi
|
|
}
|
|
|
|
build_html() {
|
|
sub_module_title "Interactive HTML dependency map"
|
|
|
|
if [[ -f "${SVG_FILE}" ]]; then
|
|
sed -e "/%%SVG_FILE_CONTENT%%/{r ${SVG_FILE}" -e "d}" "${BASE_HTML_TEMPLATE}" >"${HTML_FILE}"
|
|
sed -i "s/%%COLOR_BIN%%/${COLOR_BIN}/" "${HTML_FILE}"
|
|
sed -i "s#%%EMBA_LOGO%%#$(basename "${LOGO_FILE}")#" "${HTML_FILE}"
|
|
sed -i "s#%%JS_LIB%%#$(basename "${JS_LIB}")#" "${HTML_FILE}"
|
|
fi
|
|
if [[ -f "${SVG_FILE//\.svg/_dot.svg}" ]]; then
|
|
sed -e "/%%SVG_FILE_CONTENT%%/{r ${SVG_FILE//\.svg/_dot.svg}" -e "d}" "${BASE_HTML_TEMPLATE}" >"${HTML_FILE//-map.html/-dot_map.html}"
|
|
sed -i "s/%%COLOR_BIN%%/${COLOR_BIN}/" "${HTML_FILE//-map.html/-dot_map.html}"
|
|
sed -i "s#%%EMBA_LOGO%%#$(basename "${LOGO_FILE}")#" "${HTML_FILE//-map.html/-dot_map.html}"
|
|
sed -i "s#%%JS_LIB%%#$(basename "${JS_LIB}")#" "${HTML_FILE//-map.html/-dot_map.html}"
|
|
fi
|
|
|
|
if [[ -f "${HTML_FILE}" && -f "${SVG_FILE}" ]]; then
|
|
print_output "[+] EMBA dependency neato map generated: ${ORANGE}${HTML_FILE}${NC}"
|
|
write_link "${HTML_FILE}"
|
|
fi
|
|
if [[ -f "${HTML_FILE//-map.html/-dot_map.html}" && -f "${SVG_FILE//\.svg/_dot.svg}" ]]; then
|
|
print_output "[+] EMBA dependency dot map generated: ${ORANGE}${HTML_FILE//-map.html/-dot_map.html}${NC}"
|
|
write_link "${HTML_FILE//-map.html/-dot_map.html}"
|
|
fi
|
|
}
|