#!/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 " "${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_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_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 }