Files
emba/modules/P50_binwalk_extractor.sh
T
2026-09-23 09:10:01 +02:00

184 lines
7.1 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: Extracts firmware with binwalk to the module log directory.
# This module is a fallback module for the very rare case that our extraction process was failing
# e.g. in cases like this https://github.com/onekey-sec/sasquatch/issues/19
# Pre-checker threading mode - if set to 1, these modules will run in threaded mode
# This module extracts the firmware and is blocking modules that needs executed before the following modules can run
export PRE_THREAD_ENA=0
P50_binwalk_extractor() {
module_log_init "${FUNCNAME[0]}"
# shellcheck disable=SC2153
if [[ -d "${FIRMWARE_PATH}" ]] && [[ "${RTOS}" -eq 1 ]]; then
detect_root_dir_helper "${FIRMWARE_PATH}"
fi
# if we have a verified UEFI firmware we do not need to do anything here
# if we have already found a linux (RTOS==0) we do not need to do anything here
if [[ "${UEFI_VERIFIED}" -eq 1 ]] || [[ "${RTOS}" -eq 0 ]] || [[ "${DJI_DETECTED}" -eq 1 ]] || [[ "${WINDOWS_EXE}" -eq 1 ]] || [[ "${BINWALK_DISABLE:-0}" -eq 1 ]]; then
module_end_log "${FUNCNAME[0]}" 0
return
fi
# We have seen multiple issues in system emulation while using binwalk
# * unprintable chars in paths -> remediation in place
# * lost symlinks in different firmware extractions -> Todo: Issue
# * lost permissions of executables -> remediation in place
# Currently we disable binwalk here and switch automatically to unblob is main extractor while
# system emulation runs. If unblob fails we are going to try an additional extraction round with
# binwalk.
if [[ "${FULL_EMULATION}" -eq 1 ]]; then
print_output "[-] Binwalk v3 has issues with symbolic links and is disabled for system emulation"
module_end_log "${FUNCNAME[0]}" 0
return
fi
# we do not rely on any EMBA extraction mechanism -> we use the original firmware file
local lFW_PATH_BINWALK="${FIRMWARE_PATH_BAK}"
if [[ -d "${lFW_PATH_BINWALK}" ]]; then
print_output "[-] Binwalk module only deals with firmware files - directories should be already handled via deep extractor"
module_end_log "${FUNCNAME[0]}" 0
return
fi
local lFW_NAME_BINWALK=""
lFW_NAME_BINWALK="$(basename "${lFW_PATH_BINWALK}")"
local lFILES_BINWALK_ARR=()
local lBINARY=""
local lWAIT_PIDS_P99_ARR=()
module_title "Binwalk binary firmware extractor"
pre_module_reporter "${FUNCNAME[0]}"
local lLINUX_PATH_COUNTER_BINWALK=0
local lOUTPUT_DIR_BINWALK="${LOG_DIR}"/firmware/binwalk_extracted
local lBINWALK_LOG_FILE="${LOG_PATH_MODULE}/binwalk-${lFW_NAME_BINWALK}.log"
if [[ -f "${lFW_PATH_BINWALK}" ]]; then
binwalker_matryoshka "${lFW_PATH_BINWALK}" "${lOUTPUT_DIR_BINWALK}" "${lBINWALK_LOG_FILE}"
if [[ -f "${lBINWALK_LOG_FILE}" ]]; then
print_output "[+] Binwalk extraction output for ${lFW_NAME_BINWALK}" "" "${lBINWALK_LOG_FILE}"
fi
fi
print_ln
if [[ -d "${lOUTPUT_DIR_BINWALK}" ]]; then
remove_uprintable_paths "${lOUTPUT_DIR_BINWALK}"
mapfile -t lFILES_BINWALK_ARR < <(find "${lOUTPUT_DIR_BINWALK}" -type f)
fi
if [[ "${#lFILES_BINWALK_ARR[@]}" -gt 0 ]]; then
print_output "[*] Extracted ${ORANGE}${#lFILES_BINWALK_ARR[@]}${NC} files."
print_output "[*] Populating backend data for ${ORANGE}${#lFILES_BINWALK_ARR[@]}${NC} files ... could take some time" "no_log"
for lBINARY in "${lFILES_BINWALK_ARR[@]}"; do
binary_architecture_threader "${lBINARY}" "${FUNCNAME[0]}" &
local lTMP_PID="$!"
lWAIT_PIDS_P99_ARR+=("${lTMP_PID}")
done
lLINUX_PATH_COUNTER_BINWALK=$(linux_basic_identification "${lOUTPUT_DIR_BINWALK}" "${FUNCNAME[0]}")
wait_for_pid "${lWAIT_PIDS_P99_ARR[@]}"
sub_module_title "Firmware extraction details"
print_output "[*] ${ORANGE}Binwalk${NC} results:"
print_output "[*] Found ${ORANGE}${#lFILES_BINWALK_ARR[@]}${NC} files."
print_output "[*] Additionally the Linux path counter is ${ORANGE}${lLINUX_PATH_COUNTER_BINWALK}${NC}."
print_ln
fi
detect_root_dir_helper "${lOUTPUT_DIR_BINWALK}"
write_csv_log "FILES Binwalk" "LINUX_PATH_COUNTER Binwalk"
write_csv_log "${#lFILES_BINWALK_ARR[@]}" "${lLINUX_PATH_COUNTER_BINWALK}"
module_end_log "${FUNCNAME[0]}" "${#lFILES_BINWALK_ARR[@]}"
}
linux_basic_identification() {
local lFIRMWARE_PATH_CHECK="${1:-}"
local lIDENTIFIER="${2:-}"
local lLINUX_PATH_COUNTER_BINWALK=0
if ! [[ -d "${lFIRMWARE_PATH_CHECK}" ]]; then
return
fi
if [[ -f "${P99_CSV_LOG}" ]]; then
if [[ -n "${lIDENTIFIER}" ]]; then
lLINUX_PATH_COUNTER_BINWALK="$(grep "${lIDENTIFIER}" "${P99_CSV_LOG}" | grep -c "/bin/\|/busybox;\|/shadow;\|/passwd;\|/sbin/\|/etc/" || true)"
else
lLINUX_PATH_COUNTER_BINWALK="$(grep -c "/bin/\|/busybox;\|/shadow;\|/passwd;\|/sbin/\|/etc/" "${P99_CSV_LOG}" || true)"
fi
fi
echo "${lLINUX_PATH_COUNTER_BINWALK}"
}
remove_uprintable_paths() {
local lOUTPUT_DIR_BINWALK="${1:-}"
local lFIRMWARE_UNPRINT_FILES_ARR=()
local lFW_FILE=""
local lNEW_FILE=""
local lDIR=""
local lBASE=""
local lNEW_BASE=""
# 1. Use -depth so children are renamed BEFORE their parent directories are renamed
# 2. Use a hex-based pattern matching real binary unprintables (\x00-\x1F, \x7F)
mapfile -t lFIRMWARE_UNPRINT_FILES_ARR < <(find "${lOUTPUT_DIR_BINWALK}" -depth -regextype posix-extended -regex '.*[\x00-\x1F\x7F].*')
if [[ "${#lFIRMWARE_UNPRINT_FILES_ARR[@]}" -gt 0 ]]; then
print_output "[*] Unprintable characters detected in extracted files -> cleanup started"
for lFW_FILE in "${lFIRMWARE_UNPRINT_FILES_ARR[@]}"; do
# Verify the file still exists (handles race conditions or overlapping paths)
[[ -e "${lFW_FILE}" || -L "${lFW_FILE}" ]] || continue
# Extract the directory path and the raw filename separately
lDIR=$(dirname "${lFW_FILE}")
lBASE=$(basename "${lFW_FILE}")
# Sanitize ONLY the filename (the basename)
# Replaces carriage returns (\r), ASCII control characters, and non-ASCII byte corruption with a single '_'
# lNEW_BASE=$(printf '%s' "${lBASE}" | sed -E 's/[\x00-\x1F\x7F-\xFF]+/_/g')
# Forces raw byte matching to strip \r, control codes, and binary junk into single underscores
lNEW_BASE=$(
LC_ALL=C
printf '%s' "${lBASE}" | tr -s '\000-\037\177-\377' '_'
# printf '%s' "${lBASE}" | tr -s '[\000-\037\177-\377]' '_'
)
# Reconstruct the new full path
lNEW_FILE="${lDIR}/${lNEW_BASE}"
# Only move if the name actually changed
if [[ "${lFW_FILE}" != "${lNEW_FILE}" ]]; then
print_output "[*] Moving ${lFW_FILE} to ${lNEW_FILE}"
# just in case our new filename is already in place
if [[ -f "${lNEW_FILE}" ]]; then
lNEW_FILE="${lNEW_FILE}_${RANDOM}"
fi
mv -- "${lFW_FILE}" "${lNEW_FILE}" || print_output "[-] Cleanup of file ${lFW_FILE} not possible"
fi
done
fi
}