Feature/addruletype to sigma rule#230 (#235)
* added ruletype to SIGMA rule #230 * added ruletype to SIGMA rule converter tool #231
This commit is contained in:
@@ -11,60 +11,67 @@ from sigma.parser.modifiers.type import SigmaRegularExpressionModifier
|
||||
|
||||
SPECIAL_REGEX = re.compile("^\{(\d)+,?(\d+)?\}")
|
||||
|
||||
|
||||
class HayabusaBackend(SingleTextQueryBackend):
|
||||
"""Base class for backends that generate one text-based expression from a Sigma rule"""
|
||||
## see tools.py
|
||||
## use this value when sigmac parse argument of "-t"
|
||||
# see tools.py
|
||||
# use this value when sigmac parse argument of "-t"
|
||||
identifier = "hayabusa"
|
||||
active = True
|
||||
# the following class variables define the generation and behavior of queries from a parse tree some are prefilled with default values that are quite usual
|
||||
andToken = " and " # Token used for linking expressions with logical AND
|
||||
# Token used for linking expressions with logical AND
|
||||
andToken = " and "
|
||||
orToken = " or " # Same for OR
|
||||
notToken = " not " # Same for NOT
|
||||
subExpression = "(%s)" # Syntax for subexpressions, usually parenthesis around it. %s is inner expression
|
||||
# Syntax for subexpressions, usually parenthesis around it. %s is inner expression
|
||||
subExpression = "(%s)"
|
||||
valueExpression = "%s" # Expression of values, %s represents value
|
||||
typedValueExpression = dict() # Expression of typed values generated by type modifiers. modifier identifier -> expression dict, %s represents value
|
||||
# Expression of typed values generated by type modifiers. modifier identifier -> expression dict, %s represents value
|
||||
typedValueExpression = dict()
|
||||
sort_condition_lists = False
|
||||
mapListsSpecialHandling = True
|
||||
name_idx = 1
|
||||
selection_prefix = "SELECTION_{0}"
|
||||
name_2_selection = OrderedDict()
|
||||
|
||||
|
||||
def __init__(self, sigmaconfig, options):
|
||||
super().__init__(sigmaconfig)
|
||||
self.re_init()
|
||||
|
||||
|
||||
def re_init(self):
|
||||
self.name_idx = 1
|
||||
self.name_2_selection = OrderedDict()
|
||||
|
||||
|
||||
def cleanValue(self, val):
|
||||
return val
|
||||
|
||||
|
||||
def generateListNode(self, node):
|
||||
return self.generateORNode(node)
|
||||
|
||||
|
||||
def create_new_selection(self):
|
||||
name = self.selection_prefix.format(self.name_idx)
|
||||
self.name_idx+=1
|
||||
self.name_idx += 1
|
||||
return name
|
||||
|
||||
|
||||
def generateMapItemNode(self, node):
|
||||
fieldname, value = node
|
||||
transformed_fieldname = self.fieldNameMapping(fieldname, value)
|
||||
if self.mapListsSpecialHandling == False and type(value) in (str, int, list) or self.mapListsSpecialHandling == True and type(value) in (str, int):
|
||||
name = self.create_new_selection()
|
||||
self.name_2_selection[name] = [(transformed_fieldname, self.generateNode(value))]
|
||||
self.name_2_selection[name] = [
|
||||
(transformed_fieldname, self.generateNode(value))]
|
||||
return name
|
||||
elif type(value) == list:
|
||||
return self.generateMapItemListNode(transformed_fieldname, value)
|
||||
elif isinstance(value, SigmaTypeModifier):
|
||||
return self.generateMapItemTypedNode(transformed_fieldname, value)
|
||||
elif value is None:
|
||||
return self.generateNode((transformed_fieldname+"|re","^$")) #nullは正規表現で表す。これでいいのかちょっと不安
|
||||
# nullは正規表現で表す。これでいいのかちょっと不安
|
||||
return self.generateNode((transformed_fieldname+"|re", "^$"))
|
||||
else:
|
||||
raise TypeError("Backend does not support map values of type " + str(type(value)))
|
||||
|
||||
raise TypeError(
|
||||
"Backend does not support map values of type " + str(type(value)))
|
||||
|
||||
def generateMapItemTypedNode(self, fieldname, value):
|
||||
# `|re`オプションに対応
|
||||
if type(value) == SigmaRegularExpressionModifier:
|
||||
@@ -73,76 +80,77 @@ class HayabusaBackend(SingleTextQueryBackend):
|
||||
# pythonとかの正規表現では/(スラッシュ)や"(ダブルクオート)をエスケープしてもエラーが出ないが、Rustの正規表現エンジンではスラッシュやダブルクオートをエスケープするとエラーが出てしまう
|
||||
# そこでスラッシュやダブルクオートのエスケープは消しておく。
|
||||
# あと、この実装は結構怪しいので、将来バージョンではこの実装を無くして、hayabusa側で使用する正規表現エンジンを普通のpythonとかで使われているやつに変えた方がいいと思う。
|
||||
regex_value = value.value.replace('\/','/')
|
||||
regex_value = regex_value.replace("\\\"","\"")
|
||||
|
||||
## 追加のケースとして、pythonとかの正規表現では{はエスケープ不要だが、Rustでは必要なので、それを修正するためのコード。めんどい
|
||||
regex_value = value.value.replace('\/', '/')
|
||||
regex_value = regex_value.replace("\\\"", "\"")
|
||||
|
||||
# 追加のケースとして、pythonとかの正規表現では{はエスケープ不要だが、Rustでは必要なので、それを修正するためのコード。めんどい
|
||||
idx = 0
|
||||
prev_regex = regex_value
|
||||
regex_value = ""
|
||||
while idx < len(prev_regex):
|
||||
## 既にエスケープされているものはスキップする。
|
||||
# 既にエスケープされているものはスキップする。
|
||||
if prev_regex[idx:idx+2] == "\\{" or prev_regex[idx:idx+2] == "\\}":
|
||||
regex_value += prev_regex[idx:idx+2]
|
||||
idx += 2
|
||||
continue
|
||||
|
||||
|
||||
ch = prev_regex[idx]
|
||||
## エスケープ不要な}はここに来ないように、以降の処理でidxを調整している。なのでここにくる}はエスケープが必要。
|
||||
# エスケープ不要な}はここに来ないように、以降の処理でidxを調整している。なのでここにくる}はエスケープが必要。
|
||||
if ch == "}":
|
||||
regex_value += "\\}"
|
||||
idx += 1
|
||||
continue
|
||||
|
||||
## {じゃない場合はそのまま足すだけ
|
||||
|
||||
# {じゃない場合はそのまま足すだけ
|
||||
if ch != "{":
|
||||
regex_value += ch
|
||||
idx += 1
|
||||
continue
|
||||
|
||||
## {の場合の処理
|
||||
|
||||
# {の場合の処理
|
||||
reg_match = SPECIAL_REGEX.match(prev_regex[idx:])
|
||||
if reg_match == None:
|
||||
## 文字列としての{なので、エスケープ必要
|
||||
# 文字列としての{なので、エスケープ必要
|
||||
regex_value += "\\{"
|
||||
idx += 1
|
||||
else:
|
||||
## これは桁数を指定する{なので、エスケープ不要で}までidxをスキップ
|
||||
# これは桁数を指定する{なので、エスケープ不要で}までidxをスキップ
|
||||
regex_value += reg_match.group()
|
||||
idx += len(reg_match.group())
|
||||
|
||||
return self.generateNode((fieldname, regex_value))
|
||||
else:
|
||||
raise NotImplementedError("Type modifier '{}' is not supported by backend".format(value.identifier))
|
||||
raise NotImplementedError(
|
||||
"Type modifier '{}' is not supported by backend".format(value.identifier))
|
||||
|
||||
def generateMapItemListNode(self, fieldname, value):
|
||||
### 下記のようなケースに対応
|
||||
### selection:
|
||||
### EventID:
|
||||
# 下記のようなケースに対応
|
||||
# selection:
|
||||
# EventID:
|
||||
### - 1
|
||||
### - 2
|
||||
### 基本的にリストはORと良く、generateListNodeもORNodeを生成している。
|
||||
### しかし、上記のケースでgenerateListNode()を実行すると、下記のようなYAMLになってしまう。
|
||||
### selection:
|
||||
# 基本的にリストはORと良く、generateListNodeもORNodeを生成している。
|
||||
# しかし、上記のケースでgenerateListNode()を実行すると、下記のようなYAMLになってしまう。
|
||||
# selection:
|
||||
### EventID: 1 or 2
|
||||
### 上記のようにならないように、修正している。
|
||||
### なお、generateMapItemListNode()を有効にするために、self.mapListsSpecialHandling = Trueとしている
|
||||
# 上記のようにならないように、修正している。
|
||||
# なお、generateMapItemListNode()を有効にするために、self.mapListsSpecialHandling = Trueとしている
|
||||
if self._is_all_str(value):
|
||||
name = self.create_new_selection()
|
||||
self.name_2_selection[name] = [(fieldname,value)]
|
||||
self.name_2_selection[name] = [(fieldname, value)]
|
||||
return name
|
||||
|
||||
list_values = list()
|
||||
for sub_node in value:
|
||||
list_values.append((fieldname,sub_node))
|
||||
return self.subExpression % self.generateORNode(list_values)
|
||||
|
||||
list_values.append((fieldname, sub_node))
|
||||
return self.subExpression % self.generateORNode(list_values)
|
||||
|
||||
def _is_all_str(self, values):
|
||||
for value in values:
|
||||
if type(value) != str:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def generateAggregation(self, agg):
|
||||
# python3 tools/sigmac rules/windows/process_creation/win_dnscat2_powershell_implementation.yml --config tools/config/generic/sysmon.yml --target hayabusa
|
||||
if agg == None:
|
||||
@@ -152,100 +160,101 @@ class HayabusaBackend(SingleTextQueryBackend):
|
||||
# | 以降をそのまま出力する
|
||||
target = '|'
|
||||
condition = agg.parser.parsedyaml["detection"]["condition"]
|
||||
|
||||
### conditionはなんと複数指定されることもあるらしい!!!!!
|
||||
### If multiple conditions are given, they are logically linked with OR.と仕様書に書いてある。詳細はSigmaRuleの仕様を参照のこと。
|
||||
### とりあえず、複数指定のconditionは未対応ということでエラーにするとして、(なお、デフォルトのbase.pyの実装で複数指定のconditionはexceptionがraiseされるので、そのような処理は追加で実装しなくてよい)
|
||||
### 問題となるのはagg.parser.parsedyaml["detection"]["condition"]の型
|
||||
|
||||
# conditionはなんと複数指定されることもあるらしい!!!!!
|
||||
# If multiple conditions are given, they are logically linked with OR.と仕様書に書いてある。詳細はSigmaRuleの仕様を参照のこと。
|
||||
# とりあえず、複数指定のconditionは未対応ということでエラーにするとして、(なお、デフォルトのbase.pyの実装で複数指定のconditionはexceptionがraiseされるので、そのような処理は追加で実装しなくてよい)
|
||||
# 問題となるのはagg.parser.parsedyaml["detection"]["condition"]の型
|
||||
###
|
||||
### 下記のように指定すると、agg.parser.parsedyaml["detection"]["condition"]の型はstringになるが
|
||||
# 下記のように指定すると、agg.parser.parsedyaml["detection"]["condition"]の型はstringになるが
|
||||
### conditon: selection1
|
||||
###
|
||||
### 下記のように指定すると、agg.parser.parsedyaml["detection"]["condition"]の型はlistになる
|
||||
### conditon:
|
||||
# 下記のように指定すると、agg.parser.parsedyaml["detection"]["condition"]の型はlistになる
|
||||
# conditon:
|
||||
### - selection1
|
||||
###
|
||||
### なのでlistのケースも想定して、下記のような実装とする。
|
||||
if type(condition) == list:
|
||||
# なのでlistのケースも想定して、下記のような実装とする。
|
||||
if type(condition) == list:
|
||||
condition = condition[0]
|
||||
index = condition.find(target)
|
||||
return condition[index:]
|
||||
## count以外は対応していないので、エラーを返す
|
||||
raise NotImplementedError("This rule contains aggregation operator not implemented for this backend")
|
||||
|
||||
# count以外は対応していないので、エラーを返す
|
||||
raise NotImplementedError(
|
||||
"This rule contains aggregation operator not implemented for this backend")
|
||||
|
||||
def generateValueNode(self, node):
|
||||
## このメソッドをオーバーライドしておかないとint型もstr型として扱われてしまうので、int型やint型として、str型はstr型として処理するために実装した。
|
||||
## このメソッドは最悪無くてもいいような気もする。
|
||||
# このメソッドをオーバーライドしておかないとint型もstr型として扱われてしまうので、int型やint型として、str型はstr型として処理するために実装した。
|
||||
# このメソッドは最悪無くてもいいような気もする。
|
||||
if type(node) == int:
|
||||
return node
|
||||
else:
|
||||
return self.valueExpression % (self.cleanValue(str(node)))
|
||||
|
||||
### 全部strかどうかを判定
|
||||
def is_keyword_list(self, node ):
|
||||
|
||||
# 全部strかどうかを判定
|
||||
def is_keyword_list(self, node):
|
||||
if type(node) != ConditionOR:
|
||||
return False
|
||||
|
||||
|
||||
for item in node.items:
|
||||
if type(item) != str:
|
||||
return False
|
||||
|
||||
|
||||
return True
|
||||
|
||||
def generateANDNode(self, node):
|
||||
|
||||
def generateANDNode(self, node):
|
||||
generated = list()
|
||||
for val in node:
|
||||
if type(val) == str:
|
||||
### 普通はtupleでkeyとvalueのペアであるが、これはkeyが指定されていないケース
|
||||
### keyが指定されていない場合は、EventLog全体をgrep検索することになっている。(詳細はSigmaルールの仕様書を参照のこと)
|
||||
### 具体的には"all of"とか使うとこの分岐に来る
|
||||
# 普通はtupleでkeyとvalueのペアであるが、これはkeyが指定されていないケース
|
||||
# keyが指定されていない場合は、EventLog全体をgrep検索することになっている。(詳細はSigmaルールの仕様書を参照のこと)
|
||||
# 具体的には"all of"とか使うとこの分岐に来る
|
||||
name = self.create_new_selection()
|
||||
self.name_2_selection[name] = [(None, val)]
|
||||
generated_node = name
|
||||
else:
|
||||
### 普通はこっちにくる
|
||||
# 普通はこっちにくる
|
||||
generated_node = self.generateNode(val)
|
||||
generated.append(generated_node)
|
||||
filtered = [ g for g in generated if g is not None ]
|
||||
filtered = [g for g in generated if g is not None]
|
||||
if filtered:
|
||||
if self.sort_condition_lists:
|
||||
filtered = sorted(filtered)
|
||||
return self.andToken.join(filtered)
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def generateORNode(self, node):
|
||||
if self.is_keyword_list(node) == True:
|
||||
## 普通はtupleでkeyとvalueのペアであるが、これはkeyが指定されていないケース
|
||||
## 全てkeyが指定されていない場合はここに来る。
|
||||
# 普通はtupleでkeyとvalueのペアであるが、これはkeyが指定されていないケース
|
||||
# 全てkeyが指定されていない場合はここに来る。
|
||||
name = self.create_new_selection()
|
||||
self.name_2_selection[name] = [(None, val) for val in node]
|
||||
return name
|
||||
|
||||
|
||||
name = None
|
||||
generated = list()
|
||||
for val in node:
|
||||
### 普通はtupleでkeyとvalueのペアであるが、これはkeyが指定されていないケース
|
||||
# 普通はtupleでkeyとvalueのペアであるが、これはkeyが指定されていないケース
|
||||
if type(val) == str:
|
||||
if name is None:
|
||||
name = self.create_new_selection()
|
||||
self.name_2_selection[name] = list()
|
||||
self.name_2_selection[name].append((None,val))
|
||||
self.name_2_selection[name].append((None, val))
|
||||
else:
|
||||
generated.append(self.generateNode(val))
|
||||
if name is not None:
|
||||
generated.append(name)
|
||||
|
||||
filtered = [ g for g in generated if g is not None ]
|
||||
filtered = [g for g in generated if g is not None]
|
||||
if filtered:
|
||||
if self.sort_condition_lists:
|
||||
filtered = sorted(filtered)
|
||||
return self.orToken.join(filtered)
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def generateQuery(self, parsed):
|
||||
### このクラスのインスタンスは再利用されるので、内部のメンバ変数をresetする。
|
||||
# このクラスのインスタンスは再利用されるので、内部のメンバ変数をresetする。
|
||||
self.re_init()
|
||||
result = self.generateNode(parsed.parsedSearch)
|
||||
if parsed.parsedAgg:
|
||||
@@ -253,26 +262,27 @@ class HayabusaBackend(SingleTextQueryBackend):
|
||||
result += " " + res
|
||||
ret = ""
|
||||
with StringIO() as bs:
|
||||
## 元のyamlをいじるとこの後の処理に影響を与える可能性があるので、deepCopyする
|
||||
# 元のyamlをいじるとこの後の処理に影響を与える可能性があるので、deepCopyする
|
||||
parsed_yaml = copy.deepcopy(parsed.sigmaParser.parsedyaml)
|
||||
## なんかタイトルは先頭に来てほしいので、そのための処理
|
||||
## parsed.sigmaParser.parsedyamlがOrderedDictならこんなことしなくていい、後で別のやり方があるか調べる
|
||||
## 順番固定してもいいかも
|
||||
# なんかタイトルは先頭に来てほしいので、そのための処理
|
||||
# parsed.sigmaParser.parsedyamlがOrderedDictならこんなことしなくていい、後で別のやり方があるか調べる
|
||||
# 順番固定してもいいかも
|
||||
bs.write("title: " + parsed_yaml["title"]+"\n")
|
||||
bs.write("ruletype: SIGMA\n")
|
||||
del parsed_yaml["title"]
|
||||
|
||||
## detectionの部分だけ変更して出力する。
|
||||
# detectionの部分だけ変更して出力する。
|
||||
parsed_yaml["detection"] = {}
|
||||
parsed_yaml["detection"]["condition"] = result
|
||||
for key, values in self.name_2_selection.items():
|
||||
### fieldnameの有無を確認している
|
||||
# fieldnameの有無を確認している
|
||||
if values[0][0]:
|
||||
## 通常はfieldnameがあってその場合は連想配列で初期化
|
||||
# 通常はfieldnameがあってその場合は連想配列で初期化
|
||||
parsed_yaml["detection"][key] = {}
|
||||
else:
|
||||
## is_keyword_list() == Trueの場合だけ、ここにくる
|
||||
# is_keyword_list() == Trueの場合だけ、ここにくる
|
||||
parsed_yaml["detection"][key] = []
|
||||
|
||||
|
||||
for fieldname, value in values:
|
||||
if fieldname == None:
|
||||
## is_keyword_list() == Trueの場合
|
||||
@@ -283,5 +293,5 @@ class HayabusaBackend(SingleTextQueryBackend):
|
||||
yaml.dump(parsed_yaml, bs, indent=4, default_flow_style=False)
|
||||
ret = bs.getvalue()
|
||||
ret += "---\n"
|
||||
|
||||
return ret
|
||||
|
||||
return ret
|
||||
|
||||
Reference in New Issue
Block a user