#!/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
Comments
0 B
|0 👍
/0 👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|👍
/👎
0 B
|0 👍
/0 👎