769 lines
27 KiB
Bash
Executable File
769 lines
27 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
#
|
||
# Rapid7 Labs / Enhanced – Linux BPFDoor Detection Script
|
||
#
|
||
# Detects both “classic” and newer BPFDoor variants using:
|
||
# - Known hashes (optional, extendable)
|
||
# - Suspicious mutex/lock files in /var/run
|
||
# - Auto-start hooks in /etc/sysconfig
|
||
# - BPF filter usage via ss -0pb and /proc/net/packet
|
||
# - RAW / packet socket usage (Including SOCK_DGRAM Layer 2 Stripping)
|
||
# - Suspicious env vars (HOME=/tmp, HISTFILE=/dev/null, MYSQL_HISTFILE=/dev/null)
|
||
# - Known masqueraded process names + paths (Verified against Whitelist)
|
||
# - Process-specific Binary Signatures (66666666H, ttcompat, etc.)
|
||
# - Active C2 Reverse Shell Connections
|
||
# - Memory-resident (deleted) binary execution
|
||
# - Kernel Stack Tracing (packet_recvmsg blocking)
|
||
# - Suspicious ports (42391–43390, 8000)
|
||
# - Suspicious strings & UPX packing in candidate binaries
|
||
# - Basic persistence checks (cron, systemd, rc scripts)
|
||
#
|
||
# Requires: bash, grep, awk, ps, readlink, stat, ss OR netstat, lsof (optional), strings, find, hexdump, dd, dig (optional)
|
||
#
|
||
# This script is best-effort and may produce false positives. Use results as
|
||
# triage input, not as a sole source of truth.
|
||
|
||
set -o pipefail
|
||
|
||
VERSION="1.1"
|
||
HOSTNAME="$(hostname)"
|
||
DATE="$(date +%Y-%m-%d_%H-%M-%S)"
|
||
LOGFILE="bpfdoor_report_${HOSTNAME}_${DATE}.log"
|
||
|
||
# Global script PIDs to exclude from our own checks
|
||
SCRIPT_PID=$$
|
||
SCRIPT_PPID=$PPID
|
||
|
||
# ---- Colours ---------------------------------------------------------------
|
||
#if [ -t 1 ]; then
|
||
RED=$'\033[1;31m'
|
||
GREEN=$'\033[1;32m'
|
||
YELLOW=$'\033[1;33m'
|
||
BLUE=$'\033[1;34m'
|
||
CYAN=$'\033[1;36m'
|
||
MAGENTA=$'\033[1;35m'
|
||
ORANGE=$'\033[38;5;208m'
|
||
NC=$'\033[0m'
|
||
#else
|
||
#RED=""; GREEN=""; YELLOW=""; BLUE=""; CYAN=""; MAGENTA=""; NC=""; ORANGE=""
|
||
#fi
|
||
|
||
# ---- Known malicious hashes (minimal baseline, extend as needed) -----------
|
||
declare -A MALWARE_SHA256=()
|
||
declare -A MALWARE_MD5=()
|
||
|
||
# ---- Suspicious names and patterns ----------------------------------------
|
||
SUSPICIOUS_MUTEX_FILES=(
|
||
"/var/run/aepmonend.pid" "/var/run/auditd.lock" "/var/run/cma.lock"
|
||
"/var/run/console-kit.pid" "/var/run/consolekit.pid" "/var/run/daemon.pid"
|
||
"/var/run/hald-addon.pid" "/var/run/hald-smartd.pid" "/var/run/hp-health.pid"
|
||
"/var/run/hpasmlit.lock" "/var/run/hpasmlited.pid" "/var/run/lldpad.lock"
|
||
"/var/run/mcelog.pid" "/var/run/system.pid" "/var/run/uvp-srv.pid"
|
||
"/var/run/vmtoolagt.pid" "/var/run/xinetd.lock"
|
||
)
|
||
|
||
# Command-line strings BPFDoor uses to hide
|
||
SUSPICIOUS_PROCS=(
|
||
"/sbin/agetty" "/sbin/auditd" "/sbin/mingetty" "/sbin/sgaSolAgent" "/sbin/udevd"
|
||
"/usr/bin/python -Es /usr/sbin/tuned" "/usr/bin/uvp-srv" "/usr/lib/polkit-1/polkitd"
|
||
"/usr/lib/systemd/systemd-journald" "/usr/lib/systemd/systemd-machined"
|
||
"/usr/libexec/hald-addon-volume" "/usr/libexec/postfix/master" "/usr/libexec/rtkit-daemon"
|
||
"/usr/libexec/upowerd" "/usr/sbin/NetworkManager" "/usr/sbin/abrtd" "/usr/sbin/acpid"
|
||
"/usr/sbin/atd" "/usr/sbin/chronyd" "/usr/sbin/console-kit" "/usr/sbin/console-kit-daemon"
|
||
"/usr/sbin/crond" "/usr/sbin/mcelog" "/usr/sbin/rsyslogd" "/usr/sbin/smartd"
|
||
"/usr/sbin/sshd" "[charger_manager]" "[kaluad_sync]" "[scsi_tmf_6]" "[watchdogd]"
|
||
"[cpu/0]" "avahi-daemon: chroot helper" "cmathreshd" "dbus-daemon --system"
|
||
"hald-addon-acpi" "hald-runner" "hpasmlited" "lldpad -d" "nginx: master process"
|
||
"pickup -l -t fifo -u" "/sbin/ora_ppmond" "/usr/bin/pulse-helper"
|
||
)
|
||
|
||
# The ACTUAL physical paths of legitimate daemons. If a process masquerades as one of
|
||
# the above but isn't running from one of these files, it gets flagged.
|
||
WHITELIST_EXES=(
|
||
"/sbin/agetty" "/sbin/auditd" "/sbin/mingetty" "/sbin/udevd"
|
||
"/usr/bin/python" "/usr/bin/python2" "/usr/bin/python3"
|
||
"/usr/sbin/tuned" "/usr/lib/polkit-1/polkitd" "/usr/libexec/postfix/pickup"
|
||
"/usr/libexec/postfix/master" "/usr/sbin/NetworkManager"
|
||
"/usr/sbin/console-kit-daemon" "/usr/sbin/crond" "/usr/sbin/mcelog"
|
||
"/usr/sbin/rsyslogd" "/usr/sbin/smartd" "/usr/sbin/avahi-daemon"
|
||
"/usr/bin/dbus-daemon" "/usr/libexec/hald-addon-acpi" "/usr/libexec/hald-runner"
|
||
"/usr/sbin/lldpad" "/usr/sbin/nginx" "/usr/sbin/sshd" "/usr/sbin/acpid"
|
||
"/usr/sbin/atd" "/usr/sbin/chronyd" "/usr/sbin/console-kit"
|
||
"/usr/libexec/rtkit-daemon" "/usr/libexec/upowerd" "/usr/sbin/abrtd"
|
||
)
|
||
|
||
SUSPICIOUS_STRINGS=(
|
||
"HISTFILE=/dev/null" "MYSQL_HISTFILE=/dev/null" "ttcompat" ":h:d:l:s:b:t:"
|
||
":f:wiunomc" ":f:x:wiuoc" "LibTomCrypt 1.17"
|
||
"Private key does not match the public certificate"
|
||
"I5*AYbs@LdaWbsO" "3458" "8543" "1234"
|
||
)
|
||
|
||
KNOWN_C2_HOSTS=(
|
||
"ntpupdate.ddnsgeek.com" "ntpussl.instanthq.com" "ntpd.casacam.net" "ntpupdate.ygto.com"
|
||
)
|
||
|
||
SUSPICIOUS_PORTS_RANGE_START=42391
|
||
SUSPICIOUS_PORTS_RANGE_END=43390
|
||
SUSPICIOUS_PORT_SINGLE=8000
|
||
|
||
SUSPICIOUS_FILES_TMP=()
|
||
|
||
# ---- Logging helpers -------------------------------------------------------
|
||
log() {
|
||
local level="$1"; shift
|
||
local msg="$*"
|
||
local ts
|
||
ts="$(date +'%Y-%m-%d %H:%M:%S')"
|
||
|
||
# Log cleanly to file
|
||
echo "[$ts] [$level] $msg" >> "$LOGFILE"
|
||
|
||
# Apply colors for terminal output
|
||
local color="${NC}"
|
||
case "$level" in
|
||
"CRITICAL") color="${RED}" ;;
|
||
"ALERT") color="${YELLOW}" ;;
|
||
"INFO") color="${BLUE}" ;;
|
||
"SUCCESS") color="${GREEN}" ;;
|
||
"WARN") color="${MAGENTA}" ;;
|
||
esac
|
||
|
||
# Print colored output to terminal
|
||
echo -e "${NC}[$ts] [${color}${level}${NC}] $msg"
|
||
}
|
||
|
||
banner() {
|
||
local b=$(cat << "EOF"
|
||
██████╗ █████╗ ██████╗ ██╗██████╗ ███████╗
|
||
██╔══██╗██╔══██╗██╔══██╗██║██╔══██╗╚════██║
|
||
██████╔╝███████║██████╔╝██║██║ ██║ ██╔╝
|
||
██╔══██╗██╔══██║██╔═══╝ ██║██║ ██║ ██╔╝
|
||
██║ ██║██║ ██║██║ ██║██████╔╝ ██║
|
||
╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚═════╝ ╚═╝
|
||
M A L W A R E L A B S
|
||
==========================================================
|
||
Enhanced Linux BPFDoor Detection Script
|
||
==========================================================
|
||
EOF
|
||
)
|
||
echo -e "${ORANGE}${b}${NC}"
|
||
echo "$b" >> "$LOGFILE"
|
||
echo "Host : ${HOSTNAME}" | tee -a "$LOGFILE"
|
||
echo "Date : ${DATE}" | tee -a "$LOGFILE"
|
||
echo "Version: ${VERSION}" | tee -a "$LOGFILE"
|
||
echo "==========================================================" | tee -a "$LOGFILE"
|
||
}
|
||
|
||
require_root() {
|
||
if [ "$EUID" -ne 0 ]; then
|
||
echo -e "${RED}[!] This script requires root privileges.${NC}"
|
||
exit 1
|
||
fi
|
||
}
|
||
|
||
cmd_exists() {
|
||
command -v "$1" >/dev/null 2>&1
|
||
}
|
||
|
||
mark_suspicious_file() {
|
||
local file="$1"
|
||
SUSPICIOUS_FILES_TMP+=("$file")
|
||
}
|
||
|
||
is_self() {
|
||
# Excludes the script itself from being flagged during execution
|
||
local p="$1"
|
||
if [ "$p" = "$SCRIPT_PID" ] || [ "$p" = "$SCRIPT_PPID" ]; then
|
||
return 0
|
||
fi
|
||
return 1
|
||
}
|
||
|
||
# ---- Helper: Hash check ---------------------------------------------------
|
||
hash_matches_malware() {
|
||
local file="$1"
|
||
if cmd_exists sha256sum; then
|
||
local h="$(sha256sum "$file" 2>/dev/null | awk '{print $1}')"
|
||
[ -n "${MALWARE_SHA256[$h]}" ] && return 0
|
||
fi
|
||
if cmd_exists md5sum; then
|
||
local h="$(md5sum "$file" 2>/dev/null | awk '{print $1}')"
|
||
[ -n "${MALWARE_MD5[$h]}" ] && return 0
|
||
fi
|
||
return 1
|
||
}
|
||
|
||
# ---- Helper: Strings check ------------------------------------------------
|
||
scan_strings_for_bpfdoor() {
|
||
local file="$1"
|
||
cmd_exists strings || return 1
|
||
local found=0
|
||
local file_strings="$(strings "$file" 2>/dev/null)"
|
||
for s in "${SUSPICIOUS_STRINGS[@]}"; do
|
||
if echo "$file_strings" | grep -Fq "$s"; then
|
||
log "ALERT" "String match '$s' found in $file"
|
||
found=1
|
||
fi
|
||
done
|
||
[ "$found" -eq 1 ] && return 0
|
||
return 1
|
||
}
|
||
|
||
# ---- Helper: Hexdump Mapped Memory for Magic Bytes ------------------------
|
||
check_maps_hex() {
|
||
local pid="$1"
|
||
is_self "$pid" && return
|
||
[ -r "/proc/$pid/maps" ] || return
|
||
cmd_exists hexdump || return
|
||
|
||
local maps_paths="$(awk '{ if ($6 ~ /^\//) print $6 }' "/proc/$pid/maps" 2>/dev/null | sort -u)"
|
||
|
||
# Combined 32-bit Little-Endian hex strings of all magic bytes from BPF filters
|
||
local magic_hex_regex="55720000|93520000|39393939|6c8d0000|4f9f0000|0f270000|48200000|55110000|33540000|82310000|51100000|21330000|adde0000"
|
||
|
||
for path in $maps_paths; do
|
||
[ -r "$path" ] || continue
|
||
|
||
local size="$(stat -c '%s' "$path" 2>/dev/null || echo 0)"
|
||
[ "$size" -gt 5242880 ] && continue
|
||
|
||
# Create a single continuous string of hex characters
|
||
local hexdata="$(hexdump -ve '1/1 "%02x"' "$path" 2>/dev/null)"
|
||
|
||
if echo "$hexdata" | grep -qiE "$magic_hex_regex"; then
|
||
log "CRITICAL" "Little-Endian Magic Bytes found in mapped file: $path (PID: $pid)"
|
||
mark_suspicious_file "$path"
|
||
|
||
# Extract the magic bytes along with ~16 bytes (32 hex characters) of context on either side
|
||
local context="$(echo "$hexdata" | grep -ioE ".{0,32}($magic_hex_regex).{0,32}" | head -n 3)"
|
||
|
||
if [ -n "$context" ]; then
|
||
echo "------- HEXDUMP CONTEXT -------" >> "$LOGFILE"
|
||
echo "$context" >> "$LOGFILE"
|
||
echo "-------------------------------" >> "$LOGFILE"
|
||
|
||
echo -e "${CYAN}------- HEXDUMP CONTEXT -------${NC}"
|
||
echo "$context" | grep --color=always -iE "$magic_hex_regex"
|
||
echo -e "${CYAN}-------------------------------${NC}"
|
||
fi
|
||
fi
|
||
done
|
||
}
|
||
|
||
# ---- Check 1: Mutex / lock files ------------------------------------------
|
||
check_mutex_files() {
|
||
log "INFO" "[1/12] Checking /var/run for suspicious zero-byte mutex/lock files"
|
||
local found=0
|
||
|
||
for f in /var/run/*.pid /var/run/*.lock; do
|
||
[ -e "$f" ] || continue
|
||
local size="$(stat -c '%s' "$f" 2>/dev/null || echo "")"
|
||
local perm="$(stat -c '%a' "$f" 2>/dev/null || echo "")"
|
||
if [ "$size" = "0" ] && [ "$perm" = "644" ]; then
|
||
for known in "${SUSPICIOUS_MUTEX_FILES[@]}"; do
|
||
if [ "$f" = "$known" ]; then
|
||
log "ALERT" "Suspicious mutex/lock file: $f (size=0, perm=644)"
|
||
found=1
|
||
fi
|
||
done
|
||
fi
|
||
done
|
||
[ "$found" -eq 0 ] && log "SUCCESS" "[1/12] No known suspicious mutex/lock files found"
|
||
}
|
||
|
||
# ---- Check 2: Auto-exec / sysconfig hooks ---------------------------------
|
||
check_autostart_files() {
|
||
log "INFO" "[2/12] Checking /etc/sysconfig for suspicious auto-start entries"
|
||
local dir="/etc/sysconfig"
|
||
local pat='\[[[:space:]]*-f[[:space:]]+/[^]]+\][[:space:]]*&&[[:space:]]*/'
|
||
|
||
if [ ! -d "$dir" ]; then
|
||
log "WARN" "[2/12] /etc/sysconfig not present; skipping"
|
||
return
|
||
fi
|
||
|
||
local results="$(find "$dir" -type f -exec grep -EH "$pat" {} + 2>/dev/null || true)"
|
||
if [ -z "$results" ]; then
|
||
log "SUCCESS" "[2/12] No suspicious auto-start patterns found in /etc/sysconfig"
|
||
return
|
||
fi
|
||
|
||
log "ALERT" "[2/12] Potential suspicious auto-start entries detected:"
|
||
echo "$results" | tee -a "$LOGFILE"
|
||
|
||
while IFS= read -r line; do
|
||
local filepath="$(echo "$line" | sed -nE 's/.*\[ *-f *([^ ]+).*/\1/p')"
|
||
if [ -n "$filepath" ] && [ -e "$filepath" ]; then
|
||
log "ALERT" "Auto-start target candidate: $filepath"
|
||
mark_suspicious_file "$filepath"
|
||
fi
|
||
done <<< "$results"
|
||
}
|
||
|
||
# ---- Check 3: BPF filter usage (old + new variants) -----------------------
|
||
check_bpf_filters() {
|
||
log "INFO" "[3/12] Inspecting BPF filters via ss -0pb"
|
||
if ! cmd_exists ss; then
|
||
log "WARN" "[3/12] ss command not available; skipping BPF filter check"
|
||
return
|
||
fi
|
||
|
||
local out="$(ss -0pb 2>/dev/null || true)"
|
||
if [ -z "$out" ]; then
|
||
log "INFO" "[3/12] No packet sockets reported by ss -0pb"
|
||
return
|
||
fi
|
||
|
||
echo "$out" >> "$LOGFILE"
|
||
|
||
local magic_regex='0x5293|21139|0x7255|29269|0x39393939|960051133|0x8D6C|36204|0x9F4F|40783|12674|13089|57005|4437|21555|4177|8264|0x270f|0x2048|0x1155|0x5433|0x3182|0x1051|0x3321|0xdead'
|
||
local matches="$(echo "$out" | grep -EB1 "$magic_regex" || true)"
|
||
|
||
if [ -n "$matches" ]; then
|
||
log "CRITICAL" "[3/12] BPFDoor magic pattern found in BPF filter output!"
|
||
|
||
echo "------- MATCH CONTEXT -------" >> "$LOGFILE"
|
||
echo "$matches" >> "$LOGFILE"
|
||
echo "-----------------------------" >> "$LOGFILE"
|
||
|
||
echo -e "${CYAN}------- MATCH CONTEXT -------${NC}"
|
||
echo "$matches" | grep --color=always -E "$magic_regex"
|
||
echo -e "${CYAN}-----------------------------${NC}"
|
||
|
||
local pids="$(echo "$matches" | sed -n 's/.*pid=\([0-9]\+\).*/\1/p' | sort -u)"
|
||
|
||
if [ -n "$pids" ]; then
|
||
for pid in $pids; do
|
||
is_self "$pid" && continue
|
||
[ -d "/proc/$pid" ] || continue
|
||
local exe="$(readlink -f "/proc/$pid/exe" 2>/dev/null || echo "")"
|
||
local cmd="$(ps -p "$pid" -o user=,pid=,cmd= 2>/dev/null || echo "")"
|
||
log "ALERT" " PID $pid -> $exe :: $cmd"
|
||
[ -n "$exe" ] && mark_suspicious_file "$exe"
|
||
|
||
check_maps_hex "$pid"
|
||
done
|
||
fi
|
||
else
|
||
log "SUCCESS" "[3/12] No obvious BPFDoor-like BPF filters found"
|
||
fi
|
||
}
|
||
|
||
# ---- Check 4: RAW + packet socket usage (Enhanced) ------------------------
|
||
check_raw_and_packet_sockets() {
|
||
log "INFO" "[4/12] Checking RAW and packet socket usage (SOCK_RAW / SOCK_DGRAM)"
|
||
|
||
local pids_found=""
|
||
local flagged=0
|
||
|
||
# 1. Collect PIDs from 'ss'
|
||
if cmd_exists ss; then
|
||
pids_found+="$(ss -0 -w -n -p 2>/dev/null | grep -oP 'pid=\K[0-9]+')"
|
||
fi
|
||
|
||
# 2. Collect PIDs from /proc/net forensics
|
||
# We combine all potential inodes first, then find their owners
|
||
local all_inodes=""
|
||
for netfile in /proc/net/packet /proc/net/raw /proc/net/raw6; do
|
||
if [ -r "$netfile" ]; then
|
||
all_inodes+=" $(awk 'NR>1 && $NF ~ /^[0-9]+$/ {print $NF}' "$netfile" 2>/dev/null)"
|
||
fi
|
||
done
|
||
|
||
# Process each unique inode found
|
||
for ino in $(echo "$all_inodes" | tr ' ' '\n' | sort -u); do
|
||
[ -z "$ino" ] && continue
|
||
[ "$ino" -eq 0 ] 2>/dev/null && continue
|
||
|
||
# Find PIDs for this inode and add to our list
|
||
local p=$(grep -rlE "ino(de)?:\s*$ino" /proc/[0-9]*/fdinfo 2>/dev/null | cut -d/ -f3)
|
||
pids_found+=" $p"
|
||
done
|
||
|
||
# 3. Analyze the collected PIDs
|
||
# Unique-sort the list of PIDs to avoid duplicate alerts
|
||
local unique_pids=$(echo "$pids_found" | tr ' ' '\n' | grep -E '^[0-9]+$' | sort -u)
|
||
|
||
for pid in $unique_pids; do
|
||
# Self-exclusion
|
||
[ "$pid" -eq "$SELF_PID" ] 2>/dev/null && continue
|
||
[ -d "/proc/$pid" ] || continue
|
||
|
||
local exe_path=$(readlink -f "/proc/$pid/exe" 2>/dev/null || echo "unknown")
|
||
|
||
# Skip if it's this detection script
|
||
[ "$exe_path" == "$SELF_EXE" ] && continue
|
||
|
||
local cmd_line=$(ps -p "$pid" -o args= 2>/dev/null | head -n1)
|
||
|
||
# If we got here, we found something
|
||
log "ALERT" "Suspicious Socket detected: PID $pid ($cmd_line) -> $exe_path"
|
||
|
||
if [[ -n "$exe_path" && "$exe_path" != "unknown" && -e "$exe_path" ]]; then
|
||
mark_suspicious_file "$exe_path"
|
||
fi
|
||
|
||
check_maps_hex "$pid"
|
||
flagged=1
|
||
done
|
||
|
||
# 4. Final Status (Only Success if NO pids were ever flagged)
|
||
if [ "$flagged" -eq 0 ]; then
|
||
log "SUCCESS" "No suspicious RAW/packet socket usage detected"
|
||
fi
|
||
}
|
||
# ---- Check 5: Env vars used by BPFDoor shells -----------------------------
|
||
check_env_vars() {
|
||
log "INFO" "[5/12] Checking for suspicious environment variables"
|
||
local hits=0
|
||
|
||
for pid_dir in /proc/[0-9]*; do
|
||
[ -r "$pid_dir/environ" ] || continue
|
||
local pid="${pid_dir##*/}"
|
||
is_self "$pid" && continue
|
||
|
||
local env="$(tr '\0' '\n' < "$pid_dir/environ" 2>/dev/null || true)"
|
||
[ -z "$env" ] && continue
|
||
|
||
if echo "$env" | grep -qx "HOME=/tmp" \
|
||
&& echo "$env" | grep -qx "HISTFILE=/dev/null" \
|
||
&& echo "$env" | grep -qx "MYSQL_HISTFILE=/dev/null"; then
|
||
|
||
local exe="$(readlink -f "/proc/$pid/exe" 2>/dev/null || echo "")"
|
||
local cmd="$(ps -p "$pid" -o user=,pid=,cmd= 2>/dev/null || echo "")"
|
||
local ppid="$(ps -p "$pid" -o ppid= 2>/dev/null | tr -d ' ' || echo "")"
|
||
log "CRITICAL" "[5/12] Process with BPFDoor-like env vars: PID=$pid, PPID=$ppid, EXE=$exe, CMD=$cmd"
|
||
[ -n "$exe" ] && mark_suspicious_file "$exe"
|
||
if [ -n "$ppid" ] && [ -e "/proc/$ppid/exe" ]; then
|
||
mark_suspicious_file "$(readlink -f "/proc/$ppid/exe" 2>/dev/null || true)"
|
||
fi
|
||
hits=$((hits+1))
|
||
fi
|
||
done
|
||
|
||
[ "$hits" -eq 0 ] && log "SUCCESS" "[5/12] No processes with the full suspicious env var set found"
|
||
}
|
||
|
||
# ---- Check 6: Ports historically used by BPFDoor --------------------------
|
||
check_suspicious_ports() {
|
||
log "INFO" "[6/12] Checking TCP ports ${SUSPICIOUS_PORTS_RANGE_START}-${SUSPICIOUS_PORTS_RANGE_END} and ${SUSPICIOUS_PORT_SINGLE}"
|
||
local net_out=""
|
||
|
||
if cmd_exists netstat; then
|
||
net_out="$(netstat -antp 2>/dev/null || true)"
|
||
elif cmd_exists ss; then
|
||
net_out="$(ss -antp 2>/dev/null || true)"
|
||
else
|
||
log "WARN" "[6/12] Neither netstat nor ss available; skipping port check"
|
||
return
|
||
fi
|
||
|
||
local matches=""
|
||
while IFS= read -r line; do
|
||
[[ "$line" =~ ^tcp ]] || continue
|
||
local laddr raddr state pidprog
|
||
read -r _ _ laddr raddr state pidprog <<<"$line"
|
||
local lport="${laddr##*:}"
|
||
local rport="${raddr##*:}"
|
||
|
||
[[ "$lport" =~ ^[0-9]+$ ]] || lport=0
|
||
[[ "$rport" =~ ^[0-9]+$ ]] || rport=0
|
||
|
||
if { [ "$lport" -ge "$SUSPICIOUS_PORTS_RANGE_START" ] && [ "$lport" -le "$SUSPICIOUS_PORTS_RANGE_END" ]; } \
|
||
|| { [ "$rport" -ge "$SUSPICIOUS_PORTS_RANGE_START" ] && [ "$rport" -le "$SUSPICIOUS_PORTS_RANGE_END" ]; } \
|
||
|| [ "$lport" -eq "$SUSPICIOUS_PORT_SINGLE" ] \
|
||
|| [ "$rport" -eq "$SUSPICIOUS_PORT_SINGLE" ]; then
|
||
matches+="$line"$'\n'
|
||
fi
|
||
done <<< "$net_out"
|
||
|
||
if [ -z "$matches" ]; then
|
||
log "SUCCESS" "[6/12] No connections on known suspicious BPFDoor ports"
|
||
return
|
||
fi
|
||
|
||
log "ALERT" "[6/12] Potentially suspicious connections on historical BPFDoor ports:"
|
||
printf "%s\n" "$matches" | tee -a "$LOGFILE"
|
||
|
||
local pids="$(printf "%s\n" "$matches" | awk '{print $7}' | cut -d/ -f1 | grep -E '^[0-9]+$' | sort -u)"
|
||
for pid in $pids; do
|
||
is_self "$pid" && continue
|
||
[ -d "/proc/$pid" ] || continue
|
||
local exe="$(readlink -f "/proc/$pid/exe" 2>/dev/null || echo "")"
|
||
[ -n "$exe" ] && mark_suspicious_file "$exe"
|
||
done
|
||
}
|
||
|
||
# ---- Check 7: Process masquerading (Whitelist Integration) ----------------
|
||
check_process_masquerade() {
|
||
log "INFO" "[7/12] Checking for masqueraded processes (Verifying true execution paths)"
|
||
local found=0
|
||
|
||
# Grab all processes and their arguments
|
||
local ps_output="$(ps -eo pid=,args= 2>/dev/null || true)"
|
||
[ -z "$ps_output" ] && return
|
||
|
||
while read -r pid args; do
|
||
[ -z "$pid" ] && continue
|
||
is_self "$pid" && continue
|
||
|
||
# 1. Identify if the process claims to be a suspect
|
||
local is_suspect=0
|
||
for pat in "${SUSPICIOUS_PROCS[@]}"; do
|
||
if echo "$args" | grep -Fq "$pat"; then
|
||
is_suspect=1
|
||
break
|
||
fi
|
||
done
|
||
|
||
# 2. Unmask and Verify the suspect against the Whitelist
|
||
if [ "$is_suspect" -eq 1 ]; then
|
||
local exe="$(readlink -f "/proc/$pid/exe" 2>/dev/null || echo "")"
|
||
|
||
# Skip if no executable path can be resolved (e.g. kernel threads)
|
||
[ -z "$exe" ] && continue
|
||
|
||
local is_whitelisted=0
|
||
for wl in "${WHITELIST_EXES[@]}"; do
|
||
if [ "$exe" = "$wl" ]; then
|
||
is_whitelisted=1
|
||
break
|
||
fi
|
||
done
|
||
|
||
# 3. Trap: It's a suspect name, but not running from a whitelisted binary file
|
||
if [ "$is_whitelisted" -eq 0 ]; then
|
||
log "CRITICAL" "[7/12] Process Masquerading Detected! PID=$pid claims to be '$args' but is actually executing '$exe'"
|
||
mark_suspicious_file "$exe"
|
||
found=1
|
||
fi
|
||
fi
|
||
done <<< "$ps_output"
|
||
|
||
[ "$found" -eq 0 ] && log "SUCCESS" "[7/12] No process masquerading detected"
|
||
}
|
||
|
||
# ---- Check 8: Memory-Resident / Deleted Binaries --------------------------
|
||
check_deleted_binaries() {
|
||
log "INFO" "[8/12] Checking for processes executing deleted binaries (Fileless execution)"
|
||
local found=0
|
||
local deleted_pids="$(ls -l /proc/*/exe 2>/dev/null | grep " (deleted)" | awk -F'/proc/' '{print $2}' | cut -d'/' -f1 || true)"
|
||
|
||
if [ -n "$deleted_pids" ]; then
|
||
for d_pid in $deleted_pids; do
|
||
is_self "$d_pid" && continue
|
||
[ -d "/proc/$d_pid" ] || continue
|
||
local d_name="$(ps -p "$d_pid" -o comm= 2>/dev/null || echo "")"
|
||
local d_exe="$(readlink -f "/proc/$d_pid/exe" 2>/dev/null || echo "")"
|
||
|
||
local is_bpf_candidate=0
|
||
for pat in "${SUSPICIOUS_PROCS[@]}"; do
|
||
if [[ "$pat" == *"$d_name"* ]]; then
|
||
is_bpf_candidate=1
|
||
break
|
||
fi
|
||
done
|
||
|
||
if [ "$is_bpf_candidate" -eq 1 ]; then
|
||
log "CRITICAL" "PID: $d_pid masquerading as '$d_name' running from a deleted file: $d_exe"
|
||
mark_suspicious_file "$d_exe"
|
||
found=1
|
||
else
|
||
log "WARN" "PID: $d_pid, ProcessName: $d_name, Exec: $d_exe (Deleted Binary)"
|
||
fi
|
||
done
|
||
fi
|
||
|
||
[ "$found" -eq 0 ] && log "SUCCESS" "[8/12] No critical memory-resident/deleted binary execution found"
|
||
}
|
||
|
||
# ---- Check 9: Kernel Stack Tracing ----------------------------------------
|
||
check_kernel_stack() {
|
||
log "INFO" "[9/12] Checking kernel stacks for raw socket blocking (packet_recvmsg/wait_for_more_packets)"
|
||
local found=0
|
||
local pids="$(grep -lE "packet_recvmsg|wait_for_more_packets" /proc/*/stack 2>/dev/null | awk -F/ '{print $3}' || true)"
|
||
|
||
for pid in $pids; do
|
||
is_self "$pid" && continue
|
||
[ -d "/proc/$pid" ] || continue
|
||
local exe="$(readlink -f "/proc/$pid/exe" 2>/dev/null || echo "")"
|
||
local cmd="$(ps -p "$pid" -o user=,pid=,cmd= 2>/dev/null || echo "")"
|
||
log "CRITICAL" "Process hanging on packet_recvmsg: PID=$pid EXE=$exe CMD=$cmd"
|
||
[ -n "$exe" ] && mark_suspicious_file "$exe"
|
||
found=1
|
||
|
||
check_maps_hex "$pid"
|
||
done
|
||
|
||
[ "$found" -eq 0 ] && log "SUCCESS" "[9/12] No processes blocking on suspicious packet socket kernel functions"
|
||
}
|
||
|
||
# ---- Check 10: Deep scan suspicious files (hash, strings, UPX packer) -----
|
||
deep_scan_suspicious_files() {
|
||
log "INFO" "[10/12] Deep scanning candidate binaries (hash, strings, UPX packing)"
|
||
local uniq_files=($(printf "%s\n" "${SUSPICIOUS_FILES_TMP[@]}" | sort -u))
|
||
|
||
if [ "${#uniq_files[@]}" -eq 0 ]; then
|
||
log "INFO" "[10/12] No candidate binaries collected for deep scan"
|
||
return
|
||
fi
|
||
|
||
for f in "${uniq_files[@]}"; do
|
||
[ -e "$f" ] || continue
|
||
log "INFO" ">>> Analyzing candidate binary: $f"
|
||
|
||
if hash_matches_malware "$f"; then
|
||
log "CRITICAL" "Known BPFDoor hash match for $f"
|
||
continue
|
||
fi
|
||
|
||
if cmd_exists dd; then
|
||
local dd_out="$(dd if="$f" bs=1 count=256 2>/dev/null | grep -o 'UPX!' || true)"
|
||
if [ -n "$dd_out" ]; then
|
||
log "ALERT" "Binary is UPX packed (Common for BPFdoor): $f"
|
||
fi
|
||
fi
|
||
|
||
if scan_strings_for_bpfdoor "$f"; then
|
||
log "CRITICAL" "BPFDoor-like string pattern(s) found in $f"
|
||
fi
|
||
done
|
||
}
|
||
|
||
# ---- Check 11: C2 Connections (DNS Resolving & SS Tracking) ---------------
|
||
check_c2_connections() {
|
||
log "INFO" "[11/12] Checking for active connections to known BPFDoor C2 domains"
|
||
local found=0
|
||
|
||
if ! cmd_exists dig || ! cmd_exists ss; then
|
||
log "WARN" "[11/12] 'dig' or 'ss' missing; skipping C2 connection checks."
|
||
return
|
||
fi
|
||
|
||
for host in "${KNOWN_C2_HOSTS[@]}"; do
|
||
local ips="$(dig +short "$host" A "$host" AAAA 2>/dev/null || true)"
|
||
|
||
for ip in $ips; do
|
||
if [[ "$ip" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]] || [[ "$ip" =~ ^[0-9a-fA-F:]+$ && "$ip" =~ .*[:].* ]]; then
|
||
local ss_output="$(ss -tnp state established dst "$ip" 2>/dev/null || true)"
|
||
|
||
if [ -n "$ss_output" ]; then
|
||
log "CRITICAL" "Active Reverse Shell to BPFDoor C2: $host ($ip)"
|
||
echo "$ss_output" | tee -a "$LOGFILE"
|
||
|
||
local c2_pids="$(echo "$ss_output" | grep -oP 'pid=\K[0-9]+' | sort -u)"
|
||
for c2pid in $c2_pids; do
|
||
is_self "$c2pid" && continue
|
||
[ -d "/proc/$c2pid" ] || continue
|
||
local exe="$(readlink -f "/proc/$c2pid/exe" 2>/dev/null || echo "")"
|
||
[ -n "$exe" ] && mark_suspicious_file "$exe"
|
||
done
|
||
found=1
|
||
fi
|
||
fi
|
||
done
|
||
done
|
||
[ "$found" -eq 0 ] && log "SUCCESS" "[11/12] No active connections to known C2 domains found"
|
||
}
|
||
|
||
# ---- Check 12: Process-Specific Signatures --------------------------------
|
||
check_process_signatures() {
|
||
log "INFO" "[12/12] Checking specific processes for hardcoded BPFDoor file signatures"
|
||
local found=0
|
||
|
||
local sig_checks=(
|
||
"sshd::66666666H"
|
||
"abrtd|atd|pickup:::h:d:l:s:b:t"
|
||
"sgaSolAgent|cmathreshd|udevd|agetty|hpasmlited|\.sshd::ttcompat::127.0.0.1"
|
||
)
|
||
|
||
for check in "${sig_checks[@]}"; do
|
||
local proc_pat="${check%%::*}"
|
||
local remainder="${check#*::}"
|
||
local sig1="${remainder%%::*}"
|
||
local sig2="${remainder#*::}"
|
||
|
||
local pids="$(pgrep -E "$proc_pat" 2>/dev/null || true)"
|
||
|
||
for pid in $pids; do
|
||
is_self "$pid" && continue
|
||
[ -d "/proc/$pid" ] || continue
|
||
local path="$(readlink -f "/proc/$pid/exe" 2>/dev/null || echo "")"
|
||
[ -z "$path" ] && continue
|
||
|
||
if grep -a -q "$sig1" "$path" 2>/dev/null; then
|
||
if [ -n "$sig2" ] && [ "$sig1" != "$sig2" ]; then
|
||
if ! grep -a -q "$sig2" "$path" 2>/dev/null; then
|
||
continue
|
||
fi
|
||
fi
|
||
|
||
local cmdline="$(tr '\0' ' ' < "/proc/$pid/cmdline" 2>/dev/null || echo "")"
|
||
log "CRITICAL" "Process Signature Match: PID=$pid, Path=$path, Cmd=$cmdline matched pattern '$proc_pat'"
|
||
mark_suspicious_file "$path"
|
||
found=1
|
||
fi
|
||
done
|
||
done
|
||
|
||
[ "$found" -eq 0 ] && log "SUCCESS" "[12/12] No hardcoded process signatures detected"
|
||
}
|
||
|
||
# ---- Optional: Basic persistence checks -----------------------------------
|
||
check_persistence() {
|
||
log "INFO" "[-] Basic persistence triage (cron, systemd, rc scripts)"
|
||
|
||
for file in /etc/crontab /var/spool/cron/* /var/spool/cron/crontabs/*; do
|
||
[ -f "$file" ] || continue
|
||
if grep -E "bpf|dbus-srv|hpasmmld|smartadm|hald-addon-volume" "$file" 2>/dev/null | grep -q .; then
|
||
log "ALERT" "Suspicious entry in cron file: $file"
|
||
grep -E "bpf|dbus-srv|hpasmmld|smartadm|hald-addon-volume" "$file" 2>/dev/null | tee -a "$LOGFILE"
|
||
fi
|
||
done
|
||
|
||
for dir in /etc/systemd/system /usr/lib/systemd/system /run/systemd/system; do
|
||
[ -d "$dir" ] || continue
|
||
if grep -rE "bpf|dbus-srv|hpasmmld|smartadm|hald-addon-volume" "$dir" 2>/dev/null | grep -q .; then
|
||
log "ALERT" "Suspicious pattern in systemd units under $dir"
|
||
fi
|
||
done
|
||
|
||
for rc in /etc/rc.local /etc/init.d; do
|
||
[ -e "$rc" ] || continue
|
||
if grep -rE "bpf|dbus-srv|hpasmmld|smartadm|hald-addon-volume" "$rc" 2>/dev/null | grep -q .; then
|
||
log "ALERT" "Suspicious pattern in rc script(s) under $rc"
|
||
fi
|
||
done
|
||
}
|
||
|
||
# ---- Main ------------------------------------------------------------------
|
||
main() {
|
||
: > "$LOGFILE"
|
||
require_root
|
||
banner
|
||
|
||
echo -e "\n${CYAN}[*] Running Ultimate BPFDoor triage…${NC}"
|
||
check_mutex_files
|
||
check_autostart_files
|
||
check_bpf_filters
|
||
check_raw_and_packet_sockets
|
||
check_env_vars
|
||
check_suspicious_ports
|
||
check_process_masquerade
|
||
check_deleted_binaries
|
||
check_kernel_stack
|
||
check_c2_connections
|
||
check_process_signatures
|
||
deep_scan_suspicious_files
|
||
check_persistence
|
||
|
||
echo
|
||
echo -e "${CYAN}[*] Scan complete. Report written to: ${LOGFILE}${NC}"
|
||
echo -e "${YELLOW}[!] Any CRITICAL or ALERT entries should be investigated promptly.${NC}"
|
||
}
|
||
|
||
main "$@"
|
||
|
||
|