Added Comments and Documentation

Added Comments and Documentation
This commit is contained in:
nichogenius
2017-08-19 19:55:04 -06:00
committed by GitHub
parent 015cc6f668
commit b503b8124c

108
scan.php
View File

@@ -44,24 +44,24 @@ class MalwareScanner
); );
private $followSymlink = false; private $followSymlink = false;
//Pattern File Attributes
private $patterns_raw = array(); private $patterns_raw = array();
private $patterns_iraw = array(); private $patterns_iraw = array();
private $patterns_re = array(); private $patterns_re = array();
private $patterns_b64functions = array(); private $patterns_b64functions = array();
private $patterns_b64keywords = array(); private $patterns_b64keywords = array();
//Constructor - Likes to do as little as possible.
public function __construct() public function __construct()
{ {
//Read Run Options //Read Run Options
$this->parseArgs(); $this->parseArgs();
//Load Patterns
$this->initializePatterns();
//Initiate Scan //Initiate Scan
$this->run($this->dir); $this->run($this->dir);
} }
//Allows the -n/--no-color flag to easily remove color characters.
private function disableColor() private function disableColor()
{ {
$this->ANSI_GREEN = ''; $this->ANSI_GREEN = '';
@@ -71,6 +71,8 @@ class MalwareScanner
$this->ANSI_OFF = ''; $this->ANSI_OFF = '';
} }
//Prints the passed 'string' in red text, calls showHelp().
//Exits
private function error($msg) private function error($msg)
{ {
echo $this->ANSI_RED . 'Error: ' . $msg . $this->ANSI_OFF . PHP_EOL; echo $this->ANSI_RED . 'Error: ' . $msg . $this->ANSI_OFF . PHP_EOL;
@@ -79,29 +81,35 @@ class MalwareScanner
exit(-1); exit(-1);
} }
//Handles pattern loading and saving to the class object
private function initializePatterns() private function initializePatterns()
{ {
//Loads either the primary scanning patterns or the base64 patterns depending on -b/--base64 flag
if (!$this->flagBase64) { if (!$this->flagBase64) {
$this->patterns_raw = $this->loadPatterns(dirname(__FILE__) . '/patterns_raw.txt'); $this->patterns_raw = $this->loadPatterns(dirname(__FILE__) . '/patterns_raw.txt');
$this->patterns_iraw = $this->loadPatterns(dirname(__FILE__) . '/patterns_iraw.txt'); $this->patterns_iraw = $this->loadPatterns(dirname(__FILE__) . '/patterns_iraw.txt');
$this->patterns_re = $this->loadPatterns(dirname(__FILE__) . '/patterns_re.txt'); $this->patterns_re = $this->loadPatterns(dirname(__FILE__) . '/patterns_re.txt');
} }
else {
if ($this->flagBase64) {
$this->patterns_b64functions = $this->loadPatterns(dirname(__FILE__). '/php_functions.txt'); $this->patterns_b64functions = $this->loadPatterns(dirname(__FILE__). '/php_functions.txt');
$this->patterns_b64keywrds = $this->loadPatterns(dirname(__FILE__). '/php_keywords.txt'); $this->patterns_b64keywrds = $this->loadPatterns(dirname(__FILE__). '/php_keywords.txt');
} }
//Adds additional checks to patterns_raw
//This may be something to move into a pattern file rather than leave hardcoded.
if ($this->extraCheck) { if ($this->extraCheck) {
array_push($this->patterns_raw, "googleBot", "htaccess"); $this->patterns_raw['googleBot'] = '# ';
$this->patterns_raw['htaccess'] = '# ';
} }
} }
//Check if the md5 checksum exists in the whitelist and returns true if it does.
private function inWhitelist($hash) private function inWhitelist($hash)
{ {
return in_array($hash, $this->whitelist); return in_array($hash, $this->whitelist);
} }
//Check if -i/--ignore flag listed this path to be omitted.
private function isIgnored($pathname) private function isIgnored($pathname)
{ {
foreach ($this->ignore as $pattern) { foreach ($this->ignore as $pattern) {
@@ -113,6 +121,10 @@ class MalwareScanner
return false; return false;
} }
//Loads individual pattern files
//Skips blank linese
//Stores most recent comment with the pattern in the list[] array
//Returns an array of patterns:comments in key:value pairs
private function loadPatterns($file) private function loadPatterns($file)
{ {
$last_comment = ''; $last_comment = '';
@@ -124,6 +136,8 @@ class MalwareScanner
continue; continue;
} }
//Check if first char in pattern is a '#' which indicates a comment and skips. //Check if first char in pattern is a '#' which indicates a comment and skips.
//Stores the comment to be stored with the pattern in the list as key:value pairs.
//The pattern is the key and the comment is the value.
if ($pattern[0] === '#') { if ($pattern[0] === '#') {
$last_comment = $pattern; $last_comment = $pattern;
continue; continue;
@@ -134,6 +148,7 @@ class MalwareScanner
return $list; return $list;
} }
//Loads the whitelist file
private function loadWhitelist() private function loadWhitelist()
{ {
if (!is_file(__DIR__ . '/whitelist.txt')) { if (!is_file(__DIR__ . '/whitelist.txt')) {
@@ -146,6 +161,8 @@ class MalwareScanner
} }
} }
//Handles the getopt() function call, sets attributes according to flags.
//All flag handling stuff should be setup here.
private function parseArgs() private function parseArgs()
{ {
$options = getopt('d:e:i:bmcxlhkwntv', array('directory:', 'extension:', 'ignore:', 'base', 'checksum', 'comment', 'extra-check', 'follow-link', 'help', 'hide-ok', 'hide-whitelist', 'no-color', 'time', 'verbose')); $options = getopt('d:e:i:bmcxlhkwntv', array('directory:', 'extension:', 'ignore:', 'base', 'checksum', 'comment', 'extra-check', 'follow-link', 'help', 'hide-ok', 'hide-whitelist', 'no-color', 'time', 'verbose'));
@@ -173,13 +190,13 @@ class MalwareScanner
} }
//Simple Flag Options //Simple Flag Options
if (isset($options['base64']) || isset($options['b'])){ if (isset($options['base64']) || isset($options['b'])) {
$this->flagBase64 = true; $this->flagBase64 = true;
} }
if (isset($options['checksum']) || isset($options['m'])){ if (isset($options['checksum']) || isset($options['m'])) {
$this->flagChecksum = true; $this->flagChecksum = true;
} }
if (isset($options['comment']) || isset($options['c'])){ if (isset($options['comment']) || isset($options['c'])) {
$this->flagComments = true; $this->flagComments = true;
} }
if (isset($options['extra-check']) || isset($options['x'])) { if (isset($options['extra-check']) || isset($options['x'])) {
@@ -194,13 +211,13 @@ class MalwareScanner
if (isset($options['hide-whitelist']) || isset($options['w'])) { if (isset($options['hide-whitelist']) || isset($options['w'])) {
$this->flagHideWhitelist = true; $this->flagHideWhitelist = true;
} }
if (isset($options['no-color']) || isset($options['n'])){ if (isset($options['no-color']) || isset($options['n'])) {
$this->disableColor(); $this->disableColor();
} }
if (isset($options['time']) || isset($options['t'])){ if (isset($options['time']) || isset($options['t'])) {
$this->flagTime = true; $this->flagTime = true;
} }
if (isset($options['verbose']) || isset($options['v'])){ if (isset($options['verbose']) || isset($options['v'])) {
$this->verbose = true; $this->verbose = true;
} }
} }
@@ -231,6 +248,16 @@ class MalwareScanner
return (bool)preg_match($expr, $path); return (bool)preg_match($expr, $path);
} }
/*
Formats and prints the scan result output line by line.
Depending on specified options, it will print:
-Status code
-Last Modified Time
-MD5 Hash
-File Path
-Pattern Matched
-The last comment to appear in the pattern file before this pattern
*/
private function printPath(&$found, &$path, &$pattern, &$comment, &$hash) private function printPath(&$found, &$path, &$pattern, &$comment, &$hash)
{ {
$output_string = '# '; $output_string = '# ';
@@ -261,7 +288,7 @@ class MalwareScanner
$output_string = $output_string . $this->ANSI_BLUE . $htime . $this->ANSI_OFF . ' '; $output_string = $output_string . $this->ANSI_BLUE . $htime . $this->ANSI_OFF . ' ';
} }
//Include Checksum //Include Checksum/Hash
if ($this->flagChecksum) { if ($this->flagChecksum) {
$output_string = $output_string . $this->ANSI_BLUE . $hash . $this->ANSI_OFF . ' '; $output_string = $output_string . $this->ANSI_BLUE . $hash . $this->ANSI_OFF . ' ';
} }
@@ -286,6 +313,8 @@ class MalwareScanner
echo $output_string; echo $output_string;
} }
//Recursively scales the file system.
//Calls the scan() function for each file found.
private function process($dir) private function process($dir)
{ {
$dh = opendir($dir); $dh = opendir($dir);
@@ -315,6 +344,7 @@ class MalwareScanner
closedir($dh); closedir($dh);
} }
//Prints stats on the run.
private function report($start, $dir) private function report($start, $dir)
{ {
$end = time(); $end = time();
@@ -327,6 +357,9 @@ class MalwareScanner
echo 'Total malware identified: ' . $this->stat['files_infected'] . PHP_EOL; echo 'Total malware identified: ' . $this->stat['files_infected'] . PHP_EOL;
} }
//Validates the input directory
//Calls the load pattern and load whitelist functions
//Calls the process and report functions.
private function run($dir) private function run($dir)
{ {
//Make sure a directory was specified. //Make sure a directory was specified.
@@ -340,16 +373,22 @@ class MalwareScanner
$this->error('Specified path is not a directory: ' . $dir); $this->error('Specified path is not a directory: ' . $dir);
} }
//Load Patterns
$this->initializePatterns();
//Load Whitelist
$this->loadWhitelist();
$start = time(); $start = time();
$this->loadWhitelist();
$this->process($dir . '/'); $this->process($dir . '/');
$this->report($start, $dir . '/'); $this->report($start, $dir . '/');
} }
//Loads target file contents for scanning
//Initiates the multiple scan types by calling the scanLoop function
private function scan($path) private function scan($path)
{ {
$this->stat['files_scanned']++; $this->stat['files_scanned']++;
$fileContent = file_get_contents($path); $fileContent = file_get_contents($path);
$found = false; $found = false;
$hash = ''; $hash = '';
@@ -361,7 +400,7 @@ class MalwareScanner
$this->scanLoop('scanFunc_STRI', $fileContent, $this->patterns_iraw, $path, $found, $hash); $this->scanLoop('scanFunc_STRI', $fileContent, $this->patterns_iraw, $path, $found, $hash);
$this->scanLoop('scanFunc_RE', $fileContent, $this->patterns_re, $path, $found, $hash); $this->scanLoop('scanFunc_RE', $fileContent, $this->patterns_re, $path, $found, $hash);
} }
if ($this->flagBase64) { else {
$this->scanLoop('scanFunc_STR', $fileContent, $this->patterns_b64functions, $path, $found, $hash); $this->scanLoop('scanFunc_STR', $fileContent, $this->patterns_b64functions, $path, $found, $hash);
$this->scanLoop('scanFunc_STR', $fileContent, $this->patterns_b64keywords, $path, $found, $hash); $this->scanLoop('scanFunc_STR', $fileContent, $this->patterns_b64keywords, $path, $found, $hash);
} }
@@ -379,40 +418,52 @@ class MalwareScanner
return true; return true;
} }
//Performs raw string, case sensitive matching.
//Returns true if the raw string exists in the file contents.
private function scanFunc_STR(&$pattern, &$content) private function scanFunc_STR(&$pattern, &$content)
{ {
return (strpos($content, $pattern) !== false); return (strpos($content, $pattern) !== false);
} }
//Performs raw string, case insensitive matching.
//Returns true if the raw string exists in the file contents, ignoring case.
private function scanFunc_STRI(&$pattern, &$content) private function scanFunc_STRI(&$pattern, &$content)
{ {
return (stripos($content, $pattern) !== false); return (stripos($content, $pattern) !== false);
} }
//Performs regular expression matching.
//Returns true if the Regular Expression matches something in the file.
//Patterns will match multiple lines, though you can use ^$ to match the beginning and end of a line.
private function scanFunc_RE(&$pattern, &$content) private function scanFunc_RE(&$pattern, &$content)
{ {
return preg_match('/' . $pattern . '/im', $content); return preg_match('/' . $pattern . '/im', $content);
} }
//First parameter '$scanFunction' is a defined function name passed as a string.
//This function should accept a pattern string and a content string.
//This function will return true if the pattern exists in the content.
//See 'scanFunc_STR', 'scanFunc_STRI', 'scanFUNC_RE' above as examples.
//Loops through all patterns in a file using the passed function name to determine a match.
//Variables passed by reference for performance and modification access.
private function scanLoop($scanFunction, &$fileContent, &$patterns, &$path, &$found, &$hash) private function scanLoop($scanFunction, &$fileContent, &$patterns, &$path, &$found, &$hash)
{ {
if (!$found || $this->verbose) { if (!$found || $this->verbose) {
foreach ($patterns as $pattern => $comment) { foreach ($patterns as $pattern => $comment) {
//Call the function that is named in $scanFunction
//This allows multiple search/match functions to be used without duplicating the loop code.
if ($this->$scanFunction($pattern, $fileContent)) { if ($this->$scanFunction($pattern, $fileContent)) {
$found = true; $found = true;
if ($hash === ''){ if ($hash === ''){$hash = md5($fileContent);}
$hash = md5($fileContent);
}
$this->printPath($found, $path, $pattern, $comment, $hash); $this->printPath($found, $path, $pattern, $comment, $hash);
if (!$this->verbose){ if (!$this->verbose){return;}
return;
}
} }
} }
} }
} }
//Prints out the usage menu options.
private function showHelp() private function showHelp()
{ {
echo 'Usage: scan.php -d <directory>' . PHP_EOL; echo 'Usage: scan.php -d <directory>' . PHP_EOL;
@@ -421,17 +472,18 @@ class MalwareScanner
echo ' -e <file extension> --extension File Extension to Scan' . PHP_EOL; echo ' -e <file extension> --extension File Extension to Scan' . PHP_EOL;
echo ' -i <directory|file> --ignore Directory of file to igonre' . PHP_EOL; echo ' -i <directory|file> --ignore Directory of file to igonre' . PHP_EOL;
echo ' -b --base64 Scan for base64 encoded PHP keywords' . PHP_EOL; echo ' -b --base64 Scan for base64 encoded PHP keywords' . PHP_EOL;
echo ' -m --checksum Display MD5 Checksum of file' . PHP_EOL; echo ' -m --checksum Display MD5 Hash/Checksum of file' . PHP_EOL;
echo ' -c --comment Display comments for matched patterns' . PHP_EOL; echo ' -c --comment Display comments for matched patterns' . PHP_EOL;
echo ' -x --extra-check Adds GoogleBot and htaccess to Scan List' . 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 ' -l --follow-symlink Follow symlinked directories' . PHP_EOL;
echo ' -k --hide-ok Hide OK aka not infected messages' . PHP_EOL; echo ' -k --hide-ok Hide results with \'OK\' status' . PHP_EOL;
echo ' -w --hide-whitelist Hide whitelisted messages' . PHP_EOL; echo ' -w --hide-whitelist Hide results with \'WL\' status' . PHP_EOL;
echo ' -t --time Show time of last file change' . PHP_EOL; echo ' -t --time Show time of last file change' . PHP_EOL;
echo ' -v --verbose Continue scanning file afater first hit' . PHP_EOL; echo ' -v --verbose Continue scanning file after first hit' . PHP_EOL;
} }
} }
//Creates a new MalwareScanner object which does all the work.
new MalwareScanner(); new MalwareScanner();
?> ?>