Files
blue-team-tools/tools/sigma/backends/netwitness-epl.py
T
Johnny Walker 4e5a9a58a5 Update netwitness-epl.py
nullExpression and notNullExpression fixed to be logically coherent and compatible with EPL syntax
2021-03-09 17:41:54 +01:00

164 lines
7.6 KiB
Python

# NetWitness EPL output backend for sigmac
# Copyright 2019 Tarik BOUDJEMAA (@snake-jump)
# Inspired from John Tuckner (@tuckner) NetWitness output backend for sigmac
# NetWitness EPL backend for sigmac uses netwitness-epl.yml config file
# RSA Alerts are generated by Event Processing Language (EPL) , that uses Esper Engine (https://www.espertech.com/esper/)
# For more details see :https://community.rsa.com/docs/DOC-110246 and https://community.rsa.com/docs/DOC-80068
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import re
import sigma
from .base import SingleTextQueryBackend
from .mixins import MultiRuleOutputMixin
from sigma.parser.modifiers.base import SigmaTypeModifier
from sigma.parser.modifiers.type import SigmaRegularExpressionModifier
template="""
module_XXXXX;
@Name('RuleName')
@RSAAlert(oneInSeconds=0)
SELECT * FROM Event(
EXPRESSION
);"""
class NetWitnessEplBackend(SingleTextQueryBackend):
"""Converts Sigma rule into RSA NetWitness EPL . Contributed by @snake-jump"""
identifier = "netwitness-epl"
config_required = False
default_config = ["sysmon","netwitness-epl"]
active = True
reEscape = re.compile('(")')
#reEscape = re.compile("([\\|()\[\]{}.^$+])")
reClear = None
andToken = " AND "
orToken = " OR "
notToken = "NOT"
subExpression = "(%s)"
listExpression = "(%s)"
listSeparator = ", "
valueExpression = "\'%s\'"
keyExpression = "%s"
nullExpression = "%s is null"
notNullExpression = "%s is not null"
mapExpression = "(%s=%s)"
mapListsSpecialHandling = True
def generateMapItemNode(self, node):
key, value = node
if type(key) != int:
key = key.replace(".","_") ## replace . by _ in meta name (RSA EPL)
key = key.lower()
if self.mapListsSpecialHandling == False and type(value) in (str, int, list) or self.mapListsSpecialHandling == True and type(value) in (str, int):
if type(value) == str and "*" in value[1:-1]:
value = re.sub('([".^$]|\\\\(?![*?]))', '\\\\\g<1>', value)
value = re.sub('\\*', '.*', value)
value = re.sub('\\?', '.', value)
return "(%s REGEXP %s)" %(key, self.generateValueNode(value))
elif type(value) == str and "*" in value:
value = re.sub("(\*\\\\)|(\*)", "", value)
value = self.generateValueNode("%"+value+"%") # add "%" to construct the like expression ex: process like %psexesvc%
return "(%s LIKE %s)" % (key,value)
elif type(value) in (str, int):
return self.mapExpression % (key, self.generateValueNode(value))
else:
return self.mapExpression % (key, self.generateNode(value))
elif type(value) == list:
return self.generateMapItemListNode(key, value)
elif value is None:
return self.nullExpression % (key, )
elif type(value) == SigmaRegularExpressionModifier: ## if value is regex
regex = str(value)
## in RSA netwitness EPL regex each backslash must be escaped by backslash
## ex : c:\temp\ to regex -> c:\\temp\\ to RSA EPL regex --> c:\\\\temp\\\\
regex = regex.replace("\\","\\\\")
# Regular Expressions have to match the full value in RSA Netwitness EPL
if not (regex.startswith('^') or regex.startswith('.*')):
regex = '.*' + regex
if not (regex.endswith('$') or regex.endswith('.*')):
regex = regex + '.*'
return "(%s REGEXP %s)" %(key, self.generateValueNode(regex))
else:
raise TypeError("Backend does not support map values of type " + str(type(value)))
def generateMapItemListNode(self, key, value):
equallist = list()
containlist = list()
regexlist = list()
for item in value:
if type(item) == str and "*" in item[1:-1]:
item = re.sub('([".^$]|\\\\(?![*?]))', '\\\\\g<1>', item)
item = re.sub('\\*', '.*', item)
item = re.sub('\\?', '.', item)
regexlist.append(self.generateValueNode(item))
elif type(item) == str and (item.endswith("*") or item.startswith("*")):
item_temp=item
item = re.sub("(\*\\\\)|(\*)", "", item)
if item_temp.endswith("*") and item_temp.startswith("*"): # pattern begins with "*" and ends with "*"
containlist.append(self.generateValueNode('%'+item+'%')) # add "%" to construct the like expression ex: process like %psexesvc%
elif item_temp.startswith("*"): # pattern don't end with "*"
containlist.append(self.generateValueNode('%'+item))
else: # item_temp.endswith("*") pattern don't begin with "*"
containlist.append(self.generateValueNode(item+'%'))
else:
equallist.append(self.generateValueNode(item))
fmtitems = list()
if equallist:
if len(equallist) == 1:
fmtitems.append("%s = %s" % (key, ", ".join(equallist)))
else:
# add "(" and ")" to the first and the last item from the list to have meta_key IN ('value1','value2')
equallist[0]=("("+equallist[0])
equallist[-1]=(equallist[-1]+")")
fmtitems.append("%s IN %s" % (key, ", ".join(equallist)))
if containlist:
fmtitems.append("%s LIKE %s" % (key, (" OR "+key+" LIKE ").join(containlist)))
if regexlist:
fmtitems.append("%s REGEXP %s" % (key, "|".join(regexlist)))
fmtquery = "("+" OR ".join(filter(None, fmtitems))
# Delete the " ' " from the begin or the end of each regex pattern ex : '.*('patern1'|'patern2').*' --> '.*(patern1|patern2).*'
fmtquery = re.sub('\'\.\*\(\'','\'.*(',fmtquery)
fmtquery = re.sub('\'\)\.\*\'',').*\'',fmtquery)
fmtquery = re.sub('\'\|\'','|',fmtquery)
fmtquery = fmtquery+')'
return fmtquery
def generateValueNode(self, node):
return self.valueExpression % (str(node))
def generate(self, sigmaparser):
"""Method is called for each sigma rule and receives the parsed rule (SigmaParser)"""
for parsed in sigmaparser.condparsed:
query = self.generateQuery(parsed, sigmaparser)
query=query.replace('INDEX','`index`') # index is reserved keyword in Esper and must be escaped
query=template.replace('EXPRESSION', query)
try:
query=query.replace('RuleName', sigmaparser.parsedyaml["title"].replace(" ","")) # add rule name
query=query.replace('module_XXXXX', "module "+sigmaparser.parsedyaml["title"].replace(" ","")) # add rule name
except:
print("Error when replacing RuleName by Title from yaml")
pass
return query
def generateQuery(self, parsed, sigmaparser):
result = self.generateNode(parsed.parsedSearch)
return result