diff --git a/cmd/fibratus/app/rules/validate.go b/cmd/fibratus/app/rules/validate.go index a2c7e1432..388b64297 100644 --- a/cmd/fibratus/app/rules/validate.go +++ b/cmd/fibratus/app/rules/validate.go @@ -96,8 +96,8 @@ func validateRules() error { w := warning{rule: rule.Name} for _, fld := range f.GetFields() { - if isDeprecated, dep := fields.IsDeprecated(fld); isDeprecated { - w.addMessage(fmt.Sprintf("%s field deprecated in favor of %v", fld.String(), dep.Fields)) + if isDeprecated, dep := fields.IsDeprecated(fld.Name); isDeprecated { + w.addMessage(fmt.Sprintf("%s field deprecated in favor of %v", fld.Name.String(), dep.Fields)) } } diff --git a/pkg/filter/accessor.go b/pkg/filter/accessor.go index 7f58cc965..863f6c11c 100644 --- a/pkg/filter/accessor.go +++ b/pkg/filter/accessor.go @@ -36,20 +36,23 @@ var ( // from the non-params constructs such as process' state or PE metadata. type Accessor interface { // Get fetches the parameter value for the specified filter field. - Get(f fields.Field, kevt *kevent.Kevent) (kparams.Value, error) - // SetFields sets all fields declared in the expression - SetFields(fields []fields.Field) + Get(f Field, evt *kevent.Kevent) (kparams.Value, error) + // SetFields sets all fields declared in the expression. + SetFields(fields []Field) + // SetSegments sets all segments utilized in the function predicate expression. + SetSegments(segments []fields.Segment) // IsFieldAccessible determines if the field can be extracted from the // given event. The condition is usually based on the event category, // but it can also include different circumstances, like the presence // of the process state or callstacks. - IsFieldAccessible(kevt *kevent.Kevent) bool + IsFieldAccessible(evt *kevent.Kevent) bool } // kevtAccessor extracts generic event values. type kevtAccessor struct{} -func (kevtAccessor) SetFields([]fields.Field) {} +func (kevtAccessor) SetFields([]Field) {} +func (kevtAccessor) SetSegments([]fields.Segment) {} func (kevtAccessor) IsFieldAccessible(*kevent.Kevent) bool { return true } func newKevtAccessor() Accessor { @@ -59,8 +62,8 @@ func newKevtAccessor() Accessor { const timeFmt = "15:04:05" const dateFmt = "2006-01-02" -func (k *kevtAccessor) Get(f fields.Field, kevt *kevent.Kevent) (kparams.Value, error) { - switch f { +func (k *kevtAccessor) Get(f Field, kevt *kevent.Kevent) (kparams.Value, error) { + switch f.Name { case fields.KevtSeq: return kevt.Seq, nil case fields.KevtPID: @@ -105,30 +108,35 @@ func (k *kevtAccessor) Get(f fields.Field, kevt *kevent.Kevent) (kparams.Value, return kevt.Timestamp.Weekday().String(), nil case fields.KevtNparams: return uint64(kevt.Kparams.Len()), nil - default: - if f.IsKevtArgMap() { - name, _ := captureInBrackets(f.String()) - kpar, err := kevt.Kparams.Get(name) - if err != nil { - return nil, err - } - switch kpar.Type { - case kparams.Uint8: - return kevt.Kparams.GetUint8(name) - case kparams.Uint16, kparams.Port: - return kevt.Kparams.GetUint16(name) - case kparams.Uint32, kparams.PID, kparams.TID: - return kevt.Kparams.GetUint32(name) - case kparams.Uint64: - return kevt.Kparams.GetUint64(name) - case kparams.Time: - return kevt.Kparams.GetTime(name) - default: - return kevt.GetParamAsString(name), nil - } + case fields.KevtArg: + // lookup the parameter from the field argument + // and depending on the parameter type, return + // the respective value. The field format is + // expressed as kevt.arg[cmdline] where the string + // inside brackets represents the parameter name + name := f.Arg + par, err := kevt.Kparams.Get(name) + if err != nil { + return nil, err + } + + switch par.Type { + case kparams.Uint8: + return kevt.Kparams.GetUint8(name) + case kparams.Uint16, kparams.Port: + return kevt.Kparams.GetUint16(name) + case kparams.Uint32, kparams.PID, kparams.TID: + return kevt.Kparams.GetUint32(name) + case kparams.Uint64: + return kevt.Kparams.GetUint64(name) + case kparams.Time: + return kevt.Kparams.GetTime(name) + default: + return kevt.GetParamAsString(name), nil } - return nil, nil } + + return nil, nil } // narrowAccessors dynamically disables filter accessors by walking @@ -149,37 +157,34 @@ func (f *filter) narrowAccessors() { removeMemAccessor = true removeDNSAccessor = true ) - allFields := make([]fields.Field, 0) - allFields = append(allFields, f.fields...) - for _, field := range f.boundFields { - allFields = append(allFields, field.Field()) - } - for _, field := range allFields { + + for _, field := range f.fields { switch { - case field.IsKevtField(): + case field.Name.IsKevtField(): removeKevtAccessor = false - case field.IsPsField(): + case field.Name.IsPsField(): removePsAccessor = false - case field.IsThreadField(): + case field.Name.IsThreadField(): removeThreadAccessor = false - case field.IsImageField(): + case field.Name.IsImageField(): removeImageAccessor = false - case field.IsFileField(): + case field.Name.IsFileField(): removeFileAccessor = false - case field.IsRegistryField(): + case field.Name.IsRegistryField(): removeRegistryAccessor = false - case field.IsNetworkField(): + case field.Name.IsNetworkField(): removeNetworkAccessor = false - case field.IsHandleField(): + case field.Name.IsHandleField(): removeHandleAccessor = false - case field.IsPeField(): + case field.Name.IsPeField(): removePEAccessor = false - case field.IsMemField(): + case field.Name.IsMemField(): removeMemAccessor = false - case field.IsDNSField(): + case field.Name.IsDNSField(): removeDNSAccessor = false } } + if removeKevtAccessor { f.removeAccessor(&kevtAccessor{}) } @@ -215,7 +220,8 @@ func (f *filter) narrowAccessors() { } for _, accessor := range f.accessors { - accessor.SetFields(allFields) + accessor.SetFields(f.fields) + accessor.SetSegments(f.segments) } } diff --git a/pkg/filter/accessor_windows.go b/pkg/filter/accessor_windows.go index 23b7b63fa..d37f0830f 100644 --- a/pkg/filter/accessor_windows.go +++ b/pkg/filter/accessor_windows.go @@ -30,6 +30,7 @@ import ( "github.com/rabbitstack/fibratus/pkg/util/signature" "net" "path/filepath" + "strconv" "strings" "time" @@ -80,23 +81,24 @@ type psAccessor struct { psnap psnap.Snapshotter } -func (psAccessor) SetFields(fields []fields.Field) {} +func (psAccessor) SetFields([]Field) {} +func (psAccessor) SetSegments([]fields.Segment) {} func (psAccessor) IsFieldAccessible(kevt *kevent.Kevent) bool { return kevt.PS != nil || kevt.Category == ktypes.Process } func newPSAccessor(psnap psnap.Snapshotter) Accessor { return &psAccessor{psnap: psnap} } -func (ps *psAccessor) Get(f fields.Field, kevt *kevent.Kevent) (kparams.Value, error) { - switch f { +func (ps *psAccessor) Get(f Field, kevt *kevent.Kevent) (kparams.Value, error) { + switch f.Name { case fields.PsPid: - // the process id that is generating the event + // identifier of the process that is generating the event return kevt.PID, nil case fields.PsSiblingPid, fields.PsChildPid: if kevt.Category != ktypes.Process { return nil, nil } - // the id of a freshly created process. `kevt.PID` references the parent process + // the id of a created child process. `kevt.PID` is the parent process id return kevt.Kparams.GetPid() case fields.PsPpid: ps := kevt.PS @@ -254,16 +256,6 @@ func (ps *psAccessor) Get(f fields.Field, kevt *kevent.Kevent) (kparams.Value, e return nil, nil } return kevt.Kparams.GetUint32(kparams.SessionID) - case fields.PsEnvs: - ps := kevt.PS - if ps == nil { - return nil, ErrPsNil - } - envs := make([]string, 0, len(ps.Envs)) - for env := range ps.Envs { - envs = append(envs, env) - } - return envs, nil case fields.PsModuleNames: ps := kevt.PS if ps == nil { @@ -290,7 +282,7 @@ func (ps *psAccessor) Get(f fields.Field, kevt *kevent.Kevent) (kparams.Value, e if kevt.Category != ktypes.Process { return nil, nil } - // find child process in snapshotter + pid, err := kevt.Kparams.GetPid() if err != nil { return nil, err @@ -298,10 +290,12 @@ func (ps *psAccessor) Get(f fields.Field, kevt *kevent.Kevent) (kparams.Value, e if ps.psnap == nil { return nil, nil } + proc := ps.psnap.FindAndPut(pid) if proc == nil { return nil, ErrPsNil } + return proc.UUID(), nil case fields.PsHandleNames: ps := kevt.PS @@ -392,8 +386,8 @@ func (ps *psAccessor) Get(f fields.Field, kevt *kevent.Kevent) (kparams.Value, e return nil, ErrPsNil } envs := make([]string, 0, len(ps.Envs)) - for env := range ps.Envs { - envs = append(envs, env) + for k, v := range ps.Envs { + envs = append(envs, k+":"+v) } return envs, nil case fields.PsParentHandles: @@ -463,35 +457,80 @@ func (ps *psAccessor) Get(f fields.Field, kevt *kevent.Kevent) (kparams.Value, e return kevt.PS.Mmaps, nil } return nil, ErrPsNil - default: - switch { - case f.IsEnvsMap(): - // access the specific environment variable - env, _ := captureInBrackets(f.String()) - ps := kevt.PS - if ps == nil { - return nil, ErrPsNil + case fields.PsAncestor: + if kevt.PS != nil { + n := -1 + // if the index is given try to parse it + // to access the ancestor at the given level. + // For example, ps.ancestor[0] would retrieve + // the process parent, ps.ancestor[1] would + // return the process grandparent and so on. + if f.Arg != "" { + var err error + n, err = strconv.Atoi(f.Arg) + if err != nil { + return nil, err + } + } + + ancestors := make([]string, 0) + walk := func(proc *pstypes.PS) { + ancestors = append(ancestors, proc.Name) } + pstypes.Walk(walk, kevt.PS) + + if n >= 0 { + // return a single ancestor indicated by the index + if n < len(ancestors) { + return ancestors[n], nil + } else { + return "", nil + } + } else { + // return all ancestors + return ancestors, nil + } + } + return nil, ErrPsNil + case fields.PsEnvs: + ps := kevt.PS + if ps == nil { + return nil, ErrPsNil + } + // resolve a single env variable indicated by the arg + // For example, ps.envs[winroot] would return the value + // of the winroot environment variable + if f.Arg != "" { + env := f.Arg v, ok := ps.Envs[env] if ok { return v, nil } - // match on prefix + + // match on env variable name prefix for k, v := range ps.Envs { if strings.HasPrefix(k, env) { return v, nil } } + } else { + // return all environment variables as a string slice + envs := make([]string, 0, len(ps.Envs)) + for k, v := range ps.Envs { + envs = append(envs, k+":"+v) + } + return envs, nil } - - return nil, nil } + + return nil, nil } // threadAccessor fetches thread parameters from thread events. type threadAccessor struct{} -func (threadAccessor) SetFields(fields []fields.Field) {} +func (threadAccessor) SetFields([]Field) {} +func (threadAccessor) SetSegments([]fields.Segment) {} func (threadAccessor) IsFieldAccessible(kevt *kevent.Kevent) bool { return !kevt.Callstack.IsEmpty() || kevt.Category == ktypes.Thread } @@ -500,8 +539,8 @@ func newThreadAccessor() Accessor { return &threadAccessor{} } -func (t *threadAccessor) Get(f fields.Field, kevt *kevent.Kevent) (kparams.Value, error) { - switch f { +func (t *threadAccessor) Get(f Field, kevt *kevent.Kevent) (kparams.Value, error) { + switch f.Name { case fields.ThreadBasePrio: return kevt.Kparams.GetUint8(kparams.BasePrio) case fields.ThreadIOPrio: @@ -558,23 +597,26 @@ func (t *threadAccessor) Get(f fields.Field, kevt *kevent.Kevent) (kparams.Value case fields.ThreadCallstack: return kevt.Callstack, nil } + return nil, nil } // fileAccessor extracts file specific values. type fileAccessor struct{} -func (fileAccessor) SetFields(fields []fields.Field) { +func (fileAccessor) SetFields(fields []Field) { initLOLDriversClient(fields) } +func (fileAccessor) SetSegments([]fields.Segment) {} + func (fileAccessor) IsFieldAccessible(kevt *kevent.Kevent) bool { return kevt.Category == ktypes.File } func newFileAccessor() Accessor { return &fileAccessor{} } -func (l *fileAccessor) Get(f fields.Field, kevt *kevent.Kevent) (kparams.Value, error) { - switch f { +func (l *fileAccessor) Get(f Field, kevt *kevent.Kevent) (kparams.Value, error) { + switch f.Name { case fields.FilePath: return kevt.GetParamAsString(kparams.FilePath), nil case fields.FileName: @@ -610,7 +652,7 @@ func (l *fileAccessor) Get(f fields.Field, kevt *kevent.Kevent) (kparams.Value, return kevt.GetParamAsString(kparams.MemProtect), nil case fields.FileIsDriverVulnerable, fields.FileIsDriverMalicious: if kevt.IsCreateDisposition() && kevt.IsSuccess() { - return isLOLDriver(f, kevt) + return isLOLDriver(f.Name, kevt) } return false, nil case fields.FileIsDLL: @@ -637,15 +679,18 @@ func (l *fileAccessor) Get(f fields.Field, kevt *kevent.Kevent) (kparams.Value, return kevt.Kparams.TryGetUint32(kparams.FileInfoClass) == fs.DispositionClass && kevt.Kparams.TryGetUint64(kparams.FileExtraInfo) > 0, nil } + return nil, nil } // imageAccessor extracts image (DLL, executable, driver) event values. type imageAccessor struct{} -func (imageAccessor) SetFields(fields []fields.Field) { +func (imageAccessor) SetFields(fields []Field) { initLOLDriversClient(fields) } +func (imageAccessor) SetSegments([]fields.Segment) {} + func (imageAccessor) IsFieldAccessible(kevt *kevent.Kevent) bool { return kevt.Category == ktypes.Image } @@ -654,8 +699,8 @@ func newImageAccessor() Accessor { return &imageAccessor{} } -func (i *imageAccessor) Get(f fields.Field, kevt *kevent.Kevent) (kparams.Value, error) { - if kevt.IsLoadImage() && (f == fields.ImageSignatureType || f == fields.ImageSignatureLevel || f.IsImageCert()) { +func (i *imageAccessor) Get(f Field, kevt *kevent.Kevent) (kparams.Value, error) { + if kevt.IsLoadImage() && (f.Name == fields.ImageSignatureType || f.Name == fields.ImageSignatureLevel || f.Name.IsImageCert()) { filename := kevt.GetParamAsString(kparams.ImagePath) addr := kevt.Kparams.MustGetUint64(kparams.ImageBase) typ := kevt.Kparams.MustGetUint32(kparams.ImageSignatureType) @@ -672,7 +717,7 @@ func (i *imageAccessor) Get(f fields.Field, kevt *kevent.Kevent) (kparams.Value, Filename: filename, } } - if f.IsImageCert() { + if f.Name.IsImageCert() { err := sign.ParseCertificate() if err != nil { certErrors.Add(1) @@ -696,7 +741,7 @@ func (i *imageAccessor) Get(f fields.Field, kevt *kevent.Kevent) (kparams.Value, if sign.IsSigned() { sign.Verify() } - if f.IsImageCert() { + if f.Name.IsImageCert() { err := sign.ParseCertificate() if err != nil { certErrors.Add(1) @@ -719,7 +764,7 @@ func (i *imageAccessor) Get(f fields.Field, kevt *kevent.Kevent) (kparams.Value, } } - switch f { + switch f.Name { case fields.ImagePath: return kevt.GetParamAsString(kparams.ImagePath), nil case fields.ImageName: @@ -750,7 +795,7 @@ func (i *imageAccessor) Get(f fields.Field, kevt *kevent.Kevent) (kparams.Value, return kevt.Kparams.GetTime(kparams.ImageCertNotAfter) case fields.ImageIsDriverVulnerable, fields.ImageIsDriverMalicious: if kevt.IsLoadImage() { - return isLOLDriver(f, kevt) + return isLOLDriver(f.Name, kevt) } return false, nil case fields.ImageIsDLL: @@ -766,13 +811,15 @@ func (i *imageAccessor) Get(f fields.Field, kevt *kevent.Kevent) (kparams.Value, } return p.IsDotnet, nil } + return nil, nil } // registryAccessor extracts registry specific parameters. type registryAccessor struct{} -func (registryAccessor) SetFields(fields []fields.Field) {} +func (registryAccessor) SetFields([]Field) {} +func (registryAccessor) SetSegments([]fields.Segment) {} func (registryAccessor) IsFieldAccessible(kevt *kevent.Kevent) bool { return kevt.Category == ktypes.Registry } @@ -781,8 +828,8 @@ func newRegistryAccessor() Accessor { return ®istryAccessor{} } -func (r *registryAccessor) Get(f fields.Field, kevt *kevent.Kevent) (kparams.Value, error) { - switch f { +func (r *registryAccessor) Get(f Field, kevt *kevent.Kevent) (kparams.Value, error) { + switch f.Name { case fields.RegistryPath: return kevt.GetParamAsString(kparams.RegPath), nil case fields.RegistryKeyName: @@ -800,6 +847,7 @@ func (r *registryAccessor) Get(f fields.Field, kevt *kevent.Kevent) (kparams.Val case fields.RegistryStatus: return kevt.GetParamAsString(kparams.NTStatus), nil } + return nil, nil } @@ -808,23 +856,25 @@ type networkAccessor struct { reverseDNS *network.ReverseDNS } -func (n *networkAccessor) SetFields(flds []fields.Field) { +func (n *networkAccessor) SetFields(flds []Field) { for _, f := range flds { - if f == fields.NetSIPNames || f == fields.NetDIPNames { + if f.Name == fields.NetSIPNames || f.Name == fields.NetDIPNames { n.reverseDNS = network.GetReverseDNS(2000, time.Minute*30, time.Minute*2) break } } } +func (networkAccessor) SetSegments([]fields.Segment) {} + func (networkAccessor) IsFieldAccessible(kevt *kevent.Kevent) bool { return kevt.Category == ktypes.Net } func newNetworkAccessor() Accessor { return &networkAccessor{} } -func (n *networkAccessor) Get(f fields.Field, kevt *kevent.Kevent) (kparams.Value, error) { - switch f { +func (n *networkAccessor) Get(f Field, kevt *kevent.Kevent) (kparams.Value, error) { + switch f.Name { case fields.NetDIP: return kevt.Kparams.GetIP(kparams.NetDIP) case fields.NetSIP: @@ -846,6 +896,7 @@ func (n *networkAccessor) Get(f fields.Field, kevt *kevent.Kevent) (kparams.Valu case fields.NetSIPNames: return n.resolveNamesForIP(kevt.Kparams.MustGetIP(kparams.NetSIP)) } + return nil, nil } @@ -863,15 +914,16 @@ func (n *networkAccessor) resolveNamesForIP(ip net.IP) ([]string, error) { // handleAccessor extracts handle event values. type handleAccessor struct{} -func (handleAccessor) SetFields(fields []fields.Field) {} +func (handleAccessor) SetFields([]Field) {} +func (handleAccessor) SetSegments([]fields.Segment) {} func (handleAccessor) IsFieldAccessible(kevt *kevent.Kevent) bool { return kevt.Category == ktypes.Handle } func newHandleAccessor() Accessor { return &handleAccessor{} } -func (h *handleAccessor) Get(f fields.Field, kevt *kevent.Kevent) (kparams.Value, error) { - switch f { +func (h *handleAccessor) Get(f Field, kevt *kevent.Kevent) (kparams.Value, error) { + switch f.Name { case fields.HandleID: return kevt.Kparams.GetUint32(kparams.HandleID) case fields.HandleType: @@ -881,51 +933,66 @@ func (h *handleAccessor) Get(f fields.Field, kevt *kevent.Kevent) (kparams.Value case fields.HandleObject: return kevt.Kparams.GetUint64(kparams.HandleObject) } + return nil, nil } // peAccessor extracts PE specific values. type peAccessor struct { - fields []fields.Field + fields []Field + segments []fields.Segment } -func (pa *peAccessor) SetFields(fields []fields.Field) { +func (pa *peAccessor) SetFields(fields []Field) { pa.fields = fields } +func (pa *peAccessor) SetSegments(segments []fields.Segment) { + pa.segments = segments +} + func (peAccessor) IsFieldAccessible(kevt *kevent.Kevent) bool { return kevt.PS != nil || kevt.IsLoadImage() } -// parserOpts traverses all fields declared in the expression and +// parserOpts traverses all fields/segments declared in the expression and // dynamically determines what aspects of the PE need to be parsed. func (pa *peAccessor) parserOpts() []pe.Option { var opts []pe.Option + var peSections bool + for _, f := range pa.fields { - if f.IsPeSection() || f.IsPeModified() { + if f.Name.IsPeSectionsPseudo() { + peSections = true + } + if f.Name.IsPeSection() || f.Name.IsPeModified() { opts = append(opts, pe.WithSections()) } - if f.IsPeSymbol() { + if f.Name.IsPeSymbol() { opts = append(opts, pe.WithSymbols()) } - if f.IsPeSectionEntropy() { - opts = append(opts, pe.WithSections(), pe.WithSectionEntropy()) - } - if f.IsPeVersionResource() || f.IsPeResourcesMap() { + if f.Name.IsPeVersionResource() || f.Name.IsPeVersionResources() { opts = append(opts, pe.WithVersionResources()) } - if f.IsPeImphash() { + if f.Name.IsPeImphash() { opts = append(opts, pe.WithImphash()) } - if f.IsPeDotnet() || f.IsPeModified() { + if f.Name.IsPeDotnet() || f.Name.IsPeModified() { opts = append(opts, pe.WithCLR()) } - if f.IsPeAnomalies() { + if f.Name.IsPeAnomalies() { opts = append(opts, pe.WithSections(), pe.WithSymbols()) } - if f.IsPeSignature() { + if f.Name.IsPeSignature() { opts = append(opts, pe.WithSecurity()) } } + + for _, s := range pa.segments { + if peSections && s.IsEntropy() { + opts = append(opts, pe.WithSections(), pe.WithSectionEntropy()) + } + } + return opts } @@ -936,7 +1003,7 @@ func newPEAccessor() Accessor { return &peAccessor{} } -func (pa *peAccessor) Get(f fields.Field, kevt *kevent.Kevent) (kparams.Value, error) { +func (pa *peAccessor) Get(f Field, kevt *kevent.Kevent) (kparams.Value, error) { var p *pe.PE if kevt.PS != nil && kevt.PS.PE != nil { p = kevt.PS.PE @@ -949,10 +1016,10 @@ func (pa *peAccessor) Get(f fields.Field, kevt *kevent.Kevent) (kparams.Value, e // original file name as part of the CreateProcess event, // then the parser obtains the PE metadata for the executable // path parameter - if (kevt.PS != nil && kevt.PS.Exe != "" && p == nil) || f == fields.PePsChildFileName || f == fields.PsChildPeFilename { + if (kevt.PS != nil && kevt.PS.Exe != "" && p == nil) || f.Name == fields.PePsChildFileName || f.Name == fields.PsChildPeFilename { var err error var exe string - if (f == fields.PePsChildFileName || f == fields.PsChildPeFilename) && kevt.IsCreateProcess() { + if (f.Name == fields.PePsChildFileName || f.Name == fields.PsChildPeFilename) && kevt.IsCreateProcess() { exe = kevt.GetParamAsString(kparams.Exe) } else { exe = kevt.PS.Exe @@ -968,7 +1035,7 @@ func (pa *peAccessor) Get(f fields.Field, kevt *kevent.Kevent) (kparams.Value, e // PE for loaded executables followed by fetching the PE // from process' memory at the base address of the loaded // executable image - if kevt.IsLoadImage() && f.IsPeModified() { + if kevt.IsLoadImage() && f.Name.IsPeModified() { filename := kevt.GetParamAsString(kparams.ImagePath) isExecutable := filepath.Ext(filename) == ".exe" || kevt.Kparams.TryGetBool(kparams.FileIsExecutable) if !isExecutable { @@ -998,15 +1065,15 @@ func (pa *peAccessor) Get(f fields.Field, kevt *kevent.Kevent) (kparams.Value, e } // verify signature - if f.IsPeSignature() { + if f.Name.IsPeSignature() { p.VerifySignature() } - if f != fields.PePsChildFileName { + if f.Name != fields.PePsChildFileName { kevt.PS.PE = p } - switch f { + switch f.Name { case fields.PeEntrypoint: return p.EntryPoint, nil case fields.PeBaseAddress: @@ -1078,21 +1145,30 @@ func (pa *peAccessor) Get(f fields.Field, kevt *kevent.Kevent) (kparams.Value, e return p.VersionResources[pe.ProductVersion], nil case fields.PeSections: return p.Sections, nil - default: - switch { - case f.IsPeResourcesMap(): - // consult the resource name - key, _ := captureInBrackets(f.String()) + case fields.PeResources: + // return a single version resource indicated by the arg. + // For example, pe.resources[FileDescription] returns the + // original file description present in the resource directory + key := f.Arg + if key != "" { v, ok := p.VersionResources[key] if ok { return v, nil } - // match on prefix (e.g. pe.resources[Org] = Blackwater) + + // match on version name prefix for k, v := range p.VersionResources { if strings.HasPrefix(k, key) { return v, nil } } + } else { + // return all version resources as a string slice + resources := make([]string, 0, len(p.VersionResources)) + for k, v := range p.VersionResources { + resources = append(resources, k+":"+v) + } + return resources, nil } } @@ -1102,15 +1178,16 @@ func (pa *peAccessor) Get(f fields.Field, kevt *kevent.Kevent) (kparams.Value, e // memAccessor extracts parameters from memory alloc/free events. type memAccessor struct{} -func (memAccessor) SetFields(fields []fields.Field) {} +func (memAccessor) SetFields([]Field) {} +func (memAccessor) SetSegments([]fields.Segment) {} func (memAccessor) IsFieldAccessible(kevt *kevent.Kevent) bool { return kevt.Category == ktypes.Mem } func newMemAccessor() Accessor { return &memAccessor{} } -func (*memAccessor) Get(f fields.Field, kevt *kevent.Kevent) (kparams.Value, error) { - switch f { +func (*memAccessor) Get(f Field, kevt *kevent.Kevent) (kparams.Value, error) { + switch f.Name { case fields.MemPageType: return kevt.GetParamAsString(kparams.MemPageType), nil case fields.MemAllocType: @@ -1124,13 +1201,15 @@ func (*memAccessor) Get(f fields.Field, kevt *kevent.Kevent) (kparams.Value, err case fields.MemProtectionMask: return kevt.Kparams.GetString(kparams.MemProtectMask) } + return nil, nil } // dnsAccessor extracts values from DNS query/response event parameters. type dnsAccessor struct{} -func (dnsAccessor) SetFields(fields []fields.Field) {} +func (dnsAccessor) SetFields([]Field) {} +func (dnsAccessor) SetSegments([]fields.Segment) {} func (dnsAccessor) IsFieldAccessible(kevt *kevent.Kevent) bool { return kevt.Type.Subcategory() == ktypes.DNS } @@ -1139,8 +1218,8 @@ func newDNSAccessor() Accessor { return &dnsAccessor{} } -func (*dnsAccessor) Get(f fields.Field, kevt *kevent.Kevent) (kparams.Value, error) { - switch f { +func (*dnsAccessor) Get(f Field, kevt *kevent.Kevent) (kparams.Value, error) { + switch f.Name { case fields.DNSName: return kevt.GetParamAsString(kparams.DNSName), nil case fields.DNSRR: @@ -1152,36 +1231,21 @@ func (*dnsAccessor) Get(f fields.Field, kevt *kevent.Kevent) (kparams.Value, err case fields.DNSAnswers: return kevt.Kparams.GetSlice(kparams.DNSAnswers) } - return nil, nil -} -func captureInBrackets(s string) (string, fields.Segment) { - lbracket := strings.Index(s, "[") - if lbracket == -1 { - return "", "" - } - rbracket := strings.Index(s, "]") - if rbracket == -1 { - return "", "" - } - if lbracket+1 > len(s) { - return "", "" - } - if rbracket+2 < len(s) { - return s[lbracket+1 : rbracket], fields.Segment(s[rbracket+2:]) - } - return s[lbracket+1 : rbracket], "" + return nil, nil } // isLOLDriver interacts with the loldrivers client to determine // whether the loaded/dropped driver is malicious or vulnerable. func isLOLDriver(f fields.Field, kevt *kevent.Kevent) (kparams.Value, error) { var filename string + if kevt.Category == ktypes.File { filename = kevt.GetParamAsString(kparams.FilePath) } else { filename = kevt.GetParamAsString(kparams.ImagePath) } + isDriver := filepath.Ext(filename) == ".sys" || kevt.Kparams.TryGetBool(kparams.FileIsDriver) if !isDriver { return nil, nil @@ -1201,10 +1265,10 @@ func isLOLDriver(f fields.Field, kevt *kevent.Kevent) (kparams.Value, error) { // initLOLDriversClient initializes the loldrivers client if the filter expression // contains any of the relevant fields. -func initLOLDriversClient(flds []fields.Field) { +func initLOLDriversClient(flds []Field) { for _, f := range flds { - if f == fields.FileIsDriverVulnerable || f == fields.FileIsDriverMalicious || - f == fields.ImageIsDriverVulnerable || f == fields.ImageIsDriverMalicious { + if f.Name == fields.FileIsDriverVulnerable || f.Name == fields.FileIsDriverMalicious || + f.Name == fields.ImageIsDriverVulnerable || f.Name == fields.ImageIsDriverMalicious { loldrivers.InitClient(loldrivers.WithAsyncDownload()) } } diff --git a/pkg/filter/accessor_windows_test.go b/pkg/filter/accessor_windows_test.go index b680fc54e..a6bf4ae62 100644 --- a/pkg/filter/accessor_windows_test.go +++ b/pkg/filter/accessor_windows_test.go @@ -21,70 +21,13 @@ package filter import ( "github.com/rabbitstack/fibratus/pkg/kevent" "github.com/rabbitstack/fibratus/pkg/kevent/ktypes" - "github.com/rabbitstack/fibratus/pkg/pe" - psnapshotter "github.com/rabbitstack/fibratus/pkg/ps" ptypes "github.com/rabbitstack/fibratus/pkg/ps/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "reflect" "testing" - "time" ) -func TestPSAccessor(t *testing.T) { - psnap := new(psnapshotter.SnapshotterMock) - ps := newPSAccessor(psnap) - kevt := &kevent.Kevent{ - PS: &ptypes.PS{ - Envs: map[string]string{"ALLUSERSPROFILE": "C:\\ProgramData", "OS": "Windows_NT", "ProgramFiles(x86)": "C:\\Program Files (x86)"}, - }, - } - - env, err := ps.Get("ps.envs[ALLUSERSPROFILE]", kevt) - require.NoError(t, err) - assert.Equal(t, "C:\\ProgramData", env) - - env, err = ps.Get("ps.envs[ALLUSER]", kevt) - require.NoError(t, err) - assert.Equal(t, "C:\\ProgramData", env) - - env, err = ps.Get("ps.envs[ProgramFiles]", kevt) - require.NoError(t, err) - assert.Equal(t, "C:\\Program Files (x86)", env) -} - -func TestPEAccessor(t *testing.T) { - pea := newPEAccessor() - kevt := &kevent.Kevent{ - PS: &ptypes.PS{ - PE: &pe.PE{ - NumberOfSections: 2, - NumberOfSymbols: 10, - EntryPoint: "0x20110", - ImageBase: "0x140000000", - LinkTime: time.Now(), - Sections: []pe.Sec{ - {Name: ".text", Size: 132608, Entropy: 6.368381, Md5: "db23dce3911a42e987041d98abd4f7cd"}, - {Name: ".rdata", Size: 35840, Entropy: 5.996976, Md5: "ffa5c960b421ca9887e54966588e97e8"}, - }, - Symbols: []string{"SelectObject", "GetTextFaceW", "EnumFontsW", "TextOutW", "GetProcessHeap"}, - Imports: []string{"GDI32.dll", "USER32.dll", "msvcrt.dll", "api-ms-win-core-libraryloader-l1-2-0.dl"}, - VersionResources: map[string]string{"CompanyName": "Microsoft Corporation", "FileDescription": "Notepad", "FileVersion": "10.0.18362.693"}, - }, - }, - } - - company, err := pea.Get("pe.resources[CompanyName]", kevt) - require.NoError(t, err) - assert.Equal(t, "Microsoft Corporation", company) -} - -func TestCaptureInBrackets(t *testing.T) { - v, subfield := captureInBrackets("ps.envs[ALLUSERSPROFILE]") - assert.Equal(t, "ALLUSERSPROFILE", v) - assert.Empty(t, subfield) -} - func TestNarrowAccessors(t *testing.T) { var tests = []struct { f Filter diff --git a/pkg/filter/filter.go b/pkg/filter/filter.go index 886c89077..bfe15e639 100644 --- a/pkg/filter/filter.go +++ b/pkg/filter/filter.go @@ -49,28 +49,45 @@ type Filter interface { Compile() error // Run runs a filter with a single expression. The return value decides // if the incoming event has successfully matched the filter expression. - Run(kevt *kevent.Kevent) bool + Run(evt *kevent.Kevent) bool // RunSequence runs a filter with sequence expressions. Sequence rules depend // on the state machine transitions and partial matches to decide whether the // rule is fired. - RunSequence(kevt *kevent.Kevent, seqID uint16, partials map[uint16][]*kevent.Kevent, rawMatch bool) bool + RunSequence(evt *kevent.Kevent, seqID uint16, partials map[uint16][]*kevent.Kevent, rawMatch bool) bool // GetStringFields returns field names mapped to their string values. GetStringFields() map[fields.Field][]string - // GetFields returns all field used in the filter expression. - GetFields() []fields.Field + // GetFields returns all fields used in the filter expression. + GetFields() []Field // GetSequence returns the sequence descriptor or nil if this filter is not a sequence. GetSequence() *ql.Sequence // IsSequence determines if this filter is a sequence. IsSequence() bool } +// Field contains field meta attributes all accessors need to extract the value. +type Field struct { + Name fields.Field + Value string + Arg string +} + +// BoundField contains the field meta attributes in addition to bound field specific fields. +type BoundField struct { + Field Field + Value string + BoundVar string +} + type filter struct { expr ql.Expr seq *ql.Sequence parser *ql.Parser accessors []Accessor - fields []fields.Field + fields []Field + segments []fields.Segment boundFields []*ql.BoundFieldLiteral + // seqBoundFields contains per-sequence bound fields resolved from bound field literals + seqBoundFields map[uint16][]BoundField // stringFields contains filter field names mapped to their string values stringFields map[fields.Field][]string hasFunctions bool @@ -100,35 +117,44 @@ func (f *filter) Compile() error { switch expr := n.(type) { case *ql.BinaryExpr: if lhs, ok := expr.LHS.(*ql.FieldLiteral); ok { - field := fields.Field(lhs.Value) - f.addField(field) - f.addStringFields(field, expr.RHS) + f.addField(lhs) + f.addStringFields(lhs.Field, expr.RHS) } if rhs, ok := expr.RHS.(*ql.FieldLiteral); ok { - field := fields.Field(rhs.Value) - f.addField(field) - f.addStringFields(field, expr.LHS) + f.addField(rhs) + f.addStringFields(rhs.Field, expr.LHS) } if lhs, ok := expr.LHS.(*ql.BoundFieldLiteral); ok { + f.addField(lhs.Field) f.addBoundField(lhs) } if rhs, ok := expr.RHS.(*ql.BoundFieldLiteral); ok { + f.addField(rhs.Field) f.addBoundField(rhs) } case *ql.Function: f.hasFunctions = true for _, arg := range expr.Args { if field, ok := arg.(*ql.FieldLiteral); ok { - f.addField(fields.Field(field.Value)) + f.addField(field) } if field, ok := arg.(*ql.BoundFieldLiteral); ok { + f.addField(field.Field) f.addBoundField(field) } + switch exp := arg.(type) { + case *ql.BinaryExpr: + if segment, ok := exp.LHS.(*ql.BoundSegmentLiteral); ok { + f.addSegment(segment) + } + if segment, ok := exp.RHS.(*ql.BoundSegmentLiteral); ok { + f.addSegment(segment) + } + } } case *ql.FieldLiteral: - field := fields.Field(expr.Value) - if fields.IsBoolean(field) { - f.addField(field) + if fields.IsBoolean(expr.Field) { + f.addField(expr) } } } @@ -136,12 +162,12 @@ func (f *filter) Compile() error { if f.expr != nil { ql.WalkFunc(f.expr, walk) } else { - if !f.seq.By.IsEmpty() { + if f.seq.By != nil { f.addField(f.seq.By) } for _, expr := range f.seq.Expressions { ql.WalkFunc(expr.Expr, walk) - if !expr.By.IsEmpty() { + if expr.By != nil { f.addField(expr.By) } } @@ -179,6 +205,7 @@ func (f *filter) RunSequence(kevt *kevent.Kevent, seqID uint16, partials map[uin // without evaluating joins/bound fields return ql.Eval(expr.Expr, valuer, f.hasFunctions) } + var match bool if seqID >= 1 && expr.HasBoundFields() { // if a sequence expression contains references to @@ -196,14 +223,21 @@ func (f *filter) RunSequence(kevt *kevent.Kevent, seqID uint16, partials map[uin nslots = len(p[alias]) } } + + flds, ok := f.seqBoundFields[seqID] + if !ok { + flds = f.addSeqBoundFields(seqID, expr.BoundFields) + } + // process until partials from all slots are consumed n := 0 hash := make([]byte, 0) for nslots > 0 { nslots-- var evt *kevent.Kevent - for _, field := range expr.BoundFields { - evts := p[field.Alias()] + for _, field := range flds { + // get all events pertaining to the bounded event + evts := p[field.BoundVar] if n > len(evts)-1 { // pick the latest event if all // events for this slot are consumed @@ -211,17 +245,19 @@ func (f *filter) RunSequence(kevt *kevent.Kevent, seqID uint16, partials map[uin } else { evt = evts[n] } + + // resolve the bound field value for _, accessor := range f.accessors { if !accessor.IsFieldAccessible(evt) { continue } - v, err := accessor.Get(field.Field(), evt) + v, err := accessor.Get(field.Field, evt) if err != nil && !kerrors.IsKparamNotFound(err) { accessorErrors.Add(err.Error(), 1) continue } if v != nil { - valuer[field.String()] = v + valuer[field.Value] = v switch val := v.(type) { case uint8: hash = append(hash, val) @@ -263,13 +299,14 @@ func (f *filter) RunSequence(kevt *kevent.Kevent, seqID uint16, partials map[uin } } else { by := f.seq.By - if by.IsEmpty() { + if by == nil { by = expr.By } - if seqID >= 1 && !by.IsEmpty() { + + if seqID >= 1 && by != nil { // traverse upstream partials for join equality joins := make([]bool, seqID) - joinID := valuer[by.String()] + joinID := valuer[by.Value] outer: for i := uint16(0); i < seqID; i++ { for _, p := range partials[i+1] { @@ -283,8 +320,9 @@ func (f *filter) RunSequence(kevt *kevent.Kevent, seqID uint16, partials map[uin } else { match = ql.Eval(expr.Expr, valuer, f.hasFunctions) } - if match && !by.IsEmpty() { - if v := valuer[by.String()]; v != nil { + + if match && by != nil { + if v := valuer[by.Value]; v != nil { kevt.AddMeta(kevent.RuleSequenceByKey, v) } } @@ -302,7 +340,7 @@ func joinsEqual(joins []bool) bool { } func (f *filter) GetStringFields() map[fields.Field][]string { return f.stringFields } -func (f *filter) GetFields() []fields.Field { return f.fields } +func (f *filter) GetFields() []Field { return f.fields } func (f *filter) IsSequence() bool { return f.seq != nil } func (f *filter) GetSequence() *ql.Sequence { return f.seq } @@ -339,7 +377,8 @@ func InterpolateFields(s string, evts []*kevent.Kevent) string { var val any for _, accessor := range GetAccessors() { var err error - val, err = accessor.Get(fields.Field(m[2]), kevt) + f := Field{Value: m[2], Name: fields.Field(m[2])} + val, err = accessor.Get(f, kevt) if err != nil { continue } @@ -363,20 +402,20 @@ func InterpolateFields(s string, evts []*kevent.Kevent) string { // accessors and extract the field values that are // supplied to the valuer. The valuer feeds the // expression with correct values. -func (f *filter) mapValuer(kevt *kevent.Kevent) map[string]interface{} { +func (f *filter) mapValuer(evt *kevent.Kevent) map[string]interface{} { valuer := make(map[string]interface{}, len(f.fields)) for _, field := range f.fields { for _, accessor := range f.accessors { - if !accessor.IsFieldAccessible(kevt) { + if !accessor.IsFieldAccessible(evt) { continue } - v, err := accessor.Get(field, kevt) + v, err := accessor.Get(field, evt) if err != nil && !kerrors.IsKparamNotFound(err) { accessorErrors.Add(err.Error(), 1) continue } if v != nil { - valuer[field.String()] = v + valuer[field.Value] = v break } } @@ -385,13 +424,13 @@ func (f *filter) mapValuer(kevt *kevent.Kevent) map[string]interface{} { } // addField appends a new field to the filter fields list. -func (f *filter) addField(field fields.Field) { +func (f *filter) addField(field *ql.FieldLiteral) { for _, f := range f.fields { - if f.String() == field.String() { + if f.Value == field.Value { return } } - f.fields = append(f.fields, field) + f.fields = append(f.fields, Field{Value: field.Value, Name: field.Field, Arg: field.Arg}) } // addStringFields appends values for all string field expressions. @@ -404,11 +443,33 @@ func (f *filter) addStringFields(field fields.Field, expr ql.Expr) { } } -// addBoundField appends a new bound field +// addBoundField appends a new bound field. func (f *filter) addBoundField(field *ql.BoundFieldLiteral) { f.boundFields = append(f.boundFields, field) } +// addSegment adds a new bound segment. +func (f *filter) addSegment(segment *ql.BoundSegmentLiteral) { + f.segments = append(f.segments, segment.Segment) +} + +// addSeqBoundFields receives the sequence id and the list of bound field literals +// and populates the list of bound fields containing the field structure convenient +// for accessors. +func (f *filter) addSeqBoundFields(seqID uint16, fields []*ql.BoundFieldLiteral) []BoundField { + flds := make([]BoundField, 0, len(fields)) + for _, field := range fields { + flds = append(flds, + BoundField{ + Field: Field{Name: field.Field.Field, Value: field.Field.Value, Arg: field.Field.Arg}, + Value: field.Value, + BoundVar: field.BoundVar.Value, + }) + } + f.seqBoundFields[seqID] = flds + return flds +} + // checkBoundRefs checks if the bound field is referencing a valid alias. // If no valid alias is reference, this method returns an error specifying // an incorrect alias reference. @@ -416,6 +477,7 @@ func (f *filter) checkBoundRefs() error { if f.seq == nil { return nil } + aliases := make(map[string]bool) for _, expr := range f.seq.Expressions { if expr.Alias == "" { @@ -423,13 +485,15 @@ func (f *filter) checkBoundRefs() error { } aliases[expr.Alias] = true } + for _, field := range f.boundFields { - if _, ok := aliases[field.Alias()]; !ok { + if _, ok := aliases[field.BoundVar.Value]; !ok { return fmt.Errorf("%s bound field references "+ "an invalid '$%s' event alias", - field.String(), field.Alias()) + field.String(), field.BoundVar.Value) } } + return nil } diff --git a/pkg/filter/filter_test.go b/pkg/filter/filter_test.go index 806ee910f..03490f908 100644 --- a/pkg/filter/filter_test.go +++ b/pkg/filter/filter_test.go @@ -223,7 +223,13 @@ func TestProcFilter(t *testing.T) { {`ps.child.domain = 'TITAN'`, true}, {`ps.parent.username = 'SYSTEM'`, true}, {`ps.parent.domain = 'NT AUTHORITY'`, true}, - {`ps.envs in ('ALLUSERSPROFILE')`, true}, + {`ps.envs[ALLUSERSPROFILE] = 'C:\\ProgramData'`, true}, + {`ps.envs[ALLUSER] = 'C:\\ProgramData'`, true}, + {`ps.envs[ProgramFiles] = 'C:\\Program Files (x86)'`, true}, + {`ps.envs[windir] = 'C:\\WINDOWS'`, false}, + {`ps.envs in ('ALLUSERSPROFILE:C:\\ProgramData')`, true}, + {`foreach(ps.envs, $env, substr($env, 0, indexof($env, ':')) = 'OS')`, true}, + {`ps.child.is_wow64`, true}, {`ps.child.is_packaged`, true}, {`ps.child.is_protected`, true}, @@ -239,6 +245,13 @@ func TestProcFilter(t *testing.T) { {`kevt.name = 'CreateProcess' and kevt.pid != ps.ppid`, true}, {`ps.parent.name = 'wininit.exe'`, true}, + {`ps.ancestor[0] = 'svchost.exe'`, false}, + {`ps.ancestor[0] = 'wininit.exe'`, true}, + {`ps.ancestor[1] = 'services.exe'`, true}, + {`ps.ancestor[2] = 'System'`, true}, + {`ps.ancestor[3] = ''`, true}, + {`ps.ancestor intersects ('wininit.exe', 'services.exe', 'System')`, true}, + {`foreach(ps._ancestors, $proc, $proc.name in ('wininit.exe', 'services.exe', 'System'))`, true}, {`foreach(ps._ancestors, $proc, $proc.name in ('wininit.exe', 'services.exe', 'System') and ps.is_packaged, ps.is_packaged)`, true}, {`foreach(ps._ancestors, $proc, $proc.name not in ('svchost.exe', 'WmiPrvSE.exe'))`, true}, @@ -1011,6 +1024,8 @@ func TestPEFilter(t *testing.T) { {`foreach(pe._sections, $section, $section.md5 = 'ffa5c960b421ca9887e54966588e97e8')`, true}, {`pe.symbols IN ('GetTextFaceW', 'GetProcessHeap')`, true}, {`pe.resources[FileDesc] = 'Notepad'`, true}, + {`pe.resources[CompanyName] = 'Microsoft Corporation'`, true}, + {`pe.resources in ('FileDescription:Notepad')`, true}, {`pe.nsymbols = 10 AND pe.nsections = 2`, true}, {`pe.nsections > 1`, true}, {`pe.address.base = '140000000' AND pe.address.entrypoint = '20110'`, true}, diff --git a/pkg/filter/filter_windows.go b/pkg/filter/filter_windows.go index ff950ac92..239e1d395 100644 --- a/pkg/filter/filter_windows.go +++ b/pkg/filter/filter_windows.go @@ -93,11 +93,13 @@ func New(expr string, config *config.Config, options ...Option) Filter { } return &filter{ - parser: parser, - accessors: accessors, - fields: make([]fields.Field, 0), - stringFields: make(map[fields.Field][]string), - boundFields: make([]*ql.BoundFieldLiteral, 0), + parser: parser, + accessors: accessors, + fields: make([]Field, 0), + segments: make([]fields.Segment, 0), + stringFields: make(map[fields.Field][]string), + boundFields: make([]*ql.BoundFieldLiteral, 0), + seqBoundFields: make(map[uint16][]BoundField), } } @@ -121,11 +123,13 @@ func NewFromCLIWithAllAccessors(args []string) (Filter, error) { return nil, nil } filter := &filter{ - parser: ql.NewParser(expr), - accessors: GetAccessors(), - fields: make([]fields.Field, 0), - stringFields: make(map[fields.Field][]string), - boundFields: make([]*ql.BoundFieldLiteral, 0), + parser: ql.NewParser(expr), + accessors: GetAccessors(), + fields: make([]Field, 0), + segments: make([]fields.Segment, 0), + stringFields: make(map[fields.Field][]string), + boundFields: make([]*ql.BoundFieldLiteral, 0), + seqBoundFields: make(map[uint16][]BoundField), } if err := filter.Compile(); err != nil { return nil, fmt.Errorf("bad filter:\n %v", err) diff --git a/pkg/filter/rules.go b/pkg/filter/rules.go index d5eef89e9..e15a572e4 100644 --- a/pkg/filter/rules.go +++ b/pkg/filter/rules.go @@ -523,7 +523,7 @@ func (r *Rules) Compile() (*config.RulesCompileResult, error) { } } for _, field := range fltr.GetFields() { - deprecated, d := fields.IsDeprecated(field) + deprecated, d := fields.IsDeprecated(field.Name) if deprecated { log.Warnf("%s rule uses the [%s] field which "+ "was deprecated starting from version %s. "+