Skip to content

Instantly share code, notes, and snippets.

@ghmendonca
Created February 12, 2026 01:26
Show Gist options
  • Select an option

  • Save ghmendonca/eb09ba4a7eb69a23e9935d66b877d8bf to your computer and use it in GitHub Desktop.

Select an option

Save ghmendonca/eb09ba4a7eb69a23e9935d66b877d8bf to your computer and use it in GitHub Desktop.
NPM Supply Chain Malware Scanner (Cross-Chain TxDataHiding / OmniStealer / DPRK APT) - Detects blockchain C2 malware, IDE injection, credential theft. Based on Ransom-ISAC YARA rules. 16 detection checks.
#!/bin/bash
# NPM Supply Chain Malware Detection Script (Cross-Chain TxDataHiding / OmniStealer)
# Reference: https://ransom-isac.org/blog/cross-chain-txdatahiding-crypto-heist-part-2/
# YARA Rules: Actor_APT_DPRK_Unknown_MAL (Ransom-ISAC)
# Detects malware variant that spreads via compromised npm packages
# Usage: ./detect-infection.sh
# Supports: macOS, Linux, Windows (Git Bash / MSYS2 / Cygwin)
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
INFECTED=0
# ========================================
# OS DETECTION
# ========================================
detect_os() {
case "$(uname -s)" in
Linux*) OS="linux";;
Darwin*) OS="macos";;
CYGWIN*) OS="windows";;
MINGW*) OS="windows";;
MSYS*) OS="windows";;
*) OS="unknown";;
esac
# Double-check for WSL (reports as Linux but is Windows-adjacent)
if [ "$OS" = "linux" ] && grep -qi microsoft /proc/version 2>/dev/null; then
OS="wsl"
fi
}
detect_os
# ========================================
# PLATFORM-SPECIFIC PATH RESOLUTION
# ========================================
# Resolve Windows home directory
if [ "$OS" = "windows" ]; then
# In Git Bash/MSYS, $HOME is set but $USERPROFILE has the native path
WIN_HOME="${USERPROFILE:-$HOME}"
# Convert Windows paths to Unix-style for Git Bash
WIN_HOME_UNIX=$(cygpath -u "$WIN_HOME" 2>/dev/null || echo "$HOME")
EFFECTIVE_HOME="$WIN_HOME_UNIX"
WIN_APPDATA=$(cygpath -u "$APPDATA" 2>/dev/null || echo "$EFFECTIVE_HOME/AppData/Roaming")
WIN_LOCALAPPDATA=$(cygpath -u "$LOCALAPPDATA" 2>/dev/null || echo "$EFFECTIVE_HOME/AppData/Local")
WIN_PROGRAMFILES=$(cygpath -u "$PROGRAMFILES" 2>/dev/null || echo "/c/Program Files")
WIN_TEMP=$(cygpath -u "$TEMP" 2>/dev/null || echo "/tmp")
elif [ "$OS" = "wsl" ]; then
EFFECTIVE_HOME="$HOME"
# Access Windows filesystem from WSL
WIN_USER=$(cmd.exe /C "echo %USERNAME%" 2>/dev/null | tr -d '\r')
WIN_HOME_MNT="/mnt/c/Users/$WIN_USER"
WIN_APPDATA="$WIN_HOME_MNT/AppData/Roaming"
WIN_LOCALAPPDATA="$WIN_HOME_MNT/AppData/Local"
WIN_PROGRAMFILES="/mnt/c/Program Files"
WIN_TEMP="$WIN_HOME_MNT/AppData/Local/Temp"
else
EFFECTIVE_HOME="$HOME"
fi
# Known malware indicators from YARA rules
TRON_WALLETS="TMfKQEd7TJJa5xNZJZ2Lep838vrzrs7mAP|TXfxHUet9pJVU1BgVkBAbrES4YUc1nGzcG|TLmj13VL4p6NQ7jpxz8d9uYY6FUKCYatS"
APTOS_ADDRS="be037400670fbf1c32364f762975908dc43eeb38759263e7dfcdabc76380811e|3f0e5781d0855fb460661ac63257376db1941b2bb522499e4757ecb3ebd5dce3|3414a658f13b652f24301e986f9e0079ef506992472c1d5224180340d8105837"
BSC_HASHES="f46c86c886bbf9915f4841a8c27b38c519fe3ce54ba69c98d233d0ffc94d19fc|d33f78662df123adf2a178628980b605a0026c0d8c4f4e87e43e724cda258fef|a8cdabea3616a6d43e0893322112f9dca05b7d2f88fd1b7370c33c79076216ff"
MALWARE_MARKERS="C250617A|C250618A|C5-benefit|CHQG3L42MMQ"
OBFS_PATTERNS="_\\\$af402041|_\\\$af813180|_\\\$_2d00"
TELEGRAM_TOKEN="7870147428:AAGbYG_eYkiAziCKRmkiQF-"
echo "========================================"
echo " NPM SUPPLY CHAIN MALWARE SCANNER"
echo " (Cross-Chain TxDataHiding Variant)"
echo "========================================"
echo ""
echo " Detected OS: $OS"
echo " Home: $EFFECTIVE_HOME"
[ "$OS" = "windows" ] || [ "$OS" = "wsl" ] && echo " Windows Home: ${WIN_HOME:-$WIN_HOME_MNT}"
echo ""
# ========================================
# HELPER FUNCTIONS (cross-platform)
# ========================================
# Cross-platform process listing
list_processes() {
if [ "$OS" = "windows" ]; then
# tasklist + wmic for command line args
tasklist /V /FO CSV 2>/dev/null || wmic process get ProcessId,CommandLine 2>/dev/null
else
ps aux 2>/dev/null
fi
}
# Cross-platform network connections
list_connections() {
if [ "$OS" = "windows" ]; then
netstat -ano 2>/dev/null
elif command -v lsof &>/dev/null; then
lsof -i 2>/dev/null
elif command -v ss &>/dev/null; then
ss -tunap 2>/dev/null
else
netstat -tunap 2>/dev/null
fi
}
# Cross-platform: find node PIDs
get_node_pids() {
if [ "$OS" = "windows" ]; then
tasklist /FI "IMAGENAME eq node.exe" /FO CSV /NH 2>/dev/null | \
awk -F'","' '{gsub(/"/, "", $2); print $2}' | grep -E '^[0-9]+$'
else
pgrep -x node 2>/dev/null
fi
}
# Cross-platform: count connections for a PID
count_pid_connections() {
local pid=$1
if [ "$OS" = "windows" ]; then
netstat -ano 2>/dev/null | grep -c "$pid" | tr -d ' '
else
lsof -p "$pid" 2>/dev/null | grep -E "IPv[46]" | wc -l | tr -d ' '
fi
}
# Cross-platform: check if file is executable
is_executable() {
if [ "$OS" = "windows" ]; then
[ -f "$1" ] && echo "$1"
else
find "$(dirname "$1")" -maxdepth 1 -name "$(basename "$1")" -type f -perm /111 2>/dev/null
fi
}
# ========================================
# BUILD PATHS TO SCAN
# ========================================
# --- node_modules persistence paths ---
NODE_MODULES_PATHS=("$EFFECTIVE_HOME/.node_modules")
if [ "$OS" = "windows" ] || [ "$OS" = "wsl" ]; then
WIN_H="${WIN_HOME_UNIX:-$WIN_HOME_MNT}"
NODE_MODULES_PATHS+=(
"$WIN_H/.node_modules"
"$WIN_H/node_modules"
# NOTE: %APPDATA%/npm/node_modules is the STANDARD global npm directory on Windows.
# The malware uses hidden/non-standard paths. We scan %APPDATA%/node_modules (no npm/)
# and %LOCALAPPDATA%/node_modules which are NOT standard and indicate persistence.
"$WIN_APPDATA/node_modules"
"$WIN_LOCALAPPDATA/node_modules"
)
fi
# --- VSCode / Cursor deviceid injection paths ---
DEVICEID_PATHS=()
if [ "$OS" = "macos" ]; then
DEVICEID_PATHS=(
"/Applications/Visual Studio Code.app/Contents/Resources/app/node_modules/@vscode/deviceid/dist/index.js"
"/Applications/Cursor.app/Contents/Resources/app/node_modules/@vscode/deviceid/dist/index.js"
)
elif [ "$OS" = "linux" ]; then
DEVICEID_PATHS=(
"/usr/share/code/resources/app/node_modules/@vscode/deviceid/dist/index.js"
)
elif [ "$OS" = "windows" ] || [ "$OS" = "wsl" ]; then
WIN_H="${WIN_HOME_UNIX:-$WIN_HOME_MNT}"
DEVICEID_PATHS=(
# VS Code - user install (most common on Windows)
"$WIN_LOCALAPPDATA/Programs/Microsoft VS Code/resources/app/node_modules/@vscode/deviceid/dist/index.js"
# VS Code - system install
"$WIN_PROGRAMFILES/Microsoft VS Code/resources/app/node_modules/@vscode/deviceid/dist/index.js"
# Cursor - user install
"$WIN_LOCALAPPDATA/Programs/cursor/resources/app/node_modules/@vscode/deviceid/dist/index.js"
"$WIN_LOCALAPPDATA/Cursor/resources/app/node_modules/@vscode/deviceid/dist/index.js"
# Also check under home for scoop/portable installs
"$WIN_H/scoop/apps/vscode/current/resources/app/node_modules/@vscode/deviceid/dist/index.js"
)
# Also keep Linux paths for WSL-native VS Code
if [ "$OS" = "wsl" ]; then
DEVICEID_PATHS+=(
"/usr/share/code/resources/app/node_modules/@vscode/deviceid/dist/index.js"
)
fi
fi
# --- Lock file paths ---
LOCK_FILES=("/tmp/tmp7A863DD1.tmp")
if [ "$OS" = "windows" ] || [ "$OS" = "wsl" ]; then
LOCK_FILES+=(
"$WIN_TEMP/tmp7A863DD1.tmp"
"${WIN_HOME_UNIX:-$WIN_HOME_MNT}/AppData/Local/Temp/tmp7A863DD1.tmp"
)
fi
# --- Shell profile paths ---
SHELL_FILES=("$EFFECTIVE_HOME/.bashrc" "$EFFECTIVE_HOME/.bash_profile" "$EFFECTIVE_HOME/.zshrc" "$EFFECTIVE_HOME/.zprofile" "$EFFECTIVE_HOME/.profile" "$EFFECTIVE_HOME/.zshenv" "$EFFECTIVE_HOME/.bash_login")
if [ "$OS" = "windows" ] || [ "$OS" = "wsl" ]; then
WIN_H="${WIN_HOME_UNIX:-$WIN_HOME_MNT}"
SHELL_FILES+=(
# Git Bash profiles on Windows
"$WIN_H/.bashrc"
"$WIN_H/.bash_profile"
"$WIN_H/.profile"
"$WIN_H/.minttyrc"
# PowerShell profiles
"$WIN_H/Documents/PowerShell/Microsoft.PowerShell_profile.ps1"
"$WIN_H/Documents/WindowsPowerShell/Microsoft.PowerShell_profile.ps1"
)
fi
# --- Config search directories ---
CONFIG_SEARCH_DIRS=("$EFFECTIVE_HOME")
if [ "$OS" = "windows" ] || [ "$OS" = "wsl" ]; then
WIN_H="${WIN_HOME_UNIX:-$WIN_HOME_MNT}"
# Add Windows home if different from EFFECTIVE_HOME
if [ "$WIN_H" != "$EFFECTIVE_HOME" ]; then
CONFIG_SEARCH_DIRS+=("$WIN_H")
fi
fi
# --- Chrome extension paths ---
CHROME_PROFILE_DIRS=()
if [ "$OS" = "macos" ]; then
CHROME_PROFILE_DIRS=("$HOME/Library/Application Support/Google/Chrome")
elif [ "$OS" = "linux" ]; then
CHROME_PROFILE_DIRS=("$HOME/.config/google-chrome")
elif [ "$OS" = "windows" ] || [ "$OS" = "wsl" ]; then
CHROME_PROFILE_DIRS=(
"$WIN_LOCALAPPDATA/Google/Chrome/User Data"
)
fi
# --- Suspicious Python paths ---
PYTHON_SEARCH_DIRS=("/tmp" "/var/tmp")
PYTHON3127_DIRS=()
if [ "$OS" = "macos" ]; then
PYTHON3127_DIRS=(
"$HOME/Library/Python/Python3127"
"/tmp/Python3127"
"$HOME/.local/Python3127"
)
elif [ "$OS" = "linux" ] || [ "$OS" = "wsl" ]; then
PYTHON3127_DIRS=(
"/tmp/Python3127"
"$HOME/.local/Python3127"
)
elif [ "$OS" = "windows" ]; then
WIN_H="${WIN_HOME_UNIX:-$WIN_HOME_MNT}"
PYTHON3127_DIRS=(
"$WIN_H/Python3127"
"$WIN_LOCALAPPDATA/Python3127"
"$WIN_APPDATA/Python3127"
"$WIN_TEMP/Python3127"
)
PYTHON_SEARCH_DIRS+=("$WIN_TEMP")
fi
# ========================================
# CHECKS BEGIN
# ========================================
# 1. Check for malicious node -e processes with malware signature
echo -n "[1/16] Checking for malicious node -e processes... "
if [ "$OS" = "windows" ]; then
MALICIOUS_NODE=$(wmic process where "name='node.exe'" get ProcessId,CommandLine /FORMAT:LIST 2>/dev/null | \
grep -iE "node.*-e" | grep -iE "global\[|_V.*=|atob|eval\(")
if [ -n "$MALICIOUS_NODE" ]; then
echo -e "${RED}INFECTED${NC}"
echo "$MALICIOUS_NODE" | head -5 | while read line; do
echo " $line"
done
INFECTED=1
else
echo -e "${GREEN}Clean${NC}"
fi
else
MALICIOUS_NODE=$(ps aux 2>/dev/null | grep -E "node\s+-e" | grep -v grep)
if echo "$MALICIOUS_NODE" | grep -qE "global\[|_V.*=|atob|eval\("; then
echo -e "${RED}INFECTED${NC}"
echo "$MALICIOUS_NODE" | while read line; do
PID=$(echo "$line" | awk '{print $2}')
echo " PID: $PID"
done
INFECTED=1
else
echo -e "${GREEN}Clean${NC}"
fi
fi
# 2. Check for C2 connections (known IP ranges from IOC list)
echo -n "[2/16] Checking for C2 server connections... "
C2_IPS="198\.105\.127\.|23\.27\.20\.143|136\.0\.9\.8|23\.27\.202\.27|166\.88\.4\.2|45\.125\.|103\.136\."
if [ "$OS" = "windows" ]; then
C2_CONN=$(netstat -ano 2>/dev/null | grep -E "$(echo "$C2_IPS" | sed 's/\\\././g')" | head -5)
else
C2_CONN=$(lsof -i 2>/dev/null | grep -E "$C2_IPS" | grep -v grep | head -5)
fi
if [ -n "$C2_CONN" ]; then
echo -e "${RED}INFECTED${NC}"
echo "$C2_CONN" | while read line; do
echo " $line"
done
INFECTED=1
else
echo -e "${GREEN}Clean${NC}"
fi
# 3. Check for hidden node_modules with malware payload (ALL platform paths)
echo -n "[3/16] Checking for malware persistence in node_modules... "
NM_INFECTED=""
for NM_PATH in "${NODE_MODULES_PATHS[@]}"; do
if [ -d "$NM_PATH" ]; then
MALWARE_FILES=""
MALWARE_INDICATOR=""
if [ -f "$NM_PATH/package.json" ]; then
SUSP_PKG=$(cat "$NM_PATH/package.json" 2>/dev/null | grep -iE "postinstall|preinstall|eval|atob" | head -1)
if [ -n "$SUSP_PKG" ]; then
MALWARE_INDICATOR="suspicious package.json"
MALWARE_FILES="$NM_PATH/package.json"
fi
fi
MARKER_FILES=$(grep -rl "C250618A\|C250617A\|global\['_V'\]\|global\['_H'\]\|5-3-276\|eval.*atob" "$NM_PATH" 2>/dev/null)
if [ -n "$MARKER_FILES" ]; then
MALWARE_INDICATOR="malware signature found"
MALWARE_FILES="$MARKER_FILES"
fi
if [ -z "$MALWARE_FILES" ]; then
OBFUSCATED_FILES=$(find "$NM_PATH" -name "*.js" -exec sh -c 'if awk "length>500 {found=1; exit} END {exit !found}" "$1" 2>/dev/null; then echo "$1"; fi' _ {} \; 2>/dev/null)
if [ -n "$OBFUSCATED_FILES" ]; then
MALWARE_INDICATOR="obfuscated JS files"
MALWARE_FILES="$OBFUSCATED_FILES"
fi
fi
if [ -n "$MALWARE_INDICATOR" ]; then
NM_INFECTED="$NM_INFECTED
$NM_PATH ($MALWARE_INDICATOR)"
echo "$MALWARE_FILES" | head -5 | while read f; do
NM_INFECTED="$NM_INFECTED
- $f"
done
fi
fi
done
if [ -n "$NM_INFECTED" ]; then
echo -e "${RED}INFECTED${NC}"
echo "$NM_INFECTED"
INFECTED=1
else
echo -e "${GREEN}Clean${NC}"
fi
# 4. Check for blockchain C2 endpoint connections
echo -n "[4/16] Checking for blockchain RPC connections... "
BLOCKCHAIN_PATTERN="trongrid\|aptoslabs\|bsc-dataseed\|bsc-rpc\|publicnode\|binance"
if [ "$OS" = "windows" ]; then
# On Windows, netstat doesn't show hostnames by default, so also check DNS cache
BLOCKCHAIN_CONN=$(netstat -ano 2>/dev/null | grep -iE "trongrid|aptoslabs|bsc-dataseed|bsc-rpc|publicnode|binance" | head -3)
if [ -z "$BLOCKCHAIN_CONN" ]; then
# Check Windows DNS cache for these domains
BLOCKCHAIN_CONN=$(ipconfig /displaydns 2>/dev/null | grep -iE "trongrid|aptoslabs|bsc-dataseed|bsc-rpc|publicnode|binance" | head -3)
fi
else
BLOCKCHAIN_CONN=$(lsof -i 2>/dev/null | grep -iE "trongrid|aptoslabs|bsc-dataseed|bsc-rpc|publicnode|binance" | grep -v grep | head -3)
fi
if [ -n "$BLOCKCHAIN_CONN" ]; then
echo -e "${RED}INFECTED${NC}"
echo "$BLOCKCHAIN_CONN" | while read line; do
echo " $line"
done
INFECTED=1
else
echo -e "${GREEN}Clean${NC}"
fi
# 5. Check for the malware lock file (ALL platform paths)
echo -n "[5/16] Checking for malware lock files... "
LOCK_FOUND=""
for lf in "${LOCK_FILES[@]}"; do
if [ -f "$lf" ]; then
LOCK_FOUND="$LOCK_FOUND $lf"
fi
done
if [ -n "$LOCK_FOUND" ]; then
echo -e "${RED}INFECTED${NC}"
for f in $LOCK_FOUND; do
echo " Found: $f"
done
INFECTED=1
else
echo -e "${GREEN}Clean${NC}"
fi
# 6. Check VSCode/Cursor for injection (C250617A marker)
echo -n "[6/16] Checking IDE for payload injection... "
IDE_INFECTED=""
for path in "${DEVICEID_PATHS[@]}"; do
if [ -f "$path" ]; then
if grep -q "C250617A\|C250618A\|global\['e'\]='vscode-eval'" "$path" 2>/dev/null; then
IDE_INFECTED="$IDE_INFECTED $path"
fi
fi
done
if [ -n "$IDE_INFECTED" ]; then
echo -e "${RED}INFECTED${NC}"
for f in $IDE_INFECTED; do
echo " - $f"
done
INFECTED=1
else
echo -e "${GREEN}Clean${NC}"
fi
# 7. Check config files for malware payload (optimized, multi-dir)
echo -n "[7/16] Checking config files for embedded malware... "
MALWARE_CONFIG=""
CONFIG_FILES=""
for search_dir in "${CONFIG_SEARCH_DIRS[@]}"; do
if [ -d "$search_dir" ]; then
FOUND=$(timeout 10 find "$search_dir" -maxdepth 5 \( -name "tailwind.config.*" -o -name "next.config.*" -o -name "vite.config.*" \) -type f 2>/dev/null | head -50)
CONFIG_FILES="$CONFIG_FILES $FOUND"
fi
done
for f in $CONFIG_FILES; do
if grep -q "eval.*atob\|global\['_V'\]\|global\['_H'\]" "$f" 2>/dev/null; then
MALWARE_CONFIG="$MALWARE_CONFIG $f"
fi
done
if [ -n "$MALWARE_CONFIG" ]; then
echo -e "${RED}INFECTED${NC}"
for f in $MALWARE_CONFIG; do
echo " - $f"
done
INFECTED=1
else
echo -e "${GREEN}Clean${NC}"
fi
# 8. Check for YARA-derived malware indicators in config files
echo -n "[8/16] Checking for DPRK malware signatures... "
YARA_FOUND=""
for f in $CONFIG_FILES; do
if grep -qE "$TRON_WALLETS|$MALWARE_MARKERS|$OBFS_PATTERNS|\['_V'\]|\['_R'\]|\['_H'\]" "$f" 2>/dev/null; then
YARA_FOUND="$YARA_FOUND $f"
fi
done
if [ -n "$YARA_FOUND" ]; then
echo -e "${RED}INFECTED${NC}"
for f in $YARA_FOUND; do
echo " - $f"
done
INFECTED=1
else
echo -e "${GREEN}Clean${NC}"
fi
# 9. Check for persistence mechanisms (OS-specific)
echo -n "[9/16] Checking for persistence mechanisms... "
SUSPICIOUS_PERSIST=""
if [ "$OS" = "macos" ]; then
# macOS: LaunchAgents / LaunchDaemons
for dir in "$HOME/Library/LaunchAgents" "/Library/LaunchAgents"; do
if [ -d "$dir" ]; then
SUSP=$(find "$dir" -name "*.plist" -mtime -30 2>/dev/null | while read f; do
if grep -qiE "node|npm|pnpm|yarn|.node_modules|python.*-c" "$f" 2>/dev/null; then
if ! echo "$f" | grep -qE "watchman|com\.facebook\.|com\.github\.facebook|com\.volta\.|com\.nvm\."; then
echo "$f"
fi
fi
done)
[ -n "$SUSP" ] && SUSPICIOUS_PERSIST="$SUSPICIOUS_PERSIST $SUSP"
fi
done
elif [ "$OS" = "linux" ] || [ "$OS" = "wsl" ]; then
# Linux: crontab, systemd user services, XDG autostart
SUSP_CRON=$(crontab -l 2>/dev/null | grep -iE "node|npm|pnpm|yarn|\.node_modules|python.*-c" | grep -v "^#")
[ -n "$SUSP_CRON" ] && SUSPICIOUS_PERSIST="$SUSPICIOUS_PERSIST [crontab] $SUSP_CRON"
if [ -d "$HOME/.config/systemd/user" ]; then
SUSP=$(find "$HOME/.config/systemd/user" -name "*.service" -mtime -30 2>/dev/null | while read f; do
if grep -qiE "node|npm|\.node_modules" "$f" 2>/dev/null; then
echo "$f"
fi
done)
[ -n "$SUSP" ] && SUSPICIOUS_PERSIST="$SUSPICIOUS_PERSIST $SUSP"
fi
if [ -d "$HOME/.config/autostart" ]; then
SUSP=$(find "$HOME/.config/autostart" -name "*.desktop" -mtime -30 2>/dev/null | while read f; do
if grep -qiE "node|npm|\.node_modules|python.*-c" "$f" 2>/dev/null; then
echo "$f"
fi
done)
[ -n "$SUSP" ] && SUSPICIOUS_PERSIST="$SUSPICIOUS_PERSIST $SUSP"
fi
elif [ "$OS" = "windows" ]; then
WIN_H="${WIN_HOME_UNIX:-$WIN_HOME_MNT}"
# Windows: Registry Run keys
for key in \
"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run" \
"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\RunOnce" \
"HKLM\\Software\\Microsoft\\Windows\\CurrentVersion\\Run"; do
REG_OUT=$(reg query "$key" 2>/dev/null | grep -iE "node|npm|pnpm|yarn|node_modules|python.*-c")
[ -n "$REG_OUT" ] && SUSPICIOUS_PERSIST="$SUSPICIOUS_PERSIST [Registry: $key] $REG_OUT"
done
# Windows: Startup folder
STARTUP_DIR="$WIN_APPDATA/Microsoft/Windows/Start Menu/Programs/Startup"
if [ -d "$STARTUP_DIR" ]; then
# Only check text-based scripts (not .lnk shortcuts which are binary and can't be inspected)
SUSP=$(find "$STARTUP_DIR" \( -name "*.bat" -o -name "*.cmd" -o -name "*.vbs" -o -name "*.js" -o -name "*.ps1" -o -name "*.wsf" \) -mtime -30 2>/dev/null | while read f; do
if grep -qiE "node|npm|node_modules|python.*-c|eval.*atob" "$f" 2>/dev/null; then
echo "$f"
fi
done)
[ -n "$SUSP" ] && SUSPICIOUS_PERSIST="$SUSPICIOUS_PERSIST $SUSP"
fi
# Windows: Task Scheduler
SCHED_OUT=$(schtasks /query /FO CSV /NH 2>/dev/null | grep -iE "node|npm|node_modules" | head -5)
[ -n "$SCHED_OUT" ] && SUSPICIOUS_PERSIST="$SUSPICIOUS_PERSIST [Scheduled Tasks] $SCHED_OUT"
fi
if [ -n "$SUSPICIOUS_PERSIST" ]; then
echo -e "${RED}INFECTED${NC}"
echo "$SUSPICIOUS_PERSIST" | tr ' ' '\n' | grep -v '^$' | head -10 | while read line; do
echo " - $line"
done
INFECTED=1
else
echo -e "${GREEN}Clean${NC}"
fi
# 10. Check shell profiles for malware injection (ALL platform paths)
echo -n "[10/16] Checking shell profiles for injection... "
SUSPICIOUS_SHELL=""
SHELL_GREP_PATTERN="eval.*\\\$\(|node\s+-e|\.node_modules|atob|curl.*\|.*sh|wget.*\|.*sh|base64.*decode|python.*-c.*import"
SHELL_EXCLUDE="rbenv init|pyenv init|nvm|brew shellenv|conda init|sdkman|asdf|fnm|starship init|zoxide init|fzf|thefuck"
for f in "${SHELL_FILES[@]}"; do
if [ -f "$f" ]; then
# For PowerShell profiles, also check for Invoke-Expression, IEX, DownloadString
if echo "$f" | grep -q "\.ps1$"; then
SUSPICIOUS_LINES=$(grep -niE "Invoke-Expression|IEX |DownloadString|\.node_modules|node.*-e|atob|base64" "$f" 2>/dev/null | head -3)
else
SUSPICIOUS_LINES=$(grep -nE "$SHELL_GREP_PATTERN" "$f" 2>/dev/null | \
grep -vE "$SHELL_EXCLUDE" | head -3)
fi
if [ -n "$SUSPICIOUS_LINES" ]; then
SUSPICIOUS_SHELL="$SUSPICIOUS_SHELL $f"
fi
fi
done
if [ -n "$SUSPICIOUS_SHELL" ]; then
echo -e "${RED}INFECTED${NC}"
for f in $SUSPICIOUS_SHELL; do
echo " - $f"
done
INFECTED=1
else
echo -e "${GREEN}Clean${NC}"
fi
# 11. Check for suspicious Python installations (OmniStealer)
echo -n "[11/16] Checking for suspicious Python installations... "
SUSP_PYTHON=""
for d in "${PYTHON3127_DIRS[@]}"; do
if [ -d "$d" ]; then
SUSP_PYTHON="$SUSP_PYTHON Python3127 found at $d"
fi
done
# Check for python executable in temp directories
for search_dir in "${PYTHON_SEARCH_DIRS[@]}"; do
if [ -d "$search_dir" ]; then
if [ "$OS" = "windows" ]; then
HIDDEN_PYTHON=$(find "$search_dir" -maxdepth 3 -name "python*" -type f 2>/dev/null | head -3)
else
HIDDEN_PYTHON=$(find "$search_dir" -maxdepth 3 -name "python*" -type f -perm /111 2>/dev/null | head -3)
fi
[ -n "$HIDDEN_PYTHON" ] && SUSP_PYTHON="$SUSP_PYTHON $HIDDEN_PYTHON"
fi
done
if [ -n "$SUSP_PYTHON" ]; then
echo -e "${RED}INFECTED${NC}"
echo "$SUSP_PYTHON" | tr ' ' '\n' | grep -v '^$' | while read line; do
echo " - $line"
done
INFECTED=1
else
echo -e "${GREEN}Clean${NC}"
fi
# 12. Check git config for tampering
echo -n "[12/16] Checking git config for tampering... "
GIT_TAMPERED=""
# Check all possible home directories
GIT_HOMES=("$EFFECTIVE_HOME")
if [ "$OS" = "windows" ] || [ "$OS" = "wsl" ]; then
WIN_H="${WIN_HOME_UNIX:-$WIN_HOME_MNT}"
[ "$WIN_H" != "$EFFECTIVE_HOME" ] && GIT_HOMES+=("$WIN_H")
fi
for gh in "${GIT_HOMES[@]}"; do
if [ -f "$gh/.gitconfig" ]; then
if grep -qiE "alias.*curl|alias.*wget|alias.*node.*-e|core\.hooksPath.*\.node_modules" "$gh/.gitconfig" 2>/dev/null; then
GIT_TAMPERED="$GIT_TAMPERED $gh/.gitconfig"
fi
fi
if [ -d "$gh/.git/hooks" ]; then
if [ "$OS" = "windows" ]; then
SUSP_HOOKS=$(find "$gh/.git/hooks" -type f 2>/dev/null | xargs grep -l "curl\|wget\|node.*-e\|eval\|atob" 2>/dev/null)
else
SUSP_HOOKS=$(find "$gh/.git/hooks" -type f -perm /111 2>/dev/null | xargs grep -l "curl\|wget\|node.*-e\|eval\|atob" 2>/dev/null)
fi
[ -n "$SUSP_HOOKS" ] && GIT_TAMPERED="$GIT_TAMPERED $SUSP_HOOKS"
fi
done
if [ -n "$GIT_TAMPERED" ]; then
echo -e "${RED}INFECTED${NC}"
for f in $GIT_TAMPERED; do
echo " - $f"
done
INFECTED=1
else
echo -e "${GREEN}Clean${NC}"
fi
# 13. Check for excessive socket connections (malware opens 100+ sockets)
echo -n "[13/16] Checking for excessive socket connections... "
NODE_PIDS=$(get_node_pids)
EXCESSIVE_SOCKETS=""
for pid in $NODE_PIDS; do
SOCKET_COUNT=$(count_pid_connections "$pid")
SOCKET_COUNT=${SOCKET_COUNT:-0}
if [ "$SOCKET_COUNT" -gt 50 ]; then
if [ "$OS" = "windows" ]; then
PROC_CMD=$(wmic process where "ProcessId=$pid" get CommandLine 2>/dev/null | tail -2 | head -1 | head -c 80)
else
PROC_CMD=$(ps -p "$pid" -o args= 2>/dev/null | head -c 80)
fi
EXCESSIVE_SOCKETS="$EXCESSIVE_SOCKETS
PID $pid: $SOCKET_COUNT sockets - $PROC_CMD"
fi
done
if [ -n "$EXCESSIVE_SOCKETS" ]; then
echo -e "${RED}INFECTED${NC}"
echo "$EXCESSIVE_SOCKETS"
INFECTED=1
else
echo -e "${GREEN}Clean${NC}"
fi
# 14. Check for unusual network activity
echo -n "[14/16] Checking for unusual network activity... "
if [ "$OS" = "windows" ]; then
# Get node.exe PIDs and count their connections
NODE_PID_LIST=$(tasklist /FI "IMAGENAME eq node.exe" /FO CSV /NH 2>/dev/null | awk -F'","' '{gsub(/"/, "", $2); print $2}' | grep -E '^[0-9]+$')
TOTAL_NODE_CONNS=0
EXTERNAL_CONNS=0
for pid in $NODE_PID_LIST; do
COUNT=$(netstat -ano 2>/dev/null | grep "$pid" | wc -l | tr -d ' ')
EXT=$(netstat -ano 2>/dev/null | grep "$pid" | grep -v "127\.0\.0\.1\|::1\|\[::\]" | wc -l | tr -d ' ')
TOTAL_NODE_CONNS=$((TOTAL_NODE_CONNS + COUNT))
EXTERNAL_CONNS=$((EXTERNAL_CONNS + EXT))
done
else
TOTAL_NODE_CONNS=$(lsof -i 2>/dev/null | grep "^node" | wc -l | tr -d ' ')
TOTAL_NODE_CONNS=${TOTAL_NODE_CONNS:-0}
EXTERNAL_CONNS=$(lsof -i 2>/dev/null | grep "^node" | grep -v "localhost\|127\.0\.0\.1\|::1\|\*:" | wc -l | tr -d ' ')
EXTERNAL_CONNS=${EXTERNAL_CONNS:-0}
fi
if [ "$EXTERNAL_CONNS" -gt 20 ]; then
echo -e "${RED}INFECTED${NC} ($EXTERNAL_CONNS external connections)"
if [ "$OS" = "windows" ]; then
for pid in $NODE_PID_LIST; do
netstat -ano 2>/dev/null | grep "$pid" | grep -v "127\.0\.0\.1\|::1\|\[::\]" | head -5 | while read line; do
echo " $line"
done
done
else
lsof -i 2>/dev/null | grep "^node" | grep -v "localhost\|127\.0\.0\.1\|::1\|\*:" | head -10 | while read line; do
echo " $line"
done
fi
INFECTED=1
elif [ "$TOTAL_NODE_CONNS" -gt 100 ]; then
echo -e "${YELLOW}WARNING${NC} ($TOTAL_NODE_CONNS total node connections)"
else
echo -e "${GREEN}Clean${NC} ($TOTAL_NODE_CONNS node connections)"
fi
# 15. Check for browser extension data theft indicators
echo -n "[15/16] Checking for extension data access... "
EXTENSION_ACCESS=""
WALLET_EXTENSIONS="nkbihfbeogaeaoehlefnkodbefgpgknn|bfnaelmomeimhlpmgjnjophhpkkoljpa|ibnejdfjmmkpcnlpebklmnkoeoihofec|hnfanknocfeofbddgcijnmhnfnkdnaad"
for chrome_dir in "${CHROME_PROFILE_DIRS[@]}"; do
if [ -d "$chrome_dir" ]; then
RECENT_ACCESS=$(find "$chrome_dir" -name "*.ldb" -mmin -60 2>/dev/null | head -3)
if [ -n "$RECENT_ACCESS" ]; then
if [ "$OS" = "windows" ]; then
# On Windows, check with handle.exe or just flag LDB access from non-chrome processes
ACCESSOR=$(tasklist /V /FO CSV 2>/dev/null | grep -iE "Extensions" | grep -vi "chrome.exe" | head -3)
else
ACCESSOR=$(lsof 2>/dev/null | grep -E "Chrome.*Extensions" | grep -v "Google Chrome" | head -3)
fi
if [ -n "$ACCESSOR" ]; then
EXTENSION_ACCESS="$ACCESSOR"
fi
fi
fi
done
if [ -n "$EXTENSION_ACCESS" ]; then
echo -e "${YELLOW}WARNING${NC} (non-Chrome process accessing extensions)"
echo "$EXTENSION_ACCESS" | while read line; do
echo " $line"
done
else
echo -e "${GREEN}Clean${NC}"
fi
# 16. Check running processes for RAT/stealer indicators
echo -n "[16/16] Checking processes for RAT signatures... "
RAT_FOUND=""
if [ "$OS" = "windows" ]; then
# Use a temp file to avoid grep seeing itself in the process list
PROC_DUMP=$(mktemp 2>/dev/null || echo "/tmp/_malware_proc_$$")
wmic process get ProcessId,CommandLine /FORMAT:LIST 2>"$PROC_DUMP.err" | \
grep -vE "grep\.exe|detect-infection|wmic" > "$PROC_DUMP" 2>/dev/null
PROC_CHECK=$(grep -E "\['_V'\]|\['_R'\]|C250617A|C5-benefit" "$PROC_DUMP" 2>/dev/null)
TG_CHECK=$(grep -F "$TELEGRAM_TOKEN" "$PROC_DUMP" 2>/dev/null)
rm -f "$PROC_DUMP" "$PROC_DUMP.err" 2>/dev/null
else
# Exclude grep and the scanner script itself from results
PROC_CHECK=$(ps aux 2>/dev/null | grep -E "\['_V'\]|\['_R'\]|C250617A|C5-benefit|_\\\$af" | grep -v grep | grep -v "detect-infection")
TG_CHECK=$(ps auxe 2>/dev/null | grep -F "$TELEGRAM_TOKEN" | grep -v grep | grep -v "detect-infection")
fi
[ -n "$PROC_CHECK" ] && RAT_FOUND="$PROC_CHECK"
[ -n "$TG_CHECK" ] && RAT_FOUND="$RAT_FOUND Telegram exfil bot detected"
if [ -n "$RAT_FOUND" ]; then
echo -e "${RED}INFECTED${NC}"
echo "$RAT_FOUND" | head -5 | while read line; do
echo " $line"
done
INFECTED=1
else
echo -e "${GREEN}Clean${NC}"
fi
# ========================================
# RESULTS
# ========================================
echo ""
echo "========================================"
if [ $INFECTED -eq 1 ]; then
echo -e " RESULT: ${RED}SYSTEM IS INFECTED${NC}"
echo "========================================"
echo ""
echo "Recommended actions:"
echo " 1. Disconnect from network immediately"
echo " 2. Kill malicious processes:"
if [ "$OS" = "windows" ]; then
echo " taskkill /PID <PID> /F"
else
echo " kill -9 <PID>"
fi
echo " 3. Remove persistence:"
if [ "$OS" = "windows" ]; then
echo " rd /s /q \"%USERPROFILE%\\.node_modules\""
echo " rd /s /q \"%USERPROFILE%\\node_modules\""
echo " rd /s /q \"%APPDATA%\\node_modules\" (NOT %APPDATA%\\npm\\node_modules - that is legitimate)"
echo " rd /s /q \"%LOCALAPPDATA%\\node_modules\""
echo " Check: reg query HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run"
echo " Check: Task Scheduler (schtasks /query)"
echo " Check: %APPDATA%\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\"
else
echo " rm -rf ~/.node_modules"
fi
echo " 4. Check and reinstall VSCode/Cursor"
echo " 5. Review and restore shell profiles"
if [ "$OS" = "windows" ]; then
echo " Also check PowerShell profiles:"
echo " %USERPROFILE%\\Documents\\PowerShell\\Microsoft.PowerShell_profile.ps1"
echo " %USERPROFILE%\\Documents\\WindowsPowerShell\\Microsoft.PowerShell_profile.ps1"
fi
echo " 6. Rotate ALL credentials/tokens/SSH keys"
echo " 7. Check browser extensions & wallets"
echo " 8. Review git config: git config --global --list"
echo ""
echo "C2 IPs to block:"
echo " - 198.105.127.0/24"
echo " - 23.27.20.143:27017"
echo " - 136.0.9.8:27017"
echo " - 23.27.202.27:27017"
echo " - 166.88.4.2:27017"
echo ""
echo "Blockchain C2 domains:"
echo " - api.trongrid.io"
echo " - fullnode.mainnet.aptoslabs.com"
echo " - bsc-dataseed.binance.org"
echo " - bsc-rpc.publicnode.com"
echo ""
echo "Malware wallet addresses:"
echo " - TMfKQEd7TJJa5xNZJZ2Lep838vrzzrs7mAP (Tron)"
echo " - TXfxHUet9pJVU1BgVkBAbrES4YUc1nGzcG (Tron)"
echo " - TLmj13VL4p6NQ7jpxz8d9uYY6FUKCYatSe (Tron)"
else
echo -e " RESULT: ${GREEN}SYSTEM IS CLEAN${NC}"
echo "========================================"
fi
exit $INFECTED
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment