diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 54e4392c2..492abc8da 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -42,4 +42,4 @@ jobs: source "./external/emba_venv/bin/activate" echo "GH_action:true" > ./config/gh_action sudo docker-compose build --no-cache --pull - NO_UPDATE_CHECK=1 sudo -E sudo ./emba -d 2 -y + NO_UPDATE_CHECK=1 sudo -E ./emba -d 2 -y diff --git a/docker-compose.yml b/docker-compose.yml index 3a69e3d26..25bcc632d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,7 +2,7 @@ version: "3" services: # nosemgrep emba: - image: embeddedanalyzer/emba:1.4.1d + image: embeddedanalyzer/emba:1.4.1e container_name: emba read_only: true # all pre-checker mount modules need privileged mode @@ -12,7 +12,8 @@ services: # /root/.config is needed for cwe_checker # /root/.local is needed for cwe_checker tmpfs: - - /tmp + # exec on /tmp is needed for capa -> Todo: find better solution + - /tmp:exec - /root/.config/ - /root/.local/share/composer/ - /root/.local/share/cwe_checker/ @@ -50,7 +51,7 @@ services: soft: 0 emba_quest: - image: embeddedanalyzer/emba:1.4.1d + image: embeddedanalyzer/emba:1.4.1e container_name: emba_quest read_only: true tmpfs: diff --git a/helpers/helpers_emba_dependency_check.sh b/helpers/helpers_emba_dependency_check.sh index 7539a63da..c815236cb 100755 --- a/helpers/helpers_emba_dependency_check.sh +++ b/helpers/helpers_emba_dependency_check.sh @@ -695,6 +695,7 @@ dependency_check() # radare2 check_dep_tool "radare2" "r2" fi + check_dep_file "Identify capabilities in executable files" "${EXT_DIR}/capa" # bandit python security tester check_dep_tool "bandit - python vulnerability scanner" "bandit" diff --git a/helpers/helpers_emba_status_bar.sh b/helpers/helpers_emba_status_bar.sh index 3b4a75d0a..91fa61a6c 100755 --- a/helpers/helpers_emba_status_bar.sh +++ b/helpers/helpers_emba_status_bar.sh @@ -351,7 +351,7 @@ update_box_status_2() { fi while [[ "${BOX_SIZE}" -gt 0 ]]; do - [[ -f "${TMP_DIR}""/LINES.log" ]] && lLINES=$(cat "${TMP_DIR}""/LINES.log") + [[ -f "${TMP_DIR}""/LINES.log" ]] && lLINES=$(cat "${TMP_DIR}""/LINES.log" || true) PHASE_STR=$(grep 'phase started' "${LOG_DIR}/emba.log" 2> /dev/null | tail -1 | cut -d"-" -f2 | awk '{print $1}' || true) [[ "${PHASE_STR}" == "Pre" ]] && PHASE_STR="Extraction" diff --git a/installer/I13_disasm.sh b/installer/I13_disasm.sh index 13e938d52..2ecbaf9ac 100755 --- a/installer/I13_disasm.sh +++ b/installer/I13_disasm.sh @@ -27,6 +27,7 @@ I13_disasm() { if [[ "${LIST_DEP}" -eq 1 ]] || [[ "${IN_DOCKER}" -eq 1 ]] || [[ "${DOCKER_SETUP}" -eq 0 ]] ; then print_file_info "${BINUTIL_VERSION_NAME}" "The GNU Binutils are a collection of binary tools." "https://ftp.gnu.org/gnu/binutils/${BINUTIL_VERSION_NAME}.tar.gz" "external/${BINUTIL_VERSION_NAME}.tar.gz" "external/objdump" + print_file_info "Capa" "Capa - Open-source tool to identify capabilities in executable files." "https://github.com/mandiant/capa/releases/download/v7.1.0/capa-v7.1.0-linux.zip" "external/capa-v7.1.0-linux.zip" print_tool_info "texinfo" 1 print_tool_info "git" 1 print_tool_info "wget" 1 @@ -58,6 +59,12 @@ I13_disasm() { # apt-get install "${INSTALL_APP_LIST[@]}" -y --no-install-recommends apt-get install "${INSTALL_APP_LIST[@]}" -y + if ! [[ -f "external/capa" ]]; then + download_file "Capa" "https://github.com/mandiant/capa/releases/download/v7.1.0/capa-v7.1.0-linux.zip" "external/capa-v7.1.0-linux.zip" + unzip external/capa-v7.1.0-linux.zip -d external + rm external/capa-v7.1.0-linux.zip + fi + if ! [[ -f "external/objdump" ]] ; then download_file "${BINUTIL_VERSION_NAME}" "https://ftp.gnu.org/gnu/binutils/${BINUTIL_VERSION_NAME}.tar.gz" "external/${BINUTIL_VERSION_NAME}.tar.gz" if [[ -f "external/${BINUTIL_VERSION_NAME}.tar.gz" ]] ; then diff --git a/modules/L10_system_emulation.sh b/modules/L10_system_emulation.sh index 5751b3bba..6cd330e94 100755 --- a/modules/L10_system_emulation.sh +++ b/modules/L10_system_emulation.sh @@ -120,11 +120,9 @@ L10_system_emulation() { if [[ -n "${D_END}" ]]; then export TAPDEV_0="tap0_0" local lARCH_END="" - export D_END_lower="" - D_END_lower="$(echo "${D_END}" | tr '[:upper:]' '[:lower:]')" - lARCH_END="$(echo "${ARCH}" | tr '[:upper:]' '[:lower:]')" - lARCH_END+="${D_END_lower}" + lARCH_END="${ARCH,,}" + lARCH_END+="${D_END,,}" # default is ARM_SF -> we only need to check if it is HF # The information is based on the results of architecture_check() @@ -1520,9 +1518,9 @@ get_networking_details_emulation() { lIP="${lIP/\.}" IP_ADDRESS_="" - if [[ "${D_END_lower}" == "eb" ]]; then + if [[ "${D_END,,}" == "eb" ]]; then IP_ADDRESS_="${lIP}" - elif [[ "${D_END_lower}" == "el" ]]; then + elif [[ "${D_END,,}" == "el" ]]; then IP_ADDRESS_=$(echo "${lIP}" | tr '.' '\n' | tac | tr '\n' '.' | sed 's/\.$//') fi diff --git a/modules/S18_capa_checker.sh b/modules/S18_capa_checker.sh new file mode 100755 index 000000000..117c568bb --- /dev/null +++ b/modules/S18_capa_checker.sh @@ -0,0 +1,134 @@ +#!/bin/bash -p + +# EMBA - EMBEDDED LINUX ANALYZER +# +# Copyright 2024-2024 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: This module uses capa (https://github.com/mandiant/capa) for detecting binary behavior +# Currently capa only supports x86 architecture + +S18_capa_checker() { + module_log_init "${FUNCNAME[0]}" + module_title "Analyse binary behavior with capa" + pre_module_reporter "${FUNCNAME[0]}" + + if [[ ! -e "${EXT_DIR}"/capa ]]; then + print_output "[-] Missing capa installation ... exit module" + module_end_log "${FUNCNAME[0]}" 0 + return + fi + if [[ ${BINARY_EXTENDED} -ne 1 ]] ; then + print_output "[-] ${FUNCNAME[0]} - BINARY_EXTENDED not set to 1. You can set it up via a scan-profile." + module_end_log "${FUNCNAME[0]}" 0 + return + fi + if [[ "${FULL_TEST}" -ne 1 ]]; then + # we only need to wait if we are not using the full_scan profile + module_wait "S13_weak_func_check" + fi + if [[ -s "${CSV_DIR}"/s13_weak_func_check.csv ]]; then + local BINARIES=() + # usually binaries with strcpy or system calls are more interesting for further analysis + # to keep analysis time low we only check these bins + mapfile -t BINARIES < <(grep "strcpy\|system" "${CSV_DIR}"/s13_weak_func_check.csv | sort -k 3 -t ';' -n -r | awk '{print $1}' || true) + fi + + local lBINARY="" + local lBIN_TO_CHECK="" + local lWAIT_PIDS_S18=() + local lBIN_TO_CHECK_ARR=() + local lBINS_CHECKED_CNT=0 + + for lBINARY in "${BINARIES[@]}"; do + mapfile -t lBIN_TO_CHECK_ARR < <(find "${LOG_DIR}/firmware" -name "$(basename "${lBINARY}")" | sort -u || true) + for lBIN_TO_CHECK in "${lBIN_TO_CHECK_ARR[@]}"; do + if [[ -f "${BASE_LINUX_FILES}" && "${FULL_TEST}" -eq 0 ]]; then + # if we have the base linux config file we only test non known Linux binaries + # with this we do not waste too much time on open source Linux stuff + lNAME=$(basename "${lBIN_TO_CHECK}" 2> /dev/null) + if grep -E -q "^${lNAME}$" "${BASE_LINUX_FILES}" 2>/dev/null; then + continue 2 + fi + fi + + if ( file "${lBIN_TO_CHECK}" | grep -q "ELF.*Intel" ); then + # ensure we have not tested this binary + local lBIN_MD5="" + lBIN_MD5="$(md5sum "${lBIN_TO_CHECK}" | awk '{print $1}')" + if ( grep -q "${lBIN_MD5}" "${TMP_DIR}"/s18_checked.tmp 2>/dev/null); then + # print_output "[*] ${ORANGE}${lBIN_TO_CHECK}${NC} already tested with capa" "no_log" + continue + fi + + if [[ "${THREADED}" -eq 1 ]]; then + capa_runner_fct "${lBIN_TO_CHECK}" & + local lTMP_PID="$!" + store_kill_pids "${lTMP_PID}" + lWAIT_PIDS_S18+=( "${lTMP_PID}" ) + max_pids_protection "${MAX_MOD_THREADS}" "${lWAIT_PIDS_S18[@]}" + else + capa_runner_fct "${lBIN_TO_CHECK}" + fi + + # in normal operation we stop checking after the first 20 binaries + # if FULL_TEST is activated we are testing all binaries -> this takes a long time + lBINS_CHECKED_CNT=$(wc -l "${TMP_DIR}"/s18_checked.tmp 2>/dev/null || true) + if [[ "${lBINS_CHECKED_CNT/\ *}" -gt 20 ]] && [[ "${FULL_TEST}" -ne 1 ]]; then + print_output "[*] 20 binaries already analysed - ending capa binary analysis now." "no_log" + print_output "[*] For complete analysis enable FULL_TEST." "no_log" + break 2 + fi + else + print_output "[-] Binary behavior testing with capa for $(print_path "${lBIN_TO_CHECK}") not possible ... unsupported architecture" "no_log" + fi + done + done + + [[ "${THREADED}" -eq 1 ]] && wait_for_pid "${lWAIT_PIDS_S18[@]}" + + print_ln + lBINS_CHECKED_CNT=$(wc -l "${TMP_DIR}"/s18_checked.tmp 2>/dev/null || true) + print_output "[*] Found ${ORANGE}${lBINS_CHECKED_CNT/\ *}${NC} capa results in ${ORANGE}${#BINARIES[@]}${NC} binaries" + rm "${TMP_DIR}"/s18_checked.tmp 2>/dev/null + + module_end_log "${FUNCNAME[0]}" "${lBINS_CHECKED_CNT/\ *}" +} + +capa_runner_fct() { + local lBINARY="${1:-}" + + local lATTACK_CODES_ARR=() + local lATTACK_CODE="" + local lBIN_NAME="" + lBIN_NAME="$(basename "${lBINARY}")" + local lBIN_MD5="" + + print_output "[*] Testing binary behavior with capa for $(print_path "${lBINARY}")" "no_log" + "${EXT_DIR}"/capa "${lBINARY}" > "${LOG_PATH_MODULE}/capa_${lBIN_NAME}".log || print_output "[-] Capa analysis failed for ${lBINARY}" "no_log" + + if [[ -s "${LOG_PATH_MODULE}/capa_${lBIN_NAME}.log" ]]; then + print_output "[+] Capa results for ${ORANGE}$(print_path "${lBINARY}")${NC}" "" "${LOG_PATH_MODULE}/capa_${lBIN_NAME}.log" + mapfile -t lATTACK_CODES_ARR < <(grep -o "T[0-9]\{4\}\(\.[0-9]\{3\}\)\?" "${LOG_PATH_MODULE}/capa_${lBIN_NAME}.log" || true) + for lATTACK_CODE in "${lATTACK_CODES_ARR[@]}"; do + # check for ATT&CK framework codes and insert the correct links + sed -i "/\ ${lATTACK_CODE}\ /a\[REF\] https://attack.mitre.org/techniques/${lATTACK_CODE/\./\/}" "${LOG_PATH_MODULE}/capa_${lBIN_NAME}.log" || true + done + sed -i '/\ MBC Objective/a \[REF\] https://github.com/MBCProject/mbc-markdown' "${LOG_PATH_MODULE}/capa_${lBIN_NAME}.log" || true + lBIN_MD5="$(md5sum "${lBIN_TO_CHECK}" | awk '{print $1}')" + if ( ! grep -q "${lBIN_MD5}" "${TMP_DIR}"/s18_checked.tmp 2>/dev/null); then + echo "${lBIN_MD5}" >> "${TMP_DIR}"/s18_checked.tmp + fi + else + print_output "[*] No capa results for $(print_path "${lBINARY}")" "no_log" + rm "${LOG_PATH_MODULE}/capa_${lBIN_NAME}.log" || true + fi +}