Skip to content

Commit

Permalink
Merge branch 'improvements/improved_mdatp_mappings' of https://github…
Browse files Browse the repository at this point in the history
….com/wietze/sigma into wietze-improvements/improved_mdatp_mappings
  • Loading branch information
thomaspatzke committed Jun 5, 2020
2 parents d97d2ce + 2b38287 commit 5d88d97
Showing 1 changed file with 98 additions and 57 deletions.
155 changes: 98 additions & 57 deletions tools/sigma/backends/mdatp.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
from .base import SingleTextQueryBackend
from .exceptions import NotSupportedError


def wrapper(method):
@wraps(method)
def _impl(self, method_args):
Expand Down Expand Up @@ -72,32 +71,67 @@ def __init__(self, *args, **kwargs):
"""Initialize field mappings"""
super().__init__(*args, **kwargs)
self.fieldMappings = { # mapping between Sigma and ATP field names
# Supported values:
# (field name mapping, value mapping): distinct mappings for field name and value, may be a string (direct mapping) or function maps name/value to ATP target value
# (mapping function,): receives field name and value as parameter, return list of 2 element tuples (destination field name and value)
# (replacement, ): Replaces field occurrence with static string
"AccountName" : (self.id_mapping, self.default_value_mapping),
"CommandLine" : ("ProcessCommandLine", self.default_value_mapping),
"DeviceName" : (self.id_mapping, self.default_value_mapping),
"DestinationHostname" : ("RemoteUrl", self.default_value_mapping),
"DestinationIp" : ("RemoteIP", self.default_value_mapping),
"DestinationIsIpv6" : ("RemoteIP has \":\"", ),
"DestinationPort" : ("RemotePort", self.default_value_mapping),
"Protocol" : ("RemoteProtocol", self.default_value_mapping),
"Details" : ("RegistryValueData", self.default_value_mapping),
"EventType" : ("ActionType", self.default_value_mapping),
"Image" : ("FolderPath", self.default_value_mapping),
"ImageLoaded" : ("FolderPath", self.default_value_mapping),
"LogonType" : (self.id_mapping, self.logontype_mapping),
"NewProcessName" : ("FolderPath", self.default_value_mapping),
"ObjectValueName" : ("RegistryValueName", self.default_value_mapping),
"ParentImage" : ("InitiatingProcessFolderPath", self.default_value_mapping),
"SourceImage" : ("InitiatingProcessFolderPath", self.default_value_mapping),
"TargetFilename" : ("FolderPath", self.default_value_mapping),
"TargetImage" : ("FolderPath", self.default_value_mapping),
"TargetObject" : ("RegistryKey", self.default_value_mapping),
"User" : (self.decompose_user, ),
}
# Supported values:
# (field name mapping, value mapping): distinct mappings for field name and value, may be a string (direct mapping) or function maps name/value to ATP target value
# (mapping function,): receives field name and value as parameter, return list of 2 element tuples (destination field name and value)
# (replacement, ): Replaces field occurrence with static string
"DeviceProcessEvents": {
"AccountName": (self.id_mapping, self.default_value_mapping),
"CommandLine": ("ProcessCommandLine", self.default_value_mapping),
"Command": ("ProcessCommandLine", self.default_value_mapping),
"DeviceName": (self.id_mapping, self.default_value_mapping),
"EventType": ("ActionType", self.default_value_mapping),
"Image": ("FolderPath", self.default_value_mapping),
"ImageLoaded": ("FolderPath", self.default_value_mapping),
"LogonType": (self.id_mapping, self.logontype_mapping),
"NewProcessName": ("FolderPath", self.default_value_mapping),
"ParentImage": ("InitiatingProcessFolderPath", self.default_value_mapping),
"SourceImage": ("InitiatingProcessFolderPath", self.default_value_mapping),
"TargetImage": ("FolderPath", self.default_value_mapping),
"User": (self.decompose_user, ),
},
"DeviceEvents": {
"TargetFilename": ("FolderPath", self.default_value_mapping),
"TargetImage": ("FolderPath", self.default_value_mapping),

"Image": ("InitiatingProcessFolderPath", self.default_value_mapping),
"User": (self.decompose_user, ),
},
"DeviceRegistryEvents": {
"TargetObject": ("RegistryKey", self.default_value_mapping),
"ObjectValueName": ("RegistryValueName", self.default_value_mapping),
"Details": ("RegistryValueData", self.default_value_mapping),

"Image": ("InitiatingProcessFolderPath", self.default_value_mapping),
"User": (self.decompose_user, ),
},
"DeviceFileEvents": {
"TargetFilename": ("FolderPath", self.default_value_mapping),
"TargetFileName": ("FolderPath", self.default_value_mapping),

"Image": ("InitiatingProcessFolderPath", self.default_value_mapping),
"User": (self.decompose_user, ),
},
"DeviceNetworkEvents": {
"Initiated": ("RemotePort", self.default_value_mapping),
"Protocol": ("RemoteProtocol", self.default_value_mapping),
"DestinationPort": ("RemotePort", self.default_value_mapping),
"DestinationIp": ("RemoteIP", self.default_value_mapping),
"DestinationIsIpv6": ("RemoteIP has \":\"", ),
"SourcePort": ("LocalPort", self.default_value_mapping),
"SourceIp": ("LocalIP", self.default_value_mapping),
"DestinationHostname": ("RemoteUrl", self.default_value_mapping),

"Image": ("InitiatingProcessFolderPath", self.default_value_mapping),
"User": (self.decompose_user, ),
},
"DeviceImageLoadEvents": {
"ImageLoaded": ("FolderPath", self.default_value_mapping),

"Image": ("InitiatingProcessFolderPath", self.default_value_mapping),
"User": (self.decompose_user, ),
}
}

def id_mapping(self, src):
"""Identity mapping, source == target field name"""
Expand Down Expand Up @@ -127,16 +161,16 @@ def default_value_mapping(self, val):
def logontype_mapping(self, src):
"""Value mapping for logon events to reduced ATP LogonType set"""
logontype_mapping = {
2: "Interactive",
3: "Network",
4: "Batch",
5: "Service",
7: "Interactive", # unsure
8: "Network",
9: "Interactive", # unsure
10: "Remote interactive (RDP) logons", # really the value?
11: "Interactive"
}
2: "Interactive",
3: "Network",
4: "Batch",
5: "Service",
7: "Interactive", # unsure
8: "Network",
9: "Interactive", # unsure
10: "Remote interactive (RDP) logons", # really the value?
11: "Interactive"
}
try:
return logontype_mapping[int(src)]
except KeyError:
Expand All @@ -148,9 +182,9 @@ def decompose_user(self, src_field, src_value):
m = reUser.match(src_value)
if m:
domain, user = m.groups()
return (("InitiatingProcessAccountDomain", domain), ("InititatingProcessAccountName", user))
return (("InitiatingProcessAccountDomain", self.default_value_mapping(domain)), ("InititatingProcessAccountName", self.default_value_mapping(user)))
else: # assume only user name is given if backslash is missing
return (("InititatingProcessAccountName", src_value),)
return (("InititatingProcessAccountName", self.default_value_mapping(src_value)))

def generate(self, sigmaparser):
self.table = None
Expand Down Expand Up @@ -185,54 +219,61 @@ def generateMapItemNode(self, node):
and creates an appropriate table reference.
"""
key, value = node
if type(value) == list: # handle map items with values list like multiple OR-chained conditions
return self.generateORNode(
[(key, v) for v in value]
)
# handle map items with values list like multiple OR-chained conditions
if type(value) == list:
return self.generateORNode([(key, v) for v in value])
elif key == "EventID": # EventIDs are not reflected in condition but in table selection
if self.product == "windows":
if self.service == "sysmon" and value == 1 \
or self.service == "security" and value == 4688: # Process Execution
or self.service == "security" and value == 4688: # Process Execution
self.table = "DeviceProcessEvents"
return None
elif self.service == "sysmon" and value == 3: # Network Connection
elif self.service == "sysmon" and value == 3: # Network Connection
self.table = "DeviceNetworkEvents"
return None
elif self.service == "sysmon" and value == 7: # Image Load
elif self.service == "sysmon" and value == 7: # Image Load
self.table = "DeviceImageLoadEvents"
return None
elif self.service == "sysmon" and value == 8: # Create Remote Thread
elif self.service == "sysmon" and value == 8: # Create Remote Thread
self.table = "DeviceEvents"
return "ActionType == \"CreateRemoteThreadApiCall\""
elif self.service == "sysmon" and value == 11: # File Creation
elif self.service == "sysmon" and value == 11: # File Creation
self.table = "DeviceFileEvents"
return "ActionType == \"FileCreated\""
elif self.service == "sysmon" and value == 23: # File Deletion
self.table = "DeviceFileEvents"
return "ActionType == \"FileDeleted\""
elif self.service == "sysmon" and value == 12: # Create/Delete Registry Value
self.table = "DeviceRegistryEvents"
return None
elif self.service == "sysmon" and value == 13 \
or self.service == "security" and value == 4657: # Set Registry Value
or self.service == "security" and value == 4657: # Set Registry Value
self.table = "DeviceRegistryEvents"
return "ActionType == \"RegistryValueSet\""
elif self.service == "security" and value == 4624:
self.table = "DeviceLogonEvents"
return None
else:
if not self.table:
raise NotSupportedError("No sysmon Event ID provided")
else:
raise NotSupportedError("No mapping for Event ID %s" % value)
elif type(value) in (str, int): # default value processing
try:
mapping = self.fieldMappings[key]
mapping = self.fieldMappings[self.table][key]
except KeyError:
raise NotSupportedError("No mapping defined for field '%s'" % key)
raise NotSupportedError("No mapping defined for field '%s' in '%s'" % (key, self.table))
if len(mapping) == 1:
mapping = mapping[0]
if type(mapping) == str:
return mapping
elif callable(mapping):
conds = mapping(key, value)
return self.generateSubexpressionNode(
self.generateANDNode(
[cond for cond in mapping(key, value)]
)
)
return self.andToken.join(["{} {}".format(*cond) for cond in conds])
elif len(mapping) == 2:
result = list()
for mapitem, val in zip(mapping, node): # iterate mapping and mapping source value synchronously over key and value
# iterate mapping and mapping source value synchronously over key and value
for mapitem, val in zip(mapping, node):
if type(mapitem) == str:
result.append(mapitem)
elif callable(mapitem):
Expand Down

0 comments on commit 5d88d97

Please sign in to comment.