Writing Rules¶
Gene rules have been designed to be simple to understand and to parse. In order not to have to develop a custom rule parser, we have decided to use JSON document as container. Thus every rule, to work properly, has to follow a specific format which is going to be described in this page.
Getting the format supported by the engine¶
A starting point to understand the format of a rule is to use the gene
command
line utility to get the format supported by your engine:
gene -template
This command line should return a JSON document containing all the fields used
by your current version of gene
.
Note
When dealing with JSON documents in command line there is an amazing utility
that you must try out if you do not know it yet. This tool is called jq
. If
you are running Linux you can probably install it from your package manager,
otherwise visit jq
Rule Structure¶
{
"Name": "",
"Tags": [],
"Meta": {
"Events": {},
"Computers": [],
"ATTACK": [
{
"ID": "",
"Tactic": "",
"Reference": ""
}
],
"Criticality": 0,
"Disable": false,
"Filter": false,
"Schema": "2.0.0"
},
"Matches": [],
"Condition": "",
"Actions": []
}
Note
The fields present in the template shown above are the ones used by the engine. It means that any additional fields will not impact the engine. This trick can be used to document the rule. It is a good practice to add information such as Author, Comments and eventual Links in the Meta section.
Field | Type | Description |
---|---|---|
Name | string | Name of the rule |
Tags | []string | Contains a list of tags related to the rule. It can be used to group rules according to their tag(s). |
Meta | dict | Contains a bunch of information related the trigger of the rule. The information in there is used to match against the “System” section of the Windows events to speed up the match. |
Events | map[string][]int | List of Windows Event IDs the rule should match
against. If empty the rule will apply against any
Event ID of the Channels (c.f. see next) |
Computers | []string | List of computer names the rule should apply on. If empty, the rule applies on all the computers. |
ATTACK | []map[string]string | List of ATT&CK techniques corresponding to the detection rule. See MITRE ATT&CK Integration. |
Criticality | 0 < int < 10 | The criticality level attributed to the events matching the rule. If an event matches several rules the criticality levels are added between them and will never go above 10. |
Disable | bool | Boolean value used to disable the rule. |
Filter | bool | Boolean value used to flag this rule as being a filter. A filter rule is used to filter in some wanted events without assigning any criticality to them. It can be used to show events bringing contextual information. |
Schema | string | Schema version of the rule. This has been introduced to solve incompatibility issues between the engine and the rules. |
Matches | []string | List of Matches, should follow the syntax of Matches Format |
Condition | string | String implementing the logic on the Matches to trigger the rule. The syntax should be compliant with Condition Format |
Actions | []string | This field is used to encode Actions to be taken when the rule triggers. It is up to the code making use of the Gene engine to implement action handlers. Gene command-line utility does not implement any action handler. |
Important
The more precise Events field, the faster the rule is. This information is used to pre-filter relevant events.
Matches Format¶
A Match can be seen as an atomic check which is done on every Windows Event (pre-filtered using Meta section of the rule) going through the engine. Every match can be referenced once or more in the Condition to create complex matching rule. Currently, the latest version of the engine supports two kinds of Matches.
Important
It is very important to remember that Matches only apply on the fields
located under the EventData
section of Windows Events.
Field Matches¶
Warning
Indirect Match expressions are only available since v1.6
A Field Match is basically an equality or a regex check done on a given field value. This kind of Match brings flexibility to the engine since anything can be matched through regular expression. Field Matches come in two flavours namely Direct and Indirect. A Direct Match is used to match against values (regex, strings …) know in advance when the rule is written. An Indirect Match aims at matching against a value present in another field of the event.
Direct Match Syntax: $VAR_NAME: FIELD OPERATOR 'VALUE'
Indirect Match Syntax: $VAR_NAME: FIELD = @OTHER_FIELD
Symbols | Description |
---|---|
VAR_NAME | Name of the variable use to access the result of the Match
in the Condition, it must be preceded by a $ |
FIELD | OTHER_FIELD | Field to match with in EventData section of Windows Events |
OPERATOR |
|
VALUE | Must be surrounded by simple quotes ' . This is the
value/regex to match against to make $VAR_NAME = true |
Match Workflow:
+-------+ +---------+
| Event | | Match |
+-------+ +---------+
| +----------+ |
+----> | Engine | <----+
+----------+
|
+---------------------------+
| Extracts value from FIELD |
+---------------------------+
|
+---------------------------+
| Does value match VALUE |
| according to OPERATOR ? |
+---------------------------+
|
^
YES / \ NO
/ \
+------------------+ +-------------------+
| $VAR_NAME = true | | $VAR_NAME = false |
+------------------+ +-------------------+
\ /
\ /
v
|
+--------------------+
| $VAR_NAME value is |
| used in condition |
+--------------------+
Note
Any regular expression must follow Go regexp syntax.
Example¶
The following snippet shows a rule used to catch Windows Event log clearing attempts
using wevtutil.exe
.
{
"Name": "EventClearing",
"Tags": [
"PostExploit"
],
"Meta": {
"Events": {
"Microsoft-Windows-Sysmon/Operational": [
1
]
},
"ATTACK": [
{
"ID": "T1070",
"Tactic": "defense-evasion",
"Reference": "https://attack.mitre.org/techniques/T1070"
}
],
"Criticality": 8,
"Schema": "2.0.0"
},
"Matches": [
"$im: Image ~= '(?i:\\\\wevtutil\\.exe$)'",
"$cmd: CommandLine ~= '(?i: cl | clear-log )'"
],
"Condition": "$im and $cmd",
"Actions": null
}
Warning
- Windows path separator
\
escaping: - When using
=~
operator: needs to be escaped twice\\\\
(one for JSON and one for regex parsers) - When using
=
operator: needs to be escaped once\\
(for JSON parser)
- When using
The following additional example shows how to detect a suspicious access to lsass.exe
with the help
of the &=
operator. Basically, we want to trigger this alert on any ProcessAccess
events targeting lsass.exe
where the GrantedAccess contains process
read access flag 0x10.
{
"Name": "SuspiciousLsassAccess",
"Tags": [
"Mimikatz",
"Credentials",
"Lsass"
],
"Meta": {
"Events": {
"Microsoft-Windows-Sysmon/Operational": [
10
]
},
"ATTACK": [
{
"ID": "T1003",
"Tactic": "Credential Access",
"Reference": "https://attack.mitre.org/techniques/T1003/"
}
],
"Criticality": 8,
"Schema": "2.0.0"
},
"Matches": [
"$ctwdef: CallTrace ~= '(?i:windows defender)'",
"$ga: GrantedAccess &= '0x10'",
"$lsass: TargetImage ~= '(?i:\\\\lsass\\.exe$)'",
"$wmiprvse: SourceImage ~= '(?i:(?i:C:\\\\Windows\\\\Sys(wow64|tem32)\\\\)wbem\\\\wmiprvse\\.exe)'",
"$taskmgr: SourceImage ~= '(?i:(?i:C:\\\\Windows\\\\Sys(wow64|tem32)\\\\)taskmgr\\.exe)'",
"$boot: SourceImage ~= '(?i:C:\\\\Windows\\\\system32\\\\(wininit|csrss)\\.exe)'"
],
"Condition": "$lsass and $ga and !($ctwdef or $wmiprvse or $taskmgr or $boot)",
}
Container Matches¶
A Container Match is a little bit more advanced since it can be used to extract a part of a field value and check it against a container. For instance, with this kind of Match, we are able to extract a domain information contained in Windows DNS-Client logs and check it against a blacklist. Although, implementing this use case would be possible with Field Matches, it would be much slower due to regex engine. In addition the rule would need to be updated at every new entry to check. With Container Match only the container (a simple separate file) needs to be updated. The speed is provided by the container being implemented in a form of a set data structure.
Syntax: $VAR_NAME: extract('REGEXP', FIELD) in CONTAINER
Symbols | Description |
---|---|
VAR_NAME | Name of the variable used to access the result of the Match
in the Condition, it must be preceded by a $ |
FIELD | Field to extract from |
REGEXP | Regular expression used to extract a value from FIELD and check
it against a CONTAINER. REGEXP must follow named
regexp syntax (?P<name>re) |
CONTAINER | Container to use to check the extracted value |
Important
- If a rule makes use of an undefined container, the rule will be disabled at runtime and a warning message will be printed.
- A given container is shared across all the rules loaded into the engine
- Any regular expression must follow Go regexp syntax.
Example¶
This rule shows an example of how to extract domains and sub-domains from Windows DNS-Client logs and check it against a blacklist.
{
"Name": "BlacklistedDomain",
"Tags": [
"DNS"
],
"Meta": {
"Events": {
"Microsoft-Windows-DNS-Client/Operational": []
},
"Criticality": 10,
"Schema": "2.0.0"
},
"Matches": [
"$domainBL: extract('(?P<dom>\\w+\\.\\w+$)',QueryName) in blacklist'",
"$subdomainBL: extract('(?P<sub>\\w+\\.\\w+\\.\\w+$)',QueryName) in blacklist'",
"$subsubdomainBL: extract('(?P<subsub>\\w+\\.\\w+\\.\\w+\\.\\w+$)',QueryName) in blacklist'"
],
"Condition": "$domainBL or $subdomainBL or $subsubdomainBL",
}
Condition Format¶
A condition applies a logic to the different Matches defined in the rule. If the result of the computation of the Condition is true the event is considered as matching the rule.
Symbols | Description |
---|---|
$var |
Variable referencing a Match |
() |
Used to group / prioritize some logical expressions |
! |
Negates a Match or a grouped expression |
AND |
AND logical operator |
and |
|
&& |
|
OR |
OR logical operator |
or |
|
|| |
Important
Matches are evaluated in real time, in the same order their variables appear in the Condition. So the variables order has an impact on the rule speed. A good practice is to put first selective Matches to abort condition evaluation as soon as possible and prevent useless Matches to happen.
Example¶
The following rule is used to match suspicious explicit network logons, we can
see an example of a rule where the order of the variables in the condition matters.
In this case we first match on LogonType
, this makes the condition aborting after
the first evaluation (as it is mandatory for the condition to be met) for every other LogonType
than 3.
{
"Name": "ExplicitNetworkLogon",
"Tags": [
"Lateral",
"Security"
],
"Meta": {
"Events": {
"Security": [
4624
]
},
"Criticality": 5,
"Schema": "2.0.0"
},
"Matches": [
"$logt: LogonType = '3'",
"$user: TargetUserName = 'ANONYMOUS LOGON'",
"$iplh1: IpAddress = '-'",
"$iplh2: IpAddress = '127.0.0.1'",
"$enddol: TargetUserName ~= '\\$$'"
],
"Condition": "$logt and !($user or $iplh1 or $iplh2 or $enddol)",
}
Regular Expression Templates¶
Warning
Templates use TOML format (c.f. https://toml.io/en/) since v2.0.0
Regex templates have been introduced to remove the burden of maintaining rules
sharing the same regular expressions. Let’s take a common example of suspicious
binaries we want to create rules on, a basic matching regex would look like this
(?i:\\(certutil|rundll32)\.exe)
. Assuming this regex is used in several rules,
it is a big burden to update all of them once we want to add a new executable name
in this list. So the idea behind regex template is to centralize such shared regex
inside configuration file(s) for easier maintainance.
File Extension: .toml
(must be located in rule directory we are using)
- Syntax
- definition:
TEMPLATE_NAME = 'REGULAR_EXPRESSION'
- usage in Match:
{{TEMPLATE_NAME}}
- definition:
Example¶
There is an example of a few regex templates
# Extensions
script-exts = '(?i:(\.ps1|\.bat|\.cmd|\.vb|\.vbs|\.vbscript|\.vbe|\.js|\.jse|\.ws|\.wsf))'
exec-exts = '(?i:(\.acm|\.ax|\.com|\.cpl|\.dic|\.dll|\.drv|\.ds|\.efi|\.exe|\.grm|\.iec|\.ime|\.lex|\.msstyles|\.mui|\.ocx|\.olb|\.rll|\.rs|\.scr|\.sys|\.tlb|\.tsp|\.winmd|\.node))'
# Exe to monitor
suspicious = '(?i:\\(certutil|rundll32|powershell|wscript|cscript|cmd|mshta|regsvr32|msbuild|installutil|regasm)\.exe)'
Important
- Only Golang regexp special characters need to be escaped.
- Windows path separator
\
needs to be escaped only once (i.e.\\
) in template definitions.
- Windows path separator
To make use of the template previously defined
{
"Name": "HeurDropper",
"Tags": [
"Heuristics",
"CreateFile"
],
"Meta": {
"Events": {
"Microsoft-Windows-Sysmon/Operational": [
11
]
},
"Criticality": 8,
"Author": "0xrawsec",
"Comments": "Experimental rule to detect executable files dropped by common utilities",
"Schema": "2.0.0"
},
"Matches": [
"$susp: Image ~= '{{suspicious}}$'",
"$target: TargetFilename ~= '({{exec-exts}}|{{script-exts}})$'",
"$poltest: TargetFilename ~= '(?i:C:\\\\Users\\\\.*?\\\\AppData\\\\Local\\\\Temp\\\\__PSScriptPolicyTest_.*?\\.ps1)'"
],
"Condition": "$susp and $target and !$poltest"
}
In order to debug the rules using templates, we have introduced a new feature
in the gene
command line utility. One can use the -dump
command line switch
to dump the rule as it is after template replacement.
> gene -dump HeurDropper -r ./gene-rules | jq
{
"Name": "HeurDropper",
"Tags": [
"Heuristics",
"CreateFile"
],
"Meta": {
"Events": {
"Microsoft-Windows-Sysmon/Operational": [
11
]
},
"Criticality": 8,
"Schema": "2.0.0"
},
"Matches": [
"$susp: Image ~= '(?i:\\\\(certutil|rundll32|powershell|wscript|cscript|cmd|mshta|regsvr32|msbuild|installutil|regasm)\\.exe)$'",
"$target: TargetFilename ~= '((?i:(\\.acm|\\.ax|\\.com|\\.cpl|\\.dic|\\.dll|\\.drv|\\.ds|\\.efi|\\.exe|\\.grm|\\.iec|\\.ime|\\.lex|\\.msstyles|\\.mui|\\.ocx|\\.olb|\\.rll|\\.rs|\\.scr|\\.sys|\\.tlb|\\.tsp|\\.winmd|\\.node))|(?i:(\\.ps1|\\.bat|\\.cmd|\\.vb|\\.vbs|\\.vbscript|\\.vbe|\\.js|\\.jse|\\.ws|\\.wsf)))$'",
"$poltest: TargetFilename ~= '(?i:C:\\\\Users\\\\.*?\\\\AppData\\\\Local\\\\Temp\\\\__PSScriptPolicyTest_.*?\\.ps1)'"
],
"Condition": "$susp and $target and !$poltest",
}
Note
As you can see in the dumped rule, the simple \
becomes \\
, this is due
to JSON special characters’ encoding.
Note
See how easy it is now, just to add a new extension to the list so that it impacts all the rules using this template.
MITRE ATT&CK Integration¶
Gene has full support for the MITRE ATT&CK framework through the ATTACK field of the Meta section of the rule definition. What is documented there is purely informational and can be displayed in the alerts reported.
Example¶
Given the following rule matching suspicious ADS creation.
{
"Name": "ExecutableADS",
"Tags": [
"ADS"
],
"Meta": {
"Events": {
"Microsoft-Windows-Sysmon/Operational": [
15
]
},
"ATTACK": [
{
"ID": "T1096",
"Tactic": "defense-evasion",
"Reference": "https://attack.mitre.org/techniques/T1096"
}
],
"Criticality": 10,
"Schema": "2.0.0"
},
"Matches": [
"$unk: Hash = 'Unknown'",
"$impash: Hash ~= '(?i:(IMPHASH=00000000000000000000000000000000))'"
],
"Condition": "!($impash or $unk)",
}
The alert reported would look like the following.
{
"Event": {
"EventData": {
"CreationUtcTime": "2018-02-23 13:17:31.176",
"Hash": "SHA1=E8B4D84A28E5EA17272416EC45726964FDF25883,MD5=09F7401D56F2393C6CA534FF0241A590,SHA256=6766717B8AFAFE46B5FD66C7082CCCE6B382CBEA982C73CB651E35DC8187ACE1,IMPHASH=68E56344CAB250384904953E978B70A9",
"Image": "C:\\Windows\\system32\\cmd.exe",
"ProcessGuid": "{49F1AF32-12C5-5A90-0000-00100AEA0B00}",
"ProcessId": "2100",
"TargetFilename": "C:\\Users\\CALDUS~1\\AppData\\Local\\Temp\\test.txt:malicious.exe",
"UtcTime": "2018-02-23 13:17:31.192"
},
"GeneInfo": {
"ATTACK": [
{
"ID": "T1096",
"Tactic": "defense-evasion",
"Reference": "https://attack.mitre.org/techniques/T1096"
}
],
"Criticality": 10,
"Signature": [
"ExecutableADS"
]
},
"System": {
"...": "..."
}
}
}