├── README.md ├── us-19-Bernal-Detecting-Malicious-Files-With-YARA-Rules-As-They-Traverse-the-Network-wp.pdf ├── yaraAlert.conf └── yaraZeekAlert.py /README.md: -------------------------------------------------------------------------------- 1 | # yaraZeekAlert 2 | This script scans the files extracted by Zeek with YARA rules located on the rules folder on a Linux based Zeek sensor, if there is a match it sends email alerts to the email address specified in the mailTo parameter on yaraAlert.conf file. The alert includes network context of the file transfer and attaches the suspicious file if it is less than 10 MB. Alerted files are copied locally to the alerted files folder. 3 | -------------------------------------------------------------------------------- /us-19-Bernal-Detecting-Malicious-Files-With-YARA-Rules-As-They-Traverse-the-Network-wp.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SCILabsMX/yaraZeekAlert/380964f626ae4631939a17bc1a208ca785e7ff9e/us-19-Bernal-Detecting-Malicious-Files-With-YARA-Rules-As-They-Traverse-the-Network-wp.pdf -------------------------------------------------------------------------------- /yaraAlert.conf: -------------------------------------------------------------------------------- 1 | mailUsername=DOMAIN\username 2 | mailPassword=password 3 | mailServer=mail server IP address 4 | mailPort=25 5 | mailDisplayFrom=bro@domain.com 6 | mailTo=securityteam@domain.com 7 | -------------------------------------------------------------------------------- /yaraZeekAlert.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # Name: yaraZeekAlert.py 3 | # Author: David Bernal Michelena - SCILabs, 2019 4 | # License: CREATIVE COMMONS LICENSE BY-NC https://creativecommons.org/licenses/by-nc/4.0/ 5 | 6 | # Description: 7 | # This script scans the files extracted by Zeek with YARA rules located on the rules folder on a Linux based Zeek sensor, if there is a match it sends email alerts to the email address specified in the mailTo parameter on yaraAlert.conf file. The alert includes network context of the file transfer and attaches the suspicious file if it is less than 10 MB. Alerted files are copied locally to the alerted files folder. 8 | 9 | # A Sample yaraAlert.conf configuration file is provided below, this file should be in the same folder than this script. 10 | # mailUsername=DOMAIN\username2 11 | # mailPassword=password 12 | # mailServer=mail server IP address 13 | # mailPort=25 14 | # mailDisplayFrom=bro@domain.com 15 | # mailTo=securityteam@domain.com 16 | 17 | import subprocess 18 | import os 19 | import time 20 | import sys 21 | import hashlib 22 | import smtplib 23 | import glob 24 | from email.MIMEMultipart import MIMEMultipart 25 | from email.MIMEText import MIMEText 26 | from email.MIMEBase import MIMEBase 27 | from email import Encoders 28 | 29 | # Change the following variables based on your own configuration 30 | alertedFilesFolder = "/home/bro/YARA/alertedFiles" 31 | extractedFilePath = "/home/bro/extracted" 32 | yaraRulesPath = "/home/bro/YARA/rules" 33 | sevenZipCommand = "/bin/7za" 34 | yaraAlertConfigFile = "/home/bro/YARA/yaraAlert.conf" 35 | 36 | if not os.path.isfile(yaraAlertConfigFile): 37 | print "file does not exist: " + yaraAlertConfigFile 38 | sys.exit(1) 39 | 40 | with open(yaraAlertConfigFile,"r") as f: 41 | for line in f: 42 | lineLst = line.strip("\n").split("=") 43 | if lineLst[0] in "mailUsername": 44 | broMailUsername = lineLst[1] 45 | elif lineLst[0] in "mailPassword": 46 | broMailPassword = lineLst[1] 47 | elif lineLst[0] in "mailServer": 48 | mailServer = lineLst[1] 49 | elif lineLst[0] in "mailPort": 50 | mailPort = lineLst[1] 51 | elif lineLst[0] in "mailDisplayFrom": 52 | mailDisplayFrom = lineLst[1] 53 | elif lineLst[0] in "mailTo": 54 | mailTo = lineLst[1] 55 | def hashes(fname): 56 | md5 = hashlib.md5(open(fname,'rb').read()).hexdigest() 57 | sha1 = hashlib.sha1(open(fname,'rb').read()).hexdigest() 58 | sha256 = hashlib.sha256(open(fname,'rb').read()).hexdigest() 59 | return [md5,sha1,sha256] 60 | 61 | def searchContext(searchPath, pattern,archived): 62 | flog = open("/home/bro/YARA/actions.log","w+") 63 | flog.write("searching for pattern: " + pattern + " in " + searchPath) 64 | 65 | out = "" 66 | currentLogPath="/home/bro/logs/current" 67 | 68 | if not archived: 69 | files = glob.glob(searchPath + "/*.log") 70 | else: 71 | files = glob.glob(searchPath + "/*.log.gz") 72 | 73 | for f in files: 74 | flog.write("searching in " + f) 75 | 76 | if not archived: 77 | command = "/bin/cat " + f + " | /usr/local/bro/bin/bro-cut -d | grep " + pattern + " " 78 | flog.write("command :" + command) 79 | else: 80 | command = "/bin/zgrep " + pattern + " " + f 81 | flog.write("command :" + command) 82 | print command 83 | 84 | try: 85 | flog.write("before appending \n" + out) 86 | out += subprocess.check_output(command, shell=True) 87 | flog.write("after appending \n" + out) 88 | except: 89 | pass 90 | 91 | print "context found in path: " + searchPath 92 | flog.write("context found in path: \n" + searchPath) 93 | 94 | if out =="": 95 | out = "Context not found in current logs \n" 96 | 97 | print out 98 | flog.write("output: " + out) 99 | return out 100 | 101 | def sendAlertEmail(message,fromaddr,recipient,filepath,context): 102 | toaddr = recipient 103 | 104 | msg = MIMEMultipart() 105 | msg['From'] = fromaddr 106 | msg['To'] = recipient 107 | msg['Subject'] = "YARA Alert" 108 | 109 | body = "alerted rules: " + str(message[0]) + "\n" 110 | body = body + "filepath: " + str(message[1]) + "\n" 111 | body = body + "md5sum : " + str(message[2]) + "\n" 112 | body = body + "sha1sum: " + str(message[3]) + "\n" 113 | body = body + "sha256sum: " + str(message[4]) + "\n\n" 114 | 115 | 116 | filename = filepath.split("/")[-1] 117 | generatedZip = alertedFilesFolder + "/" + filename + ".zip" 118 | print "generatedZip: " + generatedZip 119 | 120 | if os.path.isfile(generatedZip): 121 | os.remove(generatedZip) 122 | 123 | rc = subprocess.call([sevenZipCommand, 'a', '-pinfected', '-y', generatedZip, filepath]) 124 | 125 | body = body + "saved Zip file: " + generatedZip + "\n\n" 126 | body = body + "context: " + context + "\n" 127 | 128 | filesize = os.path.getsize(generatedZip) 129 | 130 | print body 131 | 132 | print "filepath: " + filepath + " size: " + str(filesize) 133 | if os.path.getsize(generatedZip) < 10000000: 134 | part = MIMEBase('application', "zip") 135 | part.set_payload(open(generatedZip, "rb").read()) 136 | Encoders.encode_base64(part) 137 | part.add_header('Content-Disposition', 'attachment; filename="' + filename + ".zip") 138 | msg.attach(part) 139 | else: 140 | body = body + "File is too big for the attachment" 141 | 142 | msg.attach(MIMEText(body, 'plain')) 143 | server = smtplib.SMTP(mailServer,mailPort) 144 | server.ehlo() 145 | server.starttls() 146 | server.ehlo 147 | 148 | # Base64 encoding prevents issues with special characters with passwords/usernames 149 | broMailPasswordB64 = broMailPassword.encode("base64") 150 | broMailUsernameB64 = broMailUsername.encode("base64") 151 | server.login(broMailUsernameB64.decode("base64"),broMailPasswordB64.decode("base64")) 152 | text = msg.as_string() 153 | server.sendmail(fromaddr, toaddr, text) 154 | server.close() 155 | 156 | fout = open("/tmp/yaraAllRules","w") 157 | 158 | print extractedFilePath 159 | yaraRules = subprocess.check_output("find " + yaraRulesPath + " -name '*.yar' -exec cat {} + ", shell=True) 160 | 161 | fout.write(yaraRules) 162 | fout.close() 163 | 164 | start = time.time() 165 | #scanOutput = subprocess.check_output("yara -r /tmp/yaraAllRules " + extractedFilePath, shell=True) 166 | scanOutput = subprocess.check_output("yara -r /tmp/yaraAllRules " + extractedFilePath + " -d extension=\"noext\" -d filename=\"nofilename\" -d filepath=\"nofilepath\" -d filetype=\"nofiletype\"", shell=True) 167 | 168 | end = time.time() 169 | 170 | print "Run time: " + str((end - start)) 171 | i=0 172 | scanOutput = scanOutput.split("\n") 173 | 174 | filesWithAlerts = {} 175 | 176 | for line in scanOutput: 177 | if not "warning" in line and len(line) > 10: 178 | rule,filepath = line.strip().split(" ") 179 | 180 | # If the file exists, it obtains the file hash 181 | if filepath in filesWithAlerts.keys(): 182 | filesWithAlerts[filepath].append(rule) 183 | else: 184 | filesWithAlerts[filepath] = [rule] 185 | 186 | for filepath, matchedRules in filesWithAlerts.items(): 187 | print "filepath: " + filepath + " v: " + str(matchedRules) 188 | entry = [str(matchedRules),filepath] + hashes(filepath) 189 | try: 190 | print "send alert email" 191 | pattern = filepath.split("/")[-1].split("-")[-1].split(".")[-2] 192 | 193 | context = searchContext("/home/bro/logs/current", pattern,archived=False) 194 | 195 | if context == "": 196 | print "No additional context was found, searching on the historical log." 197 | else: 198 | print context 199 | 200 | sendAlertEmail(entry,mailDisplayFrom,mailTo,filepath,context) 201 | except Exception as e: 202 | print(e) 203 | 204 | files = glob.glob(extractedFilePath + "/*") 205 | 206 | for f in files: 207 | os.remove(f) 208 | --------------------------------------------------------------------------------