|
#!/bin/bash |
|
|
|
# Malware Perfctl Detection Script - Colored Output |
|
# Scans directories for known perfctl binaries and reports stat info with birth dates |
|
# Groups files by birth date at the end |
|
# More informations : https://next.ink/152853/perfctl-un-malware-linux-tenace/ |
|
|
|
declare -A birth_dates |
|
declare -a scanned_files |
|
infection_detected=false |
|
|
|
# ANSI color codes |
|
RED='\033[0;31m' |
|
GREEN='\033[0;32m' |
|
YELLOW='\033[1;33m' |
|
BLUE='\033[0;34m' |
|
PURPLE='\033[0;35m' |
|
CYAN='\033[0;36m' |
|
NC='\033[0m' # No Color |
|
|
|
echo -e "${CYAN}=== Perfctl Malware Scanner - $(date) ===${NC}" |
|
|
|
# Function to check directory and specific files |
|
check_dir_files() { |
|
local dir="$1" |
|
shift |
|
local files=("$@") |
|
|
|
if [ ! -d "$dir" ]; then |
|
echo -e "${YELLOW}Directory ${RED}$dir${YELLOW} does not exist${NC}" |
|
return |
|
fi |
|
|
|
echo -e "${BLUE}Scanning directory: ${PURPLE}$dir${NC}" |
|
ls -la "$dir" 2>/dev/null | head -20 || echo -e "${YELLOW}ls failed or empty${NC}" |
|
echo |
|
|
|
for file in "${files[@]}"; do |
|
fullpath="$dir/$file" |
|
scanned_files+=("$fullpath") |
|
|
|
echo -e "${GREEN}Checking: ${PURPLE}$fullpath${NC}" |
|
if [ -e "$fullpath" ]; then |
|
echo -e "${RED}FOUND!${NC} Path exists" |
|
else |
|
echo -e "${YELLOW}NOT FOUND${NC} (will still run stat)" |
|
fi |
|
|
|
# Always run stat, even if the file does not exist (to capture error/output) |
|
stat_output=$(stat "$fullpath" 2>&1) |
|
stat_rc=$? |
|
if [ $stat_rc -eq 0 ]; then |
|
# Extract birth date - get full timestamp first |
|
birth_full=$(echo "$stat_output" | grep "Birth:" | awk '{print $2" "$3" "$4" "$5}') |
|
if [ -n "$birth_full" ]; then |
|
# Extract only date part (without time, minutes, seconds) |
|
# Method: handle both ISO format (YYYY-MM-DD) and standard format (Mon DD YYYY) |
|
birth_date=$(echo "$birth_full" | awk '{ |
|
# Check if first field is ISO date format (YYYY-MM-DD) |
|
if ($1 ~ /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/) { |
|
print $1 # ISO format: just take YYYY-MM-DD |
|
} else { |
|
# Standard format: "Mon DD HH:MM:SS YYYY" -> take "Mon DD YYYY" |
|
# Find the year field (4 digits) and take month, day, year |
|
for (i=1; i<=NF; i++) { |
|
if ($i ~ /^[0-9]{4}$/) { |
|
# Found year, take month day year |
|
print $1" "$2" "$i |
|
break |
|
} |
|
} |
|
} |
|
}') |
|
birth_dates["$birth_date"]+="$fullpath " |
|
echo -e "${BLUE}Birth (full): ${GREEN}$birth_full${NC}" |
|
echo -e "${BLUE}Birth (date only): ${GREEN}$birth_date${NC}" |
|
else |
|
echo -e "${YELLOW}No Birth time available${NC}" |
|
fi |
|
echo "$stat_output" |
|
else |
|
echo -e "${YELLOW}stat failed (rc=$stat_rc)${NC}" |
|
echo "$stat_output" |
|
fi |
|
echo "---" |
|
done |
|
} |
|
|
|
# 1. /usr |
|
check_dir_files "/usr/bin" "perfcc" "wizlmsh" |
|
check_dir_files "/usr/local/bin" "ldd" "top" |
|
check_dir_files "/usr/lib" "libfsnldev.so" "libgcwrap.so" "libpprocps.so" |
|
|
|
# 2. /tmp |
|
check_dir_files "/tmp/.xdiag" "cp" "elog" "exi" "p" "uid" "ver" |
|
check_dir_files "/tmp/.xdiag/int" "e.lock" |
|
check_dir_files "/tmp/.xdiag/hroot" "cp" "hscheck" |
|
check_dir_files "/tmp/.xdiag/tordata" "state.tmp" |
|
check_dir_files "/tmp" ".apid" "lgctr" "lgctr2" |
|
check_dir_files "/tmp/.perf.c" "Loader" "perfctl" |
|
|
|
# 3. /root |
|
check_dir_files "/root" "sedkBrgaa" |
|
check_dir_files "/root/.cache" "pci.ids" |
|
check_dir_files "/root/.config/cron" "perfcc" |
|
|
|
# 4. /etc |
|
check_dir_files "/etc" "ld.so.preload" "profile" |
|
|
|
# 5. Scan root crontabs for perfcc references |
|
echo "" |
|
echo -e "${CYAN}=== SCANNING ROOT CRONTABS ===${NC}" |
|
cron_infection_detected=false |
|
cron_files_found=() |
|
|
|
# Check if running as root |
|
if [ "$EUID" -ne 0 ]; then |
|
echo -e "${YELLOW}Warning: Not running as root. Some crontab locations may not be accessible.${NC}" |
|
fi |
|
|
|
# Function to scan a crontab file for perfcc reference |
|
scan_crontab_file() { |
|
local file="$1" |
|
local desc="$2" |
|
|
|
if [ -f "$file" ] && [ -r "$file" ]; then |
|
echo -e "${BLUE}Checking: ${PURPLE}$desc${NC} ($file)" |
|
if grep -q "/root/.config/cron/perfcc" "$file" 2>/dev/null; then |
|
echo -e "${RED}⚠ FOUND!${NC} Reference to /root/.config/cron/perfcc detected" |
|
cron_infection_detected=true |
|
cron_files_found+=("$file ($desc)") |
|
echo -e "${YELLOW}Crontab content:${NC}" |
|
grep -n "/root/.config/cron/perfcc" "$file" 2>/dev/null | while IFS= read -r line; do |
|
echo -e " ${RED}$line${NC}" |
|
done |
|
else |
|
echo -e "${GREEN}No perfcc reference found${NC}" |
|
fi |
|
elif [ -f "$file" ] && [ ! -r "$file" ]; then |
|
echo -e "${YELLOW}File exists but is not readable: $file${NC}" |
|
fi |
|
} |
|
|
|
# Scan user crontab (root) |
|
echo -e "${BLUE}Checking root user crontab...${NC}" |
|
if crontab -l -u root 2>/dev/null | grep -q "/root/.config/cron/perfcc"; then |
|
echo -e "${RED}⚠ FOUND!${NC} Reference to /root/.config/cron/perfcc in root crontab" |
|
cron_infection_detected=true |
|
cron_files_found+=("root crontab (crontab -l)") |
|
echo -e "${YELLOW}Crontab content:${NC}" |
|
crontab -l -u root 2>/dev/null | grep -n "/root/.config/cron/perfcc" | while IFS= read -r line; do |
|
echo -e " ${RED}$line${NC}" |
|
done |
|
else |
|
echo -e "${GREEN}No perfcc reference in root crontab${NC}" |
|
fi |
|
|
|
# Scan system crontab files |
|
scan_crontab_file "/var/spool/cron/crontabs/root" "root crontab file" |
|
scan_crontab_file "/etc/crontab" "system crontab" |
|
|
|
# Scan cron.d directory |
|
if [ -d "/etc/cron.d" ]; then |
|
echo -e "${BLUE}Scanning /etc/cron.d/* files...${NC}" |
|
for cronfile in /etc/cron.d/*; do |
|
if [ -f "$cronfile" ]; then |
|
scan_crontab_file "$cronfile" "cron.d: $(basename $cronfile)" |
|
fi |
|
done |
|
fi |
|
|
|
# Scan cron directories (hourly, daily, weekly, monthly) |
|
for crondir in /etc/cron.hourly /etc/cron.daily /etc/cron.weekly /etc/cron.monthly; do |
|
if [ -d "$crondir" ]; then |
|
echo -e "${BLUE}Scanning $crondir/* files...${NC}" |
|
for cronfile in "$crondir"/*; do |
|
if [ -f "$cronfile" ] && [ -x "$cronfile" ]; then |
|
# Check if it's a script that might call perfcc |
|
if grep -q "/root/.config/cron/perfcc" "$cronfile" 2>/dev/null; then |
|
echo -e "${RED}⚠ FOUND!${NC} Reference in $(basename $crondir)/$(basename $cronfile)" |
|
cron_infection_detected=true |
|
cron_files_found+=("$cronfile ($(basename $crondir))") |
|
fi |
|
fi |
|
done |
|
fi |
|
done |
|
|
|
# Display cron infection warning if detected |
|
if [ "$cron_infection_detected" = true ]; then |
|
echo "" |
|
# Adaptive banner for cron infection |
|
box_content_width=$((TERM_WIDTH - 4)) |
|
[ $box_content_width -lt 54 ] && box_content_width=54 |
|
box_total_width=$((box_content_width + 4)) |
|
n=$((box_total_width - 2)); hline=""; while [ ${#hline} -lt $n ]; do hline="${hline}="; done |
|
_pad() { printf "%-${box_content_width}s" "$1"; } |
|
echo -e "${RED}+${hline}+${NC}" |
|
echo -e "${RED}|${NC} ${YELLOW}$( _pad "^^^ CRITICAL: CRON INFECTION DETECTED ^^^" )${NC} ${RED}|${NC}" |
|
echo -e "${RED}+${hline}+${NC}" |
|
echo -e "${RED}|${NC} $( _pad "A cron job was found that executes /root/.config/cron/perfcc" ) ${RED}|${NC}" |
|
echo -e "${RED}|${NC} $( _pad "This is a STRONG indicator of perfctl malware infection." ) ${RED}|${NC}" |
|
echo -e "${RED}|${NC} $( _pad "" ) ${RED}|${NC}" |
|
echo -e "${RED}|${NC} ${YELLOW}$( _pad "WARNING:" )${NC} ${RED}|${NC}" |
|
echo -e "${RED}|${NC} $( _pad "Simply removing the cron job is NOT sufficient!" ) ${RED}|${NC}" |
|
echo -e "${RED}|${NC} $( _pad "The malware has likely installed other persistence" ) ${RED}|${NC}" |
|
echo -e "${RED}|${NC} $( _pad "mechanisms and system modifications." ) ${RED}|${NC}" |
|
echo -e "${RED}|${NC} $( _pad "" ) ${RED}|${NC}" |
|
echo -e "${RED}|${NC} ${YELLOW}$( _pad "RECOMMENDED ACTION:" )${NC} ${RED}|${NC}" |
|
echo -e "${RED}|${NC} ${GREEN}$( _pad "-> IMMEDIATELY reinstall your operating system" )${NC} ${RED}|${NC}" |
|
echo -e "${RED}|${NC} ${GREEN}$( _pad "-> Do NOT attempt manual removal" )${NC} ${RED}|${NC}" |
|
echo -e "${RED}|${NC} ${GREEN}$( _pad "-> Backup data and reinstall from clean source" )${NC} ${RED}|${NC}" |
|
echo -e "${RED}+${hline}+${NC}" |
|
echo "" |
|
echo -e "${YELLOW}Affected crontab files:${NC}" |
|
for cron_entry in "${cron_files_found[@]}"; do |
|
echo -e " ${RED}→ $cron_entry${NC}" |
|
done |
|
# Set global infection flag |
|
infection_detected=true |
|
fi |
|
|
|
# Summary by birth date - Table format |
|
echo "" |
|
echo -e "${CYAN}========================================${NC}" |
|
echo -e "${CYAN}=== BIRTH DATE MATCHING TABLE ===${NC}" |
|
echo -e "${CYAN}=== GROUPED BY BIRTH DATE ===${NC}" |
|
echo -e "${CYAN}========================================${NC}" |
|
echo "" |
|
|
|
# Get terminal width for adaptive table formatting |
|
if command -v tput >/dev/null 2>&1; then |
|
TERM_WIDTH=$(tput cols) |
|
else |
|
TERM_WIDTH=80 # Default fallback |
|
fi |
|
|
|
# Adjust column widths based on terminal width |
|
if [ $TERM_WIDTH -lt 80 ]; then |
|
DATE_WIDTH=20 |
|
COUNT_WIDTH=5 |
|
FILE_WIDTH=$((TERM_WIDTH - DATE_WIDTH - COUNT_WIDTH - 6)) |
|
elif [ $TERM_WIDTH -lt 120 ]; then |
|
DATE_WIDTH=25 |
|
COUNT_WIDTH=6 |
|
FILE_WIDTH=$((TERM_WIDTH - DATE_WIDTH - COUNT_WIDTH - 6)) |
|
else |
|
DATE_WIDTH=30 |
|
COUNT_WIDTH=6 |
|
FILE_WIDTH=$((TERM_WIDTH - DATE_WIDTH - COUNT_WIDTH - 6)) |
|
fi |
|
|
|
# Ensure minimum widths |
|
[ $DATE_WIDTH -lt 15 ] && DATE_WIDTH=15 |
|
[ $COUNT_WIDTH -lt 5 ] && COUNT_WIDTH=5 |
|
[ $FILE_WIDTH -lt 20 ] && FILE_WIDTH=20 |
|
|
|
if [ ${#birth_dates[@]} -eq 0 ]; then |
|
echo -e "${YELLOW}No birth dates found or no files detected.${NC}" |
|
else |
|
# Sort dates for consistent output |
|
IFS=$'\n' sorted_dates=($(printf '%s\n' "${!birth_dates[@]}" | sort)) |
|
|
|
# Print table header |
|
printf "${CYAN}%-${DATE_WIDTH}s | %-${COUNT_WIDTH}s | %s${NC}\n" "BIRTH DATE" "COUNT" "FILES" |
|
separator=$(printf "%-${DATE_WIDTH}s-+-%-${COUNT_WIDTH}s-+-%s" "" "" "" | tr ' ' '-') |
|
echo -e "${CYAN}${separator}${NC}" |
|
|
|
# Print each date group |
|
for date in "${sorted_dates[@]}"; do |
|
files="${birth_dates[$date]}" |
|
# Count files (split by space and count non-empty elements) |
|
file_count=$(echo "$files" | tr ' ' '\n' | grep -v '^$' | wc -l) |
|
|
|
# Truncate date if too long |
|
date_display="$date" |
|
if [ ${#date_display} -gt $DATE_WIDTH ]; then |
|
date_display="${date_display:0:$((DATE_WIDTH-3))}..." |
|
fi |
|
|
|
# Print date and count on first line |
|
printf "${GREEN}%-${DATE_WIDTH}s${NC} | ${BLUE}%-${COUNT_WIDTH}s${NC} | " "$date_display" "$file_count" |
|
|
|
# Print first file on same line, others on continuation lines |
|
first_file=$(echo "$files" | awk '{print $1}') |
|
remaining_files=$(echo "$files" | awk '{$1=""; print $0}' | sed 's/^ *//') |
|
|
|
if [ -n "$first_file" ]; then |
|
# Truncate file path if too long |
|
first_file_display="$first_file" |
|
if [ ${#first_file_display} -gt $FILE_WIDTH ]; then |
|
first_file_display="...${first_file_display: -$((FILE_WIDTH-3))}" |
|
fi |
|
echo -e "${PURPLE}$first_file_display${NC}" |
|
# Print remaining files on new lines with proper alignment |
|
if [ -n "$remaining_files" ]; then |
|
for f in $remaining_files; do |
|
if [ -n "$f" ]; then |
|
# Truncate file path if too long |
|
f_display="$f" |
|
if [ ${#f_display} -gt $FILE_WIDTH ]; then |
|
f_display="...${f_display: -$((FILE_WIDTH-3))}" |
|
fi |
|
printf "${CYAN}%-${DATE_WIDTH}s | %-${COUNT_WIDTH}s | ${NC}${PURPLE}%s${NC}\n" "" "" "$f_display" |
|
fi |
|
done |
|
fi |
|
else |
|
echo "" # New line if no files |
|
fi |
|
echo -e "${CYAN}${separator}${NC}" |
|
done |
|
|
|
# Summary statistics |
|
total_files=0 |
|
infection_detected=false |
|
for date in "${sorted_dates[@]}"; do |
|
files="${birth_dates[$date]}" |
|
count=$(echo "$files" | tr ' ' '\n' | grep -v '^$' | wc -l) |
|
total_files=$((total_files + count)) |
|
# Check if multiple files share the same birth date (infection indicator) |
|
if [ $count -gt 1 ]; then |
|
infection_detected=true |
|
fi |
|
done |
|
|
|
echo "" |
|
echo -e "${CYAN}=== SUMMARY ===${NC}" |
|
echo -e "${GREEN}Total unique dates: ${BLUE}${#birth_dates[@]}${NC}" |
|
echo -e "${GREEN}Total files with birth date: ${BLUE}$total_files${NC}" |
|
|
|
# Highlight dates with multiple files (potential matches) |
|
echo "" |
|
echo -e "${CYAN}=== DATES WITH MULTIPLE FILES (suspicious matches) ===${NC}" |
|
found_multi=false |
|
for date in "${sorted_dates[@]}"; do |
|
files="${birth_dates[$date]}" |
|
count=$(echo "$files" | tr ' ' '\n' | grep -v '^$' | wc -l) |
|
if [ $count -gt 1 ]; then |
|
found_multi=true |
|
echo -e "${RED}⚠ DATE: ${YELLOW}$date${RED} - ${count} files:${NC}" |
|
for f in $files; do |
|
if [ -n "$f" ]; then |
|
echo -e " ${PURPLE} → $f${NC}" |
|
fi |
|
done |
|
fi |
|
done |
|
if [ "$found_multi" = false ]; then |
|
echo -e "${YELLOW}No dates with multiple files found.${NC}" |
|
fi |
|
|
|
# Infection warning - adaptive banner so right border stays aligned in any terminal width |
|
echo "" |
|
if [ "$infection_detected" = true ]; then |
|
# Box content width: fit terminal minus borders, minimum 54 for readability |
|
box_content_width=$((TERM_WIDTH - 4)) |
|
[ $box_content_width -lt 54 ] && box_content_width=54 |
|
box_total_width=$((box_content_width + 4)) |
|
n=$((box_total_width - 2)); hline=""; while [ ${#hline} -lt $n ]; do hline="${hline}="; done |
|
_pad() { printf "%-${box_content_width}s" "$1"; } |
|
echo -e "${RED}+${hline}+${NC}" |
|
echo -e "${RED}|${NC} ${YELLOW}$( _pad "^^^ WARNING: POTENTIAL INFECTION DETECTED ^^^" )${NC} ${RED}|${NC}" |
|
echo -e "${RED}+${hline}+${NC}" |
|
echo -e "${RED}|${NC} $( _pad "Multiple binaries were found with the same birth date." ) ${RED}|${NC}" |
|
echo -e "${RED}|${NC} $( _pad "This is a strong indicator of perfctl malware infection." ) ${RED}|${NC}" |
|
echo -e "${RED}|${NC} $( _pad "" ) ${RED}|${NC}" |
|
echo -e "${RED}|${NC} ${YELLOW}$( _pad "RECOMMENDED ACTION:" )${NC} ${RED}|${NC}" |
|
echo -e "${RED}|${NC} ${GREEN}$( _pad "-> Reinstall your operating system from a clean source" )${NC} ${RED}|${NC}" |
|
echo -e "${RED}|${NC} ${GREEN}$( _pad "-> Do NOT attempt to remove the malware manually" )${NC} ${RED}|${NC}" |
|
echo -e "${RED}|${NC} ${GREEN}$( _pad "-> Backup important data before reinstalling" )${NC} ${RED}|${NC}" |
|
echo -e "${RED}|${NC} ${GREEN}$( _pad "-> Scan backups with antivirus before restoring" )${NC} ${RED}|${NC}" |
|
echo -e "${RED}+${hline}+${NC}" |
|
else |
|
echo -e "${GREEN}✓ No suspicious date matches found.${NC}" |
|
fi |
|
fi |
|
|
|
echo "" |
|
echo -e "${CYAN}Scan complete.${NC}" |