Upgrade to 1.2

This commit is contained in:
2026-04-07 12:16:10 +02:00
parent 2c073c9aa0
commit 2b434f8466

View File

@@ -25,7 +25,7 @@
set -o pipefail
VERSION="1.1"
VERSION="1.2"
HOSTNAME="$(hostname)"
DATE="$(date +%Y-%m-%d_%H-%M-%S)"
LOGFILE="bpfdoor_report_${HOSTNAME}_${DATE}.log"
@@ -80,7 +80,8 @@ SUSPICIOUS_PROCS=(
# 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/sbin/agetty" "/usr/sbin/auditd" "/usr/sbin/mingetty" "/usr/sbin/udevd"
"/usr/lib/systemd/systemd-journald" "/usr/lib/systemd/systemd-machined" "/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"
@@ -96,7 +97,7 @@ 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"
"I5*AYbs@LdaWbsO"
)
KNOWN_C2_HOSTS=(
@@ -394,7 +395,10 @@ check_raw_and_packet_sockets() {
# Skip if it's this detection script
[ "$exe_path" == "$SELF_EXE" ] && continue
#exclude legitimate networking tools
if [[ "$exe_path" == *"/NetworkManager"* ]] || [[ "$exe_path" == *"/dhclient"* ]] || [[ "$exe_path" == *"/systemd-networkd"* ]]; then
continue
fi
local cmd_line=$(ps -p "$pid" -o args= 2>/dev/null | head -n1)
# If we got here, we found something
@@ -519,14 +523,25 @@ check_process_masquerade() {
# 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 "")"
# Read the exact execution path from the kernel
local raw_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
# FIX 1: Strip the " (deleted)" suffix caused by legitimate package updates
local exe="${raw_exe%" (deleted)"}"
# Skip real kernel threads
if [[ -z "$exe" ]] || [[ "$raw_exe" == "/proc/$pid/exe" ]]; then
if [ ! -s "/proc/$pid/cmdline" ]; then
continue
fi
fi
local is_whitelisted=0
for wl in "${WHITELIST_EXES[@]}"; do
if [ "$exe" = "$wl" ]; then
# FIX 2: Canonicalize the whitelist path to account for merged-/usr symlinks
local canonical_wl="$(readlink -m "$wl" 2>/dev/null || echo "$wl")"
if [ "$exe" = "$canonical_wl" ]; then
is_whitelisted=1
break
fi
@@ -534,7 +549,7 @@ check_process_masquerade() {
# 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'"
log "CRITICAL" "[7/12] Process Masquerading Detected! PID=$pid claims to be '$args' but is actually executing '$raw_exe'"
mark_suspicious_file "$exe"
found=1
fi
@@ -599,13 +614,13 @@ check_kernel_stack() {
[ "$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) -----
# ---- Check 12: Deep scan suspicious files (hash, strings, UPX packer) -----
deep_scan_suspicious_files() {
log "INFO" "[10/12] Deep scanning candidate binaries (hash, strings, UPX packing)"
log "INFO" "[12/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"
log "INFO" "[12/12] No candidate binaries collected for deep scan"
return
fi
@@ -631,45 +646,97 @@ deep_scan_suspicious_files() {
done
}
# ---- Check 11: C2 Connections (DNS Resolving & SS Tracking) ---------------
# ---- Helper: Check if IP is a globally routable address -------------------
is_global_ip() {
local ip="$1"
# 1. IPv6 FAST-PATH (Regex Bypass for performance and Bash compatibility)
if [[ "$ip" == *":"* ]]; then
# Filter common IPv6 sinkholes/local: ::1, ::, fc00::/7, fe80::/10
if [[ "$ip" == "::1" ]] || [[ "$ip" == "::" ]] || \
[[ "$ip" =~ ^[fF][cCdD] ]] || [[ "$ip" =~ ^[fF][eE][89aAbB] ]]; then
return 1
fi
return 0
fi
# 2. IPv4 CIDR MATH PATH
[[ "$ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]] || return 1
local IFS='.' nums
read -r -a nums <<< "$ip"
for o in "${nums[@]}"; do (( o > 255 )) && return 1; done
local ipnum=$(( (nums[0] << 24) | (nums[1] << 16) | (nums[2] << 8) | nums[3] ))
in_cidr() {
local IFS='/' parts
read -r -a parts <<< "$1"
local IFS='.' net
read -r -a net <<< "${parts[0]}"
local netnum=$(( (net[0] << 24) | (net[1] << 16) | (net[2] << 8) | net[3] ))
local mask=$(( 0xFFFFFFFF << (32 - parts[1]) & 0xFFFFFFFF ))
(( (ipnum & mask) == (netnum & mask) ))
}
# Non-global ranges per IANA
local non_global=(
0.0.0.0/8 10.0.0.0/8 100.64.0.0/10 127.0.0.0/8 169.254.0.0/16
172.16.0.0/12 192.0.0.0/24 192.0.2.0/24 192.168.0.0/16
198.18.0.0/15 198.51.100.0/24 203.0.113.0/24 224.0.0.0/4 240.0.0.0/4
)
for cidr in "${non_global[@]}"; do
in_cidr "$cidr" && return 1
done
return 0
}
# ---- Check 10: C2 Connections (DNS Resolving & SS Tracking) ---------------
check_c2_connections() {
log "INFO" "[11/12] Checking for active connections to known BPFDoor C2 domains"
log "INFO" "[10/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."
log "WARN" "[10/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)"
local ips
ips="$(dig +short "$host" A "$host" AAAA 2>/dev/null | grep -E '^[0-9a-fA-F:.]+$' | grep -E '\.|\:'|| 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 ! is_global_ip "$ip"; then
log "INFO" "Skipping non-global IP for $host: $ip (likely sinkhole/private)"
continue
fi
local ss_output
ss_output="$(ss -H -tnp state established dst "$ip" 2>/dev/null || true)"
if [ -n "$ss_output" ]; then
log "CRITICAL" "Active Reverse Shell to BPFDoor C2: $host ($ip)"
log "CRITICAL" "Active connection to known BPFDoor C2: $host ($ip)"
echo "$ss_output" | tee -a "$LOGFILE"
local c2_pids="$(echo "$ss_output" | grep -oP 'pid=\K[0-9]+' | sort -u)"
local c2_pids
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 "")"
local exe
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"
[ "$found" -eq 0 ] && log "SUCCESS" "[10/12] No active connections to known C2 domains found"
}
# ---- Check 12: Process-Specific Signatures --------------------------------
# ---- Check 11: Process-Specific Signatures --------------------------------
check_process_signatures() {
log "INFO" "[12/12] Checking specific processes for hardcoded BPFDoor file signatures"
log "INFO" "[11/12] Checking specific processes for hardcoded BPFDoor file signatures"
local found=0
local sig_checks=(
@@ -689,6 +756,7 @@ check_process_signatures() {
for pid in $pids; do
is_self "$pid" && continue
[ -d "/proc/$pid" ] || continue
local exe_path=$(readlink -f "/proc/$pid/exe" 2>/dev/null || echo "unknown")
local path="$(readlink -f "/proc/$pid/exe" 2>/dev/null || echo "")"
[ -z "$path" ] && continue
@@ -707,32 +775,44 @@ check_process_signatures() {
done
done
[ "$found" -eq 0 ] && log "SUCCESS" "[12/12] No hardcoded process signatures detected"
[ "$found" -eq 0 ] && log "SUCCESS" "[11/12] No hardcoded process signatures detected"
}
# ---- Optional: Basic persistence checks -----------------------------------
check_persistence() {
log "INFO" "[-] Basic persistence triage (cron, systemd, rc scripts)"
# 1. Define the regex ONCE (Safely removed the generic 'bpf' trap)
local persist_regex="bpfdoor|dbus-srv|hpasmmld|smartadm|hald-addon-volume"
# 2. Check Cron
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"
if grep -E "$persist_regex" "$file" 2>/dev/null | grep -q .; then
log "CRITICAL" "Suspicious persistence entry in cron: $file"
grep -HnE "$persist_regex" "$file" >> "$LOGFILE"
fi
done
# 3. Check Systemd
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"
# Grab the exact files that match, instead of blindly alerting on the directory
local matches="$(grep -rlE "$persist_regex" "$dir" 2>/dev/null || true)"
if [ -n "$matches" ]; then
for m in $matches; do
log "CRITICAL" "Suspicious persistence pattern found in systemd unit: $m"
# Log the exact line of code that triggered it to the report
grep -HnE "$persist_regex" "$m" >> "$LOGFILE"
done
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"
# 4. Check RC / Init scripts
for rc in /etc/rc.local /etc/init.d/*; do
[ -f "$rc" ] || continue
if grep -E "$persist_regex" "$rc" 2>/dev/null | grep -q .; then
log "CRITICAL" "Suspicious persistence pattern in rc script: $rc"
grep -HnE "$persist_regex" "$rc" >> "$LOGFILE"
fi
done
}
@@ -760,9 +840,8 @@ main() {
echo
echo -e "${CYAN}[*] Scan complete. Report written to: ${LOGFILE}${NC}"
echo -e "${YELLOW}[!] Any CRITICAL or ALERT entries should be investigated promptly.${NC}"
echo -e "${YELLOW}[!] Any CRITICAL or ALERT entries should be investigated, considering there could be an acceptable rate of false positives depending on the execution environment.${NC}"
}
main "$@"