added Malcolm

This commit is contained in:
2021-08-06 10:35:01 +02:00
parent f043730066
commit 70f1922e80
751 changed files with 195277 additions and 0 deletions

View File

@@ -0,0 +1,285 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright (c) 2021 Battelle Energy Alliance, LLC. All rights reserved.
###################################################################################################
# Monitor a directory for files extracted by zeek for processing
#
# Run the script with --help for options
###################################################################################################
import argparse
import datetime
import json
import os
import pathlib
import re
import shutil
import signal
import sys
import time
import zmq
from collections import defaultdict
from contextlib import nullcontext
from datetime import datetime
from zeek_carve_utils import *
###################################################################################################
debug = False
verboseDebug = False
debugToggled = False
pdbFlagged = False
args = None
scriptName = os.path.basename(__file__)
scriptPath = os.path.dirname(os.path.realpath(__file__))
origPath = os.getcwd()
shuttingDown = False
###################################################################################################
# handle sigint/sigterm and set a global shutdown variable
def shutdown_handler(signum, frame):
global shuttingDown
shuttingDown = True
###################################################################################################
# handle sigusr1 for a pdb breakpoint
def pdb_handler(sig, frame):
global pdbFlagged
pdbFlagged = True
###################################################################################################
# handle sigusr2 for toggling debug
def debug_toggle_handler(signum, frame):
global debug
global debugToggled
debug = not debug
debugToggled = True
###################################################################################################
#
def same_file_or_dir(path1, path2):
try:
return os.path.samefile(path1, path2)
except:
return False
###################################################################################################
# main
def main():
global args
global debug
global verboseDebug
global debugToggled
global pdbFlagged
global shuttingDown
parser = argparse.ArgumentParser(description=scriptName, add_help=False, usage='{} <arguments>'.format(scriptName))
parser.add_argument('-v', '--verbose', dest='debug', help="Verbose output", metavar='true|false', type=str2bool, nargs='?', const=True, default=False, required=False)
parser.add_argument('--extra-verbose', dest='verboseDebug', help="Super verbose output", metavar='true|false', type=str2bool, nargs='?', const=True, default=False, required=False)
parser.add_argument('--start-sleep', dest='startSleepSec', help="Sleep for this many seconds before starting", metavar='<seconds>', type=int, default=0, required=False)
parser.add_argument('--preserve', dest='preserveMode', help=f"File preservation mode (default: {PRESERVE_QUARANTINED})", metavar=f'[{PRESERVE_QUARANTINED}|{PRESERVE_ALL}|{PRESERVE_NONE}]', type=str, default=PRESERVE_QUARANTINED, required=False)
parser.add_argument('--zeek-log', dest='broSigLogSpec', help="Filespec to write Zeek signature log", metavar='<filespec>', type=str, required=False)
requiredNamed = parser.add_argument_group('required arguments')
requiredNamed.add_argument('-d', '--directory', dest='baseDir', help='Directory being monitored', metavar='<directory>', type=str, required=True)
try:
parser.error = parser.exit
args = parser.parse_args()
except SystemExit:
parser.print_help()
exit(2)
verboseDebug = args.verboseDebug
debug = args.debug or verboseDebug
if debug:
eprint(os.path.join(scriptPath, scriptName))
eprint("{} arguments: {}".format(scriptName, sys.argv[1:]))
eprint("{} arguments: {}".format(scriptName, args))
else:
sys.tracebacklimit = 0
# determine what to do with scanned files (preserve only "hits", preserve all, preserve none)
args.preserveMode = args.preserveMode.lower()
if (len(args.preserveMode) == 0):
args.preserveMode = PRESERVE_QUARANTINED
elif (args.preserveMode not in [PRESERVE_QUARANTINED, PRESERVE_ALL, PRESERVE_NONE]):
eprint(f'Invalid file preservation mode "{args.preserveMode}"')
sys.exit(1)
# handle sigint and sigterm for graceful shutdown
signal.signal(signal.SIGINT, shutdown_handler)
signal.signal(signal.SIGTERM, shutdown_handler)
signal.signal(signal.SIGUSR1, pdb_handler)
signal.signal(signal.SIGUSR2, debug_toggle_handler)
# sleep for a bit if requested
sleepCount = 0
while (not shuttingDown) and (sleepCount < args.startSleepSec):
time.sleep(1)
sleepCount += 1
# where will the fake zeek log file be written to?
broSigLogSpec = args.broSigLogSpec
if broSigLogSpec is not None:
if os.path.isdir(broSigLogSpec):
# _carved tag will be recognized by 11_zeek_logs.conf in logstash
broSigLogSpec = os.path.join(broSigLogSpec, "signatures(_carved).log")
else:
# make sure path to write to zeek signatures log file exists before we start writing
pathlib.Path(os.path.dirname(os.path.realpath(broSigLogSpec))).mkdir(parents=True, exist_ok=True)
# create quarantine/preserved directories for preserved files (see preserveMode)
quarantineDir = os.path.join(args.baseDir, PRESERVE_QUARANTINED_DIR_NAME)
preserveDir = os.path.join(args.baseDir, PRESERVE_PRESERVED_DIR_NAME)
if (args.preserveMode != PRESERVE_NONE) and (not os.path.isdir(quarantineDir)):
if debug: eprint(f'Creating "{quarantineDir}" for quarantined files')
pathlib.Path(quarantineDir).mkdir(parents=False, exist_ok=True)
if (args.preserveMode == PRESERVE_ALL) and (not os.path.isdir(preserveDir)):
if debug: eprint(f'Creating "{preserveDir}" for other preserved files')
pathlib.Path(preserveDir).mkdir(parents=False, exist_ok=True)
# initialize ZeroMQ context and socket(s) to send messages to
context = zmq.Context()
# Socket to receive scan results on
scanned_files_socket = context.socket(zmq.PULL)
scanned_files_socket.bind(f"tcp://*:{SINK_PORT}")
scanned_files_socket.SNDTIMEO = 5000
scanned_files_socket.RCVTIMEO = 5000
if debug: eprint(f"{scriptName}: bound sink port {SINK_PORT}")
scanners = set()
fileScanCounts = defaultdict(AtomicInt)
fileScanHits = defaultdict(AtomicInt)
# open and write out header for our super legit zeek signature.log file
with open(broSigLogSpec, 'w+', 1) if (broSigLogSpec is not None) else nullcontext() as broSigFile:
if (broSigFile is not None):
print('#separator \\x09', file=broSigFile, end='\n')
print('#set_separator\t,', file=broSigFile, end='\n')
print('#empty_field\t(empty)', file=broSigFile, end='\n')
print('#unset_field\t-', file=broSigFile, end='\n')
print('#path\tsignature', file=broSigFile, end='\n')
print(f'#open\t{datetime.now().strftime("%Y-%m-%d-%H-%M-%S")}', file=broSigFile, end='\n')
print(re.sub(r"\b((orig|resp)_[hp])\b", r"id.\1",
f"#fields\t{BroSignatureLine.signature_format_line()}".replace('{', '').replace('}', '')),
file=broSigFile, end='\n')
print(f'#types\t{BroSignatureLine.signature_types_line()}', file=broSigFile, end='\n')
while (not shuttingDown):
if pdbFlagged:
pdbFlagged = False
breakpoint()
triggered = False
try:
scanResult = json.loads(scanned_files_socket.recv_string())
if debug: eprint(f"{scriptName}:\t📨\t{scanResult}")
except zmq.Again as timeout:
scanResult = None
if verboseDebug: eprint(f"{scriptName}:\t🕑\t(recv)")
if isinstance(scanResult, dict):
# register/deregister scanners
if (FILE_SCAN_RESULT_SCANNER in scanResult):
scanner = scanResult[FILE_SCAN_RESULT_SCANNER].lower()
if scanner.startswith('-'):
if debug: eprint(f"{scriptName}:\t🙃\t{scanner[1:]}")
try:
scanners.remove(scanner[1:])
except KeyError:
pass
else:
if debug and (scanner not in scanners): eprint(f"{scriptName}:\t🇷\t{scanner}")
scanners.add(scanner)
# process scan results
if all (k in scanResult for k in (FILE_SCAN_RESULT_SCANNER,
FILE_SCAN_RESULT_FILE,
FILE_SCAN_RESULT_ENGINES,
FILE_SCAN_RESULT_HITS,
FILE_SCAN_RESULT_MESSAGE,
FILE_SCAN_RESULT_DESCRIPTION)):
triggered = (scanResult[FILE_SCAN_RESULT_HITS] > 0)
fileName = scanResult[FILE_SCAN_RESULT_FILE]
fileNameBase = os.path.basename(fileName)
# we won't delete or move/quarantine a file until fileScanCount < len(scanners)
fileScanCount = fileScanCounts[fileNameBase].increment()
if triggered:
# this file had a "hit" in one of the virus engines, log it!
fileScanHitCount = fileScanHits[fileNameBase].increment()
# format the line as it should appear in the signatures log file
fileSpecFields = extracted_filespec_to_fields(fileName)
broLine = BroSignatureLine(ts=f"{fileSpecFields.time}",
uid=fileSpecFields.uid if fileSpecFields.uid is not None else '-',
note=ZEEK_SIGNATURE_NOTICE,
signature_id=scanResult[FILE_SCAN_RESULT_MESSAGE],
event_message=scanResult[FILE_SCAN_RESULT_DESCRIPTION],
sub_message=fileSpecFields.fid if fileSpecFields.fid is not None else os.path.basename(fileName),
signature_count=scanResult[FILE_SCAN_RESULT_HITS],
host_count=scanResult[FILE_SCAN_RESULT_ENGINES])
broLineStr = str(broLine)
# write broLineStr event line out to the signatures log file or to stdout
if (broSigFile is not None):
print(broLineStr, file=broSigFile, end='\n', flush=True)
else:
print(broLineStr, file=broSigFile, flush=True)
else:
fileScanHitCount = fileScanHits[fileNameBase].value()
# finally, what to do with the file itself
if os.path.isfile(fileName):
# once all of the scanners have had their turn...
if (fileScanCount >= len(scanners)):
fileScanCounts.pop(fileNameBase, None)
fileScanHits.pop(fileNameBase, None)
if (fileScanHitCount > 0) and (args.preserveMode != PRESERVE_NONE):
# move triggering file to quarantine
if not same_file_or_dir(fileName, os.path.join(quarantineDir, fileNameBase)): # unless it's somehow already there
try:
shutil.move(fileName, quarantineDir)
if debug: eprint(f"{scriptName}:\t\t{fileName} ({fileScanCount}/{len(scanners)})")
except Exception as e:
eprint(f"{scriptName}:\t\t🚫\t{fileName} move exception: {e}")
# hm move failed, delete it i guess?
os.remove(fileName)
else:
if not same_file_or_dir(quarantineDir, os.path.dirname(fileName)): # don't move or delete if it's somehow already quarantined
if (args.preserveMode == PRESERVE_ALL):
# move non-triggering file to preserved directory
try:
shutil.move(fileName, preserveDir)
if verboseDebug: eprint(f"{scriptName}:\t\t{fileName} ({fileScanCount}/{len(scanners)})")
except Exception as e:
eprint(f"{scriptName}:\t\t🚫\t{fileName} move exception: {e}")
# hm move failed, delete it i guess?
os.remove(fileName)
else:
# delete the file
os.remove(fileName)
if verboseDebug: eprint(f"{scriptName}:\t🚫\t{fileName} ({fileScanCount}/{len(scanners)})")
# graceful shutdown
if debug:
eprint(f"{scriptName}: shutting down...")
if __name__ == '__main__':
main()