Files
emba/modules/L15_emulated_checks_nmap.sh
2026-07-17 12:29:56 +02:00

251 lines
11 KiB
Bash
Executable File

#!/bin/bash -p
# EMBA - EMBEDDED LINUX ANALYZER
#
# 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
# Description: Tests the emulated live system which is build and started in L10
# Currently this is an experimental module and needs to be activated separately via the -Q switch.
# It is also recommended to only use this technique in a dockerized or virtualized environment.
L15_emulated_checks_nmap() {
local lMODULE_END=0
export NMAP_PORTS_SERVICES_ARR=()
export CONFIDENCE_LEVEL=3
if [[ "${SYS_ONLINE}" -eq 1 ]] && [[ "${TCP}" == "ok" ]]; then
module_log_init "${FUNCNAME[0]}"
module_title "Nmap scans of emulated device."
pre_module_reporter "${FUNCNAME[0]}"
if [[ -v IP_ADDRESS_ ]]; then
check_live_nmap_basic "${IP_ADDRESS_}"
lMODULE_END=1
else
print_output "[!] No IP address found"
fi
write_log ""
write_log "[*] Statistics:${#NMAP_PORTS_SERVICES_ARR[@]}"
module_end_log "${FUNCNAME[0]}" "${lMODULE_END}"
fi
}
check_live_nmap_basic() {
local lIP_ADDRESS="${1:-}"
local lNMAP_RESULT_FILES_ARR=()
local lNMAP_RESULTF=""
local lNMAP_SERVICES_ARR=()
local lS09_L15_CHECK_ARR=()
local lS116_L15_CHECK_ARR=()
local lSERVICE=""
local lSERVICE_NAME=""
local lTYPE=""
local lNMAP_CPE_DETECTION_ARR=()
local lNMAP_CPE=""
local lNMAP_CPES=""
local lNMAP_CPES_ARR=()
local lS09_L15_MATCH=""
local lS116_L15_MATCH=""
export NMAP_PORTS_SERVICES_ARR=()
sub_module_title "Nmap portscans for emulated system with IP ${ORANGE}${lIP_ADDRESS}${NC}"
cp "${ARCHIVE_PATH}"/*nmap_emba_[0-9]-"${lIP_ADDRESS}"*.gnmap "${LOG_PATH_MODULE}" 2>/dev/null || true
cp "${ARCHIVE_PATH}"/*nmap_emba_[0-9]-"${lIP_ADDRESS}"*.nmap "${LOG_PATH_MODULE}" 2>/dev/null || true
# find all Nmap results
mapfile -t lNMAP_RESULT_FILES_ARR < <(find "${LOG_PATH_MODULE}" -name "*.nmap")
if [[ "${#lNMAP_RESULT_FILES_ARR[@]}" -gt 0 ]]; then
for lNMAP_RESULTF in "${lNMAP_RESULT_FILES_ARR[@]}"; do
print_output "[*] Found Nmap results ${ORANGE}$(basename "${lNMAP_RESULTF}")${NC}:"
tee -a "${LOG_FILE}" <"${lNMAP_RESULTF}"
print_ln
done
else
# if no Nmap results are found we initiate a scan
if ! system_online_check "${lIP_ADDRESS}"; then
if ! restart_emulation "${lIP_ADDRESS}" "${IMAGE_NAME}" 1 "${STATE_CHECK_MECHANISM}"; then
print_output "[-] System not responding - Not performing Nmap checks"
return
fi
fi
nmap -Pn -n -sSV -A --host-timeout 30m "${lIP_ADDRESS}" -oA "${LOG_PATH_MODULE}"/nmap-basic-"${lIP_ADDRESS}" | tee -a "${LOG_FILE}"
fi
print_ln
# extract only the service details from gnmap output file:
mapfile -t lNMAP_SERVICES_ARR < <(grep -h -a "open" "${LOG_PATH_MODULE}"/*.gnmap | cut -d: -f3- | sed s/'\t'/'\n\t'/g | sed s/'\/, '/'\n\t\t'/g | sed s/'Ports: '/'Ports:\n\t\t'/g | grep -v "/closed/\|filtered/" | grep -v "Host: \|Ports:\|Ignored State:\|OS: \|Seq Index: \|Status: \|IP ID Seq: \|^# " | sed 's/^[[:blank:]].*\/\///' | sed 's/\/$//g' | sort -u || true)
mapfile -t NMAP_PORTS_SERVICES_ARR < <(grep -h -a "open" "${LOG_PATH_MODULE}"/*.nmap | awk '{print $1,$3}' | grep "[0-9]" | tr -d '?' | sort -u || true)
# extract cpe information like the following:
# Service Info: OS: Linux; Device: WAP; CPE: cpe:/h:dlink:dir-300:2.14, cpe:/o:linux:linux_kernel, cpe:/h:d-link:dir-300
mapfile -t lNMAP_CPE_DETECTION_ARR < <(grep -ah "Service Info: " "${LOG_PATH_MODULE}"/*.nmap | grep -a "CPE: .*" | sort -u || true)
lTYPE="Nmap scan (Scan info)"
if [[ "${#lNMAP_CPE_DETECTION_ARR[@]}" -gt 0 ]]; then
for lNMAP_CPES in "${lNMAP_CPE_DETECTION_ARR[@]}"; do
lNMAP_CPES=$(echo "${lNMAP_CPES}" | grep -o "cpe:.*" || true)
# rewrite the string into an array:
readarray -d ', ' -t lNMAP_CPES_ARR < <(printf "%s" "${lNMAP_CPES}")
for lNMAP_CPE in "${lNMAP_CPES_ARR[@]}"; do
if [[ "${lNMAP_CPE}" == *"windows"* ]]; then
# we emulate only Linux -> if windows is detected this is a FP
continue
fi
lNMAP_CPE=${lNMAP_CPE/ /}
lNMAP_CPE=${lNMAP_CPE//cpe:\//}
# remove h/o/b from start -> we need to start with :
lNMAP_CPE=${lNMAP_CPE#[hob]}
# just to ensure there is some kind of version information in our entry
if [[ "$(echo "${lNMAP_CPE}" | tr ':' '\n' | wc -l)" -lt 4 ]]; then
# if the length does not match we can drop these results
print_output "[-] WARNING: Identifier ${lNMAP_CPE} is probably incorrect and will be removed" "no_log"
continue
fi
if [[ "${lNMAP_CPE}" =~ :.*[0-9].* ]]; then
print_output "[*] CPE details detected: ${ORANGE}${lNMAP_CPE}${NC}"
fi
done
done
fi
lTYPE="Nmap scan (Service info)"
if [[ "${#NMAP_PORTS_SERVICES_ARR[@]}" -gt 0 ]]; then
for lSERVICE in "${NMAP_PORTS_SERVICES_ARR[@]}"; do
print_output "[*] Service detected: ${ORANGE}${lSERVICE}${NC}"
lSERVICE_NAME="$(escape_echo "$(echo "${lSERVICE}" | awk '{print $2}')")"
# just in case we have a / in our lSERVICE_NAME
lSERVICE_NAME="${lSERVICE_NAME/\//\\\/}"
if [[ "${lSERVICE_NAME}" == "unknown" ]] || [[ "${lSERVICE_NAME}" == "tcpwrapped" ]] || [[ -z "${lSERVICE_NAME}" ]]; then
continue
fi
if [[ -f "${S09_CSV_LOG}" ]]; then
# Let's check if we have already found details about this service in our other modules (S09, S115/S116)
mapfile -t lS09_L15_CHECK_ARR < <(awk -v IGNORECASE=1 -F\; '$3 $4 ~ /'"${lSERVICE_NAME}"'/' "${S09_CSV_LOG}" | sort -u || true)
if [[ "${#lS09_L15_CHECK_ARR[@]}" -gt 0 ]]; then
for lS09_L15_MATCH in "${lS09_L15_CHECK_ARR[@]}"; do
lS09_L15_MATCH=$(cut -d ';' -f4 <<<"${lS09_L15_MATCH}") # field 4
if ! grep -q ";${lS09_L15_MATCH};" "${L15_CSV_LOG}" 2>/dev/null; then
print_output "[+] Service also detected with static analysis (S09): ${ORANGE}${lS09_L15_MATCH}${NC}"
echo "${lS09_L15_MATCH}" >>"${L15_CSV_LOG}"
fi
done
fi
fi
if [[ -f "${S116_CSV_LOG}" ]]; then
mapfile -t lS116_L15_CHECK_ARR < <(awk -v IGNORECASE=1 -F\; '$3 $4 ~ /'"${lSERVICE_NAME}"'/' "${S116_CSV_LOG}" | sort -u || true)
if [[ "${#lS116_L15_CHECK_ARR[@]}" -gt 0 ]]; then
for lS116_L15_MATCH in "${lS116_L15_CHECK_ARR[@]}"; do
lS116_L15_MATCH=$(cut -d ';' -f4 <<<"${lS116_L15_MATCH}") # field 4
if ! grep -q ";${lS116_L15_MATCH};" "${L15_CSV_LOG}" 2>/dev/null; then
print_output "[+] Service also detected with dynamic user-mode emulation (S115/S116): ${ORANGE}${lS116_L15_MATCH}${NC}"
echo "${lS116_L15_MATCH}" >>"${L15_CSV_LOG}"
fi
done
fi
fi
done
fi
if [[ "${#lNMAP_SERVICES_ARR[@]}" -gt 0 ]]; then
print_ln
local lWAIT_PIDS_L15_ARR=()
for lSERVICE in "${lNMAP_SERVICES_ARR[@]}"; do
if ! [[ "${lSERVICE}" =~ [0-9] ]]; then
continue
fi
l15_version_detector "${lSERVICE}" "${lTYPE}" &
local lTMP_PID="$!"
lWAIT_PIDS_L15_ARR+=("${lTMP_PID}")
max_pids_protection "${MAX_MOD_THREADS}" lWAIT_PIDS_L15_ARR
done
wait_for_pid "${lWAIT_PIDS_L15_ARR[@]}"
fi
print_ln
print_output "[*] Nmap portscans for emulated system with IP ${ORANGE}${lIP_ADDRESS}${NC} finished"
}
l15_version_detector() {
local lSERVICE="${1:-}"
local lTYPE="${2:-}"
local lVERSION_IDENTIFIER_ARR=()
local lVERSION_IDENTIFIER_ARR_tmp=()
local lVERSION_IDENTIFIER=""
print_output "[*] Testing detected service ${ORANGE}${lSERVICE}${NC}" "no_log"
local lVERSION_IDENTIFIER_CFG_PATH="${CONFIG_DIR}"/bin_version_identifiers
local lVERSION_IDENTIFIER_CFG_ARR=()
local lVERSION_JSON_CFG=""
local lWAIT_PIDS_L15_ARR_02=()
mapfile -t lVERSION_IDENTIFIER_CFG_ARR < <(find "${lVERSION_IDENTIFIER_CFG_PATH}" -name "*.json")
for lVERSION_JSON_CFG in "${lVERSION_IDENTIFIER_CFG_ARR[@]}"; do
l15_version_detector_threader "${lVERSION_JSON_CFG}" "${lSERVICE}" "${lTYPE}" &
local lTMP_PID="$!"
store_kill_pids "${lTMP_PID}"
WAIT_PIDS_L15_ARR_02+=("${lTMP_PID}")
max_pids_protection "${MAX_MOD_THREADS}" lWAIT_PIDS_L15_ARR_02
done
wait_for_pid "${lWAIT_PIDS_L15_ARR_02[@]}"
}
l15_version_detector_threader() {
local lVERSION_JSON_CFG="${1:-}"
local lSERVICE="${2:-}"
local lTYPE="${3:-}"
local lVERSION_IDENTIFIED=""
export PACKAGING_SYSTEM="system_emulation"
# lBINARY_ENTRY is needed for populating the source path in the final SBOM
local lBINARY_ENTRY="NA;${PACKAGING_SYSTEM};NA;NA;NA;NA;NA;NA;NA"
# print_output "[*] Testing json config ${ORANGE}${lVERSION_JSON_CFG}${NC}" "no_log"
local lRULE_IDENTIFIER=""
lRULE_IDENTIFIER=$(jq -r .identifier "${lVERSION_JSON_CFG}" || print_error "[-] Error in parsing ${lVERSION_JSON_CFG}")
local lLICENSES_ARR=()
mapfile -t lLICENSES_ARR < <(jq -r .licenses[] "${lVERSION_JSON_CFG}" 2>/dev/null || true)
local lPRODUCT_NAME_ARR=()
# shellcheck disable=SC2034
mapfile -t lPRODUCT_NAME_ARR < <(jq -r .product_names[] "${lVERSION_JSON_CFG}" 2>/dev/null || true)
local lVENDOR_NAME_ARR=()
# shellcheck disable=SC2034
mapfile -t lVENDOR_NAME_ARR < <(jq -r .vendor_names[] "${lVERSION_JSON_CFG}" 2>/dev/null || true)
local lCSV_REGEX_ARR=()
# shellcheck disable=SC2034
mapfile -t lCSV_REGEX_ARR < <(jq -r .version_extraction[] "${lVERSION_JSON_CFG}" 2>/dev/null || true)
local lVERSION_IDENTIFIER_ARR=()
mapfile -t lVERSION_IDENTIFIER_ARR < <(jq -r .grep_commands[] "${lVERSION_JSON_CFG}" 2>/dev/null || true)
mapfile -t lVERSION_IDENTIFIER_ARR_tmp < <(jq -r .live_grep_commands[] "${lVERSION_JSON_CFG}" 2>/dev/null || true)
lVERSION_IDENTIFIER_ARR+=("${lVERSION_IDENTIFIER_ARR_tmp[@]}")
for lVERSION_IDENTIFIER in "${lVERSION_IDENTIFIER_ARR[@]}"; do
lVERSION_IDENTIFIED=$(echo "${lSERVICE}" | grep -o -a -E "${lVERSION_IDENTIFIER}" | head -1 2>/dev/null || true)
if [[ -n ${lVERSION_IDENTIFIED} ]]; then
print_output "[+] Version information found ${RED}${lVERSION_IDENTIFIED}${GREEN} in emulated service ${ORANGE}${lSERVICE}${GREEN} (license: ${ORANGE}${lLICENSES_ARR[*]}${GREEN}) (${ORANGE}${lTYPE}${GREEN})."
export TYPE="${lTYPE}"
if version_parsing_logging "${S09_CSV_LOG}" "L15_emulated_checks_nmap" "${lVERSION_IDENTIFIED}" "${lBINARY_ENTRY}" "${lRULE_IDENTIFIER}" "lVENDOR_NAME_ARR" "lPRODUCT_NAME_ARR" "lLICENSES_ARR" "lCSV_REGEX_ARR"; then
# print_output "[*] back from logging for ${lVERSION_IDENTIFIED} -> continue to next service -> return"
return
fi
fi
done
}