#!/usr/bin/env bash # dns_bpf_correlate_safe.sh set -u DOMAIN=$1 WINDOW_MS=2000 HALF_WINDOW_MS=$((WINDOW_MS/2)) BASE_TS="$(date +%Y%m%d_%H%M%S)" DNSLOG="${DOMAIN}_${BASE_TS}_dns.log" BPFLOG="bpftrace_${BASE_TS}.log" INCIDENT_DIR="incidents_${BASE_TS}" mkdir -p "$INCIDENT_DIR" RED="\e[31m"; GREEN="\e[32m"; YELLOW="\e[33m"; BLUE="\e[34m"; RESET="\e[0m" TCPDUMP_PID="" BPF_PID="" now_ms() { date +%s%3N; } cleanup() { echo -e "${BLUE}[*] Stopping background processes...${RESET}" [[ -n "$TCPDUMP_PID" ]] && kill "$TCPDUMP_PID" 2>/dev/null || true [[ -n "$BPF_PID" ]] && kill "$BPF_PID" 2>/dev/null || true sleep 0.1 echo -e "${BLUE}[*] Done.${RESET}" exit 0 } trap cleanup SIGINT SIGTERM echo -e "${BLUE}[*] Domain: ${YELLOW}$DOMAIN${RESET}" echo -e "${BLUE}[*] DNS log: ${YELLOW}$DNSLOG${RESET}" echo -e "${BLUE}[*] bpftrace log: ${YELLOW}$BPFLOG${RESET}" echo -e "${BLUE}[*] Incidents directory: ${YELLOW}$INCIDENT_DIR${RESET}" echo -e "${BLUE}[*] Window for correlation: ${YELLOW}${WINDOW_MS} ms (±${HALF_WINDOW_MS} ms)${RESET}" echo "" # Start tcpdump tcpdump -n -i any port 53 -s0 -ttt -l 2>/dev/null >> "$DNSLOG" & TCPDUMP_PID=$! echo -e "${GREEN}[*] tcpdump started (PID $TCPDUMP_PID)${RESET}" # Start bpftrace (safe, prints nsecs/1000000, PID, COMM, FD) bpftrace -e ' tracepoint:syscalls:sys_enter_sendto { printf("%d PID=%d COMM=%s FD=%d\n", nsecs/1000000, pid, comm, args->fd); }' > "$BPFLOG" 2>/dev/null & BPF_PID=$! sleep 0.2 if ! kill -0 "$BPF_PID" 2>/dev/null; then echo -e "${RED}[!] bpftrace failed to start!${RESET}" cleanup fi echo -e "${GREEN}[*] bpftrace started (PID $BPF_PID)${RESET}" sleep 1 echo "" # Helper function: /proc info for PID proc_info_for_pid() { local pid="$1" if [[ -r "/proc/$pid/cmdline" ]]; then local exe=$(readlink -f "/proc/$pid/exe" 2>/dev/null || true) local cmd=$(tr '\0' ' ' < "/proc/$pid/cmdline" 2>/dev/null || true) echo "pid=$pid exe=${exe:-N/A} cmdline='${cmd:-N/A}'" else echo "pid=$pid (no /proc info)" fi } # Tail tcpdump and correlate symmetrically tail -Fn0 "$DNSLOG" | while IFS= read -r dnsline; do if echo "$dnsline" | grep -qi -- "$DOMAIN"; then detect_ms=$(now_ms) echo -e "${RED}[!] Suspicious DNS request detected @${YELLOW}$detect_ms${RESET}" echo -e "${YELLOW}$dnsline${RESET}" echo "" INCIDENT_TS="$(date +%Y%m%d_%H%M%S_%3N)" INCIDENT_FILE="${INCIDENT_DIR}/incident_${INCIDENT_TS}.txt" { echo "=== INCIDENT ${INCIDENT_TS} ===" echo "dns_line: $dnsline" echo "detected_at_ms: $detect_ms" } > "$INCIDENT_FILE" # extract source IP/port src_tok=$(echo "$dnsline" | awk '{for(i=1;i<=NF;i++) if($i=="IP" || $i=="IP6"){print $(i+1); exit}}' || true) if [[ -n "$src_tok" ]]; then srcPort="${src_tok##*.}" srcIP="${src_tok%.*}" echo -e "${GREEN}[+] Extracted source: IP=${YELLOW}$srcIP${RESET} PORT=${YELLOW}$srcPort${RESET}" echo "extracted_src_ip: $srcIP" >> "$INCIDENT_FILE" echo "extracted_src_port: $srcPort" >> "$INCIDENT_FILE" fi # Symmetric window: ±HALF_WINDOW_MS low_ms=$((detect_ms - HALF_WINDOW_MS)) high_ms=$((detect_ms + HALF_WINDOW_MS)) awk -v low="$low_ms" -v high="$high_ms" ' BEGIN{FS=" "; OFS=" "} { if ($1 ~ /^[0-9]+$/) { t=$1; if (t >= low && t <= high) print $0 } }' "$BPFLOG" | tee -a "$INCIDENT_FILE" > /tmp/_bpf_matches.$$ || true if [[ -s /tmp/_bpf_matches.$$ ]]; then echo -e "${GREEN}[+] bpftrace events found in ±${HALF_WINDOW_MS}ms window:${RESET}" sed 's/^/ /' /tmp/_bpf_matches.$$ echo "bpftrace_matches:" >> "$INCIDENT_FILE" cat /tmp/_bpf_matches.$$ >> "$INCIDENT_FILE" pids=$(sed -n 's/.*PID=\([0-9]\+\).*/\1/p' /tmp/_bpf_matches.$$ | sort -u) for pid in $pids; do info=$(proc_info_for_pid "$pid") echo " [*] $info" echo " - $info" >> "$INCIDENT_FILE" done else echo -e "${YELLOW}[!] No bpftrace events in window${RESET}" echo "bpftrace_matches: NONE in ±${HALF_WINDOW_MS}ms" >> "$INCIDENT_FILE" fi echo "" >> "$INCIDENT_FILE" echo "ss_snapshot:" >> "$INCIDENT_FILE" ss -tulpn 2>/dev/null >> "$INCIDENT_FILE" || true echo -e "${GREEN}[+] Incident saved to ${YELLOW}$INCIDENT_FILE${RESET}" echo -e "${BLUE}------------------------------------------------------${RESET}" echo "" rm -f /tmp/_bpf_matches.$$ fi done