diff --git a/tools/sigmac/README-en.md b/tools/sigmac/README-en.md new file mode 100644 index 00000000..15e28c20 --- /dev/null +++ b/tools/sigmac/README-en.md @@ -0,0 +1,50 @@ +# hayabusaGenerator +[![python](https://img.shields.io/badge/python-3.8-blue)](https://www.python.org/) +![version](https://img.shields.io/badge/Platform-Win-green) +![version](https://img.shields.io/badge/Platform-Lin-green) +![version](https://img.shields.io/badge/Platform-Mac-green) + +[Japanese](./README.md) + +`hayabusaGenerater.py` allows to convert SIGMA rules to Hayabusa ruleset. + +## sigma + +[https://github.com/SigmaHQ/sigma](https://github.com/SigmaHQ/sigma) + +## Settings + +hayabusaGenerator needs `sigmac` from the SIGMA repository. +Before using hayabusaGenerator, clone the repository. + +```sh +git clone https://github.com/SigmaHQ/sigma.git +``` + +## Quickstart + +Regist haybausa for SIGMA's backend. + +```sh +export sigma_path=/path/to/sigma_repository +cp hayabusaGenerater.py $sigma_path/tools/sigma/backends +``` + +### Convert Single Rule + +```sh +python3 $sigma_path/tools/sigmac --config --target hayabusa +``` + +Sample +```sh +python3 $sigma_path/tools/sigmac $sigma/rules/windows/create_remote_thread/sysmon_cactustorch.yml --config $sigma_path/tools/config/generic/sysmon.yml --target hayabusa > sysmon_cactustorch.yml +``` + +### Convert Multiple Rules + +This is a command sample that creates a rule file from the specified directory in the current directory. + +```sh +find $sigma/rules/windows/* | grep yml | xargs -I{} sh -c 'python $sigma/tools/sigmac {} --config $sigma/tools/config/generic/sysmon.yml --target hayabusa > "$(basename {})"' +``` diff --git a/tools/sigmac/README.md b/tools/sigmac/README.md new file mode 100644 index 00000000..a777b409 --- /dev/null +++ b/tools/sigmac/README.md @@ -0,0 +1,51 @@ +# hayabusaGenerator +[![python](https://img.shields.io/badge/python-3.8-blue)](https://www.python.org/) +![version](https://img.shields.io/badge/Platform-Win-green) +![version](https://img.shields.io/badge/Platform-Lin-green) +![version](https://img.shields.io/badge/Platform-Mac-green) + +[English](./README-en.md) + +`hayabusaGenerator.py` はSIGMAルールをHayabusaに対応したルールセットに変更することができます。 +SIGMAの持つ多くの検知ルールをHayabusaのルールセットに追加することでルールを作成する手間を省くことができます。 + +## sigma + +[https://github.com/SigmaHQ/sigma](https://github.com/SigmaHQ/sigma) + +## 環境設定 + +hayabusaGeneratorはSIGMAリポジトリの中にある`sigmac`を使います。 +事前に任意のディレクトリにSIGMAリポジトリをcloneしてください。 + +```sh +git clone https://github.com/SigmaHQ/sigma.git +``` + +## 使い方 + +hayabusaGenerator.pyをSIGMAのbackendとして登録します。 + +```sh +export sigma_path=/path/to/sigma_repository +cp hayabusaGenerater.py $sigma_path/tools/sigma/backends +``` + +### 単体のルールを変換 + +```sh +python3 $sigma_path/tools/sigmac <変換対象ruleの指定> --config --target hayabusa +``` + +サンプル +```sh +python3 $sigma_path/tools/sigmac $sigma/rules/windows/create_remote_thread/sysmon_cactustorch.yml --config $sigma_path/tools/config/generic/sysmon.yml --target hayabusa > sysmon_cactustorch.yml +``` + +### 複数のルールを変換 + +現在のディレクトリ内に指定したディレクトリ内のルールファイルを作成するコマンドサンプルです。 + +```sh +find $sigma/rules/windows/* | grep yml | xargs -I{} sh -c 'python $sigma/tools/sigmac {} --config $sigma/tools/config/generic/sysmon.yml --target hayabusa > "$(basename {})"' +``` diff --git a/tools/sigmac/hayabusaGenerater.py b/tools/sigmac/hayabusaGenerater.py new file mode 100644 index 00000000..e111564d --- /dev/null +++ b/tools/sigmac/hayabusaGenerater.py @@ -0,0 +1,141 @@ +import copy +from collections import OrderedDict +from io import StringIO + +import yaml +from sigma.backends.base import SingleTextQueryBackend +from sigma.parser.condition import SigmaAggregationParser +from sigma.parser.modifiers.base import SigmaTypeModifier +from sigma.parser.modifiers.type import SigmaRegularExpressionModifier + +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" + 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 + orToken = " or " # Same for OR + notToken = " not " # Same for NOT + subExpression = "(%s)" # Syntax for subexpressions, usually parenthesis around it. %s is inner expression + 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 + + 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) + + 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 + 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))] + 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は正規表現で表す。これでいいのかちょっと不安 + else: + raise TypeError("Backend does not support map values of type " + str(type(value))) + + def generateMapItemTypedNode(self, fieldname, value): + # `|re`オプションに対応 + if type(value) == SigmaRegularExpressionModifier: + fieldname = fieldname + "|re" + return self.generateNode((fieldname,value.value)) + else: + raise NotImplementedError("Type modifier '{}' is not supported by backend".format(value.identifier)) + + def generateMapItemListNode(self, fieldname, value): + ### 下記のようなケースに対応 + ### selection: + ### EventID: + ### - 1 + ### - 2 + + ### 基本的にリストはORと良く、generateListNodeもORNodeを生成している。 + ### しかし、上記のケースでgenerateListNode()を実行すると、下記のようなYAMLになってしまう。 + ### selection: + ### EventID: 1 or 2 + + ### 上記のようにならないように、修正している。 + ### なお、generateMapItemListNode()を有効にするために、self.mapListsSpecialHandling = Trueとしている + list_values = list() + for sub_node in value: + list_values.append((fieldname,sub_node)) + + return self.subExpression % self.generateORNode(list_values) + + 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: + return "" + if agg.aggfunc == SigmaAggregationParser.AGGFUNC_COUNT: + # condition の中に "|" は1つのみ + # | 以降をそのまま出力する + target = '|' + index = agg.parser.parsedyaml["detection"]["condition"].find(target) + return agg.parser.parsedyaml["detection"]["condition"][index:] + + ## count以外は対応していないので、エラーを返す + raise NotImplementedError("This rule contains aggregation operator not implemented for this backend") + + def generateValueNode(self, node): + ## このメソッドをオーバーライドしておかないとint型もstr型として扱われてしまうので、int型やint型として、str型はstr型として処理するために実装した。 + ## このメソッドは最悪無くてもいいような気もする。 + + if type(node) == int: + return node + else: + return self.valueExpression % (self.cleanValue(str(node))) + + def generateQuery(self, parsed): + result = self.generateNode(parsed.parsedSearch) + if parsed.parsedAgg: + res = self.generateAggregation(parsed.parsedAgg) + result += res + ret = "" + with StringIO() as bs: + ## 元のyamlをいじるとこの後の処理に影響を与える可能性があるので、deepCopyする + parsed_yaml = copy.deepcopy(parsed.sigmaParser.parsedyaml) + ## なんかタイトルは先頭に来てほしいので、そのための処理 + ## parsed.sigmaParser.parsedyamlがOrderedDictならこんなことしなくていい、後で別のやり方があるか調べる + ## 順番固定してもいいかも + bs.write("title: " + parsed_yaml["title"]+"\n") + del parsed_yaml["title"] + + ## detectionの部分だけ変更して出力する。 + parsed_yaml["detection"] = {} + parsed_yaml["detection"]["condition"] = result + for key, values in self.name_2_selection.items(): + parsed_yaml["detection"][key] = {} + for fieldname, value in values: + parsed_yaml["detection"][key][fieldname] = value + + yaml.dump(parsed_yaml, bs, indent=4, default_flow_style=False) + ret = bs.getvalue() + + return ret