Skip to content

Commit 2fd5fd5

Browse files
authored
feat(yara): Rule matches as tags in event metadata (#103)
* YARA rule matches as tags in event metadata * add space
1 parent fd39902 commit 2fd5fd5

File tree

8 files changed

+130
-24
lines changed

8 files changed

+130
-24
lines changed

docs/_coverpage.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<img src='logo.png'></img>
55
</div>
66

7-
# fibratus <small>1.4.1</small>
7+
# fibratus <small>1.4.2</small>
88

99
> A modern tool for the Windows kernel exploration and observability
1010

docs/yara/alerts.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,3 +104,40 @@ Resources:
104104
ProductName: Microsoft® Windows® Operating System
105105
ProductVersion: 10.0.18362.693
106106
```
107+
108+
## Event metadata {docsify-ignore}
109+
110+
When the event triggers a specific YARA rule, its metadata is automatically decorated with the rule matches.
111+
The `yara.matches` tag contains the JSON array payload where each object represents the YARA rule match. For example:
112+
113+
```json
114+
[
115+
{
116+
"Rule": "AnglerEKredirector ",
117+
"Namespace": "EK",
118+
"Tags": null,
119+
"Metas": [
120+
{
121+
"Identifier": "description",
122+
"Value": "Angler Exploit Kit Redirector"
123+
}
124+
],
125+
"Strings": "..."
126+
},
127+
{
128+
"Rule": "angler_flash_uncompressed ",
129+
"Namespace": "EK",
130+
"Tags": [
131+
"exploitkit"
132+
],
133+
"Metas": [
134+
{
135+
"Identifier": "description",
136+
"Value": "Angler Exploit Kit Detection"
137+
}
138+
],
139+
"Strings": "..."
140+
}
141+
]
142+
```
143+

pkg/filter/rules.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ import (
3232
"text/template"
3333
)
3434

35-
3635
const (
3736
// ruleNameMeta identifies the rule that was triggered by the event
3837
ruleNameMeta = "rule.name"

pkg/kstream/interceptors/image_windows.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ package interceptors
2020

2121
import (
2222
"expvar"
23+
2324
"github.com/rabbitstack/fibratus/pkg/fs"
2425
"github.com/rabbitstack/fibratus/pkg/kevent"
2526
"github.com/rabbitstack/fibratus/pkg/kevent/kparams"
@@ -67,7 +68,7 @@ func (i *imageInterceptor) Intercept(kevt *kevent.Kevent) (*kevent.Kevent, bool,
6768
// scan the the target filename
6869
go func() {
6970
imageYaraScans.Add(1)
70-
err := i.yara.ScanFile(filename)
71+
err := i.yara.ScanFile(filename, kevt)
7172
if err != nil {
7273
log.Warnf("unable to run yara scanner on %s image: %v", filename, err)
7374
}

pkg/kstream/interceptors/ps_windows.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,19 @@ package interceptors
2020

2121
import (
2222
"expvar"
23+
"os"
24+
"path/filepath"
25+
"regexp"
26+
"strings"
27+
"time"
28+
2329
"github.com/rabbitstack/fibratus/pkg/kevent"
2430
"github.com/rabbitstack/fibratus/pkg/kevent/kparams"
2531
"github.com/rabbitstack/fibratus/pkg/kevent/ktypes"
2632
"github.com/rabbitstack/fibratus/pkg/ps"
2733
"github.com/rabbitstack/fibratus/pkg/syscall/process"
2834
"github.com/rabbitstack/fibratus/pkg/yara"
2935
log "github.com/sirupsen/logrus"
30-
"os"
31-
"path/filepath"
32-
"regexp"
33-
"strings"
34-
"time"
3536
)
3637

3738
// systemRootRegexp is the regular expression for detecting path with unexpanded SystemRoot environment variable
@@ -122,7 +123,7 @@ func (ps psInterceptor) Intercept(kevt *kevent.Kevent) (*kevent.Kevent, bool, er
122123
// run yara scanner on the target process
123124
go func() {
124125
procYaraScans.Add(1)
125-
err := ps.yara.ScanProc(pid)
126+
err := ps.yara.ScanProc(pid, kevt)
126127
if err != nil {
127128
log.Warnf("unable to run yara scanner on pid %d: %v", pid, err)
128129
}

pkg/yara/scanner.go

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,23 +23,29 @@ package yara
2323

2424
import (
2525
"bytes"
26+
"encoding/json"
2627
"expvar"
2728
"fmt"
29+
"html/template"
30+
"os"
31+
"path/filepath"
32+
"time"
33+
2834
"github.com/hillu/go-yara/v4"
2935
"github.com/rabbitstack/fibratus/pkg/alertsender"
36+
"github.com/rabbitstack/fibratus/pkg/kevent"
3037
"github.com/rabbitstack/fibratus/pkg/ps"
3138
pstypes "github.com/rabbitstack/fibratus/pkg/ps/types"
3239
"github.com/rabbitstack/fibratus/pkg/util/multierror"
3340
"github.com/rabbitstack/fibratus/pkg/yara/config"
3441
log "github.com/sirupsen/logrus"
35-
"html/template"
36-
"os"
37-
"path/filepath"
38-
"time"
3942
)
4043

4144
const alertTitleTmpl = `{{if .PS }}YARA alert on process {{ .PS.Name }}{{ else }}YARA alert on file {{ .Filename }}{{ end }}`
4245

46+
// matchesMeta is the tag name for the yara matches
47+
const matchesMeta = "yara.matches"
48+
4349
var (
4450
// ruleMatches computes all the rule matches
4551
ruleMatches = expvar.NewInt("yara.rule.matches")
@@ -159,7 +165,7 @@ func parseCompilerErrors(errors []yara.CompilerMessage) error {
159165
return multierror.Wrap(errs...)
160166
}
161167

162-
func (s scanner) ScanProc(pid uint32) error {
168+
func (s scanner) ScanProc(pid uint32, kevt *kevent.Kevent) error {
163169
proc := s.psnap.Find(pid)
164170
if proc == nil {
165171
return fmt.Errorf("cannot scan proc. pid %d does not exist in snapshotter", pid)
@@ -183,15 +189,20 @@ func (s scanner) ScanProc(pid uint32) error {
183189
}
184190
ruleMatches.Add(int64(len(matches)))
185191

192+
if err := putMatchesMeta(matches, kevt); err != nil {
193+
return err
194+
}
195+
186196
ctx := AlertContext{
187197
PS: proc,
188198
Matches: matches,
189199
Timestamp: time.Now().Format(tsLayout),
190200
}
201+
191202
return s.send(ctx)
192203
}
193204

194-
func (s scanner) ScanFile(filename string) error {
205+
func (s scanner) ScanFile(filename string, kevt *kevent.Kevent) error {
195206
if s.config.SkipFiles || s.config.ShouldSkipFile(filename) {
196207
return nil
197208
}
@@ -210,6 +221,10 @@ func (s scanner) ScanFile(filename string) error {
210221
}
211222
ruleMatches.Add(int64(len(matches)))
212223

224+
if err := putMatchesMeta(matches, kevt); err != nil {
225+
return err
226+
}
227+
213228
ctx := AlertContext{
214229
Filename: filename,
215230
Matches: matches,
@@ -281,6 +296,16 @@ func tagsFromMatches(matches []yara.MatchRule) []string {
281296
return tags
282297
}
283298

299+
// putMatchesMeta injects rule matches into event metadata as a JSON payload.
300+
func putMatchesMeta(matches yara.MatchRules, kevt *kevent.Kevent) error {
301+
b, err := json.Marshal(matches)
302+
if err != nil {
303+
return nil
304+
}
305+
kevt.AddMeta(matchesMeta, string(b))
306+
return nil
307+
}
308+
284309
func (s scanner) Close() {
285310
if s.c != nil {
286311
s.c.Destroy()

pkg/yara/scanner_test.go

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,16 @@
2222
package yara
2323

2424
import (
25+
"os"
26+
"path/filepath"
27+
"syscall"
28+
"testing"
29+
"time"
30+
31+
"github.com/hillu/go-yara/v4"
32+
"github.com/rabbitstack/fibratus/pkg/kevent"
33+
"github.com/rabbitstack/fibratus/pkg/kevent/ktypes"
34+
2535
"github.com/rabbitstack/fibratus/pkg/alertsender"
2636
htypes "github.com/rabbitstack/fibratus/pkg/handle/types"
2737
"github.com/rabbitstack/fibratus/pkg/kevent/kparams"
@@ -34,11 +44,6 @@ import (
3444
"github.com/stretchr/testify/assert"
3545
"github.com/stretchr/testify/mock"
3646
"github.com/stretchr/testify/require"
37-
"os"
38-
"path/filepath"
39-
"syscall"
40-
"testing"
41-
"time"
4247
)
4348

4449
var yaraAlert *alertsender.Alert
@@ -167,8 +172,19 @@ func TestScan(t *testing.T) {
167172
time.Sleep(time.Millisecond * 100)
168173
}
169174

175+
kevt := &kevent.Kevent{
176+
Type: ktypes.CreateProcess,
177+
Name: "CreateProcess",
178+
Tid: 2484,
179+
PID: 859,
180+
Kparams: kevent.Kparams{
181+
kparams.ProcessName: {Name: kparams.ProcessName, Type: kparams.UnicodeString, Value: "svchost.exe"},
182+
},
183+
Metadata: make(map[string]string),
184+
}
185+
170186
// test attaching on pid
171-
require.NoError(t, s.ScanProc(pi.ProcessId))
187+
require.NoError(t, s.ScanProc(pi.ProcessId, kevt))
172188
require.NotNil(t, yaraAlert)
173189

174190
assert.Equal(t, "YARA alert on process notepad.exe", yaraAlert.Title)
@@ -177,10 +193,35 @@ func TestScan(t *testing.T) {
177193

178194
// test file scanning on DLL that merely contains
179195
// the fmt.Println("Go Yara DLL Test") statement
180-
require.NoError(t, s.ScanFile("_fixtures/yara-test.dll"))
196+
require.NoError(t, s.ScanFile("_fixtures/yara-test.dll", kevt))
181197
require.NotNil(t, yaraAlert)
182198

183199
assert.Equal(t, "YARA alert on file _fixtures/yara-test.dll", yaraAlert.Title)
184200
assert.Contains(t, yaraAlert.Tags, "dll")
185201

186202
}
203+
204+
func TestMatchesMeta(t *testing.T) {
205+
yaraMatches := []yara.MatchRule{
206+
{Rule: "test", Namespace: "ns1"},
207+
{Rule: "test2", Namespace: "ns2", Tags: []string{"dropper"}, Metas: []yara.Meta{{Identifier: "author", Value: "rabbit"}}},
208+
}
209+
210+
kevt := &kevent.Kevent{
211+
Type: ktypes.CreateProcess,
212+
Name: "CreateProcess",
213+
Tid: 2484,
214+
PID: 859,
215+
Kparams: kevent.Kparams{
216+
kparams.ProcessName: {Name: kparams.ProcessName, Type: kparams.UnicodeString, Value: "svchost.exe"},
217+
},
218+
Metadata: make(map[string]string),
219+
}
220+
221+
assert.Empty(t, kevt.Metadata)
222+
223+
putMatchesMeta(yaraMatches, kevt)
224+
225+
assert.NotEmpty(t, kevt.Metadata)
226+
assert.Contains(t, kevt.Metadata, matchesMeta)
227+
}

pkg/yara/types.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,16 @@
1818

1919
package yara
2020

21+
import "github.com/rabbitstack/fibratus/pkg/kevent"
22+
2123
// Scanner watches for certain kernel events such as process creation or image loading and
2224
// triggers the scanning either of the target process or image file. If matches occur, an
2325
// alert is emitted via specified alert sender.
2426
type Scanner interface {
2527
// ScanProc scans process memory.
26-
ScanProc(pid uint32) error
28+
ScanProc(pid uint32, kevt *kevent.Kevent) error
2729
// ScanFile scans the specified file in the file system.
28-
ScanFile(filename string) error
30+
ScanFile(filename string, kevt *kevent.Kevent) error
2931
// Close disposes any resources allocated by scanner.
3032
Close()
3133
}

0 commit comments

Comments
 (0)