Skip to content

Commit c66f028

Browse files
committed
feat(yara): Revamp Yara scanner
The Yara scanner is revamped to perform file and memory scanning triggered by multiple signals. Aside from the basic process creation and image loading, the scan is initiated when the PE file is dropped in the file system, or when the ADS (Alternate Data Stream) is created. Memory scan is triggered under suspicious memory allocation or section mapping. Lastly, when the registry binary value is set, the scan is also performed on the binary blob.
1 parent 59662d9 commit c66f028

File tree

16 files changed

+1678
-548
lines changed

16 files changed

+1678
-548
lines changed

internal/etw/source_test.go

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,6 @@ func TestEventSourceEnableFlagsDynamicallyWithYaraEnabled(t *testing.T) {
256256
HasNetworkEvents: true,
257257
HasFileEvents: false,
258258
HasThreadEvents: false,
259-
HasVAMapEvents: true,
260259
HasAuditAPIEvents: true,
261260
UsedEvents: []ktypes.Ktype{
262261
ktypes.CreateProcess,
@@ -275,11 +274,15 @@ func TestEventSourceEnableFlagsDynamicallyWithYaraEnabled(t *testing.T) {
275274
EnableImageKevents: true,
276275
EnableFileIOKevents: true,
277276
EnableAuditAPIEvents: true,
277+
EnableVAMapKevents: false,
278+
EnableMemKevents: true,
278279
},
279280
Filters: &config.Filters{},
280281
Yara: yara.Config{
281-
Enabled: true,
282-
SkipFiles: false,
282+
Enabled: true,
283+
SkipFiles: false,
284+
SkipMmaps: true,
285+
SkipAllocs: false,
283286
},
284287
}
285288

@@ -292,8 +295,15 @@ func TestEventSourceEnableFlagsDynamicallyWithYaraEnabled(t *testing.T) {
292295
// rules compile result doesn't have file events
293296
// but Yara file scanning is enabled
294297
require.True(t, flags&etw.FileIO != 0)
298+
// VAMap events are not in the ruleset and VaMap is disabled
299+
require.False(t, flags&etw.VaMap != 0)
300+
// VirtualAlloc is not present in the ruleset, but Yara
301+
// alloc scanning is enabled
302+
require.True(t, flags&etw.VirtualAlloc != 0)
295303

296304
require.False(t, cfg.Kstream.TestDropMask(ktypes.CreateFile))
305+
require.True(t, cfg.Kstream.TestDropMask(ktypes.MapViewFile))
306+
require.False(t, cfg.Kstream.TestDropMask(ktypes.VirtualAlloc))
297307
}
298308

299309
func TestEventSourceRundownEvents(t *testing.T) {
@@ -486,7 +496,7 @@ func TestEventSourceAllEvents(t *testing.T) {
486496
var sec windows.Handle
487497
var offset uintptr
488498
var baseViewAddr uintptr
489-
dll := "../../pkg/yara/_fixtures/yara-test.dll"
499+
dll := "_fixtures/yara-test.dll"
490500
f, err := os.Open(dll)
491501
if err != nil {
492502
return err
@@ -529,7 +539,7 @@ func TestEventSourceAllEvents(t *testing.T) {
529539
return e.CurrentPid() && e.Type == ktypes.MapViewFile &&
530540
e.GetParamAsString(kparams.MemProtect) == "EXECUTE_READWRITE|READONLY" &&
531541
e.GetParamAsString(kparams.FileViewSectionType) == "IMAGE" &&
532-
strings.Contains(e.GetParamAsString(kparams.FileName), "pkg\\yara\\_fixtures\\yara-test.dll")
542+
strings.Contains(e.GetParamAsString(kparams.FileName), "_fixtures\\yara-test.dll")
533543
},
534544
false,
535545
},

pkg/config/_fixtures/fibratus.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -223,13 +223,13 @@ yara:
223223
strings:
224224
- string: "rule test : tag1 { meta: author = \"Hilko Bengen\" strings: $a = \"abc\" fullword condition: $a }"
225225
namespace: default
226-
alert-via: slack
227-
alert-template:
228-
title: ""
229-
text: ""
226+
alert-template: ""
230227
fastscan: true
231228
scan-timeout: 20s
232229
skip-files: true
230+
skip-mmaps: false
231+
skip-allocs: false
232+
skip-registry: false
233233
excluded-files:
234234
- kernel32.dll
235235
excluded-procs:

pkg/config/schema_windows.go

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -484,17 +484,12 @@ var schema = `
484484
"additionalProperties": false
485485
}]
486486
},
487-
"alert-via": {"type": "string", "enum": ["slack", "mail", "systray"]},
488-
"alert-template": {
489-
"type": "object",
490-
"properties": {
491-
"text": {"type": "string"},
492-
"title": {"type": "string"}
493-
},
494-
"additionalProperties": false
495-
},
487+
"alert-template": {"type": "string"},
496488
"fastscan": {"type": "boolean"},
497489
"skip-files": {"type": "boolean"},
490+
"skip-allocs": {"type": "boolean"},
491+
"skip-mmaps": {"type": "boolean"},
492+
"skip-registry": {"type": "boolean"},
498493
"scan-timeout": {"type": "string", "minLength": 2, "pattern": "[0-9]+s"},
499494
"excluded-files": {"type": "array", "items": [{"type": "string", "minLength": 1}]},
500495
"excluded-procs": {"type": "array", "items": [{"type": "string", "minLength": 1}]}

pkg/kevent/flags.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -211,17 +211,24 @@ var MemProtectionFlags = []ParamFlag{
211211
{"WRITECOMBINE", windows.PAGE_WRITECOMBINE},
212212
}
213213

214+
const (
215+
// SectionRX designates section read/execute protection
216+
SectionRX = 0x30000
217+
// SectionRWX designates section read/write/execute protection
218+
SectionRWX = 0x60000
219+
)
220+
214221
// ViewProtectionFlags describes section protection flags. These
215222
// have different values than the memory protection flags as they
216223
// are reported by the kernel.
217224
var ViewProtectionFlags = []ParamFlag{
218-
{"EXECUTE_READWRITE", 0x60000},
225+
{"EXECUTE_READWRITE", SectionRWX},
219226
{"EXECUTE_WRITECOPY", 0x70000},
220227
{"NOCACHE", 0x80000},
221228
{"WRITECOMBINE", 0x90000},
222229
{"READONLY", 0x10000},
223230
{"EXECUTE", 0x20000},
224-
{"EXECUTE_READ", 0x30000},
231+
{"EXECUTE_READ", SectionRX},
225232
{"READWRITE", 0x40000},
226233
{"WRITECOPY", 0x50000},
227234
}

pkg/yara/_fixtures/rules/dll.yar

Lines changed: 0 additions & 10 deletions
This file was deleted.
Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,12 @@
1-
rule Notepad : notepad
1+
rule Np : T1 T2
22
{
33
meta:
4-
severity = "Normal"
4+
severity = 50
55
date = "2016-07"
6+
threat_name = "Notepad.Shell"
7+
id = "babf9101-1e6e-4268-a530-e99e2c905b0d"
68
strings:
7-
$c0 = "Notepad" fullword ascii
9+
$c1 = "Notepad" fullword ascii
810
condition:
9-
$c0
11+
$c1
1012
}
11-
12-
rule NotepadCompany
13-
{
14-
meta:
15-
severity = "Normal"
16-
date = "2016-07"
17-
strings:
18-
$c0 = "Microsoft" fullword ascii
19-
condition:
20-
$c0
21-
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
rule Regedit : T1
2+
{
3+
meta:
4+
severity = 50
5+
date = "2016-07"
6+
threat_name = "Regedit"
7+
id = "1abf9101-1e6e-4268-a530-e99e2c905b0d"
8+
strings:
9+
$c1 = "Regedit" nocase fullword ascii
10+
condition:
11+
$c1
12+
}

pkg/yara/config/config.go

Lines changed: 98 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -19,24 +19,37 @@
1919
package config
2020

2121
import (
22+
"bytes"
23+
"fmt"
2224
"github.com/mitchellh/mapstructure"
25+
"github.com/rabbitstack/fibratus/pkg/kevent"
26+
"github.com/rabbitstack/fibratus/pkg/kevent/kparams"
27+
"github.com/rabbitstack/fibratus/pkg/kevent/ktypes"
28+
"github.com/rabbitstack/fibratus/pkg/util/wildcard"
29+
ytypes "github.com/rabbitstack/fibratus/pkg/yara/types"
2330
"github.com/spf13/pflag"
2431
"github.com/spf13/viper"
25-
"path/filepath"
2632
"strings"
33+
"text/template"
2734
"time"
2835
)
2936

3037
const (
31-
enabled = "yara.enabled"
32-
alertVia = "yara.alert-via"
33-
alertTextTemplate = "yara.alert-template.text"
34-
alertTitleTemplate = "yara.alert-template.title"
35-
fastScanMode = "yara.fastscan"
36-
scanTimeout = "yara.scan-timeout"
37-
skipFiles = "yara.skip-files"
38-
excludedProcesses = "yara.excluded-procs"
39-
excludedFiles = "yara.excluded-files"
38+
enabled = "yara.enabled"
39+
alertTemplate = "yara.alert-template"
40+
fastScanMode = "yara.fastscan"
41+
scanTimeout = "yara.scan-timeout"
42+
skipFiles = "yara.skip-files"
43+
skipAllocs = "yara.skip-allocs"
44+
skipMmaps = "yara.skip-mmaps"
45+
skipRegistry = "yara.skip-registry"
46+
excludedProcesses = "yara.excluded-procs"
47+
excludedFiles = "yara.excluded-files"
48+
)
49+
50+
const (
51+
FileThreatAlertTitle = "File Threat Detected"
52+
MemoryThreatAlertTitle = "Memory Threat Detected"
4053
)
4154

4255
// RulePath contains the rule path information.
@@ -59,39 +72,44 @@ type Rule struct {
5972
Strings []RuleString `json:"yara.rule.strings" yaml:"yara.rule.strings" mapstructure:"strings"`
6073
}
6174

62-
// Config stores YARA watcher specific configuration.
75+
// Config stores YARA scanner specific configuration.
6376
type Config struct {
6477
// Enabled indicates if YARA watcher is enabled.
6578
Enabled bool `json:"yara.enabled" yaml:"yara.enabled"`
6679
// Rule contains rule-specific settings.
6780
Rule Rule `json:"yara.rule" yaml:"yara.rule" mapstructure:"rule"`
68-
// AlertVia defines which alert sender is used to emit the alert on rule matches.
69-
AlertVia string `json:"yara.alert-via" yaml:"yara.alert-via"`
70-
// AlertTemplate defines the template that is used to render the text of the alert.
71-
AlertTextTemplate string `json:"yara.alert-text-template" yaml:"yara.alert-text-template"`
72-
// AlertTitle represents the template for the alert title
73-
AlertTitleTemplate string `json:"yara.alert-title-template" yaml:"yara.alert-title-template"`
81+
// AlertTemplate represents the template for the alert title
82+
AlertTemplate string `json:"yara.alert-template" yaml:"yara.alert-template"`
7483
// FastScanMode avoids multiple matches of the same string when not necessary.
7584
FastScanMode bool `json:"yara.fastscan" yaml:"yara.fastscan"`
7685
// ScanTimeout sets the timeout for the scanner. If the timeout is reached, the scan operation is cancelled.
7786
ScanTimeout time.Duration `json:"yara.scan-timeout" yaml:"yara.scan-timeout"`
78-
// SkipFiles indicates whether file scanning is disabled
87+
// SkipFiles indicates whether file scanning is disabled.
7988
SkipFiles bool `json:"yara.skip-files" yaml:"yara.skip-files"`
80-
// ExcludedProcesses contains the list of the process' image names that shouldn't be scanned
89+
// SkipAllocs indicates whether scanning on suspicious memory allocations is disabled.
90+
SkipAllocs bool `json:"yara.skip-allocs" yaml:"yara.skip-allocs"`
91+
// SkipMmaps indicates whether scanning on suspicious mappings of sections is disabled.
92+
SkipMmaps bool `json:"yara.skip-mmaps" yaml:"yara.skip-mmaps"`
93+
// SkipRegistry indicates whether registry value scanning is disabled.
94+
SkipRegistry bool `json:"yara.skip-registry" yaml:"yara.skip-registry"`
95+
// ExcludedProcesses contains the list of the comma-separated process image paths that shouldn't be scanned.
96+
// Wildcard matching is possible.
8197
ExcludedProcesses []string `json:"yara.excluded-procs" yaml:"yara.excluded-procs"`
82-
// ExcludedProcesses contains the list of the file names that shouldn't be scanned
98+
// ExcludedProcesses contains the list of the comma-separated file paths that shouldn't be scanned.
99+
// Wildcard matching is possible.
83100
ExcludedFiles []string `json:"yara.excluded-files" yaml:"yara.excluded-files"`
84101
}
85102

86103
// InitFromViper initializes Yara config from Viper.
87104
func (c *Config) InitFromViper(v *viper.Viper) {
88105
c.Enabled = v.GetBool(enabled)
89-
c.AlertVia = v.GetString(alertVia)
90-
c.AlertTextTemplate = v.GetString(alertTextTemplate)
91-
c.AlertTitleTemplate = v.GetString(alertTitleTemplate)
106+
c.AlertTemplate = v.GetString(alertTemplate)
92107
c.FastScanMode = v.GetBool(fastScanMode)
93108
c.ScanTimeout = v.GetDuration(scanTimeout)
94109
c.SkipFiles = v.GetBool(skipFiles)
110+
c.SkipAllocs = v.GetBool(skipAllocs)
111+
c.SkipMmaps = v.GetBool(skipMmaps)
112+
c.SkipRegistry = v.GetBool(skipRegistry)
95113
c.ExcludedFiles = v.GetStringSlice(excludedFiles)
96114
c.ExcludedProcesses = v.GetStringSlice(excludedProcesses)
97115

@@ -111,36 +129,81 @@ func (c *Config) InitFromViper(v *viper.Viper) {
111129
// AddFlags registers persistent flags.
112130
func AddFlags(flags *pflag.FlagSet) {
113131
flags.Bool(enabled, false, "Specifies if Yara scanner is enabled")
114-
flags.String(alertVia, "mail", "Defines which alert sender is used to emit the alert on rule matches")
115-
flags.String(alertTextTemplate, "", "Defines the template that is used to render the text of the alert")
116-
flags.String(alertTitleTemplate, "", "Defines the template that is used to render the title of the alert")
132+
flags.String(alertTemplate, "", "Defines the template that is used to render the alert. By default only the threat/rule name is rendered")
117133
flags.Bool(fastScanMode, true, "Avoids multiple matches of the same string when not necessary")
118134
flags.Duration(scanTimeout, time.Second*10, "Specifies the timeout for the scanner. If the timeout is reached, the scan operation is cancelled")
119-
flags.Bool(skipFiles, true, "Indicates whether file scanning is disabled")
120-
flags.StringSlice(excludedFiles, []string{}, "Contains the list of the comma-separated file names that shouldn't be scanned")
121-
flags.StringSlice(excludedProcesses, []string{}, "Contains the list of the comma-separated process' image names that shouldn't be scanned")
135+
flags.Bool(skipFiles, false, "Indicates whether file scanning is disabled")
136+
flags.Bool(skipAllocs, false, "Indicates whether scanning on suspicious memory allocations is disabled")
137+
flags.Bool(skipMmaps, false, "Indicates whether scanning on suspicious mappings of sections is disabled")
138+
flags.Bool(skipRegistry, false, "Indicates whether registry value scanning is disabled")
139+
flags.StringSlice(excludedFiles, []string{}, "Contains the list of the comma-separated file paths that shouldn't be scanned. Wildcard matching is possible")
140+
flags.StringSlice(excludedProcesses, []string{}, "Contains the list of the comma-separated process image paths that shouldn't be scanned. Wildcard matching is possible")
122141
}
123142

124-
// ShouldSkipProcess determines whether the specified process name is rejected by the scanner.
125-
func (c Config) ShouldSkipProcess(ps string) bool {
126-
for _, proc := range c.ExcludedProcesses {
127-
if strings.EqualFold(proc, ps) {
143+
// ShouldSkipProcess determines whether the specified full process image path is rejected by the scanner.
144+
// Wildcard matching is possible.
145+
func (c Config) ShouldSkipProcess(proc string) bool {
146+
for _, p := range c.ExcludedProcesses {
147+
if wildcard.Match(strings.ToLower(p), strings.ToLower(proc)) {
128148
return true
129149
}
130150
}
131151
return false
132152
}
133153

134-
// ShouldSkipFile determines whether the specified file name is rejected by the scanner.
154+
// ShouldSkipFile determines whether the specified full file path is rejected by the scanner.
135155
func (c Config) ShouldSkipFile(file string) bool {
136156
for _, f := range c.ExcludedFiles {
137-
if strings.EqualFold(f, filepath.Base(file)) {
157+
if wildcard.Match(strings.ToLower(f), strings.ToLower(file)) {
138158
return true
139159
}
140160
}
141161
return false
142162
}
143163

164+
// AlertTitle returns the brief alert title depending on
165+
// whether the process scan took place or a file/registry
166+
// key was scanned.
167+
func (c Config) AlertTitle(e *kevent.Kevent) string {
168+
if (e.Category == ktypes.File && e.Kparams.Contains(kparams.FileName)) || e.Category == ktypes.Registry {
169+
return FileThreatAlertTitle
170+
}
171+
return MemoryThreatAlertTitle
172+
}
173+
174+
// AlertText returns the short alert text if the Go template is
175+
// not specified. On the contrary, the provided Go template is
176+
// parsed and executing yielding the alert text.
177+
func (c Config) AlertText(e *kevent.Kevent, match ytypes.MatchRule) (string, error) {
178+
if c.AlertTemplate == "" {
179+
threat := match.ThreatName()
180+
if threat == "" {
181+
threat = match.Rule
182+
}
183+
return fmt.Sprintf("Threat detected %s", threat), nil
184+
}
185+
186+
var writer bytes.Buffer
187+
var data = struct {
188+
Match ytypes.MatchRule
189+
Event *kevent.Kevent
190+
}{
191+
match,
192+
e,
193+
}
194+
195+
tmpl, err := template.New("yara").Parse(c.AlertTemplate)
196+
if err != nil {
197+
return "", fmt.Errorf("yara alert template syntax error: %v", err)
198+
}
199+
err = tmpl.Execute(&writer, data)
200+
if err != nil {
201+
return "", fmt.Errorf("couldn't execute yara alert template: %v", err)
202+
}
203+
204+
return writer.String(), nil
205+
}
206+
144207
func decode(input, output interface{}) error {
145208
var decoderConfig = &mapstructure.DecoderConfig{
146209
Metadata: nil,

0 commit comments

Comments
 (0)