Skip to content

Commit 022cb24

Browse files
authored
v1.0 - ScanCore refactor. Update code structure, improve config.php.
-Refactoring ScanCore to bring it up to the same level of code quality as the rest of the project. -ScanCore to v1.0, Do away with defs versioning. -Definitions can be versioned by date. -Remove false positive for jquery 3.6 minified from defs. -Pretty sure this is the 20th anniversary for the original PHP-AV codebase, which there is very litte left. -Improve config.php by adding more variables to it. -MemoryLimit, ChunkSize, Debug, Verbose. -Make the file headers more consistent. -Once the quality is up to par we will focus on adding features and capability. -Specifically an auto-updater would be nice. -Then maybe some automation tools for scraping IOCs and formatting them into the definitions file. -Recursion is now disabled by default. -This affects behaviour of scripts that use ScanCore because now you HAVE to specify if you want recursion or scans will fail.
1 parent 214dcfd commit 022cb24

File tree

1 file changed

+282
-32
lines changed

1 file changed

+282
-32
lines changed

scanCore.php

Lines changed: 282 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@
1212
// /
1313
// / FILE INFORMATION ...
1414
// / v1.0.
15-
// / This file contains the configuration data for the ScanCore application.
16-
// / Make sure to fill out the information below 100% accurately.
15+
// / This file contains the core logic of the ScanCore application.
1716
// /
1817
// / HARDWARE REQUIREMENTS ...
1918
// / This application requires at least a Raspberry Pi Model B+ or greater.
@@ -64,40 +63,291 @@
6463
// / <3 Open-Source
6564
// / -----------------------------------------------------------------------------------
6665

66+
// / -----------------------------------------------------------------------------------
67+
// / The following code sets global variables for the session.
68+
function verifySCInstallation() {
69+
// / Set variables.
70+
global $SCVersions, $SCDate, $SCTime, $SCSEP, $SCReportFile, $SCLogfile, $SCConfigFile, $SCRequiredDirs, $SCVersion, $SCVersions, $argv, $SCEOL, $SCMaxLogSize, $SCDebug, $SCVerbose, $DefaultMemoryLimit, $DefaultChunkSize, $DefaultMaxLogSize, $SCReportFileName, $SCConfigVersion, $DefsFile, $DefsFileName, $FileCount;
71+
// / Application related variables.
72+
$SCInstallationVerified = $SCConfigLoaded = $SCReportFile = $SCLogfile = $SCRequiredDirs = FALSE;
73+
$SCEOL = PHP_EOL;
74+
$SCRequiredDirs = array();
75+
$SCSEP = DIRECTORY_SEPARATOR;
76+
$SCConfigFile = 'ScanCore_Config.php';
77+
$SCVersion = 'v1.0';
78+
$SCVersions = $SCConfigVersion;
79+
$rp = realpath(dirname(__FILE__));
80+
$FileCount = 0;
81+
// / Time related variables.
82+
$SCDate = date("m_d_y");
83+
$SCTime = date("F j, Y, g:i a");
84+
// / Initialize an empty array if no arguments are set.
85+
if (!isset($argv)) $argv = array();
86+
// / Load the configuration file (ScanCore_Config.php).
87+
if (file_exists($rp.$SCSEP.$SCConfigFile)) $SCConfigLoaded = require_once ($rp.$SCSEP.$SCConfigFile);
88+
// / Check to make sure the configuration file was loaded & the configuration version is compatible with the core.
89+
if (isset($ScanLoc) && isset($DefsFile) && isset($SCConfigVersion) && $SCConfigVersion === $SCVersion && $SCConfigLoaded) {
90+
// / Configuration related variables.
91+
$SCInstallationVerified = TRUE;
92+
$SCReportFile = $ReportDir.$SCSEP.$SCReportFileName;
93+
$SCLogfile = $ReportDir.$SCLogFileName;
94+
$SCRequiredDirs = array($ReportDir);
95+
$SCMaxLogSize = $DefaultMaxLogSize;
96+
$SCDebug = $Debug;
97+
$SCVerbose = $Verbose;
98+
$SCMemoryLimit = $DefaultMemoryLimit;
99+
$SCChunkSize = $DefaultChunkSize; }
100+
// / Manually clean up sensitive memory. Helps to keep track of variable assignments.
101+
$rp = NULL;
102+
unset($rp);
103+
return array($SCInstallationVerified, $SCConfigLoaded); }
104+
// / -----------------------------------------------------------------------------------
105+
106+
// / -----------------------------------------------------------------------------------
107+
// / A function to create required directories when they do not already exist.
108+
function createDirs($SCRequiredDirs) {
109+
// / Set variables.
110+
global $SCTime;
111+
$SCRequiredDirsExist = TRUE;
112+
foreach ($SCRequiredDirs as $reqdDir) {
113+
if (!file_exists($reqdDir)) mkdir($reqdDir);
114+
if (!file_exists($reqdDir)) $SCRequiredDirsExist = FALSE; }
115+
// / Manually clean up sensitive memory. Helps to keep track of variable assignments.
116+
$reqdDir = NULL;
117+
unset($reqdDir);
118+
return array($SCRequiredDirsExist); }
119+
// / -----------------------------------------------------------------------------------
120+
121+
// / -----------------------------------------------------------------------------------
122+
// / A function to add an entry to the logs.
123+
function addLogEntry($entry, $error, $errorNumber) {
124+
// / Set variables.
125+
global $SCReportFile, $SCTime, $SCEOL;
126+
if (!is_numeric($errorNumber)) $errorNumber = 0;
127+
if ($error === TRUE) $preText = 'ERROR!!! ScanCore-'.$errorNumber.' on '.$SCTime.', ';
128+
else $preText = $SCTime.', ';
129+
$SCLogCreated = file_put_contents($SCReportFile, $preText.$entry.$SCEOL, FILE_APPEND);
130+
// / Manually clean up sensitive memory. Helps to keep track of variable assignments.
131+
$preText = $error = $entry = $errorNumber = NULL;
132+
unset($preText, $error, $entry, $errorNumber);
133+
return array($SCLogCreated); }
134+
// / -----------------------------------------------------------------------------------
135+
136+
// / -----------------------------------------------------------------------------------
137+
// / A function to handle important messages to the console & log file.
138+
function processOutput($txt, $error, $errorNumber, $requiredLog, $requiredConsole, $fatal) {
139+
global $SCEOL, $SCDebug, $SCVerbose;
140+
$OutputProcessed = FALSE;
141+
// / Verify that all inputs are of the correct type.
142+
if (!is_string($txt)) $txt = '';
143+
if (!is_bool($error)) $error = FALSE;
144+
if (!is_int($errorNumber)) $errorNumber = 0;
145+
if (!is_bool($requiredLog)) $requiredLog = FALSE;
146+
if (!is_bool($requiredConsole)) $requiredConsole = FALSE;
147+
// / Log the provided text if $SCDebug variable (-d switch) is set.
148+
if ($SCDebug or $requiredLog) list ($OutputProcessed) = addLogEntry($txt, $error, $errorNumber);
149+
// / Output the summary text to the terminal if the $SCVerbose (-v switch) variable is set.
150+
if ($SCVerbose or $requiredConsole) echo $txt.$SCEOL;
151+
// / Manually clean up sensitive memory. Helps to keep track of variable assignments.
152+
$txt = $error = $errorNumber = $requiredLog = $requiredConsole = NULL;
153+
unset($txt, $error, $errorNumber, $requiredLog, $requiredConsole);
154+
// / Stop execution as needed.
155+
if ($fatal) die();
156+
return array($OutputProcessed); }
157+
// / -----------------------------------------------------------------------------------
158+
159+
// / -----------------------------------------------------------------------------------
160+
// / A function to parse supplied command-line arguments.
161+
function parseArgs($argv) {
162+
// / Set variables.
163+
// / Most of these should already be set to the values contained in the configuration file.
164+
global $SCArgsParsed, $SCReportFile, $SCLogfile, $SCMaxLogSize, $SCDebug, $SCVerbose, $SCEOL, $SCChunkSize, $SCMemoryLimit, $DefaultMemoryLimit, $DefaultChunkSize;
165+
$SCRecursion = FALSE;
166+
$SCArgsParsed = $SCPathToScan = FALSE;
167+
foreach ($argv as $key => $arg) {
168+
$arg = htmlentities(str_replace(str_split('~#[](){};:$!#^&%@>*<"\''), '', $arg));
169+
if ($arg == '-memorylimit' or $arg == '-m') $SCMemoryLimit = $argv[$key + 1];
170+
if ($arg == '-chunksize' or $arg == '-c') $SCChunkSize = $argv[$key + 1];
171+
if ($arg == '-debug' or $arg == '-d') $SCDebug = TRUE;
172+
if ($arg == '-verbose' or $arg == '-v') $SCVerbose = TRUE;
173+
if ($arg == '-recursion' or $arg == '-r') $SCRecursion = TRUE;
174+
if ($arg == '-norecursion' or $arg == '-nr') $SCRecursion = FALSE;
175+
if ($arg == '-reportfile' or $arg == '-rf') $SCReportFile = $argv[$key + 1];
176+
if ($arg == '-logfile' or $arg == '-lf') $SCLogfile = $argv[$key + 1];
177+
if ($arg == '-maxlogsize' or $arg == '-ml') $SCMaxLogSize = $argv[$key + 1]; }
178+
// / Detect if a file path to scan was specified.
179+
if (!isset($argv[1])) processOutput('There were no arguments set!', TRUE, 100, TRUE, TRUE, FALSE);
180+
else $SCPathToScan = $argv[1];
181+
// / Detect if the MemoryLimit and ChunkSize variables are valid.
182+
if (!is_numeric($SCMemoryLimit) or !is_numeric($SCChunkSize)) {
183+
processOutput('Either the chunkSize argument or the memoryLimit argument is invalid. Attempting to use default values.', TRUE, 200, TRUE, TRUE, FALSE);
184+
$SCMemoryLimit = $DefaultMemoryLimit;
185+
$SCChunkSize = $DefaultChunkSize; }
186+
if (!file_exists($argv[1])) processOutput('The specified file was not found! The first argument must be a valid file or directory path!', TRUE, 300, TRUE, TRUE, FALSE);
187+
else {
188+
$SCArgsParsed = TRUE;
189+
processOutput('Starting ScanCore!', FALSE, 0, TRUE, FALSE, FALSE); }
190+
// / Manually clean up sensitive memory. Helps to keep track of variable assignments.
191+
$key = $arg = NULL;
192+
unset($key, $arg);
193+
return array($SCArgsParsed, $SCPathToScan, $SCMemoryLimit, $SCChunkSize, $SCDebug, $SCVerbose, $SCRecursion, $SCReportFile, $SCLogfile, $SCMaxLogSize); }
194+
// / -----------------------------------------------------------------------------------
195+
196+
// / -----------------------------------------------------------------------------------
197+
// Hunts files/folders recursively for scannable items.
198+
function file_scan($folder, $Defs, $DefsFile, $DefData, $SCDebug, $SCVerbose, $SCMemoryLimit, $SCChunkSize, $SCRecursion) {
199+
// / Set variables.
200+
global $SCSEP, $SCEOL, $FileCount;
201+
$ScanComplete = FALSE;
202+
$DirCount = 1;
203+
$Infected = 0;
204+
if (is_dir($folder)) {
205+
$files = scandir($folder);
206+
foreach ($files as $file) {
207+
if ($file === '' or $file === '.' or $file === '..') continue;
208+
$entry = str_replace($SCSEP.$SCSEP, $SCSEP, $folder.$SCSEP.$file);
209+
if (!is_dir($entry)) list($checkComplete, $Infected) = virus_check($entry, $Defs, $DefsFile, $DefData, $SCDebug, $SCVerbose, $SCMemoryLimit, $SCChunkSize);
210+
else if (is_dir($entry) && $SCRecursion) {
211+
processOutput('Scanning folder "'.$entry.'" ... ', FALSE, 0, TRUE, TRUE, FALSE);
212+
$DirCount++;
213+
list ($scanComplete, $DirCount, $FileCount, $Infected) = file_scan($entry, $Defs, $DefsFile, $DefData, $SCDebug, $SCVerbose, $SCMemoryLimit, $SCChunkSize, $SCRecursion);
214+
$entry = ''; } } }
215+
if (!is_dir($folder) && $folder !== '.' && $folder !== '..') {
216+
$FileCount++;
217+
list($checkComplete, $Infected) = virus_check($folder, $Defs, $DefsFile, $DefData, $SCDebug, $SCVerbose, $SCMemoryLimit, $SCChunkSize); }
218+
$ScanComplete = TRUE;
219+
// / Manually clean up sensitive memory. Helps to keep track of variable assignments.
220+
$files = $file = $entry = $folder = NULL;
221+
unset($files, $file, $entry, $folder);
222+
return array($ScanComplete, $DirCount, $FileCount, $Infected); }
223+
// / -----------------------------------------------------------------------------------
67224

225+
// / -----------------------------------------------------------------------------------
226+
// Reads tab-delimited defs file. Also hashes the file to avoid self-detection.
227+
function load_defs($DefsFile) {
228+
// / Set variables.
229+
global $SCEOL, $SCDebug, $SCVerbose;
230+
$SCDefsLoaded = $Defs = $DefData = FALSE;
231+
if (!file_exists($DefsFile)) processOutput('Could not load the virus definition file located at "'.$DefsFile.'"! File either does not exist or cannot be read!', TRUE, 600, TRUE, TRUE, FALSE);
232+
else {
233+
$Defs = file($DefsFile);
234+
$DefData = hash_file('sha256', $DefsFile);
235+
$counter = 0;
236+
$counttop = sizeof($Defs);
237+
while ($counter < $counttop) {
238+
$Defs[$counter] = explode(' ', $Defs[$counter]);
239+
$counter++; }
240+
processOutput('Loaded '.sizeof($Defs).' virus definitions.', FALSE, 0, FALSE, FALSE, FALSE);
241+
$SCDefsLoaded = TRUE; }
242+
// / Manually clean up sensitive memory. Helps to keep track of variable assignments.
243+
$counter = $counttop = NULL;
244+
unset($counter, $counttop);
245+
return array($SCDefsLoaded, $Defs, $DefData); }
246+
// / -----------------------------------------------------------------------------------
68247

69248
// / -----------------------------------------------------------------------------------
70-
// / General Information ...
71-
// / Number of bytes to store in each logfile before splitting to a new one.
72-
$DefaultMaxLogSize = '100000000000000000000';
73-
// / Enable "debug" mode (more logging).
74-
$Debug = FALSE;
75-
// / Enable "verbose" mode (more console).
76-
$Verbose = FALSE;
77-
// / The maximum number of bytes of memory to allocate to file scan operations.
78-
$DefaultMemoryLimit = 4000000;
79-
// / When scanning large files the file will be scanned this many bytes at a time.
80-
$DefaultChunkSize = 1000000;
81-
// / The version of this file, used for internal version integrity checks.
82-
$SCConfigVersion = 'v1.0';
249+
// Hashes and checks files/folders for viruses against static virus defs.
250+
function virus_check($file, $Defs, $DefsFile, $DefData, $SCDebug, $SCVerbose, $SCMemoryLimit, $SCChunkSize) {
251+
// / Set variables.
252+
global $Infected, $DefsFileName, $SCEOL;
253+
$CheckComplete = FALSE;
254+
if ($file !== $DefsFileName) {
255+
if (file_exists($file)) {
256+
processOutput('Scanning file ... ', FALSE, 0, TRUE, TRUE, FALSE);
257+
$filesize = filesize($file);
258+
$data1 = hash_file('md5', $file);
259+
$data2 = hash_file('sha256', $file);
260+
$data3 = hash_file('sha1', $file);
261+
// / Scan files larger than the memory limit by breaking them into chunks.
262+
if ($filesize >= $SCMemoryLimit && file_exists($file)) {
263+
processOutput('Chunking file ... ', FALSE, 0, FALSE, FALSE, FALSE);
264+
$handle = @fopen($file, "r");
265+
if ($handle) {
266+
while (($buffer = fgets($handle, $SCChunkSize)) !== FALSE) {
267+
$data = $buffer;
268+
processOutput('Scanning chunk ... ', FALSE, 0, FALSE, FALSE, FALSE);
269+
foreach ($Defs as $virus) {
270+
$virus = explode("\t", $virus[0]);
271+
if (isset($virus[1]) && !is_null($virus[1]) && $virus[1] !== '' && $virus[1] !== ' ') {
272+
if (strpos(strtolower($data), strtolower($virus[1])) !== FALSE or strpos(strtolower($file), strtolower($virus[1])) !== FALSE) {
273+
// File matches virus defs.
274+
processOutput('Infected: '.$file.' ('.$virus[0].', Data Match: '.$virus[1].')', FALSE, 0, TRUE, TRUE, FALSE);
275+
$Infected++; } } } }
276+
if (!feof($handle)) {
277+
processOutput('Unable to open "'.$file.'"!', TRUE, 800, TRUE, TRUE, FALSE);
278+
fclose($handle); }
279+
if (isset($virus[2]) && !is_null($virus[2]) && $virus[2] !== '' && $virus[2] !== ' ') {
280+
if (strpos(strtolower($data1), strtolower($virus[2])) !== FALSE) {
281+
// File matches virus defs.
282+
processOutput('Infected: '.$file.' ('.$virus[0].', MD5 Hash Match: '.$virus[2].')', FALSE, 0, TRUE, TRUE, FALSE);
283+
$Infected++; } }
284+
if (isset($virus[3]) && !is_null($virus[3]) && $virus[3] !== '' && $virus[3] !== ' ') {
285+
if (strpos(strtolower($data2), strtolower($virus[3])) !== FALSE) {
286+
// File matches virus defs.
287+
processOutput('Infected: '.$file.' ('.$virus[0].', SHA256 Hash Match: '.$virus[3].')', FALSE, 0, TRUE, TRUE, FALSE);
288+
$Infected++; } }
289+
if (isset($virus[4]) && !is_null($virus[4]) && $virus[4] !== '' && $virus[4] !== ' ') {
290+
if (strpos(strtolower($data3), strtolower($virus[4])) !== FALSE) {
291+
// File matches virus defs.
292+
processOutput('Infected: '.$file.' ('.$virus[0].', SHA1 Hash Match: '.$virus[4].')', FALSE, 0, TRUE, TRUE, FALSE);
293+
$Infected++; } } } }
294+
// / Scan files smaller than the memory limit by fitting the entire file into memory.
295+
if ($filesize < $SCMemoryLimit && file_exists($file)) {
296+
$data = file_get_contents($file); }
297+
if ($DefData !== $data2) {
298+
foreach ($Defs as $virus) {
299+
$virus = explode("\t", $virus[0]);
300+
if (isset($virus[1]) && !is_null($virus[1]) && $virus[1] !== '' && $virus[1] !== ' ') {
301+
if (strpos(strtolower($data), strtolower($virus[1])) !== FALSE or strpos(strtolower($file), strtolower($virus[1])) !== FALSE) {
302+
// File matches virus defs.
303+
processOutput('Infected: '.$file.' ('.$virus[0].', Data Match: '.$virus[1].')', FALSE, 0, TRUE, TRUE, FALSE);
304+
$Infected++; } }
305+
if (isset($virus[2]) && !is_null($virus[2]) && $virus[2] !== '' && $virus[2] !== ' ') {
306+
if (strpos(strtolower($data1), strtolower($virus[2])) !== FALSE) {
307+
// File matches virus defs.
308+
processOutput('Infected: '.$file.' ('.$virus[0].', MD5 Hash Match: '.$virus[2].')', FALSE, 0, TRUE, TRUE, FALSE);
309+
$Infected++; } }
310+
if (isset($virus[3]) && !is_null($virus[3]) && $virus[3] !== '' && $virus[3] !== ' ') {
311+
if (strpos(strtolower($data2), strtolower($virus[3])) !== FALSE) {
312+
// File matches virus defs.
313+
processOutput('Infected: '.$file.' ('.$virus[0].', SHA256 Hash Match: '.$virus[3].')', FALSE, 0, TRUE, TRUE, FALSE);
314+
$Infected++; } }
315+
if (isset($virus[4]) && !is_null($virus[4]) && $virus[4] !== '' && $virus[4] !== ' ') {
316+
if (strpos(strtolower($data3), strtolower($virus[4])) !== FALSE) {
317+
// File matches virus defs.
318+
processOutput('Infected: '.$file.' ('.$virus[0].', SHA1 Hash Match: '.$virus[4].')', FALSE, 0, TRUE, TRUE, FALSE);
319+
$Infected++; } } } } } }
320+
$CheckComplete = TRUE;
321+
// / Manually clean up sensitive memory. Helps to keep track of variable assignments.
322+
$file = $filesize = $data = $buffer = $handle = $virus = $data1 = $data2 = $data3 = NULL;
323+
unset($file, $filesize, $data, $buffer, $handle, $virus, $data1, $data2, $data3);
324+
return array($CheckComplete, $Infected); }
83325
// / -----------------------------------------------------------------------------------
84326

85327
// / -----------------------------------------------------------------------------------
86-
// / Directory locations ...
87-
// / The default location to scan if run with no input scan path argument.
88-
$ScanLoc = '';
89-
// / The absolute path where log files are stored.
90-
$LogsDir = 'Logs';
91-
// / The absolute path where report files are stored.
92-
$ReportDir = 'Logs';
93-
// / The filename for the ScanCore report file.
94-
$SCReportFileName = 'ScanCore_Report.txt';
95-
// / The filename for the ScanCore log file.
96-
$SCLogFileName = 'ScanCore_Latest-Log.txt';
97-
// / The filename for the ScanCore virus definition file.
98-
$DefsFileName = 'ScanCore_Virus.def';
99-
// / The filename for the ScanCore virus definition file.
100-
$DefsDir = realpath(dirname(__FILE__)).DIRECTORY_SEPARATOR;
101-
// / The absolute path where virus definitions are found.
102-
$DefsFile = $DefsDir.$DefsFileName;
328+
// / The main logic of the program.
329+
330+
// / Verify the installation.
331+
list($SCInstallationVerified, $SCConfigLoaded) = verifySCInstallation();
332+
if (!$SCInstallationVerified or !$SCConfigLoaded) die('ERROR!!! ScanCore-003, Cannot verify the ScanCore installation!'.PHP_EOL);
333+
334+
// / Create required directories if they don't already exist.
335+
list($SCRequiredDirsExist) = createDirs($SCRequiredDirs);
336+
if (!$SCInstallationVerified or !$SCConfigLoaded) die('ERROR!!! ScanCore-004, Cannot create required directories!'.PHP_EOL);
337+
338+
// / Process supplied command-line arguments.
339+
// / Example: C:\Path-To-PHP-Binary.exe C:\Path-To-ScanCore.php C:\Path-To-Scan\ -m [integer] -c [integer] -v -d
340+
list($SCArgsParsed, $SCPathToScan, $SCMemoryLimit, $SCChunkSize, $SCDebug, $SCVerbose, $SCRecursion, $SCReportFile, $SCLogfile, $SCMaxLogSize) = parseArgs($argv);
341+
if (!$SCArgsParsed) processOutput('Cannot verify supplied arguments!', TRUE, 005, TRUE, TRUE, TRUE);
342+
else processOutput('Verified supplied arguments.', FALSE, 0, FALSE, FALSE, FALSE);
343+
344+
// / Load the virus definitions into memory and calculate it's hash (to avoid detecting our own definitions as an infection).
345+
list($SCDefsLoaded, $Defs, $DefData) = load_defs($DefsFile);
346+
if (!$SCDefsLoaded) processOutput('Cannot load virus definitions!', TRUE, 006, TRUE, TRUE, TRUE);
347+
else processOutput('Loaded virus definitions.', FALSE, 0, FALSE, FALSE, FALSE);
348+
349+
// / Start the scanner!
350+
list($ScanComplete, $DirCount, $FileCount, $Infected) = file_scan($SCPathToScan, $Defs, $DefsFile, $DefData, $SCDebug, $SCVerbose, $SCMemoryLimit, $SCChunkSize, $SCRecursion);
351+
if (!$ScanComplete) processOutput('Could not complete requested scan!', TRUE, 007, TRUE, TRUE, TRUE);
352+
else processOutput('Scanned '.$FileCount.' files in '.$DirCount.' folders and found '.$Infected.' potentially infected items.', FALSE, 0, TRUE, FALSE, TRUE);
103353
// / -----------------------------------------------------------------------------------

0 commit comments

Comments
 (0)