Skip to content

Commit ccb3616

Browse files
committed
feat(filter): foreach function
Foreach adds iteration capabilities to the rule language. The decision to keep the function implementation outside the functions package is deliberate. The function mostly operates with raw expressions, and if the function lived in the functions package, that would create a cyclic import, but also likely to unleash more painful side effects. For the sake of simplicity it is better to keep the function close to the parser and AST evaluation. Foreach accepts three required and multiple optional arguments. The first argument is the iterable value typically yielded by the pseudo field. The function recognizes process internal state collections such as modules, threads, memory mappings, or thread stack frames. Obviously, it is also possible to iterate over a simple string slice. The second argument represents the bound variable which is an item associated with every element in the slice. The bound variable is accessed in the third argument, the predicate. It is usually followed by the segment that denotes the accessed value. Unsurprisingly, the predicate is commonly a binary expression which can be formed of not/paren expressions, other functions, and so on. The predicate is executed on every item in the slice. If the predicate evaluates to true, the function also returns the true value. Lastly, foreach function can receive an optional list of fields from the outer context, i.e. outside predicate loop. Therefore, for the predicate to access the field not defined within the scope of the iterable, it must capture the field first. Note that the side effect of introducing the foreach function is observed in the form of deprecation of previous segment/paths fields. This trend will follow in subsequent pull requests, untangling and overly simplifying the accessor codebase.
1 parent 21eb54b commit ccb3616

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+1052
-559
lines changed

pkg/filter/accessor_windows.go

Lines changed: 33 additions & 227 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,8 @@ import (
2828
"github.com/rabbitstack/fibratus/pkg/util/cmdline"
2929
"github.com/rabbitstack/fibratus/pkg/util/loldrivers"
3030
"github.com/rabbitstack/fibratus/pkg/util/signature"
31-
"golang.org/x/sys/windows"
3231
"net"
3332
"path/filepath"
34-
"strconv"
3533
"strings"
3634
"time"
3735

@@ -266,7 +264,7 @@ func (ps *psAccessor) Get(f fields.Field, kevt *kevent.Kevent) (kparams.Value, e
266264
envs = append(envs, env)
267265
}
268266
return envs, nil
269-
case fields.PsModules:
267+
case fields.PsModuleNames:
270268
ps := kevt.PS
271269
if ps == nil {
272270
return nil, ErrPsNil
@@ -305,7 +303,7 @@ func (ps *psAccessor) Get(f fields.Field, kevt *kevent.Kevent) (kparams.Value, e
305303
return nil, ErrPsNil
306304
}
307305
return proc.UUID(), nil
308-
case fields.PsHandles:
306+
case fields.PsHandleNames:
309307
ps := kevt.PS
310308
if ps == nil {
311309
return nil, ErrPsNil
@@ -439,6 +437,32 @@ func (ps *psAccessor) Get(f fields.Field, kevt *kevent.Kevent) (kparams.Value, e
439437
return nil, ErrPsNil
440438
}
441439
return ps.IsProtected, nil
440+
case fields.PsAncestors:
441+
if kevt.PS != nil {
442+
ancestors := make([]*pstypes.PS, 0)
443+
walk := func(proc *pstypes.PS) {
444+
ancestors = append(ancestors, proc)
445+
}
446+
pstypes.Walk(walk, kevt.PS)
447+
448+
return ancestors, nil
449+
}
450+
return nil, ErrPsNil
451+
case fields.PsModules:
452+
if kevt.PS != nil {
453+
return kevt.PS.Modules, nil
454+
}
455+
return nil, ErrPsNil
456+
case fields.PsThreads:
457+
if kevt.PS != nil {
458+
return kevt.PS.Threads, nil
459+
}
460+
return nil, ErrPsNil
461+
case fields.PsMmaps:
462+
if kevt.PS != nil {
463+
return kevt.PS.Mmaps, nil
464+
}
465+
return nil, ErrPsNil
442466
default:
443467
switch {
444468
case f.IsEnvsMap():
@@ -458,131 +482,12 @@ func (ps *psAccessor) Get(f fields.Field, kevt *kevent.Kevent) (kparams.Value, e
458482
return v, nil
459483
}
460484
}
461-
case f.IsModsMap():
462-
name, segment := captureInBrackets(f.String())
463-
ps := kevt.PS
464-
if ps == nil {
465-
return nil, ErrPsNil
466-
}
467-
mod := ps.FindModule(name)
468-
if mod == nil {
469-
return nil, nil
470-
}
471-
472-
switch segment {
473-
case fields.ModuleSize:
474-
return mod.Size, nil
475-
case fields.ModuleChecksum:
476-
return mod.Checksum, nil
477-
case fields.ModuleBaseAddress:
478-
return mod.BaseAddress.String(), nil
479-
case fields.ModuleDefaultAddress:
480-
return mod.DefaultBaseAddress.String(), nil
481-
case fields.ModuleLocation:
482-
return filepath.Dir(mod.Name), nil
483-
}
484-
case f.IsAncestorMap():
485-
return ancestorFields(f.String(), kevt)
486485
}
487486

488487
return nil, nil
489488
}
490489
}
491490

492-
const (
493-
rootAncestor = "root" // represents the root ancestor
494-
anyAncestor = "any" // represents any ancestor in the hierarchy
495-
frameUEnd = "uend" // represents the last user space stack frame
496-
frameUStart = "ustart" // represents the first user space stack frame
497-
frameKEnd = "kend" // represents the last kernel space stack frame
498-
frameKStart = "kstart" // represents the first kernel space stack frame
499-
)
500-
501-
// ancestorFields recursively walks the process ancestors and extracts
502-
// the required field values. If we get the `root` key, the root ancestor
503-
// fields are inspected, while `any` accumulates values of all ancestors.
504-
// Alternatively, the key may represent the depth that only returns the
505-
// ancestor located at the given depth, starting with 1 which is the immediate
506-
// process parent.
507-
func ancestorFields(field string, kevt *kevent.Kevent) (kparams.Value, error) {
508-
key, segment := captureInBrackets(field)
509-
if key == "" || segment == "" {
510-
return nil, nil
511-
}
512-
513-
var ps *pstypes.PS
514-
515-
switch key {
516-
case rootAncestor:
517-
walk := func(proc *pstypes.PS) {
518-
ps = proc
519-
}
520-
pstypes.Walk(walk, kevt.PS)
521-
case anyAncestor:
522-
values := make([]string, 0)
523-
walk := func(proc *pstypes.PS) {
524-
switch segment {
525-
case fields.ProcessName:
526-
values = append(values, proc.Name)
527-
case fields.ProcessID:
528-
values = append(values, strconv.Itoa(int(proc.PID)))
529-
case fields.ProcessSID:
530-
values = append(values, proc.SID)
531-
case fields.ProcessSessionID:
532-
values = append(values, strconv.Itoa(int(proc.SessionID)))
533-
case fields.ProcessCwd:
534-
values = append(values, proc.Cwd)
535-
case fields.ProcessCmdline:
536-
values = append(values, proc.Cmdline)
537-
case fields.ProcessArgs:
538-
values = append(values, proc.Args...)
539-
case fields.ProcessExe:
540-
values = append(values, proc.Exe)
541-
}
542-
}
543-
pstypes.Walk(walk, kevt.PS)
544-
return values, nil
545-
default:
546-
depth, err := strconv.Atoi(key)
547-
if err != nil {
548-
return nil, err
549-
}
550-
var i int
551-
walk := func(proc *pstypes.PS) {
552-
i++
553-
if i == depth {
554-
ps = proc
555-
}
556-
}
557-
pstypes.Walk(walk, kevt.PS)
558-
}
559-
560-
if ps == nil {
561-
return nil, nil
562-
}
563-
564-
switch segment {
565-
case fields.ProcessName:
566-
return ps.Name, nil
567-
case fields.ProcessID:
568-
return ps.PID, nil
569-
case fields.ProcessSID:
570-
return ps.SID, nil
571-
case fields.ProcessSessionID:
572-
return ps.SessionID, nil
573-
case fields.ProcessCwd:
574-
return ps.Cwd, nil
575-
case fields.ProcessCmdline:
576-
return ps.Cmdline, nil
577-
case fields.ProcessArgs:
578-
return ps.Args, nil
579-
case fields.ProcessExe:
580-
return ps.Exe, nil
581-
}
582-
583-
return nil, nil
584-
}
585-
586491
// threadAccessor fetches thread parameters from thread events.
587492
type threadAccessor struct{}
588493

@@ -650,94 +555,8 @@ func (t *threadAccessor) Get(f fields.Field, kevt *kevent.Kevent) (kparams.Value
650555
return kevt.Callstack.CallsiteInsns(kevt.PID, false), nil
651556
case fields.ThreadCallstackIsUnbacked:
652557
return kevt.Callstack.ContainsUnbacked(), nil
653-
default:
654-
if f.IsCallstackMap() {
655-
return callstackFields(f.String(), kevt)
656-
}
657-
}
658-
return nil, nil
659-
}
660-
661-
// callstackFields is responsible for extracting
662-
// the stack frame data for the specified frame
663-
// index. The index 0 represents the least-recent
664-
// frame, usually the base thread initialization
665-
// frames.
666-
func callstackFields(field string, kevt *kevent.Kevent) (kparams.Value, error) {
667-
if kevt.Callstack.IsEmpty() {
668-
return nil, nil
669-
}
670-
key, segment := captureInBrackets(field)
671-
if key == "" || segment == "" {
672-
return nil, nil
673-
}
674-
var i int
675-
switch key {
676-
case frameUStart:
677-
i = 0
678-
case frameUEnd:
679-
for ; i < kevt.Callstack.Depth()-1 && !kevt.Callstack[i].Addr.InSystemRange(); i++ {
680-
}
681-
i--
682-
case frameKStart:
683-
for i = kevt.Callstack.Depth() - 1; i >= 0 && kevt.Callstack[i].Addr.InSystemRange(); i-- {
684-
}
685-
i++
686-
case frameKEnd:
687-
i = kevt.Callstack.Depth() - 1
688-
default:
689-
if strings.HasSuffix(key, ".dll") {
690-
for n, frame := range kevt.Callstack {
691-
if strings.EqualFold(filepath.Base(frame.Module), key) {
692-
i = n
693-
break
694-
}
695-
}
696-
} else {
697-
var err error
698-
i, err = strconv.Atoi(key)
699-
if err != nil {
700-
return nil, err
701-
}
702-
}
703-
}
704-
705-
if i > kevt.Callstack.Depth() || i < 0 {
706-
i = 0
707-
}
708-
f := kevt.Callstack[i]
709-
710-
switch segment {
711-
case fields.FrameAddress:
712-
return f.Addr.String(), nil
713-
case fields.FrameSymbolOffset:
714-
return f.Offset, nil
715-
case fields.FrameModule:
716-
return f.Module, nil
717-
case fields.FrameSymbol:
718-
return f.Symbol, nil
719-
case fields.FrameProtection, fields.FrameAllocationSize:
720-
proc, err := windows.OpenProcess(windows.PROCESS_QUERY_INFORMATION, false, kevt.PID)
721-
if err != nil {
722-
return nil, err
723-
}
724-
defer windows.Close(proc)
725-
if segment == fields.FrameProtection {
726-
return f.Protection(proc), nil
727-
}
728-
return f.AllocationSize(proc), nil
729-
case fields.FrameCallsiteLeadingAssembly, fields.FrameCallsiteTrailingAssembly:
730-
proc, err := windows.OpenProcess(windows.PROCESS_QUERY_INFORMATION|windows.PROCESS_VM_READ, false, kevt.PID)
731-
if err != nil {
732-
return nil, err
733-
}
734-
defer windows.Close(proc)
735-
if segment == fields.FrameCallsiteLeadingAssembly {
736-
return f.CallsiteAssembly(proc, true), nil
737-
}
738-
return f.CallsiteAssembly(proc, false), nil
739-
case fields.FrameIsUnbacked:
740-
return f.IsUnbacked(), nil
558+
case fields.ThreadCallstack:
559+
return kevt.Callstack, nil
741560
}
742561
return nil, nil
743562
}
@@ -1082,7 +901,7 @@ func (peAccessor) IsFieldAccessible(kevt *kevent.Kevent) bool {
1082901
func (pa *peAccessor) parserOpts() []pe.Option {
1083902
var opts []pe.Option
1084903
for _, f := range pa.fields {
1085-
if f.IsPeSection() || f.IsPeSectionsMap() || f.IsPeModified() {
904+
if f.IsPeSection() || f.IsPeModified() {
1086905
opts = append(opts, pe.WithSections())
1087906
}
1088907
if f.IsPeSymbol() {
@@ -1257,23 +1076,10 @@ func (pa *peAccessor) Get(f fields.Field, kevt *kevent.Kevent) (kparams.Value, e
12571076
return p.VersionResources[pe.ProductName], nil
12581077
case fields.PeProductVersion:
12591078
return p.VersionResources[pe.ProductVersion], nil
1079+
case fields.PeSections:
1080+
return p.Sections, nil
12601081
default:
12611082
switch {
1262-
case f.IsPeSectionsMap():
1263-
// get the section name
1264-
section, segment := captureInBrackets(f.String())
1265-
sec := p.Section(section)
1266-
if sec == nil {
1267-
return nil, nil
1268-
}
1269-
switch segment {
1270-
case fields.SectionEntropy:
1271-
return sec.Entropy, nil
1272-
case fields.SectionMD5Hash:
1273-
return sec.Md5, nil
1274-
case fields.SectionSize:
1275-
return sec.Size, nil
1276-
}
12771083
case f.IsPeResourcesMap():
12781084
// consult the resource name
12791085
key, _ := captureInBrackets(f.String())

pkg/filter/accessor_windows_test.go

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

2121
import (
22-
"github.com/rabbitstack/fibratus/pkg/filter/fields"
2322
"github.com/rabbitstack/fibratus/pkg/kevent"
2423
"github.com/rabbitstack/fibratus/pkg/kevent/ktypes"
2524
"github.com/rabbitstack/fibratus/pkg/pe"
@@ -75,19 +74,6 @@ func TestPEAccessor(t *testing.T) {
7574
},
7675
}
7776

78-
entropy, err := pea.Get("pe.sections[.text].entropy", kevt)
79-
require.NoError(t, err)
80-
assert.Equal(t, 6.368381, entropy)
81-
82-
v, err := pea.Get("pe.sections[.text].md6", kevt)
83-
require.NoError(t, err)
84-
require.Nil(t, v)
85-
86-
md5, err := pea.Get("pe.sections[.rdata].md5", kevt)
87-
require.NoError(t, err)
88-
require.Nil(t, v)
89-
assert.Equal(t, "ffa5c960b421ca9887e54966588e97e8", md5)
90-
9177
company, err := pea.Get("pe.resources[CompanyName]", kevt)
9278
require.NoError(t, err)
9379
assert.Equal(t, "Microsoft Corporation", company)
@@ -97,10 +83,6 @@ func TestCaptureInBrackets(t *testing.T) {
9783
v, subfield := captureInBrackets("ps.envs[ALLUSERSPROFILE]")
9884
assert.Equal(t, "ALLUSERSPROFILE", v)
9985
assert.Empty(t, subfield)
100-
101-
v, subfield = captureInBrackets("ps.pe.sections[.debug$S].entropy")
102-
assert.Equal(t, ".debug$S", v)
103-
assert.Equal(t, fields.SectionEntropy, subfield)
10486
}
10587

10688
func TestNarrowAccessors(t *testing.T) {
@@ -113,11 +95,11 @@ func TestNarrowAccessors(t *testing.T) {
11395
2,
11496
},
11597
{
116-
New(`ps.modules[kernel32.dll].location = 'C:\\Windows\\System32'`, cfg),
98+
New(`foreach(ps._modules, $mod, $mod.path = 'C:\\Windows\\System32')`, cfg),
11799
1,
118100
},
119101
{
120-
New(`handle.type = 'Section' and pe.sections > 1 and kevt.name = 'CreateHandle'`, cfg),
102+
New(`handle.type = 'Section' and pe.nsections > 1 and kevt.name = 'CreateHandle'`, cfg),
121103
3,
122104
},
123105
{

0 commit comments

Comments
 (0)