0, 'files_scanned' => 0, 'files_infected' => 0, ); private $followSymlink = false; private $patterns_raw = array(); private $patterns_iraw = array(); private $patterns_re = array(); public function __construct() { $this->patterns_raw = $this->loadPatterns(dirname(__FILE__) . '/patterns_raw.txt'); $this->patterns_iraw = $this->loadPatterns(dirname(__FILE__) . '/patterns_iraw.txt'); $this->patterns_re = $this->loadPatterns(dirname(__FILE__) . '/patterns_re.txt'); $options = getopt('he:i:kwxd:lv', array('help', 'extension:', 'ignore:', 'hide-ok', 'hide-whitelist', 'extra-check', 'directory:', 'follow-symlink', 'verbose')); if (isset($options['help']) || isset($options['h'])) { $this->showHelp(); exit; } if (isset($options['extension']) || isset($options['e'])) { $ext = isset($options['extension']) ? $options['extension'] : $options['e']; if ($ext[0] != '.') { $ext = '.' . $ext; } $this->extension = strtolower($ext); } if (isset($options['ignore']) || isset($options['i'])) { $tmp = isset($options['ignore']) ? $options['ignore'] : $options['i']; $this->ignore = is_array($tmp) ? $tmp : array($tmp); } if (isset($options['hide-ok']) || isset($options['k'])) { $this->flagHideOk = true; } if (isset($options['hide-whitelist']) || isset($options['w'])) { $this->flagHideWhitelist = true; } if (isset($options['extra-check']) || isset($options['x'])) { $this->extraCheck = true; } if (isset($options['follow-symlink']) || isset($options['l'])) { $this->followSymlink = true; } if (isset($options['verbose']) || isset($options['v'])){ $this->verbose = true; } if (isset($options['directory']) || isset($options['d'])) { $tmp = isset($options['directory']) ? $options['directory'] : $options['d']; $this->run($tmp); } else { $this->out(MalwareScanner::ANSI_RED, 'ER', 'No directory specified'); $this->showHelp(); } } public function run($dir) { $dir = rtrim($dir, '/'); if (!is_dir($dir)) { $this->out(self::ANSI_RED, 'ER', 'Specified path is not a directory: ' . $dir); exit(-1); } $start = time(); $this->loadWhitelist(); $this->process($dir . '/'); $this->report($start, $dir . '/'); } private function loadWhitelist() { if (!is_file(__DIR__ . '/whitelist.txt')) { return; } $fp = fopen(__DIR__ . '/whitelist.txt', 'r'); while (!feof($fp)) { $line = fgets($fp); $this->whitelist[] = substr($line, 0, 32); } } private function inWhitelist($hash) { return in_array($hash, $this->whitelist); } private function process($dir) { $dh = opendir($dir); if (!$dh) { return; } $this->stat['directories']++; while (($file = readdir($dh)) !== false) { if ($file == '.' || $file == '..') { continue; } if ($this->isIgnored($dir . $file)) { continue; } if (!$this->followSymlink && is_link($dir . $file)) { continue; } if (is_dir($dir . $file)) { $this->process($dir . $file . '/'); } elseif (is_file($dir . $file)) { $ext = strtolower(substr($file, strrpos($file, '.'))); if ($ext == $this->extension) { $this->scan($dir . $file); } } } closedir($dh); } private function report($start, $dir) { $end = time(); echo 'Start time: ' . strftime('%Y-%m-%d %H:%M:%S', $start) . PHP_EOL; echo 'End time: ' . strftime('%Y-%m-%d %H:%M:%S', $end) . PHP_EOL; echo 'Total execution time: ' . ($end - $start) . PHP_EOL; echo 'Base directory: ' . $dir . PHP_EOL; echo 'Total directories scanned: ' . $this->stat['directories'] . PHP_EOL; echo 'Total files scanned: ' . $this->stat['files_scanned'] . PHP_EOL; echo 'Total malware identified: ' . $this->stat['files_infected'] . PHP_EOL; } private function loadPatterns($file) { $list = array(); if (is_readable($file)) { foreach (file($file) as $pattern) { $list[] = trim($pattern); } } return $list; } private function scan($path) { $this->stat['files_scanned']++; $fileContent = file_get_contents($path); $found = false; $hash = ''; $toSearch = ''; if ($this->extraCheck) { array_push($this->patterns_raw, "googleBot", "htaccess"); } foreach ($this->patterns_raw as $toSearch) { if (!$toSearch) { continue; } if ($toSearch[0] === '#') { continue; } if (strpos($fileContent, $toSearch) !== FALSE){ $found = true; if ($hash === ''){ $hash = md5($fileContent); } $this->printPath($found, $path, $toSearch, $hash); if (!$this->verbose){ break; } } } if (!$found || $this->verbose) { foreach ($this->patterns_iraw as $toSearch) { if (!$toSearch) { continue; } if ($toSearch[0] === '#') { continue; } if (stripos($fileContent, $toSearch) !== FALSE){ $found = true; if ($hash === ''){ $hash = md5($fileContent); } $this->printPath($found, $path, $toSearch, $hash); if (!$this->verbose){ break; } } } } if (!$found || $this->verbose) { foreach ($this->patterns_re as $toSearch) { if (!$toSearch) { continue; } if ($toSearch[0] === '#') { continue; } if (preg_match('/' . $toSearch . '/im', $fileContent)) { $found = true; if ($hash === ''){ $hash = md5($fileContent); } $this->printPath($found, $path, $toSearch, $hash); if (!$this->verbose){ break; } } } } if (!$found) { $this->printPath($found, $path, $toSearch, $hash); return false; } if ($found && $this->inWhitelist($hash)) { return false; } $this->stat['files_infected']++; return true; } private function printPath($found, $path, $pattern, $hash) { $blank_hash = ' '; $blank_pattern = ''; $safe_path = '{' . $path . '}'; if ($found && $this->inWhitelist($hash)) { if (!$this->flagHideWhitelist) { $this->out(self::ANSI_YELLOW, 'WL', $blank_hash, $safe_path, $blank_pattern); } } elseif ($found) { $this->out(self::ANSI_RED, 'ER', $hash, $safe_path, $pattern); } else { if (!$this->flagHideOk) { $this->out(self::ANSI_GREEN, 'OK', $blank_hash, $safe_path, $blank_pattern); } } } private function isIgnored($pathname) { foreach ($this->ignore as $pattern) { $match = $this->pathMatches($pathname, $pattern); if ($match) { return true; } } return false; } // @see http://stackoverflow.com/a/13914119 private function pathMatches($path, $pattern, $ignoreCase = false) { $expr = preg_replace_callback( '/[\\\\^$.[\\]|()?*+{}\\-\\/]/', function ($matches) { switch ($matches[0]) { case '*': return '.*'; case '?': return '.'; default: return '\\' . $matches[0]; } }, $pattern ); $expr = '/' . $expr . '/'; if ($ignoreCase) { $expr .= 'i'; } return (bool)preg_match($expr, $path); } private function out($color, $serv, $hash, $path, $pattern) { echo $color . '#' . ' ' . $serv . self::ANSI_BLUE . " $hash " . self::ANSI_OFF . "# " . $path . $color . ' # ' . $pattern . self::ANSI_OFF . PHP_EOL; } private function showHelp() { echo 'Usage: scan.php -d ' . PHP_EOL; echo ' -h --help Show this help message' . PHP_EOL; echo ' -d --directory Directory for searching' . PHP_EOL; echo ' -e .php --extension File Extension to Scan' . PHP_EOL; echo ' -i --ignore Directory of file to igonre' . PHP_EOL; echo ' -k --hide-ok Hide OK aka not infected messages' . PHP_EOL; echo ' -w --hide-whitelist Hide whitelisted messages' . PHP_EOL; echo ' -x --extra-check Adds GoogleBot and htaccess to Scan List' . PHP_EOL; echo ' -l --follow-symlink Follow symlinked directories' . PHP_EOL; echo ' -v --verbose Continue scanning file afater first hit' . PHP_EOL; } } new MalwareScanner(); ?>