#!/bin/bash # ============================================================================ # AXIOS SUPPLY CHAIN COMPROMISE — FLEET IOC HUNT SCRIPT # Author: karmine05 # Date: 2026-03-31 # # Runs via Fleet Scripts on macOS and Linux endpoints. # Checks file artifacts, SHA256 hashes, malicious npm packages, running # processes, codesign abuse, network connections, DNS, and persistence. # ============================================================================ set -u COMPROMISED=0 FINDINGS="" # --- Known SHA256 hashes (Wiz Research IOC appendix) ------------------------ HASH_MACOS_RAT="92ff08773995ebc8d55ec4b8e1a225d0d1e51efa4ef88b8849d0071230c9645a" HASH_WIN_STAGE2="617b67a8e1210e4fc87c92d1d1da45a2f311c08d26e89b12307cf583c900d101" HASH_LINUX_RAT="fcb81618bb15edfdedfb638b4c08a2af9cac9ecfa551af135a8402bf980375cf" HASH_DROPPER="58401c195fe0a6204b42f5f90995ece5fab74ce7c69c67a24c61a057325af668" HASH_AXIOS_0304="59336a964f110c25c112bcc5adca7090296b54ab33fa95c0744b94f8a0d80c0f" HASH_AXIOS_1141="5bb67e88846096f1f8d42a0f0350c9c46260591567612ff9af46f98d1b7571cd" # --- Helpers ---------------------------------------------------------------- log_finding() { local category="$1" local detail="$2" COMPROMISED=1 FINDINGS="${FINDINGS}\n[HIT] ${category}: ${detail}" printf "[HIT] %s: %s\n" "$category" "$detail" } log_clean() { echo "[OK] $1: No indicators found" } detect_os() { case "$(uname -s)" in Darwin*) echo "macos" ;; Linux*) echo "linux" ;; *) echo "unknown" ;; esac } # Cross-platform SHA256 helper sha256_of() { local filepath="$1" if command -v shasum &>/dev/null; then shasum -a 256 "$filepath" 2>/dev/null | awk '{print $1}' elif command -v sha256sum &>/dev/null; then sha256sum "$filepath" 2>/dev/null | awk '{print $1}' else echo "NO_SHA256_TOOL" fi } OS=$(detect_os) echo "============================================================" echo " Axios Supply Chain IOC Hunt (v2 — Wiz Research IOCs)" echo " Host: $(hostname)" echo " OS: ${OS} ($(uname -s) $(uname -r))" echo " Date: $(date -u '+%Y-%m-%dT%H:%M:%SZ')" echo "============================================================" echo "" # ============================================================================ # 1. FILE SYSTEM ARTIFACTS # ============================================================================ echo "--- [1/9] File System Artifacts ---" FOUND_FILES=0 # macOS RAT payload if [ "$OS" = "macos" ]; then if [ -f "/Library/Caches/com.apple.act.mond" ]; then log_finding "FILE" "macOS RAT payload: /Library/Caches/com.apple.act.mond ($(ls -la /Library/Caches/com.apple.act.mond 2>/dev/null))" FOUND_FILES=1 fi fi # Linux RAT payload if [ "$OS" = "linux" ]; then if [ -f "/tmp/ld.py" ]; then log_finding "FILE" "Linux RAT payload: /tmp/ld.py ($(ls -la /tmp/ld.py 2>/dev/null))" FOUND_FILES=1 fi fi # Cross-platform temp staging file for tmp_path in "/tmp/6202033" "/private/tmp/6202033" "${TMPDIR:-/tmp}/6202033"; do if [ -f "$tmp_path" ]; then log_finding "FILE" "Staging file: ${tmp_path} ($(ls -la "$tmp_path" 2>/dev/null))" FOUND_FILES=1 fi done [ "$FOUND_FILES" -eq 0 ] && log_clean "File artifacts" echo "" # ============================================================================ # 2. SHA256 HASH VALIDATION (Wiz Research) # ============================================================================ echo "--- [2/9] SHA256 Hash Validation ---" FOUND_HASH=0 # Build list of (file_path|expected_hash|description) tuples HASH_CHECKS=() if [ "$OS" = "macos" ]; then HASH_CHECKS=( "/Library/Caches/com.apple.act.mond|${HASH_MACOS_RAT}|macOS Stage 2 RAT (Mach-O universal binary)" ) elif [ "$OS" = "linux" ]; then HASH_CHECKS=( "/tmp/ld.py|${HASH_LINUX_RAT}|Linux Stage 2 RAT (Python script)" ) fi for check in "${HASH_CHECKS[@]}"; do IFS='|' read -r fpath expected_hash description <<< "$check" if [ -f "$fpath" ]; then actual_hash=$(sha256_of "$fpath") if [ "$actual_hash" = "$expected_hash" ]; then log_finding "SHA256" "CONFIRMED MATCH: ${description} — ${fpath} (${actual_hash})" FOUND_HASH=1 else echo "[INFO] File exists but hash differs: ${fpath}" echo " Expected: ${expected_hash}" echo " Actual: ${actual_hash}" fi fi done # Check npm cache for compromised package tarballs by hash if command -v npm &>/dev/null; then NPM_CACHE_DIR=$(npm config get cache 2>/dev/null || echo "") if [ -n "$NPM_CACHE_DIR" ] && [ -d "$NPM_CACHE_DIR" ]; then echo "[INFO] Scanning npm cache at ${NPM_CACHE_DIR} for compromised tarballs..." for tarball_hash in "$HASH_AXIOS_0304" "$HASH_AXIOS_1141" "$HASH_DROPPER"; do CACHE_HIT=$(find "${NPM_CACHE_DIR}" -type f \( -name "*.tgz" -o -name "*.tar.gz" \) 2>/dev/null | head -100 | while read -r tgz; do h=$(sha256_of "$tgz") if [ "$h" = "$tarball_hash" ]; then echo "$tgz" break fi done || true) if [ -n "$CACHE_HIT" ]; then log_finding "SHA256" "Compromised tarball in npm cache: ${CACHE_HIT} (sha256: ${tarball_hash})" FOUND_HASH=1 fi done fi fi [ "$FOUND_HASH" -eq 0 ] && log_clean "SHA256 hash validation" echo "" # ============================================================================ # 3. MALICIOUS NPM PACKAGES # ============================================================================ echo "--- [3/9] Malicious npm Packages ---" FOUND_NPM=0 # Check global npm for compromised axios versions if command -v npm &>/dev/null; then GLOBAL_AXIOS=$(npm ls -g axios 2>/dev/null | grep -E "axios@(1\.14\.1|0\.30\.4)" || true) if [ -n "$GLOBAL_AXIOS" ]; then log_finding "NPM-GLOBAL" "Compromised axios installed globally: ${GLOBAL_AXIOS}" FOUND_NPM=1 fi fi # Search for the dropper dependency and malicious packages in node_modules MALICIOUS_PKGS=( "plain-crypto-js" "@shadanai/openclaw" "@qqbrowser/openclaw-qbot" ) SEARCH_ROOTS=() if [ "$OS" = "macos" ]; then SEARCH_ROOTS=(/Users/*/node_modules /Users/*/*/node_modules /opt/*/node_modules) elif [ "$OS" = "linux" ]; then SEARCH_ROOTS=(/home/*/node_modules /home/*/*/node_modules /opt/*/node_modules /var/*/node_modules) fi for root_glob in "${SEARCH_ROOTS[@]}"; do for root_dir in $root_glob; do [ -d "$root_dir" ] || continue for pkg in "${MALICIOUS_PKGS[@]}"; do pkg_json="${root_dir}/${pkg}/package.json" if [ -f "$pkg_json" ]; then pkg_version=$(grep -o '"version"[[:space:]]*:[[:space:]]*"[^"]*"' "$pkg_json" 2>/dev/null | head -1 || echo "unknown") log_finding "NPM-LOCAL" "Malicious package found: ${pkg_json} (${pkg_version})" FOUND_NPM=1 fi done done done # Look for setup.js dropper (4,209 bytes) — the stage 1.5 installer for root_glob in "${SEARCH_ROOTS[@]}"; do for root_dir in $root_glob; do [ -d "$root_dir" ] || continue setup_js="${root_dir}/plain-crypto-js/setup.js" if [ -f "$setup_js" ]; then log_finding "NPM-LOCAL" "Dropper script found: ${setup_js} ($(ls -la "$setup_js" 2>/dev/null))" FOUND_NPM=1 fi done done # Search for compromised axios versions in local node_modules for root_glob in "${SEARCH_ROOTS[@]}"; do for root_dir in $root_glob; do [ -d "$root_dir" ] || continue axios_json="${root_dir}/axios/package.json" if [ -f "$axios_json" ]; then axios_ver=$(grep -o '"version"[[:space:]]*:[[:space:]]*"[^"]*"' "$axios_json" 2>/dev/null | head -1 || echo "") if echo "$axios_ver" | grep -qE '"(1\.14\.1|0\.30\.4)"'; then log_finding "NPM-LOCAL" "Compromised axios: ${axios_json} (${axios_ver})" FOUND_NPM=1 fi fi done done [ "$FOUND_NPM" -eq 0 ] && log_clean "npm packages" echo "" # ============================================================================ # 4. RUNNING PROCESSES # ============================================================================ echo "--- [4/9] Suspicious Processes ---" FOUND_PROC=0 # RAT binary names and IOC strings in process list PROC_PATTERNS=( "com.apple.act.mond" "ld\.py" "stage2\.ps1" "6202033" "sfrclak" "plain-crypto-js" ) PS_OUTPUT=$(ps aux 2>/dev/null || ps -ef 2>/dev/null) for pattern in "${PROC_PATTERNS[@]}"; do # Use grep -E for consistent regex behavior on Linux/macOS MATCHES=$(echo "$PS_OUTPUT" | grep -Ei "$pattern" | grep -v "grep" | grep -v "axios_ioc_hunt" || true) if [ -n "$MATCHES" ]; then log_finding "PROCESS" "Pattern '${pattern}' found in running processes:" echo "$MATCHES" | while read -r line; do printf " %s\n" "$line"; done FOUND_PROC=1 fi done [ "$FOUND_PROC" -eq 0 ] && log_clean "Running processes" echo "" # ============================================================================ # 5. macOS CODESIGN ABUSE (Wiz Research) # The Mach-O RAT self-signs injected payloads via codesign. # ============================================================================ echo "--- [5/9] macOS Codesign Abuse ---" FOUND_CODESIGN=0 if [ "$OS" = "macos" ]; then # Check if the RAT binary exists and inspect its signature if [ -f "/Library/Caches/com.apple.act.mond" ]; then CODESIGN_INFO=$(codesign -dvvv /Library/Caches/com.apple.act.mond 2>&1 || true) log_finding "CODESIGN" "RAT binary signature info:" echo "$CODESIGN_INFO" | head -20 FOUND_CODESIGN=1 fi # Look for ad-hoc signed or unsigned executables in /Library/Caches # (RAT injects and self-signs payloads here) ADHOC_BINS=$(find /Library/Caches -type f -perm +111 2>/dev/null | while read -r bin; do sig=$(codesign -d --verbose=2 "$bin" 2>&1 || true) if echo "$sig" | grep -q "adhoc\|not signed\|invalid"; then echo "$bin" fi done || true) if [ -n "$ADHOC_BINS" ]; then log_finding "CODESIGN" "Suspicious ad-hoc/unsigned executables in /Library/Caches:" echo "$ADHOC_BINS" | while read -r line; do echo " $line"; done FOUND_CODESIGN=1 fi # Check for recent codesign invocations in unified log (last 1 hour) CODESIGN_LOG=$(log show --predicate 'process == "codesign"' --last 1h 2>/dev/null | grep -i "Library/Caches" || true) if [ -n "$CODESIGN_LOG" ]; then log_finding "CODESIGN" "Recent codesign activity targeting /Library/Caches:" echo "$CODESIGN_LOG" | head -10 FOUND_CODESIGN=1 fi else echo "[SKIP] macOS-only check — skipping on ${OS}" fi [ "$FOUND_CODESIGN" -eq 0 ] && [ "$OS" = "macos" ] && log_clean "Codesign abuse" echo "" # ============================================================================ # 6. NETWORK CONNECTIONS # ============================================================================ echo "--- [6/9] Network Connections (C2: 142.11.206.73 / sfrclak.com:8000) ---" FOUND_NET=0 # Check for connections to C2 IP if command -v lsof &>/dev/null; then C2_LSOF=$(lsof -i @142.11.206.73 2>/dev/null || true) if [ -n "$C2_LSOF" ]; then log_finding "NETWORK" "Active connection to C2 IP 142.11.206.73:" echo "$C2_LSOF" FOUND_NET=1 fi # Also check port 8000 for any suspicious outbound PORT_8000=$(lsof -i :8000 2>/dev/null | grep -v "LISTEN" || true) if echo "$PORT_8000" | grep -q "142.11.206.73"; then log_finding "NETWORK" "Connection on port 8000 to C2 IP:" echo "$PORT_8000" | grep "142.11.206.73" FOUND_NET=1 fi fi # Netstat fallback if command -v netstat &>/dev/null; then NS_MATCH=$(netstat -an 2>/dev/null | grep "142.11.206.73" || true) if [ -n "$NS_MATCH" ]; then log_finding "NETWORK" "netstat hit for C2 IP 142.11.206.73:" echo "$NS_MATCH" FOUND_NET=1 fi fi # ss fallback (Linux) if [ "$OS" = "linux" ] && command -v ss &>/dev/null; then SS_MATCH=$(ss -tunap 2>/dev/null | grep "142.11.206.73" || true) if [ -n "$SS_MATCH" ]; then log_finding "NETWORK" "ss hit for C2 IP 142.11.206.73:" echo "$SS_MATCH" FOUND_NET=1 fi fi [ "$FOUND_NET" -eq 0 ] && log_clean "Network connections" echo "" # ============================================================================ # 7. DNS RESOLUTION CHECK # ============================================================================ echo "--- [7/9] DNS Resolution ---" FOUND_DNS=0 for domain in "sfrclak.com" "packages.npm.org"; do HOSTS_MATCH=$(grep -i "$domain" /etc/hosts 2>/dev/null || true) if [ -n "$HOSTS_MATCH" ]; then log_finding "DNS" "C2-related entry in /etc/hosts: ${HOSTS_MATCH}" FOUND_DNS=1 fi done # macOS DNS cache check if [ "$OS" = "macos" ]; then SCUTIL_CHECK=$(scutil --dns 2>/dev/null | grep -i "sfrclak" || true) if [ -n "$SCUTIL_CHECK" ]; then log_finding "DNS" "sfrclak.com found in macOS DNS config" FOUND_DNS=1 fi fi # Linux: check systemd-resolved cache if available if [ "$OS" = "linux" ] && command -v resolvectl &>/dev/null; then # Only flag if it actually resolves (contains an IP address) RESOLVED_CHECK=$(resolvectl query sfrclak.com 2>&1 || true) if echo "$RESOLVED_CHECK" | grep -qE "([0-9]{1,3}\.){3}[0-9]{1,3}"; then log_finding "DNS" "sfrclak.com resolved to an active IP via systemd-resolved" FOUND_DNS=1 fi fi [ "$FOUND_DNS" -eq 0 ] && log_clean "DNS" echo "" # ============================================================================ # 8. PERSISTENCE MECHANISMS # ============================================================================ echo "--- [8/9] Persistence ---" FOUND_PERSIST=0 IOC_STRINGS="act\.mond\|6202033\|sfrclak\|ld\.py\|wt\.exe\|plain-crypto-js\|stage2\.ps1\|MicrosoftUpdate" # macOS: LaunchDaemons / LaunchAgents if [ "$OS" = "macos" ]; then for plist_dir in \ /Library/LaunchDaemons \ /Library/LaunchAgents \ ~/Library/LaunchAgents; do if [ -d "$plist_dir" ]; then PLIST_HITS=$(grep -rl "$IOC_STRINGS" "$plist_dir" 2>/dev/null || true) if [ -n "$PLIST_HITS" ]; then log_finding "PERSIST" "IOC string found in plist(s): ${PLIST_HITS}" FOUND_PERSIST=1 fi fi done fi # Linux/macOS: crontab CRON_HIT=$(crontab -l 2>/dev/null | grep -E "ld\.py|6202033|sfrclak|act\.mond|stage2\.ps1" || true) if [ -n "$CRON_HIT" ]; then log_finding "PERSIST" "IOC string found in user crontab: ${CRON_HIT}" FOUND_PERSIST=1 fi # System crontabs for cron_file in /etc/crontab /etc/cron.d/*; do if [ -f "$cron_file" ]; then SYS_CRON=$(grep -E "ld\.py|6202033|sfrclak|act\.mond|stage2\.ps1" "$cron_file" 2>/dev/null || true) if [ -n "$SYS_CRON" ]; then log_finding "PERSIST" "IOC in system cron ${cron_file}: ${SYS_CRON}" FOUND_PERSIST=1 fi fi done # Linux: systemd units if [ "$OS" = "linux" ]; then for unit_dir in /etc/systemd/system /usr/lib/systemd/system ~/.config/systemd/user; do if [ -d "$unit_dir" ]; then UNIT_HITS=$(grep -rl "ld\.py\|6202033\|sfrclak\|stage2" "$unit_dir" 2>/dev/null || true) if [ -n "$UNIT_HITS" ]; then log_finding "PERSIST" "IOC in systemd unit(s): ${UNIT_HITS}" FOUND_PERSIST=1 fi fi done fi [ "$FOUND_PERSIST" -eq 0 ] && log_clean "Persistence mechanisms" echo "" # ============================================================================ # 9. SUMMARY # ============================================================================ echo "--- [9/9] Summary ---" echo "" if [ "$COMPROMISED" -eq 1 ]; then echo "============================================================" echo " RESULT: INDICATORS OF COMPROMISE DETECTED" echo "============================================================" echo "" echo "Findings:" printf "%b\n" "$FINDINGS" echo "" echo "Wiz Research SHA256 reference hashes:" echo " macOS RAT: ${HASH_MACOS_RAT}" echo " Win stage2: ${HASH_WIN_STAGE2}" echo " Linux RAT: ${HASH_LINUX_RAT}" echo " Dropper pkg: ${HASH_DROPPER}" echo " axios 0.30.4: ${HASH_AXIOS_0304}" echo " axios 1.14.1: ${HASH_AXIOS_1141}" echo "" echo "Recommended actions:" echo " 1. Isolate this host from the network immediately" echo " 2. Preserve forensic evidence (memory dump, disk image)" echo " 3. Rotate all credentials/tokens accessible from this host" echo " (Wiz: assume credential compromise if malicious packages executed)" echo " 4. Check npm cache: npm cache ls 2>/dev/null | grep -E 'axios|plain-crypto'" echo " 5. Review git history for lockfile changes in the exposure window" echo " (2026-03-31 00:21 UTC to 03:15 UTC)" echo " 6. Scan for secrets in env vars, .env files, API keys, tokens" echo " (RAT transmits system inventory to C2 every 60s)" echo "" exit 1 else echo "============================================================" echo " RESULT: CLEAN — No Axios compromise indicators found" echo "============================================================" echo "" exit 0 fi