[ SEA-GHOST MINI SHELL]
##
# Linux Malware Detect v1.6.5
# (C) 2002-2023, R-fx Networks <proj@r-fx.org>
# (C) 2023, Ryan MacDonald <ryan@r-fx.org>
# This program may be freely redistributed under the terms of the GNU GPL v2
##
#
lbreakifs() {
if [ "$1" == "set" ]; then
IFS=$(echo -en "\n\b")
else
unset IFS
fi
}
prerun() {
startdir=$(pwd);
if [ ! "$(whoami)" == "root" ]; then
if [ -z "$scan_user_access" ] || [ "$scan_user_access" == "0" ]; then
args="$@"
if [[ "$args" =~ "modsec" ]]; then
echo "1 maldet: OK"
exit
fi
header
echo "public scanning is currently disabled (scan_user_access=0), please contact your system administrator to enable scan_user_access in $cnffile."
exit 1
fi
pub=1
user="$(whoami)"
quardir="$userbasedir/$user/quar"
sessdir="$userbasedir/$user/sess"
tmpdir="$userbasedir/$user/tmp"
scan_tmpdir_paths=""
hits_history="$sessdir/hits.hist"
quar_history="$sessdir/quarantine.hist"
clean_history="$sessdir/clean.hist"
suspend_history="$sessdir/suspend.hist"
monitor_scanned_history="$sessdir/monitor.scanned.hist"
if [ ! -d "$userbasedir/$user/tmp" ]; then
header
echo "public scanning is enabled (scan_user_access=1) but paths do not exist, please contact your system administrator to run '$0 --mkpubpaths' or wait for cron.pub to execute in ~10 minutes."
exit 1
fi
maldet_log="$userbasedir/$user/event_log"
clamscan_log="$userbasedir/$user/clamscan_log"
mkdir -p $quardir $sessdir $tmpdir 2> /dev/null
chmod 711 $userbasedir 2> /dev/null
touch $maldet_log 2> /dev/null
chown -R ${user}.root $userbasedir/$user 2> /dev/null
chmod 750 $userbasedir/$user $quardir $sessdir $tmpdir 2> /dev/null
chmod 640 $maldet_log 2> /dev/null
cd $tmpdir
else
echo $ver > $lmd_version_file
fi
if [ ! -d "$sigdir" ]; then
mkdir -p $sigdir
chmod 755 $sigdir
fi
if [ ! -d "$logdir" ]; then
mkdir -p $logdir
chmod 755 $logdir
fi
if [ ! -d "$tmpdir" ]; then
mkdir -p $tmpdir
chmod 755 $tmpdir
else
chmod 755 $tmpdir
fi
if [ ! -d "$sessdir" ]; then
mkdir -p $sessdir
chmod 750 $sessdir
fi
if [ ! -d "$quardir" ]; then
mkdir -p $quardir
chmod 750 $quardir
fi
if [ -z "$md5sum" ]; then
header
echo "could not find required binary md5sum, aborting."
exit 1
fi
if [ -z "$od" ]; then
header
echo "could not find required binary od, aborting."
exit 1
fi
if [ -z "$find" ]; then
header
echo "could not find required binary find, aborting."
exit 1
fi
if [ -z "$perl" ]; then
header
echo "could not find required binary perl, aborting."
exit 1
fi
if [ "$email_alert" == "1" ] && [ ! -f "$mail" ] && [ ! -f "$sendmail" ]; then
email_alert=0
fi
if [ ! -f "$sig_user_hex_file" ]; then
touch $sig_user_hex_file
chmod 644 $sig_user_hex_file
fi
if [ ! -f "$sig_user_md5_file" ]; then
touch $sig_user_md5_file
chmod 644 $sig_user_md5_file
fi
if [ "$scan_hexfifo" == "1" ]; then
mkfifo=`which mkfifo 2> /dev/null`
if [ ! -f "$mkfifo" ]; then
scan_hexfifo=0
else
if [ -f "$mkfifo" ] && [ ! -p "$hex_fifo_path" ]; then
$mkfifo -m 666 $hex_fifo_path
fi
fi
fi
if [ "$user" == "root" ]; then
$sed -i -e '/^$/d' $ignore_paths $ignore_sigs $ignore_inotify $ignore_file_ext
fi
if [ -z "$EDITOR" ]; then
defedit=`which nano 2> /dev/null`
if [ -z "$defedit" ]; then
EDITOR=vi
else
EDITOR=nano
fi
fi
if [ ! "$scan_cpunice" ]; then
scan_cpunice=19
fi
if [ ! "$scan_ionice" ]; then
scan_ionice=6
fi
if [ -f "$nice" ]; then
nice_command="$nice -n $scan_cpunice"
fi
if [ -f "$ionice" ] && [ ! -d "/proc/vz" ]; then
nice_command="$nice_command $ionice -c2 -n $scan_ionice"
fi
if [ -f "$cpulimit" ] && [ "$scan_cpulimit" -gt 2> /dev/null "0" ]; then
max_cpulimit=$[$(grep -E -w processor /proc/cpuinfo -c)*100]
if [ "$scan_cpulimit" -gt "$max_cpulimit" ]; then
scan_cpulimit="0"
else
nice_command="$cpulimit -l $scan_cpulimit -- $nice_command"
fi
fi
if [ -z "$cron_daily_scan" ]; then
cron_daily_scan=1
fi
}
eout() {
msg="$1"
stdout="$2"
appn=maldet
if [ ! -d "$logdir" ]; then
mkdir -p $logdir ; chmod 700 $logdir
fi
if [ ! -f "$maldet_log" ]; then
touch $maldet_log
fi
if [ "$maldet_log_truncate" == "1" ]; then
log_size=`$wc -l $maldet_log | awk '{print$1}'`
if [ "$log_size" -ge "20000" ]; then
trim=1000
printf "%s\n" "$trim,${log_size}d" w | ed -s $maldet_log 2> /dev/null
fi
fi
if [ ! "$msg" == "" ]; then
echo "$(date +"%b %d %Y %H:%M:%S") $(hostname -s) $appn($$): $msg" >> $maldet_log
if [ ! -z "$stdout" ]; then
echo "$appn($$): $msg"
fi
fi
}
trap_exit() {
if [ "$svc" == "m" ]; then
echo
eout "{glob} monitor interrupt by user, sending kill." 1
monitor_kill
exit 1
elif [ "$svc" == "a" ] || [ "$svc" == "r" ] || [ "$svc" == "f" ]; then
echo
gen_report
if [ ! "$tot_hits" == "0" ]; then
if [ "$email_ignore_clean" == "1" ] && [ ! "$tot_hits" == "$tot_cl" ]; then
genalert file $nsess
elif [ "$email_ignore_clean" == "0" ]; then
genalert file $nsess
fi
fi
mv $scan_session $nsess_hits 2> /dev/null
rm -f $clamscan_results $find_results $runtime_hdb $runtime_hexstrings $runtime_ndb $scan_session $tmpdir/.find_killed.$scanid $tmpdir/.tmp* $tmpdir/.tmpf* $tmpf 2> /dev/null
eout "{glob} scan interrupt by user, aborting scan..." 1
eout "{scan} scan report saved, to view run: maldet --report $datestamp.$$" 1
if [ "$quarantine_hits" == "0" ] && [ ! "$tot_hits" == "0" ]; then
eout "{glob} quarantine is disabled! set quarantine_hits=1 in $cnffile or to quarantine results run: maldet -q $datestamp.$$" 1
fi
exit
fi
}
clean_exit() {
mv -f $scan_session $nsess_hits 2> /dev/null
rm -f $clamscan_results $find_results $list $runtime_hdb $runtime_hexstrings $runtime_ndb $scan_session $tmpdir/.find_killed.$scanid $tmpdir/.tmp* $tmpdir/.tmpf* $tmpf 0 2> /dev/null
}
detect_control_panel() {
if [[ -d /usr/local/interworx ]]; then
iworx_db_ps=$(ps -u iworx | grep iworx-db)
iworx_web_ps=$(ps -u iworx-web | grep iworx-web)
pex_script=$(readlink -e /usr/local/interworx/bin/listaccounts.pex)
siteworx=$(which siteworx)
# Check that Iworx services are running
if [[ -z "$iworx_db_ps" || -z "$iworx_web_ps" ]]; then
control_panel="error"
eout "{panel} Interworx found, but not running. Panel user alerts will not be sent."
# Verify pex script exists and is executable
elif ! [[ -x "$pex_script" ]]; then
control_panel="error"
eout "{panel} Interworx found, but scripts are missing or not executable. Panel user alerts will not be sent."
# Ensure /usr/bin/siteworx is executable
elif ! [[ -x "$siteworx" ]]; then
control_panel="error"
eout "{panel} Interworx found, but Siteworx CLI is missing or not executable. Panel user alerts will not be sent."
else
control_panel="interworx"
fi
elif [[ -d /usr/local/cpanel ]]; then
cpanel_ps=$(ps -u root | grep [c]psrvd)
cpapi=$(which cpapi2)
apitool=$(readlink -e ${cpapi})
# Ensure cpanel service is running
if [[ -z ${cpanel_ps} ]]; then
control_panel="error"
eout "{panel} cPanel found, but services are not running. Panel user alerts will not be sent."
# Verify apitool is executable
elif ! [[ -x ${apitool} ]]; then
control_panel="error"
eout "{panel} cPanel found, but apitool is missing or not found. Panel user alerts will not be sent."
else
control_panel="cpanel"
fi
else
control_panel="unknown"
fi
}
get_panel_contacts() {
panel="$1"
user="$2"
case "$panel" in
"cpanel")
if [ -f /var/cpanel/users/${user} ]; then
contact_emails=$(awk -F '=' '/^CONTACTEMAIL/{print $2}' /var/cpanel/users/${user} | sed '/^ *$/d' | tr '\n' ',' | sed 's/,$//')
else
contact_emails=$(cpapi2 --user=herpaderpadoo --output=xml CustInfo contactemails | grep -o 'value>.*</value' | sed -r 's,(</)?value>?,,g;/^$/d' | tr '\n' ',' | sed 's/,$//')
fi
;;
"interworx")
master_domain=$(/usr/local/interworx/bin/listaccounts.pex | grep "${user}" | awk '{print $2}')
contact_emails=$(/usr/bin/siteworx -un --login_domain ${master_domain} -c Users -a listUsers -o yaml | awk '/email:/{print $2}' | tr '\n' ',' | sed 's/,$//' | sed 's/,/, /')
;;
esac
}
get_remote_file() {
# $1 = URI, $2 = local service identifier, $3 boolean verbose
get_uri="$1"
service="$2"
verbose="$3"
save_path="$4"
unset return_file
if [ "$hscan" ]; then
unset verbose
fi
if [ -z "$get_uri" ]; then
eout "{internal} missing or invalid URI passed to get_remote_file()" 1
break
fi
if [ -z "$service" ]; then
svc="internal"
else
svc="$service"
fi
if [ -f "$curl" ] || [ -f "/usr/bin/curl" ]; then
get_type="curl"
if [ -z "$curl" ]; then
get_bin="/usr/bin/curl"
else
get_bin="$curl"
fi
elif [ -f "$wget" ] || [ -f "/usr/bin/wget" ]; then
get_type="wget"
if [ -z "$wget" ]; then
get_bin="/usr/bin/wget"
else
get_bin="$wget"
fi
else
eout "{internal} could not find curl or wget binaries for remote file downloads, fatal error!"
exit 1
fi
if [ "$lmd_referer" ] && [ "$get_type" == "curl" ]; then
id="--referer ${lmd_referer}:curl"
elif [ "$lmd_referer" ] && [ "$get_type" == "wget" ]; then
id="--referer=${lmd_referer}:wget"
fi
if [ "$get_type" == "curl" ]; then
if [ "$web_proxy" ]; then
get_proxy_arg="-x http://$web_proxy"
fi
get_opts="-s $get_proxy_arg --connect-timeout $remote_uri_timeout --retry $remote_uri_retries $id"
get_output_arg='-o'
elif [ "$get_type" == "wget" ]; then
if [ "$web_proxy" ]; then
get_proxy_arg="-e http_proxy=$web_proxy -e https_proxy=$web_proxy"
fi
get_opts="-q $get_proxy_arg --timeout=$remote_uri_timeout --tries=$remote_uri_retries $id"
get_output_arg='-O'
fi
if [ "$save_path" ]; then
tmpf="$save_path"
else
tmpf="$tmpdir/.tmpf_get.${RANDOM}"
touch $tmpf ; chmod 600 $tmpf
get_file=`echo "$get_uri" | tr '/' '\n' | tail -n1`
fi
$get_bin $get_opts "$get_uri" $get_output_arg "$tmpf" || get_return=$?
if [ ! -f "$tmpf" ] || [ ! -s "$tmpf" ]; then
eout "{$svc} could not download $get_uri, please try again later." $verbose
unset return_file
else
eout "{$svc} downloaded $get_uri"
return_file="$tmpf"
fi
}
import_user_sigs() {
if [ "$import_custsigs_md5_url" ]; then
get_remote_file "$import_custsigs_md5_url" "importsigs" "1"
if [ -f "$return_file" ]; then
cp -f $return_file $sig_user_md5_file
eout "{importsigs} imported custom signature data from $import_custsigs_md5_url"
fi
fi
if [ "$import_custsigs_hex_url" ]; then
get_remote_file "$import_custsigs_hex_url" "importsigs" "1"
if [ -f "$return_file" ]; then
cp -f $return_file $sig_user_hex_file
eout "{importsigs} imported custom signature data from $import_custsigs_hex_url"
fi
fi
}
import_conf() {
current_utime=`date +"%s"`
if [ -z "$import_config_expire" ]; then
import_config_expire=43200
fi
if [ -f "$sessdir/.import_conf.utime" ]; then
import_utime=`cat $sessdir/.import_conf.utime`
if [ -z "$import_utime" ]; then
import_utime="0"
fi
import_diff=$[current_utime-import_utime]
if [ "$import_diff" -lt "$import_config_expire" ]; then
import_config_skip="1"
eout "{importconf} configuration expire value has not lapsed (${import_diff}/${import_config_expire}), using cache."
import_conf_cached=1
fi
fi
if [ "$import_config_url" ]; then
if [ -z "$import_config_skip" ]; then
get_remote_file "$import_config_url" "importconf" "1"
if [ -f "$return_file" ]; then
cp -f $return_file $sessdir/.import_conf.cache
echo "$current_utime" > $sessdir/.import_conf.utime
fi
fi
if [ -f "$sessdir/.import_conf.cache" ]; then
source $cnf
source $intcnf
source $sessdir/.import_conf.cache
if [ "$import_conf_cached" ]; then
eout "{importconf} imported configuration from $import_config_url (cached)"
else
eout "{importconf} imported configuration from $import_config_url"
fi
fi
fi
}
clamav_linksigs() {
cpath="$1"
if [ -d "$cpath" ]; then
rm -f $cpath/rfxn.{hdb,ndb,yara} 2> /dev/null ; cp -f $sigdir/rfxn.{hdb,ndb,yara} $cpath/ 2> /dev/null
rm -f $cpath/lmd.user.* 2> /dev/null ; cp -f $sigdir/lmd.user.ndb $sigdir/lmd.user.hdb $cpath/ 2> /dev/null
fi
}
usage_short() {
cat <<EOF
signature set: $sig_version
usage maldet [-h|--help] [-a|--scan-all PATH] [-r|--scan-recent PATH DAYS]
[-f|--file-list PATH] [-i|--include-regex] [-x|--exclude-regex]
[-b|--background] [-m|--monitor] [-k|--kill-monitor] [-c|--checkout]
[-q|--quarantine] [-s|--restore] [-n|--clean] [-l|--log] [-e|--report]
[-u|--update-sigs] [-d|--update-ver]
EOF
}
usage_long() {
cat<<EOF
signature set: $sig_version
usage $0 [ OPTION ]
-b, --background
Execute operations in the background, ideal for large scans
e.g: maldet -b -r /home/?/public_html 7
-u, --update-sigs [--force]
Update malware detection signatures from rfxn.com
-d, --update-ver [--force]
Update the installed version from rfxn.com
-f, --file-list
Scan files or paths defined in line spaced file
e.g: maldet -f /root/scan_file_list
-r, --scan-recent PATH DAYS
Scan files created/modified in the last X days (default: 7d, wildcard: ?)
e.g: maldet -r /home/?/public_html 2
-a, --scan-all PATH
Scan all files in path (default: /home, wildcard: ?)
e.g: maldet -a /home/?/public_html
-i, --include-regex REGEX
Include paths/files from file list based on supplied posix-egrep regular
expression.
e.g: To include only paths named wp-content and files ending in .php:
--include-regex ".*/wp-content/.*|.*.php$"
-x, --exclude-regex REGEX
Exclude paths/files from file list based on supplied posix-egrep regular
expression.
e.g: To exclude paths containing 'wp-content/w3tc/' and core files:
--exclude-regex ".*wp-content/w3tc/.*|.*core.[0-9]+$"
-m, --monitor USERS|PATHS|FILE|RELOAD
Run maldet with inotify kernel level file create/modify monitoring
If USERS is specified, monitor user homedirs for UID's > 500
If FILE is specified, paths will be extracted from file, line spaced
If PATHS are specified, must be comma spaced list, NO WILDCARDS!
e.g: maldet --monitor users
e.g: maldet --monitor /root/monitor_paths
e.g: maldet --monitor /home/mike,/home/ashton
-k, --kill-monitor
Terminate inotify monitoring service
-c, --checkout FILE
Upload suspected malware to rfxn.com for review & hashing into signatures
-l, --log
View maldet log file events
-e, --report SCANID email
View scan report of most recent scan or of a specific SCANID and optionally
e-mail the report to a supplied e-mail address
e.g: maldet --report
e.g: maldet --report list
e.g: maldet --report 050910-1534.21135
e.g: maldet --report SCANID user@domain.com
-s, --restore FILE|SCANID
Restore file from quarantine queue to orginal path or restore all items from
a specific SCANID
e.g: maldet --restore $varlibpath/quarantine/config.php.23754
e.g: maldet --restore 050910-1534.21135
-q, --quarantine SCANID
Quarantine all malware from report SCANID
e.g: maldet --quarantine 050910-1534.21135
-n, --clean SCANID
Try to clean & restore malware hits from report SCANID
e.g: maldet --clean 050910-1534.21135
-U, --user USER
Set execution under specified user, ideal for restoring from user quarantine or
to view user reports.
e.g: maldet --user nobody --report
e.g: maldet --user nobody --restore 050910-1534.21135
-co, --config-option VAR1=VALUE,VAR2=VALUE,VAR3=VALUE
Set or redefine the value of $cnffile config options
e.g: maldet --config-option email_addr=you@domain.com,quarantine_hits=1
-p, --purge
Clear logs, quarantine queue, session and temporary data.
--web-proxy IP:PORT
Enable use of HTTP/HTTPS proxy for all remote URL calls.
EOF
}
clean() {
file="$1"
file_signame="$2"
file_owner="$3"
file_chmod="$4"
file_size="$5"
file_md5="$6"
sh_hitname=`echo $hitname | sed -r -e 's/\{(HEX|MD5|CAV|YARA)\}//' -e 's/\.[0-9]+$//'`
if [ -d "$cldir" ] && [ "$quarantine_clean" == "1" ] && [ "$quarantine_hits" == "1" ] && [ -f "$file" ]; then
if [ -f "$cldir/$sh_hitname" ] || [ -f "$cldir/custom.$sh_hitname" ] && [ -f "${file}.info" ]; then
file_path=`grep -E -v '\#' ${file}.info | cut -d':' -f9`
eout "{clean} restoring $file for cleaning attempt" 1
restore "$file" >> /dev/null 2>&1
if [ -f "$cldir/$sh_hitname" ]; then
eout "{clean} attempting to clean $file_path with $sh_hitname rule" 1
$cldir/$sh_hitname "$file_path" "$file_signame" "$file_owner" "$file_chmod" "$file_size" "$file_md5"
fi
if [ -f "$cldir/custom.$sh_hitname" ]; then
eout "{clean} attempting to clean $file_path with custom.$sh_hitname rule" 1
$cldir/custom.$sh_hitname "$file_path" "$file_signame" "$file_owner" "$file_chmod" "$file_size" "$file_md5"
fi
eout "{clean} rescanning $file_path for malware hits" 1
clean_state="1"
scan_stage1 "$file_path" >> /dev/null 2>&1
unset clean_state
if [ -f "$file_path" ]; then
echo "$file_path" >> $sessdir/clean.$$
echo "$file_path" >> $clean_history
eout "{clean} clean successful on $file_path" 1
else
eout "{clean} clean failed on $file_path and returned to quarantine" 1
fi
elif [ -f "$cldir/$sh_hitname" ] || [ -f "$cldir/custom.$sh_hitname" ] && [ -f "$file" ]; then
file_path="$file"
if [ -f "$cldir/$sh_hitname" ]; then
eout "{clean} attempting to clean $file with $sh_hitname rule" 1
$cldir/$sh_hitname "$file_path"
fi
if [ -f "$cldir/custom.$sh_hitname" ]; then
eout "{clean} attempting to clean $file with custom.$sh_hitname rule" 1
$cldir/custom.$sh_hitname "$file_path"
fi
eout "{clean} scanning $file for malware hits"
clean_state="1"
unset clean_failed
scan_stage1 "$file_path" 1 >> /dev/null 2>&1
unset clean_state
if [ "$clean_failed" == "1" ]; then
eout "{clean} clean failed on $file" 1
else
echo "$file" >> $sessdir/clean.$$
echo "$file_path" >> $clean_history
eout "{clean} clean successful on $file" 1
fi
else
eout "{clean} could not find clean rule for hit $sh_hitname or file $file no longer exists." 1
fi
else
if [ "$quarantine_clean" == "1" ] && [ "$quarantine_hits" == "1" ]; then
eout "file path error on $file, aborting."
exit
else
eout "quarantine_clean and quarantine_hits are disabled; skipped file $file"
fi
fi
}
quar_get_filestat() {
fstat="$1"
if [ -f "$fstat" ]; then
# owner:group:mode:size(b):md5:atime(epoch):mtime(epoch):ctime(epoch):file(path)
file_owner=`grep -E -v '\#' "$fstat" | awk -F':' '{print$1}'`
file_group=`grep -E -v '\#' "$fstat" | awk -F':' '{print$2}'`
file_mode=`grep -E -v '\#' "$fstat" | awk -F':' '{print$3}'`
file_size=`grep -E -v '\#' "$fstat" | awk -F':' '{print$4}'`
md5_hash=`grep -E -v '\#' "$fstat" | awk -F':' '{print$5}'`
file_atime=`grep -E -v '\#' "$fstat" | awk -F':' '{print$6}'`
file_mtime=`grep -E -v '\#' "$fstat" | awk -F':' '{print$7}'`
file_ctime=`grep -E -v '\#' "$fstat" | awk -F':' '{print$8}'`
file_path=`grep -E -v '\#' "$fstat" | cut -d':' -f9`
fi
}
restore() {
file="$1"
fname=`basename "$file"`
if [ -f "$quardir/$file" ] && [ -f "$quardir/${file}.info" ]; then
quar_get_filestat "$quardir/${file}.info"
chown ${file_owner}.${file_group} "$quardir/$file" >> /dev/null 2>&1
chmod $file_mode "$quardir/$file" >> /dev/null 2>&1
mv -f "$quardir/$file" "$file_path"
touch -m --date=@${file_mtime} "$file_path"
eout "{restore} quarantined file '$file' restored to '$file_path'" 1
elif [ -f "$file" ] && [ -f "${file}.info" ]; then
quar_get_filestat "${file}.info"
chown ${file_owner}.${file_group} "$file" >> /dev/null 2>&1
chmod $file_mode "$file" >> /dev/null 2>&1
mv -f "$file" "$file_path"
touch -m --date=@${file_mtime} "$file_path"
eout "{restore} quarantined file '$file' restored to '$file_path'" 1
else
eout "{restore} '$file' is not eligible for restore or could not be found" 1
fi
}
restore_hitlist() {
hitlist="$sessdir/session.hits.$1"
if [ -f "$hitlist" ]; then
lbreakifs set
is_autoquar=`tail -n1 $hitlist | awk -F'>' '{print$2}' | grep -E -v '^$' | sed 's/.//'`
if [ "$is_autoquar" ]; then
for file in `cat $hitlist | cut -d':' -f2 | cut -d'>' -f2 | sed 's/.//'`; do
if [ -f "$file" ]; then
restore "$file"
fi
done
elif [ ! "$is_autoquar" ]; then
for file in `cat $hitlist | cut -d':' -f2 | sed 's/.//'`; do
quar_file=`cat $quar_history | grep -E -w "$file" | cut -d':' -f8 | tail -n1`
restore "$quar_file"
done
else
eout "{restore} could not find a valid hit list to restore." 1
fi
lbreakifs unset
fi
}
clean_hitlist() {
if [ "$quarantine_clean" == "0" ] || [ "$quarantine_hits" == "0" ]; then
eout "{clean} quarantine_clean and/or quarantine_hits are disabled, nothing to do here." 1
exit 0
fi
scanid="$1"
hitlist="$sessdir/session.hits.$scanid"
if [ -f "$hitlist" ]; then
is_quared=`egrep '=>' $hitlist`
if [ ! "$is_quared" ]; then
lbreakifs set
for file in `cat $hitlist | cut -d':' -f2 | sed 's/.//'`; do
get_filestat "$file" 1
hitname=`cat $hitlist | grep $file | awk '{print$1}'`
if [ ! "$md5_hash" ]; then
md5_hash=`eval $md5sum \"$file\" | awk '{print$1}'`
fi
clean "$file" "$hitname" "$file_owner.$file_group" "$file_mode" "$file_size" "$md5_hash"
done
lbreakifs unset
else
lbreakifs set
for file in `cat $hitlist | cut -d'>' -f2 | sed 's/.//'`; do
quar_get_filestat "${file}.info" 1
hitname=`cat $hitlist | grep $file | awk '{print$1}'`
if [ ! "$md5_hash" ]; then
md5_hash=`eval $md5sum \"$file\" | awk '{print$1}'`
fi
clean "$file" "$hitname" "$file_owner.$file_group" "$file_mode" "$file_size" "$md5_hash"
done
lbreakifs unset
fi
else
eout "{clean} invalid scanid $scanid or unknown error, aborting." 1
exit
fi
}
view_report() {
rid="$1"
if [ "$rid" == "list" ]; then
tmpf="$tmpdir/.areps$$"
for file in `ls $sessdir/session.[0-9]* 2> /dev/null`; do
SCANID=`cat $file | grep "SCAN ID" | sed 's/SCAN ID/SCANID/'`
FILES=`cat $file | grep "TOTAL FILES" | sed 's/TOTAL //'`
HITS=`cat $file | grep "TOTAL HITS" | sed 's/TOTAL //'`
CLEAN=`cat $file | grep "TOTAL CLEANED" | sed 's/TOTAL //'`
TIME=`cat $file | grep -E "^TIME|^STARTED" | sed -e 's/TIME: //' -e 's/STARTED: //' | awk '{print$1,$2,$3,$4}'`
TIME_U=`date -d "$TIME" "+%s" 2> /dev/null`
ETIME=`cat $file | grep "ELAPSED" | awk '{print$1,$2}' | sed 's/ELAPSED/RUNTIME/'`
if [ -z "$ETIME" ]; then
ETIME="RUNTIME: unknown"
fi
if [ ! -z "$SCANID" ] && [ ! -z "$TIME" ]; then
clean_zero=`echo $CLEAN | awk '{print$2}'`
if [ -z "$clean_zero" ]; then
CLEAN="CLEANED: 0"
fi
echo "$TIME_U | $TIME | $SCANID | $ETIME | $FILES | $HITS | $CLEAN" >> $tmpf
fi
done
if [ -f "$tmpf" ]; then
if [ "$OSTYPE" == "FreeBSD" ]; then
cat $tmpf | sort -k1 -n | cut -d'|' -f2-7 | column -t | more
else
cat $tmpf | sort -k1 -n | tac | cut -d'|' -f2-7 | column -t | more
fi
rm -f $tmpf 2> /dev/null
exit 0
else
echo "error no report data found"
exit 1
fi
fi
if [ -f "$sessdir/session.$rid" ] && [ ! -z "$(echo $2 | grep '\@')" ]; then
if [ -f "$mail" ]; then
cat $sessdir/session.$rid | $mail -s "$email_subj" "$2"
elif [ -f "$sendmail" ]; then
if ! grep -q "SUBJECT: " "$sessdir/session.$rid"; then
echo -e "SUBJECT: $email_subj\n$(cat $sessdir/session.$rid)" > $sessdir/session.$rid
fi
cat $sessdir/session.$rid | $sendmail -t "$2"
else
eout "{scan} no \$mail or \$sendmail binaries found, e-mail alerts disabled."
exit
fi
eout "{report} report ID $rid sent to $2" 1
exit
fi
if [ "$rid" == "" ] && [ -f "$sessdir/session.last" ]; then
rid=`cat $sessdir/session.last`
if [ "$2" == "dump" ]; then
cat $sessdir/session.$rid
exit
fi
$EDITOR $sessdir/session.$rid
elif [ -f "$sessdir/session.$rid" ]; then
if [ "$2" == "dump" ]; then
cat $sessdir/session.$rid
exit
fi
$EDITOR $sessdir/session.$rid
else
echo "{report} no report found, aborting."
exit
fi
}
dump_report() {
rid="$1"
if [ "$rid" == "" ]; then
rid=`cat $sessdir/session.last`
fi
view_report $rid dump
}
view() {
echo "Viewing last 50 lines from $maldet_log:"
tail -n 50 $maldet_log
}
purge() {
:> $maldet_log
log_size=`$wc -l $inotify_log | awk '{print$1}'`
if [ "$inotify_trim" ]; then
trim=$(($log_size - 1000))
log_chars=`printf "%s\n" "1,${trim}p" | ed -s $inotify_log 2> /dev/null | wc -c`
tlog_new=$(( `cat $inspath/tmp/inotify` - $log_chars ))
echo $tlog_new > $inspath/tmp/inotify
printf "%s\n" "1,${trim}d" w | ed -s $inotify_log 2> /dev/null
eout "{mon} inotify log file trimmed"
fi
rm -f $tmpdir/* $quardir/* $sessdir/* 2> /dev/null
eout "{glob} logs and quarantine data cleared by user request (-p)" 1
}
quarantine_suspend_user() {
file="$1"
get_filestat "$file"
user="$file_owner"
user_id=`id -u $user`
if [ ! "$user" == "" ] && [ "$user_id" -ge "$quarantine_suspend_user_minuid" ]; then
if [ -f "/scripts/suspendacct" ]; then
if [ ! -f "/var/cpanel/suspended/$user" ]; then
/scripts/suspendacct $user "maldet --report $datestamp.$$" >> /dev/null 2>&1
eout "{quar} account $user cpanel suspended" 1
echo "$user" >> $sessdir/suspend.users.$$
echo "$user" >> $suspend_history
fi
else
if [ "$(grep $user /etc/passwd | cut -d':' -f7 | grep /bin/false)" == "" ]; then
/usr/sbin/usermod -s /bin/false $user >> /dev/null 2>&1
eout "{quar} account $user suspended; set 'usermod -s /bin/false'"
echo "$user" >> $sessdir/suspend.users.$$
echo "$user" >> $suspend_history
fi
fi
fi
}
get_filestat() {
file="$1"
times="$2"
if [ "$OSTYPE" == "FreeBSD" ]; then
file_owner=`$stat -f '%Su' "$file"`
file_group=`$stat -f '%Sg' "$file"`
file_mode=`$stat -f '%p' "$file" | sed 's/^.//'`
file_size=`$stat -f '%Z' "$file"`
md5_hash=`eval $md5sum \"$file\" | awk '{print$1}'`
if [ "$times" ]; then
# atime mtime ctime (since epoch)
file_times=`$stat -f '%a:%m:%c' "$file"`
fi
else
file_owner=`$stat -c '%U' "$file"`
file_group=`$stat -c '%G' "$file"`
file_mode=`$stat -c '%a' "$file"`
file_size=`$stat -c '%s' "$file"`
md5_hash=`eval $md5sum \"$file\" | awk '{print$1}'`
if [ "$times" ]; then
# atime mtime ctime (since epoch)
file_times=`$stat -c '%X:%Y:%Z' "$file"`
fi
fi
}
record_hit() {
file="$1"
hitname="$2"
if [ -f "$file" ]; then
get_filestat "$file" 1
file_name=`basename "$file" 2> /dev/null`
if [ ! "$md5_hash" ]; then
md5_hash=`eval $md5sum \"$file\" | awk '{print$1}'`
fi
eout "{hit} malware hit $hitname found for $file"
echo "${utime}:${hostid}:${hitname}:${md5_hash}:${file_size}:${file_owner}.${file_group}:${file_mode}:${file_times}:${file}" >> $hits_history
fi
}
quarantine() {
file="$1"
hitname="$2"
file_name=`basename "$file"`
if [ -f "$file" ] && [ -d "$quardir" ]; then
if [ "$quarantine_hits" == "1" ]; then
file_namewc=`echo $file_name | $wc -m`
rnd="${RANDOM}${RANDOM}"
if [ "$quarantine_suspend_user" == "1" ]; then
quarantine_suspend_user "$file"
fi
chattr -ia "$file"
mv "$file" "$quardir/$file_name.$rnd"
touch --no-create "$quardir/$file_name.$rnd"
if [ "$pub" == "1" ]; then
chmod 400 "$quardir/$file_name.$rnd"
else
chmod 000 "$quardir/$file_name.$rnd"
chown root.root "$quardir/$file_name.$rnd"
fi
echo -e "# owner:group:mode:size(b):md5:atime(epoch):mtime(epoch):ctime(epoch):file(path)\n$file_owner:$file_group:$file_mode:$file_size:$md5_hash:$file_times:$file" > $quardir/$file_name.$rnd.info
eout "{quar} malware quarantined from '$file' to '$quardir/$file_name.$rnd'"
echo "$utime:$hitname:$file:$file_owner:$file_group:$md5_hash:$file_size:$quardir/$file_name.$rnd" >> $quar_history
if [ ! -z "$scan_session" ]; then
echo "$hitname : $file => $quardir/$file_name.$rnd" >> $scan_session
fi
if [ "$quarantine_clean" == "1" ] && [ ! "$clean_state" == "1" ]; then
unset clean_state
clean "$quardir/$file_name.$rnd" "$hitname" "$file_owner.$file_group" "$file_mode" "$file_size" "$md5_hash" "$file"
fi
else
if [ ! -z "$scan_session" ]; then
echo "$hitname : $file" >> $scan_session
fi
fi
else
eout "{quar} fatal error handling '$file'"
fi
}
quar_hitlist() {
hitlist="$sessdir/session.hits.$1"
if [ -f "$hitlist" ]; then
lbreakifs set
for file in `cat $hitlist | cut -d':' -f2 | sed 's/.//'`; do
if [ -f "$file" ]; then
get_filestat "$file" 1
file_name=`basename "$file"`
file_namewc=`echo "$file_name" | $wc -m`
file_hitname=`egrep -m1 "$file" $hitlist | awk '{print$1}'`
if [ ! "$md5_hash" ]; then
md5_hash=`eval $md5sum \"$file\" | awk '{print$1}'`
fi
if [ "$pub" == "1" ]; then
chattr -ia "$file" >> /dev/null 2>&1
chmod 400 "$file"
else
chattr -ia "$file"
chmod 000 "$file"
fi
rnd="${RANDOM}${RANDOM}"
mv "$file" "$quardir/$file_name.$rnd"
touch --no-create "$quardir/$file_name.$rnd"
echo -e "# owner:group:mode:size(b):md5:atime(epoch):mtime(epoch):ctime(epoch):file(path)\n$file_owner:$file_group:$file_mode:$file_size:$md5_hash:$file_times:$file" > $quardir/$file_name.$rnd.info
eout "{quar} malware quarantined from '$file' to '$quardir/$file_name.$rnd'" 1
echo "$utime:$file_hitname:$file:$file_owner:$file_group:$md5_hash:$file_size:$quardir/$file_name.$rnd" >> $quar_history
if [ "$quarantine_suspend_user" == "1" ]; then
quarantine_suspend_user "$file"
fi
if [ "$quarantine_clean" == "1" ] && [ ! "$clean_state" == "1" ]; then
unset clean_state
hitname=`cat $hitlist | grep $file | awk '{print$1}'`
clean "$quardir/$file_name.$rnd" "$hitname" "$file_owner.$file_group" "$file_mode" "$file_size" "$md5_hah" "$file"
fi
fi
done
lbreakifs unset
else
echo "{quar} invalid quarantine hit list, aborting."
exit
fi
}
clamselector() {
scan_max_filesize=`cat $sig_md5_file | cut -d':' -f2 | sort -n | tail -n1`
if [ "$scan_max_filesize" -gt "1" 2> /dev/null ]; then
scan_max_filesize=$[scan_max_filesize+1]
clamscan_max_filesize="${scan_max_filesize}"
scan_max_filesize="${scan_max_filesize}c"
else
scan_max_filesize="2048k"
clamscan_max_filesize="2592000"
fi
if [ "$scan_clamscan" == "1" ]; then
trim_log $clamscan_log 10000 1
for dpath in $clamav_paths; do
if [ -f "${dpath}/main.cld" ] || [ -f "${dpath}/main.cvd" ]; then
clamav_db="-d $dpath"
fi
done
isclamd=`pgrep -x clamd 2> /dev/null`
isclamd_root=`pgrep -x -u root clamd 2> /dev/null`
if [ "$scan_clamd_remote" == "1" ] && [ -f "$remote_clamd_config" ]; then
clamd=1
clambin="clamdscan"
clamopts="--fdpass -c $remote_clamd_config"
elif [ "$isclamd" ] && [ "$isclamd_root" ]; then
clamd=1
clambin="clamdscan"
clamopts="$clamdscan_extraopts"
elif [ "$isclamd" ] && [ ! "$isclamd_root" ]; then
clamd=1
clambin="clamdscan"
clamopts="--fdpass $clamdscan_extraopts"
else
clambin="clamscan"
clamopts="$clamscan_extraopts --max-filesize=$clamscan_max_filesize --max-scansize=$[clamscan_max_filesize*2] -d $runtime_hdb -d $runtime_ndb $clamav_db -r"
if [ "$monitor_mode" ]; then
inotify_sleep="120"
eout "{mon} warning clamd service not running; force-set monitor mode file scanning to every 120s"
fi
fi
if [ -f "/usr/local/cpanel/3rdparty/bin/$clambin" ]; then
clamscan="/usr/local/cpanel/3rdparty/bin/$clambin"
elif [ -f "$(which $clambin 2> /dev/null)" ]; then
clamscan=`which $clambin 2> /dev/null`
else
scan_clamscan="0"
fi
if [ "$clamd" ] && [ "$scan_clamscan" == "1" ]; then
## test clamdscan for errors as not all 'running' instances of clamd are indicative of working setup
if [ "$scan_clamd_remote" == "1" ] && [ -f "$remote_clamd_config" ]; then
try=0
while [ $try -le $remote_clamd_max_retry ]; do
clamd_test=`$clamscan $clamopts --fdpass --quiet --no-summary /etc/passwd 2> /dev/null || echo $?`
if [ "$clamd_test" = "2" ]; then
((try++))
sleep $remote_clamd_retry_sleep
else
break
fi
done
else
clamd_test=`$clamscan --fdpass --quiet --no-summary /etc/passwd 2> /dev/null || echo $?`
fi
if [ ! -z "$clamd_test" ]; then
clamd=0
clambin="clamscan"
clamopts="$clamscan_extraopts --max-filesize=$clamscan_max_filesize --max-scansize=$[clamscan_max_filesize*2] -d $runtime_hdb -d $runtime_ndb $clamav_db -r"
if [ -f "/usr/local/cpanel/3rdparty/bin/$clambin" ]; then
clamscan="/usr/local/cpanel/3rdparty/bin/$clambin"
elif [ -f "$(which $clambin 2> /dev/null)" ]; then
clamscan=`which $clambin 2> /dev/null`
else
scan_clamscan="0"
fi
fi
fi
fi
}
scan() {
scan_start_hr=`date +"%b %e %Y %H:%M:%S %z"`
scan_start=`date +"%s"`
lbreakifs set
spath=$(for i in `echo "$1" | tr '?' '*' | tr ',' '\n'`; do echo "\"$(printf %q $i)\""; done | tr '\n' ' ' | sed 's/.$//')
lbreakifs unset
days="$2"
scanid="$datestamp.$$"
if [ "$file_list" ]; then
spath="\"$file_list\""
elif [ ! -f "$find" ]; then
eout "{scan} could not locate find command" 1
clean_exit
exit 1
fi
if [ -f "$spath" ] && [ -z "$file_list" ]; then
single_filescan=1
fi
if [ ! -f "$sig_md5_file" ]; then
eout "{scan} required signature file not found ($sig_md5_file), try running -u|--update, aborting!" 1
clean_exit
exit 1
fi
if [ ! -f "$sig_hex_file" ]; then
eout "{scan} required signature file not found ($sig_hex_file), try running -u|--update, aborting!" 1
clean_exit
exit 1
fi
if [ ! -f "$ignore_paths" ]; then
touch $ignore_paths
chmod 640 $ignore_paths
elif [ ! -f "$ignore_sigs" ]; then
touch $ignore_sigs
chmod 640 $ignore_sigs
fi
if [ ! "$days" == "all" ] && [ -z "$file_list" ]; then
val=`echo $days | grep "[[:alpha:]]"`
if [ ! -z "$val" ]; then
eout "{scan} days value must be numeric value in the range of 1 - 90, reverting to default (7)." 1
days=7
elif [ "$days" -gt "90" ]; then
eout "{scan} days value must be numeric value in the range of 1 - 90, reverting to default (7)." 1
days=7
fi
fi
if [ ! "${spath:1:1}" = "/" ]; then
eout "{scan} must use absolute path, provided relative path $spath" 1
exit
fi
scan_session="$tmpdir/.sess.$$"
find_results="$tmpdir/.find.$$"
touch $find_results
touch $scan_session
sigignore
gensigs
if [ "$scan_clamscan" == "1" ]; then
clamselector
fi
hex_sigs=`$wc -l $sig_hex_file | awk '{print$1}'`
md5_sigs=`$wc -l $sig_md5_file | awk '{print$1}'`
yara_sigs=`grep -E -c ^rule $sig_yara_file | awk '{print$1}'`
user_hex_sigs=`$wc -l $sig_user_hex_file | awk '{print$1}'`
user_md5_sigs=`$wc -l $sig_user_md5_file | awk '{print$1}'`
user_sigs=$[user_hex_sigs+user_md5_sigs]
tot_sigs=$[md5_sigs+hex_sigs+user_hex_sigs+user_md5_sigs+yara_sigs]
if [ -z "$hscan" ]; then
eout "{scan} signatures loaded: $tot_sigs ($md5_sigs MD5 | $hex_sigs HEX | $yara_sigs YARA | $user_sigs USER)" 1
fi
if [ -f "$ignore_file_ext" ]; then
if [ ! "$(cat $ignore_file_ext)" == "" ]; then
for i in `cat $ignore_file_ext`; do
if [ "$ignore_fext" == "" ]; then
ignore_fext="-not -iname \"*$i\""
else
ignore_fext="$ignore_fext -not -iname \"*$i\""
fi
done
fi
fi
if [ "$scan_ignore_root" == "1" ]; then
ignore_root="-not -uid 0 -not -gid 0"
fi
if [ "$scan_ignore_user" ]; then
for i in `echo $scan_ignore_user | tr ', ' ' '`; do
if [ "$ignore_user" == "" ]; then
ignore_user="-not -user $i"
else
ignore_user="$ignore_user -not -user $i"
fi
done
fi
if [ "$scan_ignore_group" ]; then
for i in `echo $scan_ignore_group | tr ', ' ' '`; do
if [ "$ignore_group" == "" ]; then
ignore_group="-not -group $i"
else
ignore_group="$ignore_group -not -group $i"
fi
done
fi
if [ "$scan_tmpdir_paths" ] && [ -z "$hscan" ] && [ -z "$single_filescan" ]; then
spath_tmpdirs="$scan_tmpdir_paths"
fi
if [ "$file_list" ]; then
cat $file_list | grep -E -vf $ignore_paths > $find_results
else
if [ "$single_filescan" ]; then
find_recentops=""
elif [ "$days" == "all" ]; then
if [ -z "$hscan" ]; then
eout "{scan} building file list for $hrspath, this might take awhile..." 1
fi
find_recentops=""
else
rscan=1
if [ -z "$hscan" ]; then
eout "{scan} building file list for $hrspath of new/modified files from last $days days, this might take awhile..." 1
fi
find_recentopts="\( -mtime -$days -o -ctime -$days \)"
fi
if [ -z "$scan_find_timeout" ];then
scan_find_timeout=0
fi
if [ "$scan_find_timeout" -ge "60" ]; then
echo -e "sleep $scan_find_timeout\ntouch $tmpdir/.find_killed.$scanid\npkill -f lmd_find" > $tmpdir/.lmd_find_sleep.$$
sh -c "sh $tmpdir/.lmd_find_sleep.$$ >> /dev/null 2>&1 &" >> /dev/null 2>&1 &
rm -f $tmpdir/.lmd_find_sleep.$$ 2> /dev/null
eout "{scan} setting maximum execution time for 'find' file list: ${scan_find_timeout}sec" 1
fi
if [ -z "$hscan" ]; then
eout "{scan} setting nice scheduler priorities for all operations: cpunice $scan_cpunice , ionice $scan_ionice" 1
fi
file_list_start=`date +"%s"`
tmpscandir="$tmpdir/scan.$RANDOM"
mkdir -p "$tmpscandir" ; chmod 700 $tmpscandir ; cd $tmpscandir
eout "{scan} executed eval $nice_command $find $spath $spath_tmpdirs -maxdepth $scan_max_depth $find_opts -type f $find_recentopts -size +${scan_min_filesize}c -size -$scan_max_filesize $include_regex -not -perm 000 $exclude_regex $ignore_fext $ignore_root $ignore_user $ignore_group"
eval $nice_command $find /lmd_find/ $(echo $spath) $spath_tmpdirs -maxdepth $scan_max_depth $find_opts -type f $find_recentopts -size +${scan_min_filesize}c -size -$scan_max_filesize $include_regex -not -perm 000 $exclude_regex $ignore_fext $ignore_root $ignore_user $ignore_group 2> /dev/null | grep -E -vf $ignore_paths > $find_results
cd $tmpdir
rm -rf $tmpscandir
if [ "$rscan" = "1" ] && [ "$scan_export_filelist" == "1" ]; then
rm -f $tmpdir/.find_results.* 2> /dev/null ; cp $find_results $tmpdir/.find_results.shared.$$ 2> /dev/null
ln -fs $tmpdir/.find_results.shared.$$ $tmpdir/find_results.last 2> /dev/null
fi
file_list_end=`date +"%s"`
file_list_et=$[file_list_end-file_list_start]
if [ -f "$tmpdir/.find_killed.$scanid" ]; then
rm -f $tmpdir/.find_killed.$scanid
echo && eout "{scan} file list 'find' operation reached maximum execution time (${scan_find_timeout}sec) and was terminated" 1
else
pkill -f lmd_find_sleep >> /dev/null 2>&1
fi
fi
if [ ! -f "$find_results" ] || [ ! -s "$find_results" ]; then
if [ -z "$hscan" ]; then
if [ "$days" == "all" ]; then
eout "{scan} scan returned empty file list; check that path exists and contains files in scope of configuration." 1
rm -f $find_results $scan_session $runtime_ndb $runtime_hdb $runtime_hexstrings $clamscan_results $tmpdir/.tmpf*
exit 0
else
eout "{scan} scan returned empty file list; check that path exists, contains files in days range or files in scope of configuration." 1
rm -f $find_results $scan_session $runtime_ndb $runtime_hdb $runtime_hexstrings $clamscan_results
exit 0
fi
fi
fi
res_col="1"
move_to_col="echo -en \\033[${res_col}G"
tot_files=`$wc -l $find_results | awk '{print$1}'`
if [ -z "$hscan" ] && [ -z "$single_filescan" ]; then
if [ "$file_list" ]; then
eout "{scan} user supplied file list '$file_list', found $tot_files files..." 1
else
eout "{scan} file list completed in ${file_list_et}s, found $tot_files files..." 1
fi
fi
touch $sessdir/clean.$$
if [ ! -f "$scan_session" ]; then
touch $scan_session
fi
if [ ! -z "$hscan" ]; then
eout "{scan.hook} scan of $spath in progress (id: $datestamp.$$)"
fi
cnt=0
if [ -z "$mail" ] && [ -z "$sendmail" ]; then
eout "{scan} no \$mail or \$sendmail binaries found, e-mail alerts disabled."
fi
if [ -f "$clamscan" ] && [ "$scan_clamscan" == "1" ]; then
if [ -z "$hscan" ]; then
eout "{scan} found clamav binary at $clamscan, using clamav scanner engine..." 1
fi
if [ "$string_length_scan" == "1" ]; then
if [ -z "$hscan" ]; then
eout "{scan} preprocessing file list for string length hits..." 1
scan_strlen list "$find_results" >> /dev/null 2>&1
fi
fi
if [ -z "$hscan" ]; then
eout "{scan} scan of $hrspath ($tot_files files) in progress..." 1
fi
echo "$(date +"%b %d %Y %H:%M:%S") $(hostname -s) clamscan start" >> $clamscan_log
clamscan_results="$tmpdir/.clamscan.$$"
echo "$(date +"%b %d %Y %H:%M:%S") $(hostname -s) executed: $nice_command $clamscan $clamopts --infected --no-summary -f $find_results" >> $clamscan_log
if [ "$scan_clamd_remote" == "1" ] && [ -f "$remote_clamd_config" ]; then
try=0
while [ $try -le $remote_clamd_max_retry ]; do
$nice_command $clamscan $clamopts --infected --no-summary -f $find_results > $clamscan_results 2>> $clamscan_log
clamscan_return=$?
if [ "$clamscan_return" == "2" ]; then
((try++))
echo "$(date +"%b %d %H:%M:%S") $(hostname -s) remote clamd error - retrying in $retry_sleep seconds ($try)"
sleep $remote_clamd_retry_sleep
else
break
fi
done
else
$nice_command $clamscan $clamopts --infected --no-summary -f $find_results > $clamscan_results 2>> $clamscan_log
clamscan_return=$?
fi
if [ "$clamscan_return" == "2" ]; then
if [ "$quarantine_on_error" == "0" ] || [ -z "$quarantine_on_error" ]; then
quarantine_hits=0
eout "{scan} clamscan returned an error, check $clamscan_log for details; quarantine_on_error=0 or unset, quarantine has been disabled!" 1
else
eout "{scan} clamscan returned an error, check $clamscan_log for details!" 1
fi
fi
clamscan_fatal_error=`grep -m1 'no reply from clamd' $clamscan_results`
if [ "$clamscan_fatal_error" ]; then
quarantine_hits=0
eout "{scan} clamscan returned a fatal error in scan results, check $clamscan_log for details; quarantine has been disabled!" 1
fi
echo "$(date +"%b %d %Y %H:%M:%S") $(hostname -s) clamscan end return $clamscan_return" >> $clamscan_log
lbreakifs set
for hit in `grep -E -v 'ERROR$|lstat()|no reply from clamd' $clamscan_results | sed -e 's/.UNOFFICIAL//' -e 's/ FOUND$//' | awk -F':' '{print$2":"$1}' | sed 's/.//'`; do
file=`echo "$hit" | cut -d':' -f2`
signame=`echo "$hit" | cut -d':' -f1`
if [ ! -z "$(echo $signame | grep -E 'YARA')" ]; then
signame=`echo "$signame" | sed 's/YARA\./{YARA}/'`
elif [ -z "$(echo $signame | grep -E 'HEX|MD5')" ]; then
signame="{CAV}$signame"
fi
ignore_hit=`echo $signame | grep -E -vf $ignore_sigs`
if [ ! -z "$ignore_hit" ]; then
record_hit "$file" "$signame"
quarantine "$file" "$signame"
if [ ! "$set_background" == "1" ]; then
tot_hits=`$wc -l $scan_session | awk '{print$1}'`
tot_cl=`$wc -l $sessdir/clean.$$ | awk '{print$1}'`
if [ -z "$hscan" ]; then
echo -en "\\033[${res_col}G" && echo -n "maldet($$): {scan} processing scan results for hits: $tot_hits hits $tot_cl cleaned"
fi
cnt="$tot_files"
fi
fi
unset ignore_hit
done
lbreakifs unset
echo "$(date +"%b %d %Y %H:%M:%S") $(hostname -s) clamscan end" >> $clamscan_log
else
if [ -z "$hscan" ]; then
eout "{scan} scan of $hrspath ($tot_files files) in progress..." 1
fi
lbreakifs set
if [ ! -f "$scan_session" ]; then
touch $scan_session
fi
while read rpath; do
((cnt++))
if [ -z "$hscan" ] && [ ! "$set_background" == "1" ] && [ -z "$single_filescan" ]; then
tot_hit=`$wc -l $scan_session | awk '{print$1}'`
cl_hit=`$wc -l $sessdir/clean.$$ | awk '{print$1}'`
echo -en "\\033[${res_col}G" && echo -n "maldet($$): {scan} $cnt/$tot_files files scanned: $tot_hit hits $cl_hit cleaned"
fi
if [ -f "$rpath" ]; then
scan_stage1 "$rpath" >> /dev/null 2>&1
fi
done < $find_results
lbreakifs unset
fi
if [ -z "$hscan" ]; then
if [ ! "$set_background" == "1" ] && [ "$scan_clamscan" == "0" ] && [ -z "$single_filescan" ]; then
echo
fi
fi
scan_end_hr=`date +"%b %e %Y %H:%M:%S %z"`
scan_end=`date +"%s"`
scan_et=$[scan_end-scan_start]
scan_et_nofl=$[scan_et-file_list_et]
tot_hits=`$wc -l $scan_session | awk '{print$1}'`
tot_cl=`$wc -l $sessdir/clean.$$ | awk '{print$1}'`
gen_report
if [ ! -z "$hscan" ]; then
if [ ! "$tot_hits" == "0" ]; then
echo "0 maldet: $hitname $spath"
eout "{scan.hook} results returned FAIL hit found on $spath (id: $datestamp.$$)"
else
echo "1 maldet: OK"
eout "{scan.hook} results returned OK on $spath (id: $datestamp.$$)"
fi
else
if [ -z "$hscan" ]; then
echo
fi
eout "{scan} scan completed on $hrspath: files $tot_files, malware hits $tot_hits, cleaned hits $tot_cl, time ${scan_et}s" 1
eout "{scan} scan report saved, to view run: maldet --report $datestamp.$$" 1
if [ "$quarantine_hits" == "0" ] && [ ! "$tot_hits" == "0" ]; then
eout "{scan} quarantine is disabled! set quarantine_hits=1 in $cnffile or to quarantine results run: maldet -q $datestamp.$$" 1
fi
fi
if [ ! "$tot_hits" == "0" ]; then
if [ "$email_ignore_clean" == "1" ] && [ ! "$tot_hits" == "$tot_cl" ]; then
genalert file $nsess
elif [ "$email_ignore_clean" == "0" ]; then
genalert file $nsess
fi
if [ "$email_panel_user_alerts" == "1" ]; then
genalert panel $nsess
fi
fi
mv $scan_session $nsess_hits
rm -f $find_results $scan_session $runtime_ndb $runtime_hdb $runtime_hexstrings $clamscan_results
}
scan_strlen() {
type="$1"
file="$2"
if [ "$string_length_scan" == "1" ] && [ "$type" == "file" ]; then
flen=`$wc -L $file 2> /dev/null | awk '{print$1}'`
if [ "$flen" -ge "$string_length" ]; then
eout "{strlen} malware string length hit on $file"
quarantine "$file" "{SA}stat.strlength"
fi
elif [ "$string_length_scan" == "1" ] && [ "$type" == "list" ]; then
list="$tmpdir/.strlen.flist.$$"
cp $file $list
sed -i -e "s/'/\\\\'/g" $list
cat $list | xargs wc -L 2> /dev/null | grep -vw total >> $list.strlen
awk "{if (\$1>=$string_length) print\$2}" $list.strlen >> $list.hits
for i in `cat $list.hits`; do
if [ -f "$i" ]; then
eout "{strlen} malware string length hit on $i"
quarantine "$i" "{SA}stat.strlength"
fi
done
rm -f $list*
fi
}
scan_stage1() {
file="$1"
clean_check="$2"
hash=`eval $md5sum \"$file\" | awk '{print$1}'`
if [ -z "$runtime_hexstrings" ]; then
sigignore
gensigs
fi
if [ ! -z "$hash" ]; then
val_hash=`grep -m1 $hash $sig_user_md5_file $sig_md5_file`
if [ ! -z "$val_hash" ]; then
md5_hit="$hash"
md5_hitname=`echo $val_hash | cut -d':' -f4`
md5_hash="$hash"
if [ "$clean_check" == "1" ]; then
clean_failed=1
else
record_hit "$file" "$md5_hitname"
quarantine "$file" "$md5_hitname"
fi
unset val_hash md5_hit md5_hitname md5_hash
else
if [ -f "$file" ]; then
scan_stage2 "$file" $clean_check >> /dev/null 2>&1
fi
if [ -f "$file" ]; then
scan_strlen file "$file" >> /dev/null 2>&1
fi
fi
else
eout "{scan} error could not read or hash $file, do we have permission?"
fi
}
scan_stage2() {
file="$1"
clean_check="$2"
if [ -z "$ftype" ]; then
if [ -p "$hex_fifo_path" ] && [ "$scan_hexfifo" == "1" ]; then
if [ "$OSTYPE" == "FreeBSD" ]; then
$od -v -N$scan_hexfifo_depth -tx1 "$file" | cut -c12-256 | tr -d ' \n' > $hex_fifo_path 2>&1 &
else
$od -v -w64 -N$scan_hexfifo_depth -tx1 "$file" | cut -c9-256 | tr -d '\n ' > $hex_fifo_path 2>&1 &
fi
val_hex=`$perl $hex_fifo_script $runtime_hexstrings`
else
if [ "$OSTYPE" == "FreeBSD" ]; then
val_hex=`$perl $runtime_hexstrings $hex_string_script $($od -v -N$scan_hexdepth -tx1 "$file" | cut -c12-256 | tr -d ' \n')`
else
val_hex=`$perl $runtime_hexstrings $hex_string_script $($od -v -w$scan_hexdepth -N$scan_hexdepth -tx1 "$file" | tr -d '\n ')`
fi
fi
if [ ! -z "$val_hex" ]; then
hex_hit=`echo $val_hex | awk '{print$1}'`
hex_hitname=`echo $val_hex | awk '{print$2}'`
if [ "$clean_check" == "1" ]; then
clean_failed=1
else
record_hit "$file" "$hex_hitname"
quarantine "$file" "$hex_hitname"
fi
unset val_hex hex_hit hex_hitname
fi
fi
}
gen_report() {
if [ -f "$scan_session" ]; then
tot_hits=`$wc -l $scan_session | awk '{print$1}'`
nsess_hits="$sessdir/session.hits.$datestamp.$$"
echo "$datestamp.$$" > $sessdir/session.last
nsess="$sessdir/session.$datestamp.$$"
tmpf="$nsess"
. $email_template
fi
}
trim_log() {
log="$1"
logtrim="$2"
if [ -f "$log" ]; then
log_size=`$wc -l $log | awk '{print$1}'`
if [ "$log_size" -gt "$logtrim" 2> /dev/null ]; then
trim=$[logtrim/10]
printf "%s\n" "$trim,${log_size}d" w | ed -s $log 2> /dev/null
fi
elif [ ! -f "$log" ] && [ "$3" == "1" ]; then
touch $log ; chmod 640 $log
fi
}
genalert() {
type="$1"
file="$2"
if [ "$email_alert" == "1" ] || [ "$type" == "digest" ] || [ "$type" == "daily" ]; then
if [ "$type" == "file" ] && [ -f "$file" ]; then
if [ -f "$mail" ]; then
cat $file | $mail -s "$email_subj" $email_addr
elif [ -f "$sendmail" ]; then
if ! grep -q "SUBJECT: " "$file"; then
echo -e "SUBJECT: $email_subj\n$(cat $file)" > $file
fi
cat $file | $sendmail -t $email_addr
else
eout "{scan} no \$mail or \$sendmail binaries found, e-mail alerts disabled."
fi
if [ ! "$(whoami)" == "root" ] && [ -z "$(echo $2 | grep '\@')" ]; then
if [ -z "$hscan" ]; then
eout "{alert} sent scan report to config default $email_addr" 1
eout "{alert} send scan report to an alternate address with: maldet --report $datestamp.$$ you@domain.com" 1
else
eout "{alert} sent scan report to config default $email_addr"
fi
else
if [ -z "$hscan" ]; then
eout "{alert} sent scan report to $email_addr" 1
fi
fi
elif [ "$type" == "panel" ] && [ -f "$file" ]; then
eout "{panel} Detecting control panel and sending alerts..." 1
control_panel=""
detect_control_panel
if [ "$control_panel" == "error" ] || [ "$control_panel" == "unknown" ]; then
eout "{panel} Failed to set control panel. Will not send alerts to control panel account contacts." 1
else
# Sort malware hits from $file and map the detected files to their system user owner
file_hits=$(awk '/FILE HIT LIST:/{flag=1;next}/^=======/{flag=0}flag{print $3}' $file)
for hit in $file_hits; do
hit_line=$(grep "$hit" $file)
if [ -f "$hit" ]; then
file_owner=$(stat -c "%U" $hit)
elif ! [ -f "$hit" ] && [ "$quarantine_hits" == "1" ] && [[ "$hit_line" == *"=>"* ]]; then
quarantined_file=$(echo $hit_line | awk '{print $NF}')
file_owner=$(awk -F':' '/^[^#]/{print $1}' ${quarantined_file}.info)
fi
echo "$file_owner : $hit" >> $tmpdir/.panel_alert.hits
done
# Sort cleaned files too
if [ "$quarantine_clean" == "1" ]; then
for clean_file in $(cat $sessdir/clean.$$); do
if [ -f $clean_file ]; then
clean_owner=$(stat -c "%U" $clean_file)
fi
echo "$clean_owner : $clean_file" >> $tmpdir/.panel_alert.clean
done
fi
# Determine control panel, noop if error or none detected
eout "{panel} Detected control panel $control_panel. Will send alerts to control panel account contacts." 1
user_list=$(awk '{print $1}' $tmpdir/.panel_alert.hits | sort | uniq)
if [ -n "$user_list" ]; then
for sys_user in $user_list; do
contact_emails=""
get_panel_contacts $control_panel $sys_user
grep "^$sys_user " $tmpdir/.panel_alert.hits | awk '{print $3}' > $tmpdir/.${sys_user}.hits
user_tot_hits=$($wc -l $tmpdir/.${sys_user}.hits | awk '{print$1}')
if [ -f $tmpdir/.panel_alert.clean ]; then
grep "^$sys_user " $tmpdir/.panel_alert.clean | awk '{print $3}' > $tmpdir/.${sys_user}.clean
user_tot_cl=$($wc -l $tmpdir/.${sys_user}.clean | awk '{print$1}')
fi
tmpf=$tmpdir/.${sys_user}.alert
if [ -f "$sendmail" ]; then
echo "TO: $contact_emails" > $tmpf
echo "FROM: $email_panel_from" >> $tmpf
echo "REPLY-TO: $email_panel_replyto" >> $tmpf
echo -e "SUBJECT: $email_panel_alert_subj\n" >> $tmpf
. $email_panel_alert_etpl
cat $tmpf | $sendmail -t
elif [ -f "$mail" ] && [ "$control_panel" == "cpanel" ]; then
. $email_panel_alert_etpl
cat $tmpf | $mail -r $email_panel_from -s $email_panel_alert_subj $contact_emails
else
eout "{panel} No compatible \$sendmail or \$mail binaries found, control panel account alerts disabled."
fi
done
fi
rm -f $tmpdir/.panel_alert.hits $tmpdir/.panel_alert.clean $tmpdir/.${sys_user}.hits $tmpdir/.${sys_user}.clean $tmpf
fi
elif [ "$type" == "daily" ] || [ "$type" == "digest" ]; then
inotify_start_time=`ps -p $(ps -A -o 'pid cmd' | grep -E maldetect | grep -E inotifywait | awk '{print$1}' | head -n1) -o lstart= 2> /dev/null`
scan_start_hr=`date -d "$inotify_start_time" +"%b %e %Y %H:%M:%S %z"`
scan_start_elapsed=$(($(date +'%s')-$(date -d "$scan_start_hr" +'%s')))
inotify_run_time=`echo $(($scan_start_elapsed/86400))d:$(($(($scan_start_elapsed - $scan_start_elapsed/86400*86400))/3600))h:$(($(($scan_start_elapsed - $scan_start_elapsed/86400*86400))%3600/60))m:$(($(($scan_start_elapsed - $scan_start_elapsed/86400*86400))%60))s`
rm -f $tmpdir/.digest.alert.hits $tmpdir/.digest.clean.hits $tmpdir/.digest.monitor.alert $tmpdir/.digest.susp.hits
scanid="$datestamp.$$"
scan_session=`cat $sessdir/session.monitor.current`
$tlog $scan_session digest.alert > $tmpdir/.digest.alert.hits
$tlog $clean_history digest.clean.alert > $tmpdir/.digest.clean.hits
$tlog $monitor_scanned_history digest.monitor.alert > $tmpdir/.digest.monitor.alert
$tlog $suspend_history digest.susp.alert > $tmpdir/.digest.susp.hits
tot_hits=`$wc -l $tmpdir/.digest.alert.hits | awk '{print$1}'`
tot_cl=`$wc -l $tmpdir/.digest.clean.hits | awk '{print$1}'`
tot_files=`$wc -l $tmpdir/.digest.monitor.alert | awk '{print$1}'`
tot_susp=`$wc -l $tmpdir/.digest.susp.hits | awk '{print$1}'`
trim_log $monitor_scanned_history 50000
trim_log $clean_history 50000
trim_log $suspend_history 50000
$tlog $sessdir/session.hits.$datestamp.$$ digest.alert >> /dev/null 2>&1
$tlog $clean_history digest.clean.alert >> /dev/null 2>&1
$tlog $monitor_scanned_history digest.monitor.alert >> /dev/null 2>&1
$tlog $suspend_history digest.susp.alert >> /dev/null 2>&1
if [ ! -z "$(cat $tmpdir/.digest.alert.hits)" ]; then
tmpf="$tmpdir/.alert.$RANDOM.$$"
if [ "$tot_hits" -gt "$tot_files" ]; then
tot_files="$tot_hits"
fi
. $email_template
cp $tmpf $sessdir/session.$scanid
grep -E '^{.*}' $sessdir/session.$scanid > $sessdir/session.hits.$scanid
echo "$scanid" > $sessdir/session.last
email_subj="${email_subj}: monitor summary"
if [ -f "$mail" ]; then
cat $tmpf | $mail -s "$email_subj" $email_addr
eout "{alert} sent $type alert to $email_addr"
elif [ -f "$sendmail" ]; then
if ! grep -q "SUBJECT: " "$tmpf"; then
echo -e "SUBJECT: $email_subj\n$(cat $tmpf)" > $tmpf
fi
cat $tmpf | $sendmail -t $email_addr
eout "{alert} sent $type alert to $email_addr"
else
eout "{scan} no \$mail or \$sendmail binaries found, e-mail alerts disabled."
fi
rm -f $tmpf $tmpdir/.digest.alert.hits $tmpdir/.digest.clean.hits $tmpdir/.digest.monitor.alert $tmpdir/.digest.susp.hits
fi
else
eout "{alert} file input error, alert discarded."
fi
fi
if [ "$slack_alert" == "1" ]; then
if [ "$type" == "file" ] && [ -f "$file" ] && [ -f "$curl" ]; then
slack_response=$($curl -s -F "token=$slack_token" -F "file=@$file" -F "filename=$slack_subj" -F "channels=$slack_channels" -X POST https://slack.com/api/files.upload | grep -oP '^{"ok":true')
if [ "$slack_response" ]; then
eout "{alert} scan report sent to slack channel(s): $slack_channels" 0
else
eout "{alert} could not upload scan report to slack channel(s), alert discarded" 1
fi
else
eout "{alert} could not upload scan report to slack channel(s), alert discarded" 1
fi
fi
}
monitor_kill() {
touch $tmpdir/stop_monitor
inotify_pid=`pgrep -f inotify.paths.[0-9]+`
if [ -f "$tmpdir/monitor.pid" ]; then
monitor_pid=`cat $tmpdir/monitor.pid`
exit_code="0"
else
exit_code="1"
fi
monitor_pgid=`ps -p "$monitor_pid" -o pgid= | tr -d ' '`
kill -9 -- $inotify_pid -$monitor_pgid >> /dev/null 2>&1
exit $exit_code
}
monitor_cycle() {
if [ "$BASHPID" ]; then
echo "$BASHPID" > $tmpdir/monitor.pid
else
pgrep maldet > $tmpdir/monitor.pid
fi
inotify_cycle_runtime=0
while [ ! -f "$tmpdir/stop_monitor" ]; do
inotify_pid=`pgrep -f inotify.paths.[0-9]+`
if [ -z "$inotify_pid" ]; then
eout "{mon} no inotify process found, exiting (are we a zombie process?)" 1
exit
fi
log_size=`$wc -l $inotify_log | awk '{print$1}'`
if [ "$log_size" -ge "$inotify_trim" ]; then
trim=$(($log_size - 1000))
log_chars=`printf "%s\n" "1,${trim}p" | ed -s $inotify_log 2> /dev/null | wc -c`
tlog_new=$(( `cat $inspath/tmp/inotify` - $log_chars ))
echo $tlog_new > $inspath/tmp/inotify
printf "%s\n" "1,${trim}d" w | ed -s $inotify_log 2> /dev/null
eout "{mon} inotify log file trimmed"
fi
if [ "$inotify_cycle_runtime" -ge "$inotify_reloadtime" ] || [ -f "$inspath/reload_monitor" ]; then
if [ -f "$inspath/reload_monitor" ]; then
rm -f $inspath/reload_monitor
fi
source $cnf
source $intcnf
import_conf
inotify_cycle_runtime=0
if [ -f "$ignore_file_ext" ]; then
if [ ! "$(cat $ignore_file_ext)" == "" ]; then
for i in `cat $ignore_file_ext`; do
if [ "$ignore_fext" == "" ]; then
ignore_fext="-not -iname \"*$i\""
else
ignore_fext="$ignore_fext -not -iname \"*$i\""
fi
done
fi
fi
if [ "$scan_ignore_root" == "1" ]; then
ignore_root="-not -uid 0 -not -gid 0"
fi
if [ "$scan_ignore_user" ]; then
for i in `echo $scan_ignore_user | tr ', ' ' '`; do
if [ "$ignore_user" == "" ]; then
ignore_user="-not -user $i"
else
ignore_user="$ignore_user -not -user $i"
fi
done
fi
if [ "$scan_ignore_group" ]; then
for i in `echo $scan_ignore_group | tr ', ' ' '`; do
if [ "$ignore_group" == "" ]; then
ignore_group="-not -group $i"
else
ignore_group="$ignore_group -not -group $i"
fi
done
fi
eout "{mon} reloaded configuration data" 1
fi
sleep $inotify_sleep
inotify_cycle_runtime=$[inotify_sleep+inotify_cycle_runtime]
if [ -f "$ignore_sigs" ]; then
ignore_sigs_current_md5=`md5sum $ignore_sigs | awk '{print$1}'`
if [ ! "$ignore_sigs_current_md5" == "$ignore_sigs_last_md5" ]; then
sigignore 1
gensigs
ignore_sigs_current_md5=`md5sum $ignore_sigs | awk '{print$1}'`
eout "{mon} regenerated signature files on ignore_sigs file change detected" 1
fi
ignore_sigs_last_md5="$ignore_sigs_current_md5"
fi
if [ "$scan_clamscan" == "1" ]; then
monitor_mode=1
clamselector
fi
monitor_check
done
rm -f $tmpdir/stop_monitor
eout "{mon} monitoring terminated by user, inotify killed."
exit
}
cleanup_scanlist() {
# Checks for files that are already gone (temporary files, cache
# files, ...) and removes them from $monitor_scanlist, so we don't
# get errors and too many retries
TMP_FILE=$(mktemp -p /var/lib/maldetect/tmp)
lbreakifs set
for FILE in $(cat $monitor_scanlist); do
if [ -f "$FILE" ]; then
echo "$FILE" >> $TMP_FILE
fi
done
lbreakifs unset
mv $TMP_FILE $monitor_scanlist
}
monitor_check() {
monitor_scanlist="$tmpdir/.monitor.scan.${RANDOM}${RANDOM}"
touch $monitor_scanlist ; chmod 600 $monitor_scanlist
$tlog $inotify_log inotify | grep -E " CREATE| MODIFY| MOVED_TO" | awk -F" CREATE| MODIFY| MOVED_TO" '{print $1}' | sort -u | grep -vf $ignore_paths> $monitor_scanlist
if [ "$scan_clamscan" == "1" ]; then
clamscan_results="$tmpdir/.clamscan.result.${RANDOM}${RANDOM}"
touch $clamscan_results ; chmod 600 $clamscan_results
$nice_command $clamscan $clamopts --infected --no-summary -f $monitor_scanlist > $clamscan_results 2>> $clamscan_log || clamscan_return=$?
if [ "$scan_clamd_remote" == "1" ] && [ -f "$remote_clamd_config" ]; then
try=0
while [ $try -le $remote_clamd_max_retry ]; do
cleanup_scanlist $monitor_scanlist
$nice_command $clamscan $clamopts --infected --no-summary -f $monitor_scanlist > $clamscan_results 2>> $clamscan_log
clamscan_return=$?
if [ "$clamscan_return" == "2" ]; then
((try++))
echo "$(date +"%b %d %H:%M:%S") $(hostname -s) remote clamd error - retrying in $retry_sleep seconds ($try)" >> $clamscan_log
sleep $remote_clamd_retry_sleep
else
break
fi
done
else
$nice_command $clamscan $clamopts --infected --no-summary -f $monitor_scanlist > $clamscan_results 2>> $clamscan_log || clamscan_return=$?
fi
if [ "$inotify_verbose" == "1" ]; then
for file in `cat $monitor_scanlist | tr ' ' '%'`; do
file=`echo $file | tr '%' ' '`
eout "{mon} inotify clamav file scan on $file"
done
fi
lbreakifs set
for hit in `grep -E -v 'ERROR$|lstat()' $clamscan_results | sed -e 's/.UNOFFICIAL//' -e 's/ FOUND$//' | awk -F':' '{print$2":"$1}' | sed 's/.//'`; do
file=`echo "$hit" | cut -d':' -f2`
signame=`echo "$hit" | cut -d':' -f1`
if [ ! -z "$(echo $signame | grep -E 'YARA')" ]; then
signame=`echo "$signame" | sed 's/YARA\./{YARA}/'`
elif [ -z "$(echo $signame | grep -E 'HEX|MD5')" ]; then
signame="{CAV}$signame"
fi
ignore_hit=`echo $signame | grep -E -vf $ignore_sigs`
if [ -f "$file" ] && [ ! -z "$ignore_hit" ]; then
record_hit "$file" "$signame"
quarantine "$file" "$signame"
fi
unset ignore_hit
done
lbreakifs unset
scanned_count=`wc -l $monitor_scanlist | awk '{print$1}'`
eout "{mon} scanned ${scanned_count} new/changed files with clamav engine"
rm -f $clamscan_results $monitor_scanlist
else
for file in `cat $monitor_scanlist | tr ' ' '%'`; do
file=`echo $file | tr '%' ' '`
if [ -f "$file" ]; then
for fscan in `$nice_command $find "$file" -maxdepth 1 $find_opts -type f -size +${scan_min_filesize}c -size -$scan_max_filesize -not -perm 000 $ignore_fext $ignore_root $ignore_user $ignore_group 2> /dev/null`; do
if [ "$inotify_verbose" == "1" ]; then
eout "{mon} inotify native file scan on $file"
fi
echo "$file" >> $monitor_scanned_history
scan_stage1 "$fscan" >> /dev/null 2>&1
done
fi
done
scanned_count=`wc -l $monitor_scanlist | awk '{print$1}'`
eout "{mon} scanned ${scanned_count} new/changed files with native engine"
rm -f $clamscan_results $monitor_scanlist
fi
}
monitor_init() {
inopt="$1"
scan_session="$sessdir/session.hits.$datestamp.$$"
touch $scan_session
echo "$scan_session" > $sessdir/session.monitor.current
if [ "$inopt" == "" ]; then
eout "invalid usage of -m|--monitor, aborting." 1
exit
fi
if [ ! -f "$inotify" ]; then
eout "{mon} could not find inotifywait command, install yum package inotify-tools or download from https://github.com/rvoicilas/inotify-tools/wiki/" 1
exit
fi
ksup=0
if [ -f "/boot/System.map-$(uname -r)" ]; then
ksup=`grep inotify_ /boot/System.map-$(uname -r)`
fi
if [ -f "/boot/config-$(uname -r)" ]; then
ksup=`grep -m1 CONFIG_INOTIFY /boot/config-$(uname -r)`
fi
if [ -z "$ksup" ]; then
eout "{mon} kernel does not support inotify(), aborting." 1
exit
fi
inotify_pid=`pgrep -f inotify.paths.[0-9]+`
if [ ! -z "$inotify_pid" ]; then
eout "{mon} existing inotify process detected (try -k): $inotify_pid" 1
exit
fi
rm -f $tmpdir/stop_monitor $tmpdir/inotifywait.pid
if [ -f "/proc/sys/fs/inotify/max_user_instances" ] && [ -f "/proc/sys/fs/inotify/max_user_watches" ]; then
cur_user_watches=`cat /proc/sys/fs/inotify/max_user_watches`
cur_user_instances=`cat /proc/sys/fs/inotify/max_user_instances`
else
eout "{mon} could not find fs.inotify.max_user_instances|watches tunable files, aborting." 1
exit
fi
users_tot=`cat /etc/passwd | grep -ic home`
inotify_user_watches=$[inotify_base_watches*users_tot]
if [ "$cur_user_instances" -lt "$inotify_user_instances" ]; then
eout "{mon} set inotify max_user_instances to $inotify_user_instances" 1
echo $inotify_user_instances > /proc/sys/fs/inotify/max_user_instances
fi
if [ "$cur_user_watches" -lt "$inotify_user_watches" ]; then
eout "{mon} set inotify max_user_watches to $inotify_user_watches" 1
echo $inotify_user_watches > /proc/sys/fs/inotify/max_user_watches
fi
icnt=0
inotify_fpaths="$sessdir/inotify.paths.$$"
rm -f $inotify_fpaths
touch $inotify_log
chmod 640 $inotify_log
if [ "$(echo $inopt | grep -iE 'user(s?)')" ]; then
for i in `cat /etc/passwd | cut -d':' -f1,3,6 | sort`; do
user=`echo $i | cut -d':' -f1`
user_id=`echo $i | cut -d':' -f2`
user_home=`echo $i | cut -d':' -f3`
icnt=$[icnt+1]
if [ "$user_id" -ge "$inotify_minuid" ]; then
if [ ! -z "$inotify_docroot" ] && [ -d "$user_home" ]; then
lbreakifs set
for docroot in `echo $inotify_docroot | tr ', ' '\n'`; do
if [ -d "$user_home/$docroot" ]; then
echo "$user_home/$docroot" >> $inotify_fpaths
eout "{mon} added $user_home/$docroot to inotify monitoring array" 1
fi
done
lbreakifs unset
elif [ -d "$user_home" ]; then
echo "$user_home" >> $inotify_fpaths
eout "{mon} added $user_home to inotify monitoring array" 1
else
eout "{mon} could not find any suitable user home paths"
fi
fi
done
if [ -d "/dev/shm" ]; then
echo "/dev/shm" >> $inotify_fpaths
eout "{mon} added /dev/shm to inotify monitoring array" 1
fi
if [ -d "/var/tmp" ]; then
echo "/var/tmp" >> $inotify_fpaths
eout "{mon} added /var/tmp to inotify monitoring array" 1
fi
if [ -d "/tmp" ]; then
echo "/tmp" >> $inotify_fpaths
eout "{mon} added /tmp to inotify monitoring array" 1
fi
elif [ -f "$inopt" ]; then
tot_paths=`$wc -l $inopt | awk '{print$1}'`
if [ "$tot_paths" == "0" ]; then
eout "{mon} no paths specified in $inopt, aborting." 1
exit
fi
for i in `cat $inopt`; do
if [ -d "$i" ]; then
eout "{mon} added $i to inotify monitoring array" 1
echo "$i" >> $inotify_fpaths
else
eout "{mon} ignored invalid path $i" 1
fi
done
elif [ -d "$inopt" ] || [ "$(echo $inopt | grep -E ".*,.*")" ]; then
for i in `echo $inopt | tr ',' '\n'`; do
if [ -d "$i" ]; then
eout "{mon} added $i to inotify monitoring array" 1
echo "$i" >> $inotify_fpaths
else
eout "{mon} invalid path $i specified, ignoring." 1
fi
done
else
eout "{mon} no valid option or invalid file/path provided, aborting." 1
exit
fi
if [ -f "$ignore_inotify" ] && [ -s "$ignore_inotify" ]; then
for igfile in `cat $ignore_inotify | grep -vE '^$'`; do
if [ "$igregexp" ]; then
igregexp="$igregexp|$igfile"
else
igregexp="($igfile"
fi
done
if [ "$igregexp" ]; then
igregexp="$igregexp)"
exclude="--exclude $igregexp"
fi
fi
tot_paths=`$wc -l $inotify_fpaths | awk '{print$1}'`
eout "{mon} starting inotify process on $tot_paths paths, this might take awhile..." 1
if [ ! "$inotify_cpunice" ]; then
inotify_cpunice=19
fi
if [ ! "$inotify_ionice" ]; then
inotify_ionice=6
fi
if [ -f "$nice" ]; then
nice_command="$nice -n $inotify_cpunice"
fi
if [ -f "$ionice" ]; then
nice_command="$nice_command $ionice -c2 -n $inotify_ionice"
fi
if [ -f "$cpulimit" ] && [ "$inotify_cpulimit" -gt 2> /dev/null "0" ]; then
max_cpulimit=$[$(grep -E -w processor /proc/cpuinfo -c)*100]
if [ "$inotify_cpulimit" -gt "$max_cpulimit" ]; then
scan_cpulimit="0"
else
nice_command="$cpulimit -l $scan_cpulimit -- $nice_command"
fi
fi
$nice_command $inotify -r --fromfile $inotify_fpaths $exclude --timefmt "%d %b %H:%M:%S" --format "%w%f %e %T" -m -e create,move,modify >> $inotify_log 2>&1 &
sleep 2
inotify_pid=`pgrep -f inotify.paths.[0-9]+`
if [ -z "$inotify_pid" ]; then
eout "{mon} no inotify process found, check $inotify_log for errors." 1
exit
else
eout "{mon} inotify startup successful (pid: $inotify_pid)" 1
eout "{mon} inotify monitoring log: $inotify_log" 1
echo "$inotify_pid" > $tmpdir/inotifywait.pid
fi
monitor_cycle >> /dev/null 2>&1 &
}
checkout() {
file="$1"
host=ftp.rfxn.com
user=anonymous@rfxn.com
passwd=anonymous@rfxn.com
upath=incoming
cfile="$startdir/$file"
if [ -f "$cfile" ]; then
file="$cfile"
fi
if [ -f "$file" ]; then
filename=`basename "$file" | tr -d '[:cntrl:]' | tr -d '[:space:]'`
if [ -z "$filename" ]; then
storename="$storename_prefix"
else
storename="$storename_prefix.$filename"
fi
eout "{checkout} uploading $file to $host" 1
(ftp -v -n -p -i $host || ftp -v -n -i $host) << EOT
user $user@rfxn.com $passwd
prompt
cd $upath
lcd $lcd
binary
put "$file" "$storename.bin"
ascii
put "$file" "$storename.ascii"
bye
EOT
elif [ -d "$file" ]; then
tmpf="$tmpdir/.co$$"
find $file -type f > $tmpf
cofiles=`wc -l $tmpf | awk '{print$1}'`
if [ "$cofiles" -ge "25" ]; then
eout "{checkout} path $file contains $cofiles, limit of 50 file uploads, aborting!" 1
rm -f $tmpf
fi
for i in `cat $tmpf`; do
filename=`basename "$i" | tr -d '[:cntrl:]' | tr -d '[:space:]'`
if [ -z "$filename" ]; then
storename="$storename_prefix"
else
storename="$storename_prefix.$filename"
fi
(ftp -v -n -p -i $host || ftp -v -n -i $host) << EOT
user $user $passwd
prompt
cd $upath
lcd $lcd
binary
put "$i" "$storename.bin"
ascii
put "$i" "$storename.ascii"
bye
EOT
done
fi
}
gensigs() {
runtime_ndb="$tmpdir/.runtime.user.$$.ndb"
runtime_hdb="$tmpdir/.runtime.user.$$.hdb"
runtime_hexstrings="$tmpdir/.runtime.hexsigs.$$"
rm -f $runtime_ndb $runtime_hdb $runtime_hexstrings 2> /dev/null
ln -fs $runtime_ndb $sigdir/lmd.user.ndb 2> /dev/null
ln -fs $runtime_hdb $sigdir/lmd.user.hdb 2> /dev/null
if [ -s "$sig_user_hex_file" ]; then
cat "$sig_hex_file" "$sig_user_hex_file" | grep -vE '^\s*$' > $runtime_hexstrings
else
cat "$sig_hex_file" > $runtime_hexstrings
fi
for cp in $clamav_paths; do
clamav_linksigs "$cp"
done
if [ "$scan_clamscan" == "1" ]; then
if [ -s "$sig_user_hex_file" ]; then
for i in `cat $sig_user_hex_file | sed 's/{HEX}//' | tr ':' '%' | grep -vE "^\s*$"`; do
name=`echo $i | tr '%' ' ' | awk '{print$2}'`
hex=`echo $i | tr '%' ' ' | awk '{print$1}'`
if [ ! -z "$name" ] && [ ! -z "$hex" ]; then
echo "{HEX}$name:0:*:$hex" >> $runtime_ndb
fi
done
cat $sig_cav_hex_file >> $runtime_ndb
else
cp $sig_cav_hex_file $runtime_ndb
fi
if [ -s "$sig_user_md5_file" ]; then
cat "$sig_user_md5_file" "$sig_md5_file" | grep -vE "^\s*$" | sort -u > $runtime_hdb
else
cp "$sig_cav_md5_file" "$runtime_hdb"
fi
fi
}
sigignore() {
sil="$1"
chk=`$wc -l $ignore_sigs | awk '{print$1}'`
if [ ! "$chk" == "0" ]; then
cat $sig_hex_file | grep -E -vf $ignore_sigs > $sig_hex_file.new
mv $sig_hex_file.new $sig_hex_file
cat $sig_md5_file | grep -E -vf $ignore_sigs > $sig_md5_file.new
mv $sig_md5_file.new $sig_md5_file
chmod 640 $sig_md5_file $sig_hex_file
if [ "$sil" == "1" ] || [ "$hscan" == "1" ]; then
eout "{glob} processed $chk signature ignore entries"
else
eout "{glob} processed $chk signature ignore entries" 1
fi
fi
}
lmdup() {
tmpwd="$tmpdir/.lmdup.$RANDOM.$$"
upstreamver="$tmpwd/.lmdup_vercheck.$$"
mkdir -p $tmpwd ; chmod 700 $tmpwd
if [ "$lmdup_beta" ]; then
lmd_hash_url="${lmd_hash_url}.beta"
lmd_version_url="${lmd_version_url}.beta"
lmd_current_tgzfile="maldetect-beta.tar.gz"
fi
eout "{update} checking for available updates..." 1
get_remote_file "$lmd_version_url" "update" "1"
upstreamver="$return_file"
if [ -s "$upstreamver" ]; then
installedver=`echo $ver | tr -d '.'`
if [ "$(echo $installedver | wc -L)" -eq "2" ]; then
installedver="${installedver}0"
fi
upstreamver_readable=`cat $upstreamver`
upstreamver=`cat $upstreamver | tr -d '.'`
if [ "$(echo $upstreamver | wc -L)" -eq "2" ]; then
upstreamver="${upstreamver}0"
fi
if [ "$upstreamver" -gt "$installedver" ]; then
eout "{update} new version $upstreamver_readable found, updating..." 1
doupdate=1
elif [ "$lmdup_force" ]; then
eout "{update} version update with --force requested, updating..." 1
doupdate=1
elif [ "$autoupdate_version_hashed" == "1" ]; then
eout "{update} hashing install files and checking against server..." 1
eval $md5sum $inspath/maldet $intfunc | awk '{print$1}' | tr '\n' ' ' | tr -d ' ' > $lmd_hash_file
upstreamhash="$tmpwd/.lmdup_hashcheck$$"
get_remote_file "$lmd_hash_url" "update" "1"
upstreamhash="$return_file"
if [ -s "$upstreamhash" ]; then
installed_hash=`cat $lmd_hash_file`
current_hash=`cat $upstreamhash`
if [ ! "$installed_hash" == "$current_hash" ]; then
eout "{update} version check shows latest but hash check failed, forcing update..." 1
doupdate=1
else
eout "{update} latest version already installed." 1
fi
else
eout "{update} could not download upstream hash file ($lmd_hash_url), please try again later." 1
cd $inspath ; rm -rf $tmpwd
clean_exit
exit 1
fi
else
eout "{update} no updates available, latest version already installed." 1
fi
else
eout "{update} could not download version file from server, please try again later." 1
cd $inspath ; rm -rf $tmpwd
clean_exit
exit 1
fi
if [ "$doupdate" ]; then
cd $tmpwd/
get_remote_file "${lmd_current_tgzbase_url}/${lmd_current_tgzfile}" "update" "1" "$tmpwd/${lmd_current_tgzfile}"
get_remote_file "${lmd_current_tgzbase_url}/${lmd_current_tgzfile}.md5" "update" "1" "$tmpwd/${lmd_current_tgzfile}.md5"
if [ -s "$tmpwd/${lmd_current_tgzfile}.md5" ] && [ -s "$tmpwd/${lmd_current_tgzfile}" ]; then
upstream_md5=`cat $tmpwd/${lmd_current_tgzfile}.md5 | awk '{print$1}'`
local_md5=`eval $md5sum $tmpwd/${lmd_current_tgzfile} | awk '{print$1}'`
if [ ! "$upstream_md5" == "$local_md5" ]; then
eout "{update} unable to verify md5sum of ${lmd_current_tgzfile}, update failed!" 1
cd $inspath ; rm -rf $tmpwd
clean_exit
exit 1
else
eout "{update} verified md5sum of ${lmd_current_tgzfile}" 1
fi
else
eout "{update} could not download ${lmd_current_tgzfile} or .md5, please try again later." 1
cd $inspath ; rm -rf $tmpwd
clean_exit
exit 1
fi
if [ -s "$tmpwd/${lmd_current_tgzfile}" ]; then
tar xfz ${lmd_current_tgzfile}
rm -f ${lmd_current_tgzfile} ${lmd_current_tgzfile}.md5
cd maldetect-${upstreamver_readable}
chmod 750 install.sh
sh -c './install.sh' >> /dev/null 2>&1
cp -f $inspath.last/sigs/custom.* $sigdir/ 2> /dev/null
cp -f $inspath.last/clean/custom.* $inspath/clean/ 2> /dev/null
eout "{update} completed update v$ver ${installed_hash:0:6} => v$upstreamver_readable ${upstream_md5:0:6}, running signature updates..." 1
$inspath/maldet --update 1
eout "{update} update and config import completed" 1
else
eout "{update} could not download ${lmd_current_tgzfile}, please try again later." 1
cd $inspath ; rm -rf $tmpwd
clean_exit
exit 1
fi
fi
cd $inspath ; rm -rf $tmpwd
}
sigup() {
eout "{sigup} performing signature update check..." 1
tmpwd="$tmpdir/.sigup.$RANDOM.$$"
mkdir -p $tmpwd ; chmod 700 $tmpwd
import_user_sigs
if [ -z "$sig_version" ]; then
eout "{sigup} could not determine signature version" 1
sig_version=0
else
eout "{sigup} local signature set is version $sig_version" 1
fi
get_remote_file "$sig_version_url" "sigup" "1"
upstream_sigver="$return_file"
if [ ! -f "$upstream_sigver" ] || [ ! -s "$upstream_sigver" ]; then
eout "{sigup} could not download signature data from server, please try again later." 1
clean_exit
exit 1
else
nver=`cat $upstream_sigver`
fi
if [ -f "$sig_md5_file" ]; then
lines_md5=`$wc -l $sig_md5_file | awk '{print$1}'`
else
lines_md5=0
fi
if [ -f "$sig_hex_file" ]; then
lines_hex=`$wc -l $sig_hex_file | awk '{print$1}'`
else
lines_hex="0"
fi
if [ ! -f "$sig_md5_file" ] || [ ! -f "$sig_hex_file" ]; then
sig_version=2012010100000
eout "{sigup} signature files missing or corrupted, forcing update..." 1
elif [ "$lines_md5" -lt "1000" ] || [ "$lines_hex" -lt "1000" ]; then
sig_version=2012010100000
eout "{sigup} signature files corrupted, forcing update..." 1
elif [ "$sigup_force" ]; then
sig_version=2012010100000
eout "{sigup} signature update with --force requested, forcing update..." 1
fi
if [ "$nver" != "$sig_version" ]; then
cd $tmpwd/
tar=`which tar 2> /dev/null`
eout "{sigup} new signature set $nver available" 1
eout "{sigup} downloading $sig_sigpack_url" 1
get_remote_file "$sig_sigpack_url" "sigup" "1" "$tmpwd/maldet-sigpack.tgz"
get_remote_file "${sig_sigpack_url}.md5" "sigup" "1" "$tmpwd/maldet-sigpack.tgz.md5"
eout "{sigup} downloading $sig_clpack_url" 1
get_remote_file "$sig_clpack_url" "sigup" "1" "$tmpwd/maldet-clean.tgz"
get_remote_file "${sig_clpack_url}.md5" "sigup" "1" "$tmpwd/maldet-clean.tgz.md5"
if [ -f "$tmpwd/maldet-sigpack.tgz.md5" ]; then
sigpack_md5=`eval $md5sum maldet-sigpack.tgz | awk '{print$1}'`
sigpack_goodmd5=`cat maldet-sigpack.tgz.md5 | awk '{print$1}'`
if [ ! "$sigpack_md5" == "$sigpack_goodmd5" ]; then
eout "{sigup} unable to verify md5sum of maldet-sigpack.tgz, please try again or contact proj@rfxn.com" 1
sigpackfail=1
else
eout "{sigup} verified md5sum of maldet-sigpack.tgz" 1
if [ -f "$tmpwd/maldet-sigpack.tgz" ] && [ -s "$tmpwd/maldet-sigpack.tgz" ]; then
tar xfz $tmpwd/maldet-sigpack.tgz 2> /dev/null
if [ -d "$tmpwd/sigs" ]; then
mkdir -p $sigdir.old 2> /dev/null
rm -f $sigdir.old/* 2> /dev/null
cp -f $sigdir/* $sigdir.old/ 2> /dev/null
cp -f $tmpwd/sigs/* $sigdir 2> /dev/null
eout "{sigup} unpacked and installed maldet-sigpack.tgz" 1
for cp in $clamav_paths; do
clamav_linksigs "$cp"
done
killall -SIGUSR2 clamd 2> /dev/null
else
eout "{sigup} something went wrong unpacking $sig_sigpack_url, aborting!" 1
sigpackfail=1
fi
else
eout "{sigup} could not download $sig_sigpack_url" 1
sigpackfail=1
fi
fi
else
eout "{sigup} could not download ${sig_sigpack_url}.md5" 1
sigpackfail=1
fi
if [ -f "$tmpwd/maldet-clean.tgz.md5" ]; then
clpack_md5=`eval $md5sum maldet-clean.tgz | awk '{print$1}'`
clpack_goodmd5=`cat maldet-clean.tgz.md5 | awk '{print$1}'`
if [ ! "$clpack_md5" == "$clpack_goodmd5" ]; then
eout "{sigup} unable to verify md5sum of maldet-clean.tgz, please try again or contact proj@rfxn.com" 1
clpackfail=1
else
eout "{sigup} verified md5sum of maldet-clean.tgz" 1
if [ -f "$tmpwd/maldet-clean.tgz" ] && [ -s "$tmpwd/maldet-clean.tgz" ]; then
tar xfz $tmpwd/maldet-clean.tgz
cp -f $tmpwd/clean/* $cldir
eout "{sigup} unpacked and installed maldet-clean.tgz" 1
else
eout "{sigup} error handling $sig_clpack_url, file is either missing or zero sized, aborting!" 1
clpackfail=1
fi
fi
else
eout "{sigup} could not download ${sig_sigpack_url}.md5" 1
clpackfail=1
fi
if [ "$sigpackfail" ]; then
cd $inspath
rm -rf $tmpwd
clean_exit
exit 1
else
eout "{sigup} signature set update completed" 1
sigignore
hex_sigs=`$wc -l $sig_hex_file | awk '{print$1}'`
md5_sigs=`$wc -l $sig_md5_file | awk '{print$1}'`
yara_sigs=`grep -E -c ^rule $sig_yara_file | awk '{print$1}'`
if [ ! -f "$sig_user_md5_file" ]; then
user_hex_sigs=0
else
user_hex_sigs=`$wc -l $sig_user_hex_file | awk '{print$1}'`
fi
if [ ! -f "$sig_user_hex_file" ]; then
user_md5_sigs=0
else
user_md5_sigs=`$wc -l $sig_user_md5_file | awk '{print$1}'`
fi
user_sigs=$[user_hex_sigs+user_md5_sigs]
tot_sigs=$[md5_sigs+hex_sigs+user_hex_sigs+user_md5_sigs+yara_sigs]
eout "{sigup} $tot_sigs signatures ($md5_sigs MD5 | $hex_sigs HEX | $yara_sigs YARA | $user_sigs USER)" 1
fi
cd $inspath
rm -rf $tmpwd
else
eout "{sigup} latest signature set already installed" 1
cd $inspath
rm -rf $tmpwd
fi
}
postrun() {
rm -f $find_results $scan_session $runtime_ndb $runtime_hdb $runtime_hexstrings $clamscan_results $tmpdir/.tmpf* 2> /dev/null
if [ ! "$tot_hits" ]; then
exit 0
elif [ "$tot_hits" == "0" ]; then
exit 0
elif [ "$tot_hits" -ge "1" ]; then
exit 2
fi
}
SEA-GHOST - SHELL CODING BY SEA-GHOST