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/fields/fields.go b/pkg/filter/fields/fields.go index 2b57a7902..07544d370 100644 --- a/pkg/filter/fields/fields.go +++ b/pkg/filter/fields/fields.go @@ -30,6 +30,28 @@ type FieldInfo struct { Type kparams.Type Examples []string Deprecation *Deprecation + Argument *Argument +} + +// Argument defines field argument information. +type Argument struct { + // Optional indicates if the argument is optional. + Optional bool + // ValidationFunc is the field argument validation function. + // It returns true if the provided argument is valid, or false + // otherwise. + ValidationFunc func(string) bool + // Pattern contains the regular expression like string that + // represents the character set allowed for the argument value. + Pattern string +} + +// Validate validates the provided field argument. +func (a *Argument) Validate(v string) bool { + if a.ValidationFunc == nil { + return true + } + return a.ValidationFunc(v) } // IsDeprecated determines if the field is deprecated. diff --git a/pkg/filter/fields/fields_windows.go b/pkg/filter/fields/fields_windows.go index 7199e92d6..e86f03332 100644 --- a/pkg/filter/fields/fields_windows.go +++ b/pkg/filter/fields/fields_windows.go @@ -19,22 +19,15 @@ package fields import ( - "regexp" "strings" + "unicode" "github.com/rabbitstack/fibratus/pkg/kevent/kparams" ) -// pathRegexp splits the provided path into different components. The first capture -// contains the indexed field name and the second capture contains the argument key. -var pathRegexp = regexp.MustCompile(`(pe.resources|ps.envs|kevt.arg)\[(.+\s*)]`) - // Field represents the type alias for the field type Field string -// IsEmpty determines if this field is empty. -func (f Field) IsEmpty() bool { return f == "" } - const ( // PsPid represents the process id field PsPid Field = "ps.pid" @@ -100,7 +93,7 @@ const ( PsParentHandleTypes Field = "ps.parent.handle.types" // PsParentDTB represents the parent process directory table base address field PsParentDTB Field = "ps.parent.dtb" - // PsAncestor represents the process ancestor sequence field + // PsAncestor represents the process ancestor field PsAncestor Field = "ps.ancestor" // PsAccessMask represents the process access rights field PsAccessMask Field = "ps.access.mask" @@ -489,9 +482,6 @@ const ( DNSAnswers Field = "dns.answers" // DNSRcode identifies the field that represents the DNS response code DNSRcode Field = "dns.rcode" - - // None represents the unknown field - None Field = "" ) // String casts the field type to string. @@ -510,17 +500,14 @@ func (f Field) IsMemField() bool { return strings.HasPrefix(string(f), "mem func (f Field) IsDNSField() bool { return strings.HasPrefix(string(f), "dns.") } func (f Field) IsPeSection() bool { return f == PeNumSections } -func (f Field) IsPeSectionEntropy() bool { - fld := string(f) - return fld == "entropy" -} -func (f Field) IsPeSymbol() bool { return f == PeSymbols || f == PeNumSymbols || f == PeImports } +func (f Field) IsPeSymbol() bool { return f == PeSymbols || f == PeNumSymbols || f == PeImports } func (f Field) IsPeVersionResource() bool { return f == PeCompany || f == PeCopyright || f == PeDescription || f == PeFileName || f == PeFileVersion || f == PeProduct || f == PeProductVersion || f == PePsChildFileName || f == PsChildPeFilename } -func (f Field) IsPeImphash() bool { return f == PeImphash } -func (f Field) IsPeDotnet() bool { return f == PeIsDotnet } -func (f Field) IsPeAnomalies() bool { return f == PeAnomalies } +func (f Field) IsPeVersionResources() bool { return f == PeResources } +func (f Field) IsPeImphash() bool { return f == PeImphash } +func (f Field) IsPeDotnet() bool { return f == PeIsDotnet } +func (f Field) IsPeAnomalies() bool { return f == PeAnomalies } func (f Field) IsPeSignature() bool { return f == PeIsTrusted || f == PeIsSigned || f == PeCertIssuer || f == PeCertSerial || f == PeCertSubject || f == PeCertBefore || f == PeCertAfter } @@ -620,6 +607,8 @@ var allowedSegments = map[Field][]Segment{ ThreadCallstack: {AddressSegment, OffsetSegment, SymbolSegment, ModuleSegment, AllocationSizeSegment, ProtectionSegment, IsUnbackedSegment, CallsiteLeadingAssemblySegment, CallsiteTrailingAssemblySegment}, } +func (s Segment) IsEntropy() bool { return s == EntropySegment } + // IsSegmentAllowed determines if the segment is valid for the pseudo field. func IsSegmentAllowed(f Field, s Segment) bool { segs := allowedSegments[f] @@ -673,270 +662,287 @@ func IsPseudoField(f Field) bool { return f == PsAncestors || f == PsModules || f == PsThreads || f == PsMmaps || f == ThreadCallstack || f == PeSections } -func (f Field) IsEnvsMap() bool { return strings.HasPrefix(f.String(), "ps.envs[") } -func (f Field) IsPeResourcesMap() bool { return strings.HasPrefix(f.String(), "pe.resources[") } -func (f Field) IsKevtArgMap() bool { return strings.HasPrefix(f.String(), "kevt.arg[") } +func (f Field) IsPeSectionsPseudo() bool { return f == PeSections } var fields = map[Field]FieldInfo{ - KevtSeq: {KevtSeq, "event sequence number", kparams.Uint64, []string{"kevt.seq > 666"}, nil}, - KevtPID: {KevtPID, "process identifier generating the kernel event", kparams.Uint32, []string{"kevt.pid = 6"}, nil}, - KevtTID: {KevtTID, "thread identifier generating the kernel event", kparams.Uint32, []string{"kevt.tid = 1024"}, nil}, - KevtCPU: {KevtCPU, "logical processor core where the event was generated", kparams.Uint8, []string{"kevt.cpu = 2"}, nil}, - KevtName: {KevtName, "symbolical kernel event name", kparams.AnsiString, []string{"kevt.name = 'CreateThread'"}, nil}, - KevtCategory: {KevtCategory, "event category", kparams.AnsiString, []string{"kevt.category = 'registry'"}, nil}, - KevtDesc: {KevtDesc, "event description", kparams.AnsiString, []string{"kevt.desc contains 'Creates a new process'"}, nil}, - KevtHost: {KevtHost, "host name on which the event was produced", kparams.UnicodeString, []string{"kevt.host contains 'kitty'"}, nil}, - KevtTime: {KevtTime, "event timestamp as a time string", kparams.Time, []string{"kevt.time = '17:05:32'"}, nil}, - KevtTimeHour: {KevtTimeHour, "hour within the day on which the event occurred", kparams.Time, []string{"kevt.time.h = 23"}, nil}, - KevtTimeMin: {KevtTimeMin, "minute offset within the hour on which the event occurred", kparams.Time, []string{"kevt.time.m = 54"}, nil}, - KevtTimeSec: {KevtTimeSec, "second offset within the minute on which the event occurred", kparams.Time, []string{"kevt.time.s = 0"}, nil}, - KevtTimeNs: {KevtTimeNs, "nanoseconds specified by event timestamp", kparams.Int64, []string{"kevt.time.ns > 1591191629102337000"}, nil}, - KevtDate: {KevtDate, "event timestamp as a date string", kparams.Time, []string{"kevt.date = '2018-03-03'"}, nil}, - KevtDateDay: {KevtDateDay, "day of the month on which the event occurred", kparams.Time, []string{"kevt.date.d = 12"}, nil}, - KevtDateMonth: {KevtDateMonth, "month of the year on which the event occurred", kparams.Time, []string{"kevt.date.m = 11"}, nil}, - KevtDateYear: {KevtDateYear, "year on which the event occurred", kparams.Uint32, []string{"kevt.date.y = 2020"}, nil}, - KevtDateTz: {KevtDateTz, "time zone associated with the event timestamp", kparams.AnsiString, []string{"kevt.date.tz = 'UTC'"}, nil}, - KevtDateWeek: {KevtDateWeek, "week number within the year on which the event occurred", kparams.Uint8, []string{"kevt.date.week = 2"}, nil}, - KevtDateWeekday: {KevtDateWeekday, "week day on which the event occurred", kparams.AnsiString, []string{"kevt.date.weekday = 'Monday'"}, nil}, - KevtNparams: {KevtNparams, "number of parameters", kparams.Int8, []string{"kevt.nparams > 2"}, nil}, - - PsPid: {PsPid, "process identifier", kparams.PID, []string{"ps.pid = 1024"}, nil}, - PsPpid: {PsPpid, "parent process identifier", kparams.PID, []string{"ps.ppid = 45"}, nil}, - PsName: {PsName, "process image name including the file extension", kparams.UnicodeString, []string{"ps.name contains 'firefox'"}, nil}, - PsComm: {PsComm, "process command line", kparams.UnicodeString, []string{"ps.comm contains 'java'"}, &Deprecation{Since: "1.10.0", Fields: []Field{PsCmdline}}}, - PsCmdline: {PsCmdline, "process command line", kparams.UnicodeString, []string{"ps.cmdline contains 'java'"}, nil}, - PsExe: {PsExe, "full name of the process' executable", kparams.UnicodeString, []string{"ps.exe = 'C:\\Windows\\system32\\cmd.exe'"}, nil}, - PsArgs: {PsArgs, "process command line arguments", kparams.Slice, []string{"ps.args in ('/cdir', '/-C')"}, nil}, - PsCwd: {PsCwd, "process current working directory", kparams.UnicodeString, []string{"ps.cwd = 'C:\\Users\\Default'"}, nil}, - PsSID: {PsSID, "security identifier under which this process is run", kparams.UnicodeString, []string{"ps.sid contains 'SYSTEM'"}, nil}, - PsSessionID: {PsSessionID, "unique identifier for the current session", kparams.Int16, []string{"ps.sessionid = 1"}, nil}, - PsDomain: {PsDomain, "process domain", kparams.UnicodeString, []string{"ps.domain contains 'SERVICE'"}, nil}, - PsUsername: {PsUsername, "process username", kparams.UnicodeString, []string{"ps.username contains 'system'"}, nil}, - PsEnvs: {PsEnvs, "process environment variables", kparams.Slice, []string{"ps.envs in ('MOZ_CRASHREPORTER_DATA_DIRECTORY')"}, nil}, - PsHandleNames: {PsHandleNames, "allocated process handle names", kparams.Slice, []string{"ps.handles in ('\\BaseNamedObjects\\__ComCatalogCache__')"}, nil}, - PsHandleTypes: {PsHandleTypes, "allocated process handle types", kparams.Slice, []string{"ps.handle.types in ('Key', 'Mutant', 'Section')"}, nil}, - PsDTB: {PsDTB, "process directory table base address", kparams.Address, []string{"ps.dtb = '7ffe0000'"}, nil}, - PsModuleNames: {PsModuleNames, "modules loaded by the process", kparams.Slice, []string{"ps.modules in ('crypt32.dll', 'xul.dll')"}, nil}, - PsParentName: {PsParentName, "parent process image name including the file extension", kparams.UnicodeString, []string{"ps.parent.name contains 'cmd.exe'"}, nil}, - PsParentPid: {PsParentPid, "parent process id", kparams.Uint32, []string{"ps.parent.pid = 4"}, nil}, - PsParentComm: {PsParentComm, "parent process command line", kparams.UnicodeString, []string{"ps.parent.comm contains 'java'"}, &Deprecation{Since: "1.10.0", Fields: []Field{PsParentCmdline}}}, - PsParentCmdline: {PsParentCmdline, "parent process command line", kparams.UnicodeString, []string{"ps.parent.cmdline contains 'java'"}, nil}, - PsParentExe: {PsParentExe, "full name of the parent process' executable", kparams.UnicodeString, []string{"ps.parent.exe = 'C:\\Windows\\system32\\explorer.exe'"}, nil}, - PsParentArgs: {PsParentArgs, "parent process command line arguments", kparams.Slice, []string{"ps.parent.args in ('/cdir', '/-C')"}, nil}, - PsParentCwd: {PsParentCwd, "parent process current working directory", kparams.UnicodeString, []string{"ps.parent.cwd = 'C:\\Temp'"}, nil}, - PsParentSID: {PsParentSID, "security identifier under which the parent process is run", kparams.UnicodeString, []string{"ps.parent.sid contains 'SYSTEM'"}, nil}, - PsParentDomain: {PsParentDomain, "parent process domain", kparams.UnicodeString, []string{"ps.parent.domain contains 'SERVICE'"}, nil}, - PsParentUsername: {PsParentUsername, "parent process username", kparams.UnicodeString, []string{"ps.parent.username contains 'system'"}, nil}, - PsParentSessionID: {PsParentSessionID, "unique identifier for the current session of parent process", kparams.Int16, []string{"ps.parent.sessionid = 1"}, nil}, - PsParentEnvs: {PsParentEnvs, "parent process environment variables", kparams.Slice, []string{"ps.parent.envs in ('MOZ_CRASHREPORTER_DATA_DIRECTORY')"}, nil}, - PsParentHandles: {PsParentHandles, "allocated parent process handle names", kparams.Slice, []string{"ps.parent.handles in ('\\BaseNamedObjects\\__ComCatalogCache__')"}, nil}, - PsParentHandleTypes: {PsParentHandleTypes, "allocated parent process handle types", kparams.Slice, []string{"ps.parent.handle.types in ('File', 'SymbolicLink')"}, nil}, - PsParentDTB: {PsParentDTB, "parent process directory table base address", kparams.Address, []string{"ps.parent.dtb = '7ffe0000'"}, nil}, - PsAccessMask: {PsAccessMask, "process desired access rights", kparams.AnsiString, []string{"ps.access.mask = '0x1400'"}, nil}, - PsAccessMaskNames: {PsAccessMaskNames, "process desired access rights as a string list", kparams.Slice, []string{"ps.access.mask.names in ('SUSPEND_RESUME')"}, nil}, - PsAccessStatus: {PsAccessStatus, "process access status", kparams.UnicodeString, []string{"ps.access.status = 'access is denied.'"}, nil}, - PsSiblingPid: {PsSiblingPid, "created or terminated process identifier", kparams.PID, []string{"ps.sibling.pid = 320"}, &Deprecation{Since: "1.10.0", Fields: []Field{PsChildPid}}}, - PsChildPid: {PsChildPid, "created or terminated process identifier", kparams.PID, []string{"ps.child.pid = 320"}, nil}, - PsSiblingName: {PsSiblingName, "created or terminated process name", kparams.UnicodeString, []string{"ps.sibling.name = 'notepad.exe'"}, &Deprecation{Since: "1.10.0", Fields: []Field{PsChildName}}}, - PsChildName: {PsChildName, "created or terminated process name", kparams.UnicodeString, []string{"ps.child.name = 'notepad.exe'"}, nil}, - PsSiblingComm: {PsSiblingComm, "created or terminated process command line", kparams.UnicodeString, []string{"ps.sibling.comm contains '\\k \\v'"}, &Deprecation{Since: "1.10.0", Fields: []Field{PsChildCmdline}}}, - PsChildCmdline: {PsChildCmdline, "created or terminated process command line", kparams.UnicodeString, []string{"ps.child.cmdline contains '\\k \\v'"}, nil}, - PsSiblingArgs: {PsSiblingArgs, "created process command line arguments", kparams.Slice, []string{"ps.sibling.args in ('/cdir', '/-C')"}, &Deprecation{Since: "1.10.0", Fields: []Field{PsChildArgs}}}, - PsChildArgs: {PsChildArgs, "created process command line arguments", kparams.Slice, []string{"ps.child.args in ('/cdir', '/-C')"}, nil}, - PsSiblingExe: {PsSiblingExe, "created, terminated, or opened process id", kparams.UnicodeString, []string{"ps.sibling.exe contains '\\Windows\\cmd.exe'"}, &Deprecation{Since: "1.10.0", Fields: []Field{PsChildExe}}}, - PsChildExe: {PsChildExe, "created, terminated, or opened process id", kparams.UnicodeString, []string{"ps.child.exe contains '\\Windows\\cmd.exe'"}, nil}, - PsSiblingSID: {PsSiblingSID, "created or terminated process security identifier", kparams.UnicodeString, []string{"ps.sibling.sid contains 'SERVICE'"}, &Deprecation{Since: "1.10.0", Fields: []Field{PsChildSID}}}, - PsChildSID: {PsChildSID, "created or terminated process security identifier", kparams.UnicodeString, []string{"ps.child.sid contains 'SERVICE'"}, nil}, - PsSiblingSessionID: {PsSiblingSessionID, "created or terminated process session identifier", kparams.Int16, []string{"ps.sibling.sessionid == 1"}, &Deprecation{Since: "1.10.0", Fields: []Field{PsChildSessionID}}}, - PsChildSessionID: {PsChildSessionID, "created or terminated process session identifier", kparams.Int16, []string{"ps.child.sessionid == 1"}, nil}, - PsSiblingDomain: {PsSiblingDomain, "created or terminated process domain", kparams.UnicodeString, []string{"ps.sibling.domain contains 'SERVICE'"}, &Deprecation{Since: "1.10.0", Fields: []Field{PsChildDomain}}}, - PsChildDomain: {PsChildDomain, "created or terminated process domain", kparams.UnicodeString, []string{"ps.child.domain contains 'SERVICE'"}, nil}, - PsSiblingUsername: {PsSiblingUsername, "created or terminated process username", kparams.UnicodeString, []string{"ps.sibling.username contains 'system'"}, &Deprecation{Since: "1.10.0", Fields: []Field{PsChildUsername}}}, - PsChildUsername: {PsChildUsername, "created or terminated process username", kparams.UnicodeString, []string{"ps.child.username contains 'system'"}, nil}, - PsUUID: {PsUUID, "unique process identifier", kparams.Uint64, []string{"ps.uuid > 6000054355"}, nil}, - PsParentUUID: {PsParentUUID, "unique parent process identifier", kparams.Uint64, []string{"ps.parent.uuid > 6000054355"}, nil}, - PsChildUUID: {PsChildUUID, "unique child process identifier", kparams.Uint64, []string{"ps.child.uuid > 6000054355"}, nil}, - PsChildPeFilename: {PsChildPeFilename, "original file name of the child process executable supplied at compile-time", kparams.UnicodeString, []string{"ps.child.pe.file.name = 'NOTEPAD.EXE'"}, nil}, - PsChildIsWOW64Field: {PsChildIsWOW64Field, "indicates if the 32-bit child process is created in 64-bit Windows system", kparams.Bool, []string{"ps.child.is_wow64"}, nil}, - PsChildIsPackagedField: {PsChildIsPackagedField, "indicates if the child process is packaged with the MSIX technology", kparams.Bool, []string{"ps.child.is_packaged"}, nil}, - PsChildIsProtectedField: {PsChildIsProtectedField, "indicates if the child process is a protected process", kparams.Bool, []string{"ps.child.is_protected"}, nil}, - PsIsWOW64Field: {PsIsWOW64Field, "indicates if the process generating the event is a 32-bit process created in 64-bit Windows system", kparams.Bool, []string{"ps.is_wow64"}, nil}, - PsIsPackagedField: {PsIsPackagedField, "indicates if the process generating the event is packaged with the MSIX technology", kparams.Bool, []string{"ps.is_packaged"}, nil}, - PsIsProtectedField: {PsIsProtectedField, "indicates if the process generating the event is a protected process", kparams.Bool, []string{"ps.is_protected"}, nil}, - PsParentIsWOW64Field: {PsParentIsWOW64Field, "indicates if the parent process generating the event is a 32-bit process created in 64-bit Windows system", kparams.Bool, []string{"ps.parent.is_wow64"}, nil}, - PsParentIsPackagedField: {PsParentIsPackagedField, "indicates if the parent process generating the event is packaged with the MSIX technology", kparams.Bool, []string{"ps.parent.is_packaged"}, nil}, - PsParentIsProtectedField: {PsParentIsProtectedField, "indicates if the the parent process generating the event is a protected process", kparams.Bool, []string{"ps.parent.is_protected"}, nil}, - - ThreadBasePrio: {ThreadBasePrio, "scheduler priority of the thread", kparams.Int8, []string{"thread.prio = 5"}, nil}, - ThreadIOPrio: {ThreadIOPrio, "I/O priority hint for scheduling I/O operations", kparams.Int8, []string{"thread.io.prio = 4"}, nil}, - ThreadPagePrio: {ThreadPagePrio, "memory page priority hint for memory pages accessed by the thread", kparams.Int8, []string{"thread.page.prio = 12"}, nil}, - ThreadKstackBase: {ThreadKstackBase, "base address of the thread's kernel space stack", kparams.Address, []string{"thread.kstack.base = 'a65d800000'"}, nil}, - ThreadKstackLimit: {ThreadKstackLimit, "limit of the thread's kernel space stack", kparams.Address, []string{"thread.kstack.limit = 'a85d800000'"}, nil}, - ThreadUstackBase: {ThreadUstackBase, "base address of the thread's user space stack", kparams.Address, []string{"thread.ustack.base = '7ffe0000'"}, nil}, - ThreadUstackLimit: {ThreadUstackLimit, "limit of the thread's user space stack", kparams.Address, []string{"thread.ustack.limit = '8ffe0000'"}, nil}, - ThreadEntrypoint: {ThreadEntrypoint, "starting address of the function to be executed by the thread", kparams.Address, []string{"thread.entrypoint = '7efe0000'"}, &Deprecation{Since: "2.3.0", Fields: []Field{ThreadStartAddress}}}, - ThreadStartAddress: {ThreadStartAddress, "thread start address", kparams.Address, []string{"thread.start_address = '7efe0000'"}, nil}, - ThreadPID: {ThreadPID, "the process identifier where the thread is created", kparams.Uint32, []string{"kevt.pid != thread.pid"}, nil}, - ThreadTEB: {ThreadTEB, "the base address of the thread environment block", kparams.Address, []string{"thread.teb_address = '8f30893000'"}, nil}, - ThreadAccessMask: {ThreadAccessMask, "thread desired access rights", kparams.AnsiString, []string{"thread.access.mask = '0x1fffff'"}, nil}, - ThreadAccessMaskNames: {ThreadAccessMaskNames, "thread desired access rights as a string list", kparams.Slice, []string{"thread.access.mask.names in ('IMPERSONATE')"}, nil}, - ThreadAccessStatus: {ThreadAccessStatus, "thread access status", kparams.UnicodeString, []string{"thread.access.status = 'success'"}, nil}, - ThreadCallstackSummary: {ThreadCallstackSummary, "callstack summary", kparams.UnicodeString, []string{"thread.callstack.summary contains 'ntdll.dll|KERNELBASE.dll'"}, nil}, - ThreadCallstackDetail: {ThreadCallstackDetail, "detailed information of each stack frame", kparams.UnicodeString, []string{"thread.callstack.detail contains 'KERNELBASE.dll!CreateProcessW'"}, nil}, - ThreadCallstackModules: {ThreadCallstackModules, "list of modules comprising the callstack", kparams.Slice, []string{"thread.callstack.modules in ('C:\\WINDOWS\\System32\\KERNELBASE.dll')"}, nil}, - ThreadCallstackSymbols: {ThreadCallstackSymbols, "list of symbols comprising the callstack", kparams.Slice, []string{"thread.callstack.symbols in ('ntdll.dll!NtCreateProcess')"}, nil}, - ThreadCallstackAllocationSizes: {ThreadCallstackAllocationSizes, "allocation sizes of private pages", kparams.Slice, []string{"thread.callstack.allocation_sizes > 10000"}, nil}, - ThreadCallstackProtections: {ThreadCallstackProtections, "page protections masks of each frame", kparams.Slice, []string{"thread.callstack.protections in ('RWX', 'WX')"}, nil}, - ThreadCallstackCallsiteLeadingAssembly: {ThreadCallstackCallsiteLeadingAssembly, "callsite leading assembly instructions", kparams.Slice, []string{"thread.callstack.callsite_leading_assembly in ('mov r10,rcx', 'syscall')"}, nil}, - ThreadCallstackCallsiteTrailingAssembly: {ThreadCallstackCallsiteTrailingAssembly, "callsite trailing assembly instructions", kparams.Slice, []string{"thread.callstack.callsite_trailing_assembly in ('add esp, 0xab')"}, nil}, - ThreadCallstackIsUnbacked: {ThreadCallstackIsUnbacked, "indicates if the callstack contains unbacked regions", kparams.Bool, []string{"thread.callstack.is_unbacked"}, nil}, - - ImagePath: {ImagePath, "full image path", kparams.UnicodeString, []string{"image.patj = 'C:\\Windows\\System32\\advapi32.dll'"}, nil}, - ImageName: {ImageName, "image name", kparams.UnicodeString, []string{"image.name = 'advapi32.dll'"}, nil}, - ImageBase: {ImageBase, "the base address of process in which the image is loaded", kparams.Address, []string{"image.base.address = 'a65d800000'"}, nil}, - ImageChecksum: {ImageChecksum, "image checksum", kparams.Uint32, []string{"image.checksum = 746424"}, nil}, - ImageSize: {ImageSize, "image size", kparams.Uint32, []string{"image.size > 1024"}, nil}, - ImageDefaultAddress: {ImageDefaultAddress, "default image address", kparams.Address, []string{"image.default.address = '7efe0000'"}, nil}, - ImagePID: {ImagePID, "target process identifier", kparams.Uint32, []string{"image.pid = 80"}, nil}, - ImageSignatureType: {ImageSignatureType, "image signature type", kparams.AnsiString, []string{"image.signature.type != 'NONE'"}, nil}, - ImageSignatureLevel: {ImageSignatureLevel, "image signature level", kparams.AnsiString, []string{"image.signature.level = 'AUTHENTICODE'"}, nil}, - ImageCertSerial: {ImageCertSerial, "image certificate serial number", kparams.UnicodeString, []string{"image.cert.serial = '330000023241fb59996dcc4dff000000000232'"}, nil}, - ImageCertSubject: {ImageCertSubject, "image certificate subject", kparams.UnicodeString, []string{"image.cert.subject contains 'Washington, Redmond, Microsoft Corporation'"}, nil}, - ImageCertIssuer: {ImageCertIssuer, "image certificate CA", kparams.UnicodeString, []string{"image.cert.issuer contains 'Washington, Redmond, Microsoft Corporation'"}, nil}, - ImageCertAfter: {ImageCertAfter, "image certificate expiration date", kparams.Time, []string{"image.cert.after contains '2024-02-01 00:05:42 +0000 UTC'"}, nil}, - ImageCertBefore: {ImageCertBefore, "image certificate enrollment date", kparams.Time, []string{"image.cert.before contains '2024-02-01 00:05:42 +0000 UTC'"}, nil}, - ImageIsDriverMalicious: {ImageIsDriverMalicious, "indicates if the loaded driver is malicious", kparams.Bool, []string{"image.is_driver_malicious"}, nil}, - ImageIsDriverVulnerable: {ImageIsDriverVulnerable, "indicates if the loaded driver is vulnerable", kparams.Bool, []string{"image.is_driver_vulnerable"}, nil}, - ImageIsDLL: {ImageIsDLL, "indicates if the loaded image is a DLL", kparams.Bool, []string{"image.is_dll'"}, nil}, - ImageIsDriver: {ImageIsDriver, "indicates if the loaded image is a driver", kparams.Bool, []string{"image.is_driver'"}, nil}, - ImageIsExecutable: {ImageIsExecutable, "indicates if the loaded image is an executable", kparams.Bool, []string{"image.is_exec'"}, nil}, - ImageIsDotnet: {ImageIsDotnet, "indicates if the loaded image is a .NET assembly", kparams.Bool, []string{"image.is_dotnet'"}, nil}, - - FileObject: {FileObject, "file object address", kparams.Uint64, []string{"file.object = 18446738026482168384"}, nil}, - FilePath: {FilePath, "full file path", kparams.UnicodeString, []string{"file.path = 'C:\\Windows\\System32'"}, nil}, - FileName: {FileName, "full file name", kparams.UnicodeString, []string{"file.name contains 'mimikatz'"}, nil}, - FileOperation: {FileOperation, "file operation", kparams.AnsiString, []string{"file.operation = 'open'"}, nil}, - FileShareMask: {FileShareMask, "file share mask", kparams.AnsiString, []string{"file.share.mask = 'rw-'"}, nil}, - FileIOSize: {FileIOSize, "file I/O size", kparams.Uint32, []string{"file.io.size > 512"}, nil}, - FileOffset: {FileOffset, "file offset", kparams.Uint64, []string{"file.offset = 1024"}, nil}, - FileType: {FileType, "file type", kparams.AnsiString, []string{"file.type = 'directory'"}, nil}, - FileExtension: {FileExtension, "file extension", kparams.AnsiString, []string{"file.extension = '.dll'"}, nil}, - FileAttributes: {FileAttributes, "file attributes", kparams.Slice, []string{"file.attributes in ('archive', 'hidden')"}, nil}, - FileStatus: {FileStatus, "file operation status message", kparams.UnicodeString, []string{"file.status != 'success'"}, nil}, - FileViewBase: {FileViewBase, "view base address", kparams.Address, []string{"file.view.base = '25d42170000'"}, nil}, - FileViewSize: {FileViewSize, "size of the mapped view", kparams.Uint64, []string{"file.view.size > 1024"}, nil}, - FileViewType: {FileViewType, "type of the mapped view section", kparams.Enum, []string{"file.view.type = 'IMAGE'"}, nil}, - FileViewProtection: {FileViewProtection, "protection rights of the section view", kparams.AnsiString, []string{"file.view.protection = 'READONLY'"}, nil}, - FileIsDriverMalicious: {FileIsDriverMalicious, "indicates if the dropped driver is malicious", kparams.Bool, []string{"file.is_driver_malicious"}, nil}, - FileIsDriverVulnerable: {FileIsDriverVulnerable, "indicates if the dropped driver is vulnerable", kparams.Bool, []string{"file.is_driver_vulnerable"}, nil}, - FileIsDLL: {FileIsDLL, "indicates if the created file is a DLL", kparams.Bool, []string{"file.is_dll'"}, nil}, - FileIsDriver: {FileIsDriver, "indicates if the created file is a driver", kparams.Bool, []string{"file.is_driver'"}, nil}, - FileIsExecutable: {FileIsExecutable, "indicates if the created file is an executable", kparams.Bool, []string{"file.is_exec'"}, nil}, - FilePID: {FilePID, "denotes the process id performing file operation", kparams.PID, []string{"file.pid = 4"}, nil}, - FileKey: {FileKey, "uniquely identifies the file object", kparams.Uint64, []string{"file.key = 12446738026482168384"}, nil}, - FileInfoClass: {FileInfoClass, "identifies the file information class", kparams.Enum, []string{"file.info_class = 'Allocation'"}, nil}, - FileInfoAllocationSize: {FileInfoAllocationSize, "file allocation size", kparams.Uint64, []string{"file.info.allocation_size > 645400"}, nil}, - FileInfoEOFSize: {FileInfoEOFSize, "file EOF size", kparams.Uint64, []string{"file.info.eof_size > 1000"}, nil}, - FileInfoIsDispositionDeleteFile: {FileInfoIsDispositionDeleteFile, "indicates if the file is deleted when its handle is closed", kparams.Bool, []string{"file.info.is_disposition_file_delete = true"}, nil}, - - RegistryPath: {RegistryPath, "fully qualified registry path", kparams.UnicodeString, []string{"registry.path = 'HKEY_LOCAL_MACHINE\\SYSTEM'"}, nil}, - RegistryKeyName: {RegistryKeyName, "registry key name", kparams.UnicodeString, []string{"registry.key.name = 'CurrentControlSet'"}, nil}, - RegistryKeyHandle: {RegistryKeyHandle, "registry key object address", kparams.Address, []string{"registry.key.handle = 'FFFFB905D60C2268'"}, nil}, - RegistryValue: {RegistryValue, "registry value content", kparams.UnicodeString, []string{"registry.value = '%SystemRoot%\\system32'"}, nil}, - RegistryValueType: {RegistryValueType, "type of registry value", kparams.UnicodeString, []string{"registry.value.type = 'REG_SZ'"}, nil}, - RegistryStatus: {RegistryStatus, "status of registry operation", kparams.UnicodeString, []string{"registry.status != 'success'"}, nil}, - - NetDIP: {NetDIP, "destination IP address", kparams.IP, []string{"net.dip = 172.17.0.3"}, nil}, - NetSIP: {NetSIP, "source IP address", kparams.IP, []string{"net.sip = 127.0.0.1"}, nil}, - NetDport: {NetDport, "destination port", kparams.Uint16, []string{"net.dport in (80, 443, 8080)"}, nil}, - NetSport: {NetSport, "source port", kparams.Uint16, []string{"net.sport != 3306"}, nil}, - NetDportName: {NetDportName, "destination port name", kparams.AnsiString, []string{"net.dport.name = 'dns'"}, nil}, - NetSportName: {NetSportName, "source port name", kparams.AnsiString, []string{"net.sport.name = 'http'"}, nil}, - NetL4Proto: {NetL4Proto, "layer 4 protocol name", kparams.AnsiString, []string{"net.l4.proto = 'TCP"}, nil}, - NetPacketSize: {NetPacketSize, "packet size", kparams.Uint32, []string{"net.size > 512"}, nil}, - NetSIPNames: {NetSIPNames, "source IP names", kparams.Slice, []string{"net.sip.names in ('github.com.')"}, nil}, - NetDIPNames: {NetDIPNames, "destination IP names", kparams.Slice, []string{"net.dip.names in ('github.com.')"}, nil}, - - HandleID: {HandleID, "handle identifier", kparams.Uint16, []string{"handle.id = 24"}, nil}, - HandleObject: {HandleObject, "handle object address", kparams.Address, []string{"handle.object = 'FFFFB905DBF61988'"}, nil}, - HandleName: {HandleName, "handle name", kparams.UnicodeString, []string{"handle.name = '\\Device\\NamedPipe\\chrome.12644.28.105826381'"}, nil}, - HandleType: {HandleType, "handle type", kparams.AnsiString, []string{"handle.type = 'Mutant'"}, nil}, - - PeNumSections: {PeNumSections, "number of sections", kparams.Uint16, []string{"pe.nsections < 5"}, nil}, - PeNumSymbols: {PeNumSymbols, "number of entries in the symbol table", kparams.Uint32, []string{"pe.nsymbols > 230"}, nil}, - PeBaseAddress: {PeBaseAddress, "image base address", kparams.Address, []string{"pe.address.base = '140000000'"}, nil}, - PeEntrypoint: {PeEntrypoint, "address of the entrypoint function", kparams.Address, []string{"pe.address.entrypoint = '20110'"}, nil}, - PeSymbols: {PeSymbols, "imported symbols", kparams.Slice, []string{"pe.symbols in ('GetTextFaceW', 'GetProcessHeap')"}, nil}, - PeImports: {PeImports, "imported dynamic linked libraries", kparams.Slice, []string{"pe.imports in ('msvcrt.dll', 'GDI32.dll'"}, nil}, - PeResources: {PeResources, "version and other resources", kparams.Map, []string{"pe.resources[FileDescription] = 'Notepad'"}, nil}, - PeCompany: {PeCompany, "internal company name of the file provided at compile-time", kparams.UnicodeString, []string{"pe.company = 'Microsoft Corporation'"}, nil}, - PeCopyright: {PeCopyright, "copyright notice for the file emitted at compile-time", kparams.UnicodeString, []string{"pe.copyright = '© Microsoft Corporation'"}, nil}, - PeDescription: {PeDescription, "internal description of the file provided at compile-time", kparams.UnicodeString, []string{"pe.description = 'Notepad'"}, nil}, - PeFileName: {PeFileName, "original file name supplied at compile-time", kparams.UnicodeString, []string{"pe.file.name = 'NOTEPAD.EXE'"}, nil}, - PeFileVersion: {PeFileVersion, "file version supplied at compile-time", kparams.UnicodeString, []string{"pe.file.version = '10.0.18362.693 (WinBuild.160101.0800)'"}, nil}, - PeProduct: {PeProduct, "internal product name of the file provided at compile-time", kparams.UnicodeString, []string{"pe.product = 'Microsoft® Windows® Operating System'"}, nil}, - PeProductVersion: {PeProductVersion, "internal product version of the file provided at compile-time", kparams.UnicodeString, []string{"pe.product.version = '10.0.18362.693'"}, nil}, - PeIsDLL: {PeIsDLL, "indicates if the loaded image or created file is a DLL", kparams.Bool, []string{"pe.is_dll'"}, &Deprecation{Since: "2.0.0", Fields: []Field{FileIsDLL, ImageIsDLL}}}, - PeIsDriver: {PeIsDriver, "indicates if the loaded image or created file is a driver", kparams.Bool, []string{"pe.is_driver'"}, &Deprecation{Since: "2.0.0", Fields: []Field{FileIsDriver, ImageIsDriver}}}, - PeIsExecutable: {PeIsExecutable, "indicates if the loaded image or created file is an executable", kparams.Bool, []string{"pe.is_exec'"}, &Deprecation{Since: "2.0.0", Fields: []Field{FileIsExecutable, ImageIsExecutable}}}, - PeImphash: {PeImphash, "import hash", kparams.AnsiString, []string{"pe.impash = '5d3861c5c547f8a34e471ba273a732b2'"}, nil}, - PeIsDotnet: {PeIsDotnet, "indicates if PE contains CLR data", kparams.Bool, []string{"pe.is_dotnet"}, nil}, - PeAnomalies: {PeAnomalies, "contains PE anomalies detected during parsing", kparams.Slice, []string{"pe.anomalies in ('number of sections is 0')"}, nil}, - PeIsSigned: {PeIsSigned, "indicates if the PE has embedded or catalog signature", kparams.Bool, []string{"pe.is_signed"}, nil}, - PeIsTrusted: {PeIsTrusted, "indicates if the PE certificate chain is trusted", kparams.Bool, []string{"pe.is_trusted"}, nil}, - PeCertSerial: {PeCertSerial, "PE certificate serial number", kparams.UnicodeString, []string{"pe.cert.serial = '330000023241fb59996dcc4dff000000000232'"}, nil}, - PeCertSubject: {PeCertSubject, "PE certificate subject", kparams.UnicodeString, []string{"pe.cert.subject contains 'Washington, Redmond, Microsoft Corporation'"}, nil}, - PeCertIssuer: {PeCertIssuer, "PE certificate CA", kparams.UnicodeString, []string{"pe.cert.issuer contains 'Washington, Redmond, Microsoft Corporation'"}, nil}, - PeCertAfter: {PeCertAfter, "PE certificate expiration date", kparams.Time, []string{"pe.cert.after contains '2024-02-01 00:05:42 +0000 UTC'"}, nil}, - PeCertBefore: {PeCertBefore, "PE certificate enrollment date", kparams.Time, []string{"pe.cert.before contains '2024-02-01 00:05:42 +0000 UTC'"}, nil}, - PeIsModified: {PeIsModified, "indicates if disk and in-memory PE headers differ", kparams.Bool, []string{"pe.is_modified"}, nil}, - PePsChildFileName: {PePsChildFileName, "original file name of the child process executable supplied at compile-time", kparams.UnicodeString, []string{"pe.ps.child.file.name = 'NOTEPAD.EXE'"}, &Deprecation{Since: "2.3.0", Fields: []Field{PsChildPeFilename}}}, - - MemBaseAddress: {MemBaseAddress, "region base address", kparams.Address, []string{"mem.address = '211d13f2000'"}, nil}, - MemRegionSize: {MemRegionSize, "region size", kparams.Uint64, []string{"mem.size > 438272"}, nil}, - MemAllocType: {MemAllocType, "region allocation or release type", kparams.Flags, []string{"mem.alloc = 'COMMIT'"}, nil}, - MemPageType: {MemPageType, "page type of the allocated region", kparams.Enum, []string{"mem.type = 'PRIVATE'"}, nil}, - MemProtection: {MemProtection, "allocated region protection type", kparams.Enum, []string{"mem.protection = 'READWRITE'"}, nil}, - MemProtectionMask: {MemProtectionMask, "allocated region protection in mask notation", kparams.Enum, []string{"mem.protection.mask = 'RWX'"}, nil}, - - DNSName: {DNSName, "dns query name", kparams.UnicodeString, []string{"dns.name = 'example.org'"}, nil}, - DNSRR: {DNSRR, "dns resource record type", kparams.AnsiString, []string{"dns.rr = 'AA'"}, nil}, - DNSOptions: {DNSOptions, "dns query options", kparams.Flags64, []string{"dns.options in ('ADDRCONFIG', 'DUAL_ADDR')"}, nil}, - DNSRcode: {DNSRR, "dns response status", kparams.AnsiString, []string{"dns.rcode = 'NXDOMAIN'"}, nil}, - DNSAnswers: {DNSAnswers, "dns response answers", kparams.Slice, []string{"dns.answers in ('o.lencr.edgesuite.net', 'a1887.dscq.akamai.net')"}, nil}, + KevtSeq: {KevtSeq, "event sequence number", kparams.Uint64, []string{"kevt.seq > 666"}, nil, nil}, + KevtPID: {KevtPID, "process identifier generating the kernel event", kparams.Uint32, []string{"kevt.pid = 6"}, nil, nil}, + KevtTID: {KevtTID, "thread identifier generating the kernel event", kparams.Uint32, []string{"kevt.tid = 1024"}, nil, nil}, + KevtCPU: {KevtCPU, "logical processor core where the event was generated", kparams.Uint8, []string{"kevt.cpu = 2"}, nil, nil}, + KevtName: {KevtName, "symbolical kernel event name", kparams.AnsiString, []string{"kevt.name = 'CreateThread'"}, nil, nil}, + KevtCategory: {KevtCategory, "event category", kparams.AnsiString, []string{"kevt.category = 'registry'"}, nil, nil}, + KevtDesc: {KevtDesc, "event description", kparams.AnsiString, []string{"kevt.desc contains 'Creates a new process'"}, nil, nil}, + KevtHost: {KevtHost, "host name on which the event was produced", kparams.UnicodeString, []string{"kevt.host contains 'kitty'"}, nil, nil}, + KevtTime: {KevtTime, "event timestamp as a time string", kparams.Time, []string{"kevt.time = '17:05:32'"}, nil, nil}, + KevtTimeHour: {KevtTimeHour, "hour within the day on which the event occurred", kparams.Time, []string{"kevt.time.h = 23"}, nil, nil}, + KevtTimeMin: {KevtTimeMin, "minute offset within the hour on which the event occurred", kparams.Time, []string{"kevt.time.m = 54"}, nil, nil}, + KevtTimeSec: {KevtTimeSec, "second offset within the minute on which the event occurred", kparams.Time, []string{"kevt.time.s = 0"}, nil, nil}, + KevtTimeNs: {KevtTimeNs, "nanoseconds specified by event timestamp", kparams.Int64, []string{"kevt.time.ns > 1591191629102337000"}, nil, nil}, + KevtDate: {KevtDate, "event timestamp as a date string", kparams.Time, []string{"kevt.date = '2018-03-03'"}, nil, nil}, + KevtDateDay: {KevtDateDay, "day of the month on which the event occurred", kparams.Time, []string{"kevt.date.d = 12"}, nil, nil}, + KevtDateMonth: {KevtDateMonth, "month of the year on which the event occurred", kparams.Time, []string{"kevt.date.m = 11"}, nil, nil}, + KevtDateYear: {KevtDateYear, "year on which the event occurred", kparams.Uint32, []string{"kevt.date.y = 2020"}, nil, nil}, + KevtDateTz: {KevtDateTz, "time zone associated with the event timestamp", kparams.AnsiString, []string{"kevt.date.tz = 'UTC'"}, nil, nil}, + KevtDateWeek: {KevtDateWeek, "week number within the year on which the event occurred", kparams.Uint8, []string{"kevt.date.week = 2"}, nil, nil}, + KevtDateWeekday: {KevtDateWeekday, "week day on which the event occurred", kparams.AnsiString, []string{"kevt.date.weekday = 'Monday'"}, nil, nil}, + KevtNparams: {KevtNparams, "number of parameters", kparams.Int8, []string{"kevt.nparams > 2"}, nil, nil}, + KevtArg: {KevtArg, "event parameter", kparams.Object, []string{"kevt.arg[cmdline] istartswith 'C:\\Windows'"}, nil, &Argument{Optional: false, Pattern: "[a-z0-9_]+", ValidationFunc: func(s string) bool { + for _, c := range s { + switch { + case unicode.IsLower(c): + case unicode.IsNumber(c): + case c == '_': + default: + return false + } + } + return true + }}}, + + PsPid: {PsPid, "process identifier", kparams.PID, []string{"ps.pid = 1024"}, nil, nil}, + PsPpid: {PsPpid, "parent process identifier", kparams.PID, []string{"ps.ppid = 45"}, nil, nil}, + PsName: {PsName, "process image name including the file extension", kparams.UnicodeString, []string{"ps.name contains 'firefox'"}, nil, nil}, + PsComm: {PsComm, "process command line", kparams.UnicodeString, []string{"ps.comm contains 'java'"}, &Deprecation{Since: "1.10.0", Fields: []Field{PsCmdline}}, nil}, + PsCmdline: {PsCmdline, "process command line", kparams.UnicodeString, []string{"ps.cmdline contains 'java'"}, nil, nil}, + PsExe: {PsExe, "full name of the process' executable", kparams.UnicodeString, []string{"ps.exe = 'C:\\Windows\\system32\\cmd.exe'"}, nil, nil}, + PsArgs: {PsArgs, "process command line arguments", kparams.Slice, []string{"ps.args in ('/cdir', '/-C')"}, nil, nil}, + PsCwd: {PsCwd, "process current working directory", kparams.UnicodeString, []string{"ps.cwd = 'C:\\Users\\Default'"}, nil, nil}, + PsSID: {PsSID, "security identifier under which this process is run", kparams.UnicodeString, []string{"ps.sid contains 'SYSTEM'"}, nil, nil}, + PsSessionID: {PsSessionID, "unique identifier for the current session", kparams.Int16, []string{"ps.sessionid = 1"}, nil, nil}, + PsDomain: {PsDomain, "process domain", kparams.UnicodeString, []string{"ps.domain contains 'SERVICE'"}, nil, nil}, + PsUsername: {PsUsername, "process username", kparams.UnicodeString, []string{"ps.username contains 'system'"}, nil, nil}, + PsEnvs: {PsEnvs, "process environment variables", kparams.Slice, []string{"ps.envs in ('SystemRoot:C:\\WINDOWS')", "ps.envs[windir] = 'C:\\WINDOWS'"}, nil, &Argument{Optional: true, ValidationFunc: func(arg string) bool { return true }}}, + PsHandleNames: {PsHandleNames, "allocated process handle names", kparams.Slice, []string{"ps.handles in ('\\BaseNamedObjects\\__ComCatalogCache__')"}, nil, nil}, + PsHandleTypes: {PsHandleTypes, "allocated process handle types", kparams.Slice, []string{"ps.handle.types in ('Key', 'Mutant', 'Section')"}, nil, nil}, + PsDTB: {PsDTB, "process directory table base address", kparams.Address, []string{"ps.dtb = '7ffe0000'"}, nil, nil}, + PsModuleNames: {PsModuleNames, "modules loaded by the process", kparams.Slice, []string{"ps.modules in ('crypt32.dll', 'xul.dll')"}, nil, nil}, + PsParentName: {PsParentName, "parent process image name including the file extension", kparams.UnicodeString, []string{"ps.parent.name contains 'cmd.exe'"}, nil, nil}, + PsParentPid: {PsParentPid, "parent process id", kparams.Uint32, []string{"ps.parent.pid = 4"}, nil, nil}, + PsParentComm: {PsParentComm, "parent process command line", kparams.UnicodeString, []string{"ps.parent.comm contains 'java'"}, &Deprecation{Since: "1.10.0", Fields: []Field{PsParentCmdline}}, nil}, + PsParentCmdline: {PsParentCmdline, "parent process command line", kparams.UnicodeString, []string{"ps.parent.cmdline contains 'java'"}, nil, nil}, + PsParentExe: {PsParentExe, "full name of the parent process' executable", kparams.UnicodeString, []string{"ps.parent.exe = 'C:\\Windows\\system32\\explorer.exe'"}, nil, nil}, + PsParentArgs: {PsParentArgs, "parent process command line arguments", kparams.Slice, []string{"ps.parent.args in ('/cdir', '/-C')"}, nil, nil}, + PsParentCwd: {PsParentCwd, "parent process current working directory", kparams.UnicodeString, []string{"ps.parent.cwd = 'C:\\Temp'"}, nil, nil}, + PsParentSID: {PsParentSID, "security identifier under which the parent process is run", kparams.UnicodeString, []string{"ps.parent.sid contains 'SYSTEM'"}, nil, nil}, + PsParentDomain: {PsParentDomain, "parent process domain", kparams.UnicodeString, []string{"ps.parent.domain contains 'SERVICE'"}, nil, nil}, + PsParentUsername: {PsParentUsername, "parent process username", kparams.UnicodeString, []string{"ps.parent.username contains 'system'"}, nil, nil}, + PsParentSessionID: {PsParentSessionID, "unique identifier for the current session of parent process", kparams.Int16, []string{"ps.parent.sessionid = 1"}, nil, nil}, + PsParentEnvs: {PsParentEnvs, "parent process environment variables", kparams.Slice, []string{"ps.parent.envs in ('MOZ_CRASHREPORTER_DATA_DIRECTORY')"}, nil, nil}, + PsParentHandles: {PsParentHandles, "allocated parent process handle names", kparams.Slice, []string{"ps.parent.handles in ('\\BaseNamedObjects\\__ComCatalogCache__')"}, nil, nil}, + PsParentHandleTypes: {PsParentHandleTypes, "allocated parent process handle types", kparams.Slice, []string{"ps.parent.handle.types in ('File', 'SymbolicLink')"}, nil, nil}, + PsParentDTB: {PsParentDTB, "parent process directory table base address", kparams.Address, []string{"ps.parent.dtb = '7ffe0000'"}, nil, nil}, + PsAccessMask: {PsAccessMask, "process desired access rights", kparams.AnsiString, []string{"ps.access.mask = '0x1400'"}, nil, nil}, + PsAccessMaskNames: {PsAccessMaskNames, "process desired access rights as a string list", kparams.Slice, []string{"ps.access.mask.names in ('SUSPEND_RESUME')"}, nil, nil}, + PsAccessStatus: {PsAccessStatus, "process access status", kparams.UnicodeString, []string{"ps.access.status = 'access is denied.'"}, nil, nil}, + PsSiblingPid: {PsSiblingPid, "created or terminated process identifier", kparams.PID, []string{"ps.sibling.pid = 320"}, &Deprecation{Since: "1.10.0", Fields: []Field{PsChildPid}}, nil}, + PsChildPid: {PsChildPid, "created or terminated process identifier", kparams.PID, []string{"ps.child.pid = 320"}, nil, nil}, + PsSiblingName: {PsSiblingName, "created or terminated process name", kparams.UnicodeString, []string{"ps.sibling.name = 'notepad.exe'"}, &Deprecation{Since: "1.10.0", Fields: []Field{PsChildName}}, nil}, + PsChildName: {PsChildName, "created or terminated process name", kparams.UnicodeString, []string{"ps.child.name = 'notepad.exe'"}, nil, nil}, + PsSiblingComm: {PsSiblingComm, "created or terminated process command line", kparams.UnicodeString, []string{"ps.sibling.comm contains '\\k \\v'"}, &Deprecation{Since: "1.10.0", Fields: []Field{PsChildCmdline}}, nil}, + PsChildCmdline: {PsChildCmdline, "created or terminated process command line", kparams.UnicodeString, []string{"ps.child.cmdline contains '\\k \\v'"}, nil, nil}, + PsSiblingArgs: {PsSiblingArgs, "created process command line arguments", kparams.Slice, []string{"ps.sibling.args in ('/cdir', '/-C')"}, &Deprecation{Since: "1.10.0", Fields: []Field{PsChildArgs}}, nil}, + PsChildArgs: {PsChildArgs, "created process command line arguments", kparams.Slice, []string{"ps.child.args in ('/cdir', '/-C')"}, nil, nil}, + PsSiblingExe: {PsSiblingExe, "created, terminated, or opened process id", kparams.UnicodeString, []string{"ps.sibling.exe contains '\\Windows\\cmd.exe'"}, &Deprecation{Since: "1.10.0", Fields: []Field{PsChildExe}}, nil}, + PsChildExe: {PsChildExe, "created, terminated, or opened process id", kparams.UnicodeString, []string{"ps.child.exe contains '\\Windows\\cmd.exe'"}, nil, nil}, + PsSiblingSID: {PsSiblingSID, "created or terminated process security identifier", kparams.UnicodeString, []string{"ps.sibling.sid contains 'SERVICE'"}, &Deprecation{Since: "1.10.0", Fields: []Field{PsChildSID}}, nil}, + PsChildSID: {PsChildSID, "created or terminated process security identifier", kparams.UnicodeString, []string{"ps.child.sid contains 'SERVICE'"}, nil, nil}, + PsSiblingSessionID: {PsSiblingSessionID, "created or terminated process session identifier", kparams.Int16, []string{"ps.sibling.sessionid == 1"}, &Deprecation{Since: "1.10.0", Fields: []Field{PsChildSessionID}}, nil}, + PsChildSessionID: {PsChildSessionID, "created or terminated process session identifier", kparams.Int16, []string{"ps.child.sessionid == 1"}, nil, nil}, + PsSiblingDomain: {PsSiblingDomain, "created or terminated process domain", kparams.UnicodeString, []string{"ps.sibling.domain contains 'SERVICE'"}, &Deprecation{Since: "1.10.0", Fields: []Field{PsChildDomain}}, nil}, + PsChildDomain: {PsChildDomain, "created or terminated process domain", kparams.UnicodeString, []string{"ps.child.domain contains 'SERVICE'"}, nil, nil}, + PsSiblingUsername: {PsSiblingUsername, "created or terminated process username", kparams.UnicodeString, []string{"ps.sibling.username contains 'system'"}, &Deprecation{Since: "1.10.0", Fields: []Field{PsChildUsername}}, nil}, + PsChildUsername: {PsChildUsername, "created or terminated process username", kparams.UnicodeString, []string{"ps.child.username contains 'system'"}, nil, nil}, + PsUUID: {PsUUID, "unique process identifier", kparams.Uint64, []string{"ps.uuid > 6000054355"}, nil, nil}, + PsParentUUID: {PsParentUUID, "unique parent process identifier", kparams.Uint64, []string{"ps.parent.uuid > 6000054355"}, nil, nil}, + PsChildUUID: {PsChildUUID, "unique child process identifier", kparams.Uint64, []string{"ps.child.uuid > 6000054355"}, nil, nil}, + PsChildPeFilename: {PsChildPeFilename, "original file name of the child process executable supplied at compile-time", kparams.UnicodeString, []string{"ps.child.pe.file.name = 'NOTEPAD.EXE'"}, nil, nil}, + PsChildIsWOW64Field: {PsChildIsWOW64Field, "indicates if the 32-bit child process is created in 64-bit Windows system", kparams.Bool, []string{"ps.child.is_wow64"}, nil, nil}, + PsChildIsPackagedField: {PsChildIsPackagedField, "indicates if the child process is packaged with the MSIX technology", kparams.Bool, []string{"ps.child.is_packaged"}, nil, nil}, + PsChildIsProtectedField: {PsChildIsProtectedField, "indicates if the child process is a protected process", kparams.Bool, []string{"ps.child.is_protected"}, nil, nil}, + PsIsWOW64Field: {PsIsWOW64Field, "indicates if the process generating the event is a 32-bit process created in 64-bit Windows system", kparams.Bool, []string{"ps.is_wow64"}, nil, nil}, + PsIsPackagedField: {PsIsPackagedField, "indicates if the process generating the event is packaged with the MSIX technology", kparams.Bool, []string{"ps.is_packaged"}, nil, nil}, + PsIsProtectedField: {PsIsProtectedField, "indicates if the process generating the event is a protected process", kparams.Bool, []string{"ps.is_protected"}, nil, nil}, + PsParentIsWOW64Field: {PsParentIsWOW64Field, "indicates if the parent process generating the event is a 32-bit process created in 64-bit Windows system", kparams.Bool, []string{"ps.parent.is_wow64"}, nil, nil}, + PsParentIsPackagedField: {PsParentIsPackagedField, "indicates if the parent process generating the event is packaged with the MSIX technology", kparams.Bool, []string{"ps.parent.is_packaged"}, nil, nil}, + PsParentIsProtectedField: {PsParentIsProtectedField, "indicates if the the parent process generating the event is a protected process", kparams.Bool, []string{"ps.parent.is_protected"}, nil, nil}, + PsAncestor: {PsAncestor, "the process ancestor name", kparams.UnicodeString, []string{"ps.ancestor[1] = 'svchost.exe'", "ps.ancestor in ('winword.exe')"}, nil, &Argument{Optional: true, Pattern: "[0-9]+", ValidationFunc: func(s string) bool { + for _, c := range s { + if !unicode.IsNumber(c) { + return false + } + } + return true + }}}, + + ThreadBasePrio: {ThreadBasePrio, "scheduler priority of the thread", kparams.Int8, []string{"thread.prio = 5"}, nil, nil}, + ThreadIOPrio: {ThreadIOPrio, "I/O priority hint for scheduling I/O operations", kparams.Int8, []string{"thread.io.prio = 4"}, nil, nil}, + ThreadPagePrio: {ThreadPagePrio, "memory page priority hint for memory pages accessed by the thread", kparams.Int8, []string{"thread.page.prio = 12"}, nil, nil}, + ThreadKstackBase: {ThreadKstackBase, "base address of the thread's kernel space stack", kparams.Address, []string{"thread.kstack.base = 'a65d800000'"}, nil, nil}, + ThreadKstackLimit: {ThreadKstackLimit, "limit of the thread's kernel space stack", kparams.Address, []string{"thread.kstack.limit = 'a85d800000'"}, nil, nil}, + ThreadUstackBase: {ThreadUstackBase, "base address of the thread's user space stack", kparams.Address, []string{"thread.ustack.base = '7ffe0000'"}, nil, nil}, + ThreadUstackLimit: {ThreadUstackLimit, "limit of the thread's user space stack", kparams.Address, []string{"thread.ustack.limit = '8ffe0000'"}, nil, nil}, + ThreadEntrypoint: {ThreadEntrypoint, "starting address of the function to be executed by the thread", kparams.Address, []string{"thread.entrypoint = '7efe0000'"}, &Deprecation{Since: "2.3.0", Fields: []Field{ThreadStartAddress}}, nil}, + ThreadStartAddress: {ThreadStartAddress, "thread start address", kparams.Address, []string{"thread.start_address = '7efe0000'"}, nil, nil}, + ThreadPID: {ThreadPID, "the process identifier where the thread is created", kparams.Uint32, []string{"kevt.pid != thread.pid"}, nil, nil}, + ThreadTEB: {ThreadTEB, "the base address of the thread environment block", kparams.Address, []string{"thread.teb_address = '8f30893000'"}, nil, nil}, + ThreadAccessMask: {ThreadAccessMask, "thread desired access rights", kparams.AnsiString, []string{"thread.access.mask = '0x1fffff'"}, nil, nil}, + ThreadAccessMaskNames: {ThreadAccessMaskNames, "thread desired access rights as a string list", kparams.Slice, []string{"thread.access.mask.names in ('IMPERSONATE')"}, nil, nil}, + ThreadAccessStatus: {ThreadAccessStatus, "thread access status", kparams.UnicodeString, []string{"thread.access.status = 'success'"}, nil, nil}, + ThreadCallstackSummary: {ThreadCallstackSummary, "callstack summary", kparams.UnicodeString, []string{"thread.callstack.summary contains 'ntdll.dll|KERNELBASE.dll'"}, nil, nil}, + ThreadCallstackDetail: {ThreadCallstackDetail, "detailed information of each stack frame", kparams.UnicodeString, []string{"thread.callstack.detail contains 'KERNELBASE.dll!CreateProcessW'"}, nil, nil}, + ThreadCallstackModules: {ThreadCallstackModules, "list of modules comprising the callstack", kparams.Slice, []string{"thread.callstack.modules in ('C:\\WINDOWS\\System32\\KERNELBASE.dll')"}, nil, nil}, + ThreadCallstackSymbols: {ThreadCallstackSymbols, "list of symbols comprising the callstack", kparams.Slice, []string{"thread.callstack.symbols in ('ntdll.dll!NtCreateProcess')"}, nil, nil}, + ThreadCallstackAllocationSizes: {ThreadCallstackAllocationSizes, "allocation sizes of private pages", kparams.Slice, []string{"thread.callstack.allocation_sizes > 10000"}, nil, nil}, + ThreadCallstackProtections: {ThreadCallstackProtections, "page protections masks of each frame", kparams.Slice, []string{"thread.callstack.protections in ('RWX', 'WX')"}, nil, nil}, + ThreadCallstackCallsiteLeadingAssembly: {ThreadCallstackCallsiteLeadingAssembly, "callsite leading assembly instructions", kparams.Slice, []string{"thread.callstack.callsite_leading_assembly in ('mov r10,rcx', 'syscall')"}, nil, nil}, + ThreadCallstackCallsiteTrailingAssembly: {ThreadCallstackCallsiteTrailingAssembly, "callsite trailing assembly instructions", kparams.Slice, []string{"thread.callstack.callsite_trailing_assembly in ('add esp, 0xab')"}, nil, nil}, + ThreadCallstackIsUnbacked: {ThreadCallstackIsUnbacked, "indicates if the callstack contains unbacked regions", kparams.Bool, []string{"thread.callstack.is_unbacked"}, nil, nil}, + + ImagePath: {ImagePath, "full image path", kparams.UnicodeString, []string{"image.patj = 'C:\\Windows\\System32\\advapi32.dll'"}, nil, nil}, + ImageName: {ImageName, "image name", kparams.UnicodeString, []string{"image.name = 'advapi32.dll'"}, nil, nil}, + ImageBase: {ImageBase, "the base address of process in which the image is loaded", kparams.Address, []string{"image.base.address = 'a65d800000'"}, nil, nil}, + ImageChecksum: {ImageChecksum, "image checksum", kparams.Uint32, []string{"image.checksum = 746424"}, nil, nil}, + ImageSize: {ImageSize, "image size", kparams.Uint32, []string{"image.size > 1024"}, nil, nil}, + ImageDefaultAddress: {ImageDefaultAddress, "default image address", kparams.Address, []string{"image.default.address = '7efe0000'"}, nil, nil}, + ImagePID: {ImagePID, "target process identifier", kparams.Uint32, []string{"image.pid = 80"}, nil, nil}, + ImageSignatureType: {ImageSignatureType, "image signature type", kparams.AnsiString, []string{"image.signature.type != 'NONE'"}, nil, nil}, + ImageSignatureLevel: {ImageSignatureLevel, "image signature level", kparams.AnsiString, []string{"image.signature.level = 'AUTHENTICODE'"}, nil, nil}, + ImageCertSerial: {ImageCertSerial, "image certificate serial number", kparams.UnicodeString, []string{"image.cert.serial = '330000023241fb59996dcc4dff000000000232'"}, nil, nil}, + ImageCertSubject: {ImageCertSubject, "image certificate subject", kparams.UnicodeString, []string{"image.cert.subject contains 'Washington, Redmond, Microsoft Corporation'"}, nil, nil}, + ImageCertIssuer: {ImageCertIssuer, "image certificate CA", kparams.UnicodeString, []string{"image.cert.issuer contains 'Washington, Redmond, Microsoft Corporation'"}, nil, nil}, + ImageCertAfter: {ImageCertAfter, "image certificate expiration date", kparams.Time, []string{"image.cert.after contains '2024-02-01 00:05:42 +0000 UTC'"}, nil, nil}, + ImageCertBefore: {ImageCertBefore, "image certificate enrollment date", kparams.Time, []string{"image.cert.before contains '2024-02-01 00:05:42 +0000 UTC'"}, nil, nil}, + ImageIsDriverMalicious: {ImageIsDriverMalicious, "indicates if the loaded driver is malicious", kparams.Bool, []string{"image.is_driver_malicious"}, nil, nil}, + ImageIsDriverVulnerable: {ImageIsDriverVulnerable, "indicates if the loaded driver is vulnerable", kparams.Bool, []string{"image.is_driver_vulnerable"}, nil, nil}, + ImageIsDLL: {ImageIsDLL, "indicates if the loaded image is a DLL", kparams.Bool, []string{"image.is_dll'"}, nil, nil}, + ImageIsDriver: {ImageIsDriver, "indicates if the loaded image is a driver", kparams.Bool, []string{"image.is_driver'"}, nil, nil}, + ImageIsExecutable: {ImageIsExecutable, "indicates if the loaded image is an executable", kparams.Bool, []string{"image.is_exec'"}, nil, nil}, + ImageIsDotnet: {ImageIsDotnet, "indicates if the loaded image is a .NET assembly", kparams.Bool, []string{"image.is_dotnet'"}, nil, nil}, + + FileObject: {FileObject, "file object address", kparams.Uint64, []string{"file.object = 18446738026482168384"}, nil, nil}, + FilePath: {FilePath, "full file path", kparams.UnicodeString, []string{"file.path = 'C:\\Windows\\System32'"}, nil, nil}, + FileName: {FileName, "full file name", kparams.UnicodeString, []string{"file.name contains 'mimikatz'"}, nil, nil}, + FileOperation: {FileOperation, "file operation", kparams.AnsiString, []string{"file.operation = 'open'"}, nil, nil}, + FileShareMask: {FileShareMask, "file share mask", kparams.AnsiString, []string{"file.share.mask = 'rw-'"}, nil, nil}, + FileIOSize: {FileIOSize, "file I/O size", kparams.Uint32, []string{"file.io.size > 512"}, nil, nil}, + FileOffset: {FileOffset, "file offset", kparams.Uint64, []string{"file.offset = 1024"}, nil, nil}, + FileType: {FileType, "file type", kparams.AnsiString, []string{"file.type = 'directory'"}, nil, nil}, + FileExtension: {FileExtension, "file extension", kparams.AnsiString, []string{"file.extension = '.dll'"}, nil, nil}, + FileAttributes: {FileAttributes, "file attributes", kparams.Slice, []string{"file.attributes in ('archive', 'hidden')"}, nil, nil}, + FileStatus: {FileStatus, "file operation status message", kparams.UnicodeString, []string{"file.status != 'success'"}, nil, nil}, + FileViewBase: {FileViewBase, "view base address", kparams.Address, []string{"file.view.base = '25d42170000'"}, nil, nil}, + FileViewSize: {FileViewSize, "size of the mapped view", kparams.Uint64, []string{"file.view.size > 1024"}, nil, nil}, + FileViewType: {FileViewType, "type of the mapped view section", kparams.Enum, []string{"file.view.type = 'IMAGE'"}, nil, nil}, + FileViewProtection: {FileViewProtection, "protection rights of the section view", kparams.AnsiString, []string{"file.view.protection = 'READONLY'"}, nil, nil}, + FileIsDriverMalicious: {FileIsDriverMalicious, "indicates if the dropped driver is malicious", kparams.Bool, []string{"file.is_driver_malicious"}, nil, nil}, + FileIsDriverVulnerable: {FileIsDriverVulnerable, "indicates if the dropped driver is vulnerable", kparams.Bool, []string{"file.is_driver_vulnerable"}, nil, nil}, + FileIsDLL: {FileIsDLL, "indicates if the created file is a DLL", kparams.Bool, []string{"file.is_dll'"}, nil, nil}, + FileIsDriver: {FileIsDriver, "indicates if the created file is a driver", kparams.Bool, []string{"file.is_driver'"}, nil, nil}, + FileIsExecutable: {FileIsExecutable, "indicates if the created file is an executable", kparams.Bool, []string{"file.is_exec'"}, nil, nil}, + FilePID: {FilePID, "denotes the process id performing file operation", kparams.PID, []string{"file.pid = 4"}, nil, nil}, + FileKey: {FileKey, "uniquely identifies the file object", kparams.Uint64, []string{"file.key = 12446738026482168384"}, nil, nil}, + FileInfoClass: {FileInfoClass, "identifies the file information class", kparams.Enum, []string{"file.info_class = 'Allocation'"}, nil, nil}, + FileInfoAllocationSize: {FileInfoAllocationSize, "file allocation size", kparams.Uint64, []string{"file.info.allocation_size > 645400"}, nil, nil}, + FileInfoEOFSize: {FileInfoEOFSize, "file EOF size", kparams.Uint64, []string{"file.info.eof_size > 1000"}, nil, nil}, + FileInfoIsDispositionDeleteFile: {FileInfoIsDispositionDeleteFile, "indicates if the file is deleted when its handle is closed", kparams.Bool, []string{"file.info.is_disposition_file_delete = true"}, nil, nil}, + + RegistryPath: {RegistryPath, "fully qualified registry path", kparams.UnicodeString, []string{"registry.path = 'HKEY_LOCAL_MACHINE\\SYSTEM'"}, nil, nil}, + RegistryKeyName: {RegistryKeyName, "registry key name", kparams.UnicodeString, []string{"registry.key.name = 'CurrentControlSet'"}, nil, nil}, + RegistryKeyHandle: {RegistryKeyHandle, "registry key object address", kparams.Address, []string{"registry.key.handle = 'FFFFB905D60C2268'"}, nil, nil}, + RegistryValue: {RegistryValue, "registry value content", kparams.UnicodeString, []string{"registry.value = '%SystemRoot%\\system32'"}, nil, nil}, + RegistryValueType: {RegistryValueType, "type of registry value", kparams.UnicodeString, []string{"registry.value.type = 'REG_SZ'"}, nil, nil}, + RegistryStatus: {RegistryStatus, "status of registry operation", kparams.UnicodeString, []string{"registry.status != 'success'"}, nil, nil}, + + NetDIP: {NetDIP, "destination IP address", kparams.IP, []string{"net.dip = 172.17.0.3"}, nil, nil}, + NetSIP: {NetSIP, "source IP address", kparams.IP, []string{"net.sip = 127.0.0.1"}, nil, nil}, + NetDport: {NetDport, "destination port", kparams.Uint16, []string{"net.dport in (80, 443, 8080)"}, nil, nil}, + NetSport: {NetSport, "source port", kparams.Uint16, []string{"net.sport != 3306"}, nil, nil}, + NetDportName: {NetDportName, "destination port name", kparams.AnsiString, []string{"net.dport.name = 'dns'"}, nil, nil}, + NetSportName: {NetSportName, "source port name", kparams.AnsiString, []string{"net.sport.name = 'http'"}, nil, nil}, + NetL4Proto: {NetL4Proto, "layer 4 protocol name", kparams.AnsiString, []string{"net.l4.proto = 'TCP"}, nil, nil}, + NetPacketSize: {NetPacketSize, "packet size", kparams.Uint32, []string{"net.size > 512"}, nil, nil}, + NetSIPNames: {NetSIPNames, "source IP names", kparams.Slice, []string{"net.sip.names in ('github.com.')"}, nil, nil}, + NetDIPNames: {NetDIPNames, "destination IP names", kparams.Slice, []string{"net.dip.names in ('github.com.')"}, nil, nil}, + + HandleID: {HandleID, "handle identifier", kparams.Uint16, []string{"handle.id = 24"}, nil, nil}, + HandleObject: {HandleObject, "handle object address", kparams.Address, []string{"handle.object = 'FFFFB905DBF61988'"}, nil, nil}, + HandleName: {HandleName, "handle name", kparams.UnicodeString, []string{"handle.name = '\\Device\\NamedPipe\\chrome.12644.28.105826381'"}, nil, nil}, + HandleType: {HandleType, "handle type", kparams.AnsiString, []string{"handle.type = 'Mutant'"}, nil, nil}, + + PeNumSections: {PeNumSections, "number of sections", kparams.Uint16, []string{"pe.nsections < 5"}, nil, nil}, + PeNumSymbols: {PeNumSymbols, "number of entries in the symbol table", kparams.Uint32, []string{"pe.nsymbols > 230"}, nil, nil}, + PeBaseAddress: {PeBaseAddress, "image base address", kparams.Address, []string{"pe.address.base = '140000000'"}, nil, nil}, + PeEntrypoint: {PeEntrypoint, "address of the entrypoint function", kparams.Address, []string{"pe.address.entrypoint = '20110'"}, nil, nil}, + PeSymbols: {PeSymbols, "imported symbols", kparams.Slice, []string{"pe.symbols in ('GetTextFaceW', 'GetProcessHeap')"}, nil, nil}, + PeImports: {PeImports, "imported dynamic linked libraries", kparams.Slice, []string{"pe.imports in ('msvcrt.dll', 'GDI32.dll'"}, nil, nil}, + + PeResources: {PeResources, "version resources", kparams.Map, []string{"pe.resources[FileDescription] = 'Notepad'"}, nil, &Argument{Optional: true, Pattern: "[a-zA-Z0-9_]+", ValidationFunc: func(s string) bool { + for _, c := range s { + switch { + case unicode.IsLower(c): + case unicode.IsUpper(c): + case unicode.IsNumber(c): + case c == '_': + default: + return false + } + } + return true + }}}, + + PeCompany: {PeCompany, "internal company name of the file provided at compile-time", kparams.UnicodeString, []string{"pe.company = 'Microsoft Corporation'"}, nil, nil}, + PeCopyright: {PeCopyright, "copyright notice for the file emitted at compile-time", kparams.UnicodeString, []string{"pe.copyright = '© Microsoft Corporation'"}, nil, nil}, + PeDescription: {PeDescription, "internal description of the file provided at compile-time", kparams.UnicodeString, []string{"pe.description = 'Notepad'"}, nil, nil}, + PeFileName: {PeFileName, "original file name supplied at compile-time", kparams.UnicodeString, []string{"pe.file.name = 'NOTEPAD.EXE'"}, nil, nil}, + PeFileVersion: {PeFileVersion, "file version supplied at compile-time", kparams.UnicodeString, []string{"pe.file.version = '10.0.18362.693 (WinBuild.160101.0800)'"}, nil, nil}, + PeProduct: {PeProduct, "internal product name of the file provided at compile-time", kparams.UnicodeString, []string{"pe.product = 'Microsoft® Windows® Operating System'"}, nil, nil}, + PeProductVersion: {PeProductVersion, "internal product version of the file provided at compile-time", kparams.UnicodeString, []string{"pe.product.version = '10.0.18362.693'"}, nil, nil}, + PeIsDLL: {PeIsDLL, "indicates if the loaded image or created file is a DLL", kparams.Bool, []string{"pe.is_dll'"}, &Deprecation{Since: "2.0.0", Fields: []Field{FileIsDLL, ImageIsDLL}}, nil}, + PeIsDriver: {PeIsDriver, "indicates if the loaded image or created file is a driver", kparams.Bool, []string{"pe.is_driver'"}, &Deprecation{Since: "2.0.0", Fields: []Field{FileIsDriver, ImageIsDriver}}, nil}, + PeIsExecutable: {PeIsExecutable, "indicates if the loaded image or created file is an executable", kparams.Bool, []string{"pe.is_exec'"}, &Deprecation{Since: "2.0.0", Fields: []Field{FileIsExecutable, ImageIsExecutable}}, nil}, + PeImphash: {PeImphash, "import hash", kparams.AnsiString, []string{"pe.impash = '5d3861c5c547f8a34e471ba273a732b2'"}, nil, nil}, + PeIsDotnet: {PeIsDotnet, "indicates if PE contains CLR data", kparams.Bool, []string{"pe.is_dotnet"}, nil, nil}, + PeAnomalies: {PeAnomalies, "contains PE anomalies detected during parsing", kparams.Slice, []string{"pe.anomalies in ('number of sections is 0')"}, nil, nil}, + PeIsSigned: {PeIsSigned, "indicates if the PE has embedded or catalog signature", kparams.Bool, []string{"pe.is_signed"}, nil, nil}, + PeIsTrusted: {PeIsTrusted, "indicates if the PE certificate chain is trusted", kparams.Bool, []string{"pe.is_trusted"}, nil, nil}, + PeCertSerial: {PeCertSerial, "PE certificate serial number", kparams.UnicodeString, []string{"pe.cert.serial = '330000023241fb59996dcc4dff000000000232'"}, nil, nil}, + PeCertSubject: {PeCertSubject, "PE certificate subject", kparams.UnicodeString, []string{"pe.cert.subject contains 'Washington, Redmond, Microsoft Corporation'"}, nil, nil}, + PeCertIssuer: {PeCertIssuer, "PE certificate CA", kparams.UnicodeString, []string{"pe.cert.issuer contains 'Washington, Redmond, Microsoft Corporation'"}, nil, nil}, + PeCertAfter: {PeCertAfter, "PE certificate expiration date", kparams.Time, []string{"pe.cert.after contains '2024-02-01 00:05:42 +0000 UTC'"}, nil, nil}, + PeCertBefore: {PeCertBefore, "PE certificate enrollment date", kparams.Time, []string{"pe.cert.before contains '2024-02-01 00:05:42 +0000 UTC'"}, nil, nil}, + PeIsModified: {PeIsModified, "indicates if disk and in-memory PE headers differ", kparams.Bool, []string{"pe.is_modified"}, nil, nil}, + PePsChildFileName: {PePsChildFileName, "original file name of the child process executable supplied at compile-time", kparams.UnicodeString, []string{"pe.ps.child.file.name = 'NOTEPAD.EXE'"}, &Deprecation{Since: "2.3.0", Fields: []Field{PsChildPeFilename}}, nil}, + + MemBaseAddress: {MemBaseAddress, "region base address", kparams.Address, []string{"mem.address = '211d13f2000'"}, nil, nil}, + MemRegionSize: {MemRegionSize, "region size", kparams.Uint64, []string{"mem.size > 438272"}, nil, nil}, + MemAllocType: {MemAllocType, "region allocation or release type", kparams.Flags, []string{"mem.alloc = 'COMMIT'"}, nil, nil}, + MemPageType: {MemPageType, "page type of the allocated region", kparams.Enum, []string{"mem.type = 'PRIVATE'"}, nil, nil}, + MemProtection: {MemProtection, "allocated region protection type", kparams.Enum, []string{"mem.protection = 'READWRITE'"}, nil, nil}, + MemProtectionMask: {MemProtectionMask, "allocated region protection in mask notation", kparams.Enum, []string{"mem.protection.mask = 'RWX'"}, nil, nil}, + + DNSName: {DNSName, "dns query name", kparams.UnicodeString, []string{"dns.name = 'example.org'"}, nil, nil}, + DNSRR: {DNSRR, "dns resource record type", kparams.AnsiString, []string{"dns.rr = 'AA'"}, nil, nil}, + DNSOptions: {DNSOptions, "dns query options", kparams.Flags64, []string{"dns.options in ('ADDRCONFIG', 'DUAL_ADDR')"}, nil, nil}, + DNSRcode: {DNSRR, "dns response status", kparams.AnsiString, []string{"dns.rcode = 'NXDOMAIN'"}, nil, nil}, + DNSAnswers: {DNSAnswers, "dns response answers", kparams.Slice, []string{"dns.answers in ('o.lencr.edgesuite.net', 'a1887.dscq.akamai.net')"}, nil, nil}, } -// Lookup finds the field literal in the map. For the nested fields, it checks the pattern matches -// the expected one and compares the paths. If all checks pass, the full argument key is returned. -func Lookup(name string) Field { - f := Field(name) - - if IsPseudoField(f) { - return f - } - - if _, ok := fields[f]; ok { - return f +// ArgumentOf returns argument data for the specified field. +func ArgumentOf(name string) *Argument { + f, ok := fields[Field(name)] + if !ok { + // this can happen for pseudo fields + return nil } + return f.Argument +} - groups := pathRegexp.FindStringSubmatch(name) - if len(groups) != 3 { - return None +// IsField returns true if the provided string is a +// recognized field or pseudo field. Otherwise, it +// returns false. +func IsField(name string) bool { + if _, ok := fields[Field(name)]; ok || IsPseudoField(Field(name)) { + return true } - - field := groups[1] // `ps.envs` is a field in ps.envs[PATH] - key := groups[2] // `PATH` is a key in ps.envs[PATH] - - switch Field(field) { - case PeResources: - if key != "" { - return Field(name) - } - case PsEnvs, KevtArg: - if key != "" { - return Field(name) - } - } - - return None + return false } diff --git a/pkg/filter/fields/fields_windows_test.go b/pkg/filter/fields/fields_windows_test.go index 1dccb130f..4359593ff 100644 --- a/pkg/filter/fields/fields_windows_test.go +++ b/pkg/filter/fields/fields_windows_test.go @@ -23,15 +23,24 @@ import ( "testing" ) -func TestLookup(t *testing.T) { - assert.Equal(t, PsPid, Lookup("ps.pid")) - assert.Equal(t, Field("ps.envs[ALLUSERSPROFILE]"), Lookup("ps.envs[ALLUSERSPROFILE]")) - assert.Empty(t, Lookup("ps.envs[ALLUSERSPROFILE")) - assert.Empty(t, Lookup("ps.envs[")) - assert.Empty(t, Lookup("ps.envs[]")) - assert.Equal(t, PsEnvs, Lookup("ps.envs")) - assert.Equal(t, Field("kevt.arg[exe]"), Lookup("kevt.arg[exe]")) - assert.Empty(t, Lookup("kevt.arg")) +func TestIsField(t *testing.T) { + var tests = []struct { + name string + isField bool + }{ + {"ps.pid", true}, + {"ps.none", false}, + {"ps.envs[ALLUSERSPROFILE]", false}, + {"kevt.arg", true}, + {"thread._callstack", true}, + {"kevt._callstack", false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.isField, IsField(tt.name)) + }) + } } func TestIsDeprecated(t *testing.T) { 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/ql/ast.go b/pkg/filter/ql/ast.go index 28c7e5c05..ba8147122 100644 --- a/pkg/filter/ql/ast.go +++ b/pkg/filter/ql/ast.go @@ -138,7 +138,7 @@ func (v *ValuerEval) Eval(expr Expr) interface{} { // binary and unary expressions. if exp.IsForeach() { switch { - case exp.IsBinaryExprArg(i) || exp.IsNotExprArg(i) || exp.IsBoundFieldArg(i): + case exp.IsBinaryExprArg(i) || exp.IsNotExprArg(i) || exp.IsBareBoundVariableArg(i): args[i] = exp.Args[i] case exp.IsFieldArg(i): if i != 0 { @@ -209,6 +209,18 @@ func (v *ValuerEval) Eval(expr Expr) interface{} { return nil } return val + case *BoundSegmentLiteral: + val, ok := v.Valuer.Value(expr.Value) + if !ok { + return nil + } + return val + case *BareBoundVariableLiteral: + val, ok := v.Valuer.Value(expr.Value) + if !ok { + return nil + } + return val case *IPLiteral: return expr.Value case *Function: @@ -224,7 +236,7 @@ func (v *ValuerEval) Eval(expr Expr) interface{} { // binary and unary expressions. if expr.IsForeach() { switch { - case expr.IsBinaryExprArg(i) || expr.IsNotExprArg(i) || expr.IsBoundFieldArg(i): + case expr.IsBinaryExprArg(i) || expr.IsNotExprArg(i) || expr.IsBareBoundVariableArg(i): args[i] = expr.Args[i] case expr.IsFieldArg(i): if i != 0 { diff --git a/pkg/filter/ql/function.go b/pkg/filter/ql/function.go index cf0372504..b2f7d24fe 100644 --- a/pkg/filter/ql/function.go +++ b/pkg/filter/ql/function.go @@ -164,7 +164,7 @@ func (f *Foreach) Call(args []interface{}) (interface{}, bool) { return false, false } - v, ok := args[1].(*BoundFieldLiteral) // item (variable) + v, ok := args[1].(*BareBoundVariableLiteral) // item (variable) if !ok { return false, false } @@ -185,14 +185,14 @@ func (f *Foreach) Call(args []interface{}) (interface{}, bool) { } } - flds := make([]*BoundFieldLiteral, 0) + segments := make([]*BoundSegmentLiteral, 0) - // obtain bound fields used in expression + // obtain bound segments used in expression var useCallValuer bool walk := func(n Node) { switch exp := n.(type) { - case *BoundFieldLiteral: - flds = append(flds, exp) + case *BoundSegmentLiteral: + segments = append(segments, exp) case *Function: useCallValuer = true } @@ -214,31 +214,31 @@ func (f *Foreach) Call(args []interface{}) (interface{}, bool) { } case []*pstypes.PS: for _, proc := range elems { - if f.evalExpr(e, useCallValuer, f.procMapValuer(flds, proc), valuer) { + if f.evalExpr(e, useCallValuer, f.procMapValuer(segments, proc), valuer) { return true, true } } case []pstypes.Module: for _, mod := range elems { - if f.evalExpr(e, useCallValuer, f.moduleMapValuer(flds, mod), valuer) { + if f.evalExpr(e, useCallValuer, f.moduleMapValuer(segments, mod), valuer) { return true, true } } case map[uint32]pstypes.Thread: for _, thread := range elems { - if f.evalExpr(e, useCallValuer, f.threadMapValuer(flds, thread), valuer) { + if f.evalExpr(e, useCallValuer, f.threadMapValuer(segments, thread), valuer) { return true, true } } case []pstypes.Mmap: for _, mmap := range elems { - if f.evalExpr(e, useCallValuer, f.mmapMapValuer(flds, mmap), valuer) { + if f.evalExpr(e, useCallValuer, f.mmapMapValuer(segments, mmap), valuer) { return true, true } } case []pe.Sec: for _, sec := range elems { - if f.evalExpr(e, useCallValuer, f.sectionMapValuer(flds, sec), valuer) { + if f.evalExpr(e, useCallValuer, f.sectionMapValuer(segments, sec), valuer) { return true, true } } @@ -254,9 +254,10 @@ func (f *Foreach) Call(args []interface{}) (interface{}, bool) { // open process handle with required access mask var desiredAccess uint32 loop: - for _, fld := range flds { - switch fld.Segment() { + for _, seg := range segments { + switch seg.Segment { case fields.CallsiteLeadingAssemblySegment, fields.CallsiteTrailingAssemblySegment: + // break on broader access rights desiredAccess = windows.PROCESS_QUERY_INFORMATION | windows.PROCESS_VM_READ break loop case fields.AllocationSizeSegment, fields.ProtectionSegment: @@ -272,7 +273,7 @@ func (f *Foreach) Call(args []interface{}) (interface{}, bool) { } for _, frame := range elems { - if f.evalExpr(e, useCallValuer, f.callstackMapValuer(flds, frame, proc), valuer) { + if f.evalExpr(e, useCallValuer, f.callstackMapValuer(segments, frame, proc), valuer) { return true, true } } @@ -286,7 +287,7 @@ func (f *Foreach) Desc() functions.FunctionDesc { Name: functions.ForeachFn, Args: []functions.FunctionArgDesc{ {Keyword: "iterable", Types: []functions.ArgType{functions.Field}, Required: true}, - {Keyword: "var", Types: []functions.ArgType{functions.BoundField}, Required: true}, + {Keyword: "var", Types: []functions.ArgType{functions.BareBoundVariable}, Required: true}, {Keyword: "predicate", Types: []functions.ArgType{functions.Expression}, Required: true}, }, ArgsValidationFunc: func(args []string) error { @@ -313,10 +314,10 @@ func (f *Foreach) Desc() functions.FunctionDesc { } var hasBoundVar bool - var boundFieldRegexp = regexp.MustCompile(`(\$[a-zA-Z_0-9]+)\.?([a-zA-Z0-9_.$]*)`) + var boundSegmentRegexp = regexp.MustCompile(`(\$[a-zA-Z_0-9]+)\.?([a-zA-Z0-9_.$]*)`) // scan all bound fields inside expression - matches := boundFieldRegexp.FindAllStringSubmatch(e, -1) + matches := boundSegmentRegexp.FindAllStringSubmatch(e, -1) for _, match := range matches { // check the bound variable references @@ -433,16 +434,16 @@ func (f *Foreach) evalExpr(e any, useCallValuer bool, valuers ...Valuer) bool { } // stringMapValuer returns the map valuer composed of primitive string values. -func (f *Foreach) stringMapValuer(v *BoundFieldLiteral, s string) MapValuer { - return MapValuer{v.Alias(): s} +func (f *Foreach) stringMapValuer(v *BareBoundVariableLiteral, s string) MapValuer { + return MapValuer{v.Value: s} } // moduleMapValuer returns the map valuer with process module attributes. -func (f *Foreach) moduleMapValuer(flds []*BoundFieldLiteral, mod pstypes.Module) MapValuer { +func (f *Foreach) moduleMapValuer(segments []*BoundSegmentLiteral, mod pstypes.Module) MapValuer { var valuer = MapValuer{} - for _, field := range flds { - key := field.Value - switch field.Segment() { + for _, seg := range segments { + key := seg.Value + switch seg.Segment { case fields.PathSegment: valuer[key] = mod.Name case fields.NameSegment: @@ -459,11 +460,11 @@ func (f *Foreach) moduleMapValuer(flds []*BoundFieldLiteral, mod pstypes.Module) } // procMapValuer returns the map valuer with process attributes. -func (f *Foreach) procMapValuer(flds []*BoundFieldLiteral, proc *pstypes.PS) MapValuer { +func (f *Foreach) procMapValuer(segments []*BoundSegmentLiteral, proc *pstypes.PS) MapValuer { var valuer = MapValuer{} - for _, field := range flds { - key := field.Value - switch field.Segment() { + for _, seg := range segments { + key := seg.Value + switch seg.Segment { case fields.PIDSegment: valuer[key] = proc.PID case fields.NameSegment: @@ -490,11 +491,11 @@ func (f *Foreach) procMapValuer(flds []*BoundFieldLiteral, proc *pstypes.PS) Map } // threadMapValuer returns the map valuer with thread information. -func (f *Foreach) threadMapValuer(flds []*BoundFieldLiteral, thread pstypes.Thread) MapValuer { +func (f *Foreach) threadMapValuer(segments []*BoundSegmentLiteral, thread pstypes.Thread) MapValuer { var valuer = MapValuer{} - for _, field := range flds { - key := field.Value - switch field.Segment() { + for _, seg := range segments { + key := seg.Value + switch seg.Segment { case fields.TidSegment: valuer[key] = thread.Tid case fields.StartAddressSegment: @@ -513,11 +514,11 @@ func (f *Foreach) threadMapValuer(flds []*BoundFieldLiteral, thread pstypes.Thre } // mmapMapValuer returns map valuer with memory mapping details. -func (f *Foreach) mmapMapValuer(flds []*BoundFieldLiteral, mmap pstypes.Mmap) MapValuer { +func (f *Foreach) mmapMapValuer(segments []*BoundSegmentLiteral, mmap pstypes.Mmap) MapValuer { var valuer = MapValuer{} - for _, field := range flds { - key := field.Value - switch field.Segment() { + for _, seg := range segments { + key := seg.Value + switch seg.Segment { case fields.AddressSegment: valuer[key] = mmap.BaseAddress.String() case fields.SizeSegment: @@ -534,11 +535,11 @@ func (f *Foreach) mmapMapValuer(flds []*BoundFieldLiteral, mmap pstypes.Mmap) Ma } // callstackMapValuer returns map valuer with thread stack frame data. -func (f *Foreach) callstackMapValuer(flds []*BoundFieldLiteral, frame kevent.Frame, proc windows.Handle) MapValuer { +func (f *Foreach) callstackMapValuer(segments []*BoundSegmentLiteral, frame kevent.Frame, proc windows.Handle) MapValuer { var valuer = MapValuer{} - for _, field := range flds { - key := field.Value - switch field.Segment() { + for _, seg := range segments { + key := seg.Value + switch seg.Segment { case fields.AddressSegment: valuer[key] = frame.Addr.String() case fields.OffsetSegment: @@ -563,11 +564,11 @@ func (f *Foreach) callstackMapValuer(flds []*BoundFieldLiteral, frame kevent.Fra } // sectionMapValuer returns map valuer with PE section data. -func (f *Foreach) sectionMapValuer(flds []*BoundFieldLiteral, section pe.Sec) MapValuer { +func (f *Foreach) sectionMapValuer(segments []*BoundSegmentLiteral, section pe.Sec) MapValuer { var valuer = MapValuer{} - for _, field := range flds { - key := field.Value - switch field.Segment() { + for _, seg := range segments { + key := seg.Value + switch seg.Segment { case fields.NameSegment: valuer[key] = section.Name case fields.SizeSegment: diff --git a/pkg/filter/ql/functions/base.go b/pkg/filter/ql/functions/base.go index e884d5b73..154032c3e 100644 --- a/pkg/filter/ql/functions/base.go +++ b/pkg/filter/ql/functions/base.go @@ -47,7 +47,7 @@ func (f Base) Desc() FunctionDesc { desc := FunctionDesc{ Name: BaseFn, Args: []FunctionArgDesc{ - {Keyword: "path", Types: []ArgType{Field, BoundField, Func, String, Slice}, Required: true}, + {Keyword: "path", Types: []ArgType{Field, BoundField, BoundSegment, BareBoundVariable, Func, String, Slice}, Required: true}, {Keyword: "ext", Types: []ArgType{Bool}, Required: false}, }, } diff --git a/pkg/filter/ql/functions/cidr.go b/pkg/filter/ql/functions/cidr.go index 2f3408d70..7a797ba24 100644 --- a/pkg/filter/ql/functions/cidr.go +++ b/pkg/filter/ql/functions/cidr.go @@ -69,7 +69,7 @@ func (f CIDRContains) Desc() FunctionDesc { desc := FunctionDesc{ Name: CIDRContainsFn, Args: []FunctionArgDesc{ - {Keyword: "ip", Types: []ArgType{IP, Field, BoundField}, Required: true}, + {Keyword: "ip", Types: []ArgType{IP, Field, BoundField, BoundSegment, BareBoundVariable}, Required: true}, {Keyword: "cidr", Types: []ArgType{String}, Required: true}, }, } diff --git a/pkg/filter/ql/functions/concat.go b/pkg/filter/ql/functions/concat.go index e0bb7eebe..4e0df0ed3 100644 --- a/pkg/filter/ql/functions/concat.go +++ b/pkg/filter/ql/functions/concat.go @@ -65,8 +65,8 @@ func (f Concat) Desc() FunctionDesc { desc := FunctionDesc{ Name: ConcatFn, Args: []FunctionArgDesc{ - {Keyword: "string1", Types: []ArgType{String, Number, Field, BoundField, Func}, Required: true}, - {Keyword: "string2", Types: []ArgType{String, Number, Field, BoundField, Func}, Required: true}, + {Keyword: "string1", Types: []ArgType{String, Number, Field, BoundField, BoundSegment, BareBoundVariable, Func}, Required: true}, + {Keyword: "string2", Types: []ArgType{String, Number, Field, BoundField, BoundSegment, BareBoundVariable, Func}, Required: true}, }, } offset := len(desc.Args) diff --git a/pkg/filter/ql/functions/dir.go b/pkg/filter/ql/functions/dir.go index 75645d8d1..e49872103 100644 --- a/pkg/filter/ql/functions/dir.go +++ b/pkg/filter/ql/functions/dir.go @@ -44,7 +44,7 @@ func (f Dir) Desc() FunctionDesc { desc := FunctionDesc{ Name: DirFn, Args: []FunctionArgDesc{ - {Keyword: "path", Types: []ArgType{Field, BoundField, Func, String, Slice}, Required: true}, + {Keyword: "path", Types: []ArgType{Field, BoundField, BoundSegment, BareBoundVariable, Func, String, Slice}, Required: true}, }, } return desc diff --git a/pkg/filter/ql/functions/entropy.go b/pkg/filter/ql/functions/entropy.go index 257684cb7..8d3d423f3 100644 --- a/pkg/filter/ql/functions/entropy.go +++ b/pkg/filter/ql/functions/entropy.go @@ -55,7 +55,7 @@ func (f Entropy) Desc() FunctionDesc { desc := FunctionDesc{ Name: LengthFn, Args: []FunctionArgDesc{ - {Keyword: "string", Types: []ArgType{Field, BoundField, Func}, Required: true}, + {Keyword: "string", Types: []ArgType{Field, BoundField, BoundSegment, BareBoundVariable, Func}, Required: true}, {Keyword: "algo", Types: []ArgType{String}}, }, ArgsValidationFunc: func(args []string) error { diff --git a/pkg/filter/ql/functions/ext.go b/pkg/filter/ql/functions/ext.go index 7b540d5fe..167aece3e 100644 --- a/pkg/filter/ql/functions/ext.go +++ b/pkg/filter/ql/functions/ext.go @@ -45,7 +45,7 @@ func (f Ext) Desc() FunctionDesc { desc := FunctionDesc{ Name: ExtFn, Args: []FunctionArgDesc{ - {Keyword: "path", Types: []ArgType{Field, BoundField, Func, String}, Required: true}, + {Keyword: "path", Types: []ArgType{Field, BoundField, Func, BoundSegment, BareBoundVariable, String}, Required: true}, {Keyword: "dot", Types: []ArgType{Bool}, Required: false}, }, } diff --git a/pkg/filter/ql/functions/get_reg_value.go b/pkg/filter/ql/functions/get_reg_value.go index ef790db59..ecdd934f8 100644 --- a/pkg/filter/ql/functions/get_reg_value.go +++ b/pkg/filter/ql/functions/get_reg_value.go @@ -74,7 +74,7 @@ func (f GetRegValue) Desc() FunctionDesc { desc := FunctionDesc{ Name: GetRegValueFn, Args: []FunctionArgDesc{ - {Keyword: "path", Types: []ArgType{Field, BoundField, String, Func}, Required: true}, + {Keyword: "path", Types: []ArgType{Field, BoundField, String, BoundSegment, BareBoundVariable, Func}, Required: true}, }, } return desc diff --git a/pkg/filter/ql/functions/glob.go b/pkg/filter/ql/functions/glob.go index f7a6aeaef..0c6cacbe5 100644 --- a/pkg/filter/ql/functions/glob.go +++ b/pkg/filter/ql/functions/glob.go @@ -41,7 +41,7 @@ func (f Glob) Desc() FunctionDesc { desc := FunctionDesc{ Name: GlobFn, Args: []FunctionArgDesc{ - {Keyword: "pattern", Types: []ArgType{Field, BoundField, Func, String}, Required: true}, + {Keyword: "pattern", Types: []ArgType{Field, BoundField, Func, BoundSegment, BareBoundVariable, String}, Required: true}, }, } return desc diff --git a/pkg/filter/ql/functions/indexof.go b/pkg/filter/ql/functions/indexof.go index 37b491475..4381e38dc 100644 --- a/pkg/filter/ql/functions/indexof.go +++ b/pkg/filter/ql/functions/indexof.go @@ -75,7 +75,7 @@ func (f IndexOf) Desc() FunctionDesc { desc := FunctionDesc{ Name: IndexOfFn, Args: []FunctionArgDesc{ - {Keyword: "string", Types: []ArgType{Field, BoundField, Func}, Required: true}, + {Keyword: "string", Types: []ArgType{Field, BoundField, BoundSegment, BareBoundVariable, Func}, Required: true}, {Keyword: "substr", Types: []ArgType{String, Func}, Required: true}, {Keyword: "index", Types: []ArgType{String}}, }, diff --git a/pkg/filter/ql/functions/is_abs.go b/pkg/filter/ql/functions/is_abs.go index 492f3e43b..c2f51beeb 100644 --- a/pkg/filter/ql/functions/is_abs.go +++ b/pkg/filter/ql/functions/is_abs.go @@ -35,7 +35,7 @@ func (f IsAbs) Desc() FunctionDesc { desc := FunctionDesc{ Name: IsAbsFn, Args: []FunctionArgDesc{ - {Keyword: "path", Types: []ArgType{Field, BoundField, Func, String}, Required: true}, + {Keyword: "path", Types: []ArgType{Field, BoundField, Func, BoundSegment, BareBoundVariable, String}, Required: true}, }, } return desc diff --git a/pkg/filter/ql/functions/length.go b/pkg/filter/ql/functions/length.go index 76c2d260e..d1a59824d 100644 --- a/pkg/filter/ql/functions/length.go +++ b/pkg/filter/ql/functions/length.go @@ -39,7 +39,7 @@ func (f Length) Desc() FunctionDesc { desc := FunctionDesc{ Name: LengthFn, Args: []FunctionArgDesc{ - {Keyword: "string|slice", Types: []ArgType{Field, BoundField, Slice, Func}, Required: true}, + {Keyword: "string|slice", Types: []ArgType{Field, BoundField, BoundSegment, BareBoundVariable, Slice, Func}, Required: true}, }, } return desc diff --git a/pkg/filter/ql/functions/lower.go b/pkg/filter/ql/functions/lower.go index 890ce684a..0b69345ae 100644 --- a/pkg/filter/ql/functions/lower.go +++ b/pkg/filter/ql/functions/lower.go @@ -35,7 +35,7 @@ func (f Lower) Desc() FunctionDesc { desc := FunctionDesc{ Name: LowerFn, Args: []FunctionArgDesc{ - {Keyword: "string", Types: []ArgType{String, Field, BoundField, Func}, Required: true}, + {Keyword: "string", Types: []ArgType{String, Field, BoundField, BoundSegment, BareBoundVariable, Func}, Required: true}, }, } return desc diff --git a/pkg/filter/ql/functions/ltrim.go b/pkg/filter/ql/functions/ltrim.go index 9c590ae8b..b88305e72 100644 --- a/pkg/filter/ql/functions/ltrim.go +++ b/pkg/filter/ql/functions/ltrim.go @@ -36,7 +36,7 @@ func (f Ltrim) Desc() FunctionDesc { desc := FunctionDesc{ Name: LtrimFn, Args: []FunctionArgDesc{ - {Keyword: "string", Types: []ArgType{String, Field, BoundField, Func}, Required: true}, + {Keyword: "string", Types: []ArgType{String, Field, BoundField, BoundSegment, BareBoundVariable, Func}, Required: true}, {Keyword: "prefix", Types: []ArgType{String, Func}, Required: true}, }, } diff --git a/pkg/filter/ql/functions/md5.go b/pkg/filter/ql/functions/md5.go index 26a9975ea..b538b98ce 100644 --- a/pkg/filter/ql/functions/md5.go +++ b/pkg/filter/ql/functions/md5.go @@ -51,7 +51,7 @@ func (f MD5) Desc() FunctionDesc { return FunctionDesc{ Name: MD5Fn, Args: []FunctionArgDesc{ - {Keyword: "data", Types: []ArgType{Field, String, BoundField, Func}, Required: true}, + {Keyword: "data", Types: []ArgType{Field, String, BoundField, BoundSegment, BareBoundVariable, Func}, Required: true}, }, } } diff --git a/pkg/filter/ql/functions/minidump.go b/pkg/filter/ql/functions/minidump.go index 8a34803d3..9ef1b1720 100644 --- a/pkg/filter/ql/functions/minidump.go +++ b/pkg/filter/ql/functions/minidump.go @@ -55,7 +55,7 @@ func (f IsMinidump) Desc() FunctionDesc { desc := FunctionDesc{ Name: IsMinidumpFn, Args: []FunctionArgDesc{ - {Keyword: "path", Types: []ArgType{String, Field, BoundField, Func}, Required: true}, + {Keyword: "path", Types: []ArgType{String, Field, BoundField, BoundSegment, BareBoundVariable, Func}, Required: true}, }, } return desc diff --git a/pkg/filter/ql/functions/regex.go b/pkg/filter/ql/functions/regex.go index d734ec2f0..055d0ff27 100644 --- a/pkg/filter/ql/functions/regex.go +++ b/pkg/filter/ql/functions/regex.go @@ -73,7 +73,7 @@ func (f *Regex) Desc() FunctionDesc { desc := FunctionDesc{ Name: RegexFn, Args: []FunctionArgDesc{ - {Keyword: "string", Types: []ArgType{Field, BoundField, String, Func}, Required: true}, + {Keyword: "string", Types: []ArgType{Field, BoundField, String, BoundSegment, BareBoundVariable, Func}, Required: true}, {Keyword: "regexp", Types: []ArgType{String}, Required: true}, }, } diff --git a/pkg/filter/ql/functions/replace.go b/pkg/filter/ql/functions/replace.go index 25a81b0af..f547826b0 100644 --- a/pkg/filter/ql/functions/replace.go +++ b/pkg/filter/ql/functions/replace.go @@ -58,9 +58,9 @@ func (f Replace) Desc() FunctionDesc { desc := FunctionDesc{ Name: ReplaceFn, Args: []FunctionArgDesc{ - {Keyword: "string", Types: []ArgType{String, Field, BoundField, Func}, Required: true}, - {Keyword: "old", Types: []ArgType{String, Field, BoundField, Func}, Required: true}, - {Keyword: "new", Types: []ArgType{String, Field, BoundField, Func}, Required: true}, + {Keyword: "string", Types: []ArgType{String, Field, BoundField, BoundSegment, BareBoundVariable, Func}, Required: true}, + {Keyword: "old", Types: []ArgType{String, Field, BoundField, BoundSegment, BareBoundVariable, Func}, Required: true}, + {Keyword: "new", Types: []ArgType{String, Field, BoundField, BoundSegment, BareBoundVariable, Func}, Required: true}, }, ArgsValidationFunc: func(args []string) error { if len(args) == 3 { diff --git a/pkg/filter/ql/functions/rtrim.go b/pkg/filter/ql/functions/rtrim.go index b9d758685..58bf206f6 100644 --- a/pkg/filter/ql/functions/rtrim.go +++ b/pkg/filter/ql/functions/rtrim.go @@ -36,7 +36,7 @@ func (f Rtrim) Desc() FunctionDesc { desc := FunctionDesc{ Name: LtrimFn, Args: []FunctionArgDesc{ - {Keyword: "string", Types: []ArgType{String, Field, BoundField, Func}, Required: true}, + {Keyword: "string", Types: []ArgType{String, Field, BoundField, BoundSegment, BareBoundVariable, Func}, Required: true}, {Keyword: "suffix", Types: []ArgType{String, Func}, Required: true}, }, } diff --git a/pkg/filter/ql/functions/split.go b/pkg/filter/ql/functions/split.go index 6418cf6c7..187acb1f9 100644 --- a/pkg/filter/ql/functions/split.go +++ b/pkg/filter/ql/functions/split.go @@ -38,7 +38,7 @@ func (f Split) Desc() FunctionDesc { desc := FunctionDesc{ Name: SplitFn, Args: []FunctionArgDesc{ - {Keyword: "string", Types: []ArgType{String, Field, BoundField, Func}, Required: true}, + {Keyword: "string", Types: []ArgType{String, Field, BoundField, BoundSegment, BareBoundVariable, Func}, Required: true}, {Keyword: "sep", Types: []ArgType{String}, Required: true}, }, } diff --git a/pkg/filter/ql/functions/substr.go b/pkg/filter/ql/functions/substr.go index f42bd551b..96b397a24 100644 --- a/pkg/filter/ql/functions/substr.go +++ b/pkg/filter/ql/functions/substr.go @@ -25,18 +25,34 @@ func (f Substr) Call(args []interface{}) (interface{}, bool) { if len(args) < 3 { return false, false } + s := parseString(0, args) - start, ok := args[1].(int) - if !ok { + + var start int + var end int + + switch v := args[1].(type) { + case int: + start = v + case int64: + start = int(v) + default: return false, false } - end, ok := args[2].(int) - if !ok { + + switch v := args[2].(type) { + case int: + end = v + case int64: + end = int(v) + default: return false, false } + if start >= 0 && (end >= start && end < len(s)) { return s[start:end], true } + return s, true } @@ -44,7 +60,7 @@ func (f Substr) Desc() FunctionDesc { desc := FunctionDesc{ Name: SubstrFn, Args: []FunctionArgDesc{ - {Keyword: "string", Types: []ArgType{Func, Field, BoundField}, Required: true}, + {Keyword: "string", Types: []ArgType{Func, Field, BoundField, BoundSegment, BareBoundVariable}, Required: true}, {Keyword: "start", Types: []ArgType{Func, Number}, Required: true}, {Keyword: "end", Types: []ArgType{Func, Number}, Required: true}, }, diff --git a/pkg/filter/ql/functions/symlink.go b/pkg/filter/ql/functions/symlink.go index 358fa3ce5..e10c16925 100644 --- a/pkg/filter/ql/functions/symlink.go +++ b/pkg/filter/ql/functions/symlink.go @@ -39,7 +39,7 @@ func (f Symlink) Desc() FunctionDesc { desc := FunctionDesc{ Name: SymlinkFn, Args: []FunctionArgDesc{ - {Keyword: "path", Types: []ArgType{Field, BoundField, Func, String}, Required: true}, + {Keyword: "path", Types: []ArgType{Field, BoundField, BoundSegment, BareBoundVariable, Func, String}, Required: true}, }, } return desc diff --git a/pkg/filter/ql/functions/types.go b/pkg/filter/ql/functions/types.go index 31d6970f6..626b32352 100644 --- a/pkg/filter/ql/functions/types.go +++ b/pkg/filter/ql/functions/types.go @@ -104,6 +104,10 @@ const ( Expression // BoundField represents the bound field argument type. BoundField + // BoundSegment represents the bound segment argument type. + BoundSegment + // BareBoundVariable represents the bare bound variable argument type. + BareBoundVariable // Unknown is the unknown argument type. Unknown ) @@ -129,6 +133,10 @@ func (typ ArgType) String() string { return "expression" case BoundField: return "boundfield" + case BoundSegment: + return "boundsegment" + case BareBoundVariable: + return "bareboundvar" } return "unknown" } diff --git a/pkg/filter/ql/functions/upper.go b/pkg/filter/ql/functions/upper.go index 89bc7a5a1..579fa7a0c 100644 --- a/pkg/filter/ql/functions/upper.go +++ b/pkg/filter/ql/functions/upper.go @@ -35,7 +35,7 @@ func (f Upper) Desc() FunctionDesc { desc := FunctionDesc{ Name: UpperFn, Args: []FunctionArgDesc{ - {Keyword: "string", Types: []ArgType{String, Field, BoundField, Func}, Required: true}, + {Keyword: "string", Types: []ArgType{String, Field, BoundField, BoundSegment, BareBoundVariable, Func}, Required: true}, }, } return desc diff --git a/pkg/filter/ql/functions/volume.go b/pkg/filter/ql/functions/volume.go index bc16ae44a..4e2fb0e83 100644 --- a/pkg/filter/ql/functions/volume.go +++ b/pkg/filter/ql/functions/volume.go @@ -35,7 +35,7 @@ func (f Volume) Desc() FunctionDesc { desc := FunctionDesc{ Name: VolumeFn, Args: []FunctionArgDesc{ - {Keyword: "path", Types: []ArgType{Field, BoundField, Func, String}, Required: true}, + {Keyword: "path", Types: []ArgType{Field, BoundField, BoundSegment, BareBoundVariable, Func, String}, Required: true}, }, } return desc diff --git a/pkg/filter/ql/functions/yara.go b/pkg/filter/ql/functions/yara.go index 8c86983d7..bebb70839 100644 --- a/pkg/filter/ql/functions/yara.go +++ b/pkg/filter/ql/functions/yara.go @@ -91,7 +91,7 @@ func (f Yara) Desc() FunctionDesc { desc := FunctionDesc{ Name: YaraFn, Args: []FunctionArgDesc{ - {Keyword: "pid|file|bytes", Types: []ArgType{Field, BoundField, Func, String, Number}, Required: true}, + {Keyword: "pid|file|bytes", Types: []ArgType{Field, BoundField, BoundSegment, BareBoundVariable, Func, String, Number}, Required: true}, {Keyword: "rules", Types: []ArgType{Field, BoundField, Func, String}, Required: true}, {Keyword: "vars", Types: []ArgType{Field, BoundField, Func, String}}, }, diff --git a/pkg/filter/ql/lexer.go b/pkg/filter/ql/lexer.go index 9acd55bda..e6b69d650 100644 --- a/pkg/filter/ql/lexer.go +++ b/pkg/filter/ql/lexer.go @@ -109,12 +109,16 @@ func (s *scanner) scan() (tok token, pos int, lit string) { return Pipe, pos, "" case ',': return Comma, pos, "" + case '[': + return LBracket, pos, "" + case ']': + return RBracket, pos, "" case '$': tok, _, lit = s.scanIdent() if tok != Ident { return tok, pos, "$" + lit } - return BoundField, pos, "$" + lit + return BoundVar, pos, "$" + lit } return Illegal, pos, string(ch0) } @@ -515,5 +519,5 @@ func isDigit(ch rune) bool { return ch >= '0' && ch <= '9' } // isIdentChar returns true if the rune can be used in an unquoted identifier. $ rune is for special PE section names (e.g. .debug$ | .tls$) func isIdentChar(ch rune) bool { - return isLetter(ch) || isDigit(ch) || ch == '_' || ch == '.' || ch == '[' || ch == ']' || ch == '$' + return isLetter(ch) || isDigit(ch) || ch == '_' || ch == '.' || ch == '$' } diff --git a/pkg/filter/ql/lexer_test.go b/pkg/filter/ql/lexer_test.go index 9b29085f9..ee229eb21 100644 --- a/pkg/filter/ql/lexer_test.go +++ b/pkg/filter/ql/lexer_test.go @@ -59,11 +59,6 @@ func TestScanner(t *testing.T) { {s: `,`, tok: Comma}, {s: `|`, tok: Pipe}, - // fields - {s: `ps.name`, tok: Field, lit: "ps.name"}, - {s: `pe.is_exec`, tok: Field, lit: "pe.is_exec"}, - {s: `ps.envs[CommonProgramFiles86]`, tok: Field, lit: "ps.envs[CommonProgramFiles86]"}, - // identifiers {s: `foo`, tok: Ident, lit: `foo`}, {s: `_foo`, tok: Ident, lit: `_foo`}, diff --git a/pkg/filter/ql/literal.go b/pkg/filter/ql/literal.go index b49738819..29c263b62 100644 --- a/pkg/filter/ql/literal.go +++ b/pkg/filter/ql/literal.go @@ -41,6 +41,8 @@ type StringLiteral struct { // FieldLiteral represents a field literal. type FieldLiteral struct { Value string + Field fields.Field + Arg string } // IntegerLiteral represents a signed number literal. @@ -68,7 +70,21 @@ type IPLiteral struct { Value net.IP } +// BoundFieldLiteral represents the bound field literal. type BoundFieldLiteral struct { + Value string + BoundVar BareBoundVariableLiteral + Field *FieldLiteral +} + +// BoundSegmentLiteral represents the bound segment literal. +type BoundSegmentLiteral struct { + Value string + BoundVar BareBoundVariableLiteral + Segment fields.Segment +} + +type BareBoundVariableLiteral struct { Value string } @@ -104,27 +120,11 @@ func (b BoundFieldLiteral) String() string { return b.Value } -func (b BoundFieldLiteral) Field() fields.Field { - n := strings.Index(b.Value, ".") - if n > 0 { - return fields.Field(b.Value[n+1:]) - } - return "" -} - -func (b BoundFieldLiteral) Segment() fields.Segment { - n := strings.Index(b.Value, ".") - if n > 0 { - return fields.Segment(b.Value[n+1:]) - } - return "" +func (b BoundSegmentLiteral) String() string { + return b.Value } -func (b BoundFieldLiteral) Alias() string { - n := strings.Index(b.Value, ".") - if n > 0 { - return b.Value[1:n] - } +func (b BareBoundVariableLiteral) String() string { return b.Value } @@ -201,8 +201,8 @@ func (f *Function) IsNotExprArg(i int) bool { return ok } -func (f *Function) IsBoundFieldArg(i int) bool { - _, ok := f.Args[i].(*BoundFieldLiteral) +func (f *Function) IsBareBoundVariableArg(i int) bool { + _, ok := f.Args[i].(*BareBoundVariableLiteral) return ok } @@ -243,6 +243,10 @@ func (f *Function) validate() error { typ = functions.Field case reflect.TypeOf(&BoundFieldLiteral{}): typ = functions.BoundField + case reflect.TypeOf(&BoundSegmentLiteral{}): + typ = functions.BoundSegment + case reflect.TypeOf(&BareBoundVariableLiteral{}): + typ = functions.BareBoundVariable case reflect.TypeOf(&IPLiteral{}): typ = functions.IP case reflect.TypeOf(&StringLiteral{}): @@ -269,10 +273,13 @@ func (f *Function) validate() error { // SequenceExpr represents a single binary expression within the sequence. type SequenceExpr struct { - Expr Expr - By fields.Field + Expr Expr + // By contains the field literal if the sequence expression is constrained. + By *FieldLiteral + // BoundFields is a group of bound fields referenced in the sequence expression. BoundFields []*BoundFieldLiteral - Alias string + // Alias represents the sequence expression alias. + Alias string buckets map[uint32]bool ktypes []ktypes.Ktype @@ -300,6 +307,7 @@ func (e *SequenceExpr) walk() { stringFields[field] = append(stringFields[field], v.Values...) } } + switch rhs := expr.RHS.(type) { case *BoundFieldLiteral: e.BoundFields = append(e.BoundFields, rhs) @@ -313,6 +321,7 @@ func (e *SequenceExpr) walk() { } } } + if expr, ok := n.(*Function); ok { for _, arg := range expr.Args { switch v := arg.(type) { @@ -357,14 +366,14 @@ func (e *SequenceExpr) HasBoundFields() bool { // Sequence is a collection of two or more sequence expressions. type Sequence struct { MaxSpan time.Duration - By fields.Field + By *FieldLiteral Expressions []SequenceExpr IsUnordered bool } // IsConstrained determines if the sequence has the global or per-expression `BY` statement. func (s Sequence) IsConstrained() bool { - return !s.By.IsEmpty() || !s.Expressions[0].By.IsEmpty() + return s.By != nil || s.Expressions[0].By != nil } func (s *Sequence) init() { @@ -382,6 +391,7 @@ func (s *Sequence) init() { guids[k.GUID()] = true } } + if s.IsUnordered && len(guids) == 1 { s.IsUnordered = false } @@ -390,9 +400,9 @@ func (s *Sequence) init() { func (s Sequence) impairBy() bool { b := make(map[bool]int, len(s.Expressions)) for _, expr := range s.Expressions { - b[!expr.By.IsEmpty()]++ + b[expr.By != nil]++ } - if !s.By.IsEmpty() && (b[true] == len(s.Expressions) || b[false] == len(s.Expressions)) { + if s.By != nil && (b[true] == len(s.Expressions) || b[false] == len(s.Expressions)) { return false } return b[true] > 0 && b[false] > 0 @@ -403,7 +413,7 @@ func (s Sequence) impairBy() bool { // returns true if such condition is satisfied. func (s Sequence) incompatibleConstraints() bool { for _, expr := range s.Expressions { - if !expr.By.IsEmpty() && !s.By.IsEmpty() { + if expr.By != nil && s.By != nil { return true } } diff --git a/pkg/filter/ql/parser.go b/pkg/filter/ql/parser.go index d192366e9..536e20578 100644 --- a/pkg/filter/ql/parser.go +++ b/pkg/filter/ql/parser.go @@ -75,10 +75,14 @@ func (p *Parser) ParseSequence() (*Sequence, error) { tok, _, _ = p.scanIgnoreWhitespace() if tok == By { tok, pos, lit := p.scanIgnoreWhitespace() - if tok != Field { + if !fields.IsField(lit) { return nil, newParseError(tokstr(tok, lit), []string{"field"}, pos, p.expr) } - seq.By = fields.Field(lit) + var err error + seq.By, err = p.parseField(lit) + if err != nil { + return nil, err + } } else { p.unscan() } @@ -89,6 +93,7 @@ func (p *Parser) ParseSequence() (*Sequence, error) { if len(exprs) < 1 { return nil, fmt.Errorf("%s: sequences require at least two expressions", p.expr) } + const maxExpressions = 5 if len(exprs) > maxExpressions { return nil, fmt.Errorf("%s: maximum number of expressions reached", p.expr) @@ -100,7 +105,9 @@ func (p *Parser) ParseSequence() (*Sequence, error) { if seq.incompatibleConstraints() { return nil, fmt.Errorf("%s: sequence mixes global and per-expression 'by' statements", p.expr) } + seq.init() + return seq, nil } p.unscan() @@ -119,14 +126,20 @@ func (p *Parser) ParseSequence() (*Sequence, error) { } var seqexpr SequenceExpr + + // parse sequence BY or AS constraints tok, _, _ = p.scanIgnoreWhitespace() switch tok { case By: tok, pos, lit := p.scanIgnoreWhitespace() - if tok != Field { + if !fields.IsField(lit) { return nil, newParseError(tokstr(tok, lit), []string{"field"}, pos, p.expr) } - seqexpr = SequenceExpr{Expr: expr, By: fields.Field(lit)} + field, err := p.parseField(lit) + if err != nil { + return nil, err + } + seqexpr = SequenceExpr{Expr: expr, By: field} case As: tok, pos, lit := p.scanIgnoreWhitespace() if tok != Ident { @@ -137,6 +150,7 @@ func (p *Parser) ParseSequence() (*Sequence, error) { seqexpr = SequenceExpr{Expr: expr} p.unscan() } + seqexpr.init() seqexpr.walk() exprs = append(exprs, seqexpr) @@ -279,6 +293,10 @@ func (p *Parser) parseUnaryExpr() (Expr, error) { tok, pos, lit := p.scanIgnoreWhitespace() switch tok { case Ident: + if fields.IsField(lit) { + return p.parseField(lit) + } + if tok0, _, _ := p.scan(); tok0 == Lparen { return p.parseFunction(lit) } @@ -306,14 +324,28 @@ func (p *Parser) parseUnaryExpr() (Expr, error) { return &IPLiteral{Value: net.ParseIP(lit)}, nil case Str: return &StringLiteral{Value: lit}, nil - case Field: - return &FieldLiteral{Value: lit}, nil - case BoundField: + case BoundVar: n := strings.Index(lit, ".") - if n > 0 && fields.Lookup(lit[n+1:]) == "" && !fields.IsSegment(lit[n+1:]) { - return nil, newParseError(tokstr(tok, lit), []string{"field/segment after bound ref"}, pos+n, p.expr) + if n == -1 { + return &BareBoundVariableLiteral{Value: lit}, nil } - return &BoundFieldLiteral{Value: lit}, nil + + // for recognized segment return bound segment literal + s := lit[n+1:] + if fields.IsSegment(s) { + return &BoundSegmentLiteral{Value: lit, BoundVar: BareBoundVariableLiteral{lit[1:n]}, Segment: fields.Segment(s)}, nil + } + + // parse field literal for recognized field + if fields.IsField(s) { + field, err := p.parseField(s) + if err != nil { + return nil, err + } + return &BoundFieldLiteral{Value: lit, BoundVar: BareBoundVariableLiteral{lit[1:n]}, Field: field}, nil + } + + return nil, newParseError(tokstr(tok, lit), []string{"field/segment after bound ref"}, pos+n, p.expr) case True, False: return &BoolLiteral{Value: tok == True}, nil case Integer: @@ -346,6 +378,49 @@ func (p *Parser) parseUnaryExpr() (Expr, error) { return nil, newParseError(tokstr(tok, lit), expectations, pos, p.expr) } +// parseField parses the field and its argument. This method +// assumes the field name has been consumed. +func (p *Parser) parseField(name string) (*FieldLiteral, error) { + argument := fields.ArgumentOf(name) + + // parse field argument + tok, pos, lit := p.scan() + if tok == LBracket { + arg, pos, lit := p.scan() + if arg != Ident && arg != Integer { + return nil, newParseError(tokstr(arg, lit), []string{"ident", "integer"}, pos, p.expr) + } + + // field argument given, but the field doesn't require one + if argument == nil { + return nil, newParseError(tokstr(tok, lit), []string{"field without argument"}, pos, p.expr) + } + + // validate argument + if argument != nil && !argument.Validate(lit) { + exp := fmt.Sprintf("a valid field argument matching the pattern %s", argument.Pattern) + return nil, newParseError(tokstr(arg, lit), []string{exp}, pos, p.expr) + } + + if tok, pos, lit := p.scan(); tok != RBracket { + return nil, newParseError(tokstr(tok, lit), []string{"]"}, pos, p.expr) + } + + return &FieldLiteral{Value: name, Field: fields.Field(name), Arg: lit}, nil + } else { + // unscan lbracket + p.unscan() + // field argument not given, but it is required + if argument != nil && !argument.Optional { + return nil, newParseError(tokstr(tok, lit), []string{"field argument"}, pos, p.expr) + } + + return &FieldLiteral{Value: name, Field: fields.Field(name)}, nil + } +} + +// parseList parses the list of strings. This method assumes the +// LPAREN token has been consumed. func (p *Parser) parseList() ([]string, error) { tok, pos, lit := p.scanIgnoreWhitespace() if tok != Str && tok != IP && tok != Integer { @@ -369,7 +444,7 @@ func (p *Parser) parseList() ([]string, error) { } } -// parseFunction parses a function call. This function assumes +// parseFunction parses a function call. This method assumes // the function name and LPAREN have been consumed. func (p *Parser) parseFunction(name string) (*Function, error) { name = strings.ToLower(name) diff --git a/pkg/filter/ql/parser_test.go b/pkg/filter/ql/parser_test.go index ba1c220da..ddf6afcc0 100644 --- a/pkg/filter/ql/parser_test.go +++ b/pkg/filter/ql/parser_test.go @@ -21,6 +21,8 @@ package ql import ( "errors" "github.com/rabbitstack/fibratus/pkg/config" + "github.com/rabbitstack/fibratus/pkg/filter/fields" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "testing" "time" @@ -34,37 +36,39 @@ func TestParser(t *testing.T) { {expr: "ps.name = 'cmd.exe'"}, {expr: "ps.name != 'cmd.exe'"}, {expr: "ps.name <> 'cmd.exe'"}, - {expr: "ps.name <> 'cmd.exe", err: errors.New("ps.name <> 'cmd.exe\n" + - " ^ expected field, string, number, bool, ip")}, + {expr: "ps.name <> 'cmd.exe", err: errors.New("ps.name <> 'cmd.exe\n╭─────────^\n|\n|\n╰─────────────────── expected a valid string but bad string or escape found")}, {expr: "ps.name = 123"}, {expr: "net.dip = 172.17.0.9"}, {expr: "net.dip = 172.17.0.9 and net.dip in ('172.15.9.2')"}, {expr: "net.dip = 172.17.0.9 and (net.dip not in ('172.15.9.2'))"}, - {expr: "net.dip = 172.17.0", err: errors.New("net.dip = 172.17.0\n" + - " ^ expected a valid IP address")}, + {expr: "net.dip = 172.17.0", err: errors.New("net.dip = 172.17.0\n╭─────────^\n|\n|\n╰─────────────────── expected a valid IP address")}, {expr: "ps.name = 'cmd.exe' OR ps.name contains 'svc'"}, {expr: "ps.name = 'cmd.exe' AND (ps.name contains 'svc' OR ps.name != 'lsass')"}, - {expr: "ps.name = 'cmd.exe' AND (ps.name contains 'svc' OR ps.name != 'lsass'", err: errors.New("ps.name = 'cmd.exe' AND (ps.name contains 'svc' OR ps.name != 'lsass'" + - "^ expected")}, + {expr: "ps.name = 'cmd.exe' AND (ps.name contains 'svc' OR ps.name != 'lsass'", err: errors.New("ps.name = 'cmd.exe' AND (ps.name contains 'svc' OR ps.name != 'lsass'\n╭─────────────────────────────────────────────────────────────────────^\n|\n|\n╰─────────────────── expected ')'")}, {expr: "ps.name = 'cmd.exe' OR ((ps.name contains 'svc' AND ps.name != 'lsass') AND ps.ppid != 1)"}, - {expr: "ps.name = 'cmd.exe' OR ((ps.name contains 'svc' AND ps.name != 'lsass' AND ps.ppid != 1)", err: errors.New("ps.name = 'cmd.exe' OR ((ps.name contains 'svc' AND ps.name != 'lsass' AND ps.ppid != 1)" + - " ^ expected )")}, + {expr: "ps.name = 'cmd.exe' OR ((ps.name contains 'svc' AND ps.name != 'lsass' AND ps.ppid != 1)", err: errors.New("ps.name = 'cmd.exe' OR ((ps.name contains 'svc' AND ps.name != 'lsass' AND ps.ppid != 1)\n╭────────────────────────────────────────────────────────────────────────────────────────^\n|\n|\n╰─────────────────── expected ')'")}, - {expr: "ps.name = 'cmd.exe' OR ((ps.name contains 'svc' AND ps.name != 'lsass') AND ps.ppid != 1", err: errors.New("ps.name = 'cmd.exe' OR ((ps.name contains 'svc' AND ps.name != 'lsass') AND ps.ppid != 1" + - " ^ expected )")}, + {expr: "ps.name = 'cmd.exe' OR ((ps.name contains 'svc' AND ps.name != 'lsass') AND ps.ppid != 1", err: errors.New("ps.name = 'cmd.exe' OR ((ps.name contains 'svc' AND ps.name != 'lsass') AND ps.ppid != 1\n╭────────────────────────────────────────────────────────────────────────────────────────^\n|\n|\n╰─────────────────── expected ')'")}, - {expr: "ps.none = 'cmd.exe'", err: errors.New("ps.none = 'cmd.exe'" + - " ^ expected field, string, number, bool, ip")}, + {expr: "ps.none = 'cmd.exe'", err: errors.New("ps.none = 'cmd.exe'\n╭^\n|\n|\n╰─────────────────── expected field, bound field, string, number, bool, ip, function")}, - {expr: "ps.name = 'cmd.exe' AND ps.name IN ('exe') ps.name", err: errors.New("ps.name = 'cmd.exe' AND ps.name IN ('exe') ps.name" + - " ^ expected operator")}, - {expr: "ip_cidr(net.dip) = '24'", err: errors.New("ip_cidr function is undefined. Did you mean one of CIDR_CONTAINS|MD5?")}, + {expr: "ps.name = 'cmd.exe' AND ps.name IN ('exe') ps.name", err: errors.New("ps.name = 'cmd.exe' AND ps.name IN ('exe') ps.name\n╭──────────────────────────────────────────^\n|\n|\n╰─────────────────── expected operator, ')', ',', '|'")}, + {expr: "ip_cidr(net.dip) = '24'", err: errors.New("ip_cidr function is undefined. Did you mean one of BASE|CIDR_CONTAINS|CONCAT|DIR|ENTROPY|EXT|FOREACH|GET_REG_VALUE|GLOB|INDEXOF|IS_ABS|IS_MINIDUMP|LENGTH|LOWER|LTRIM|MD5|REGEX|REPLACE|RTRIM|SPLIT|SUBSTR|UNDEFINED|UPPER|VOLUME|YARA?")}, {expr: "ps.name = 'cmd.exe' and not cidr_contains(net.sip, '172.14.0.0')"}, + {expr: `ps.envs[ProgramFiles] = 'C:\\Program Files'`}, + {expr: `ps.envs imatches 'C:\\Program Files'`}, + {expr: `ps.pid[1] = 'svchost.exe'`, err: errors.New("ps.pid[1] = 'svchost.exe'\n╭──────^\n|\n|\n╰─────────────────── expected field without argument")}, + {expr: `ps.envs[ProgramFiles = 'svchost.exe'`, err: errors.New("ps.envs[ProgramFiles = 'svchost.exe'\n╭───────────────────^\n|\n|\n╰─────────────────── expected ]")}, + {expr: `kevt.arg = 'svchost.exe'`, err: errors.New("kevt.arg = 'svchost.exe'\n╭───────^\n|\n|\n╰─────────────────── expected field argument")}, + {expr: `kevt.arg[name] = 'svchost.exe'`}, + {expr: `kevt.arg[Name$] = 'svchost.exe'`, err: errors.New("kevt.arg[Name$] = 'svchost.exe'\n╭────────^\n|\n|\n╰─────────────────── expected a valid field argument matching the pattern [a-z0-9_]+")}, + {expr: `ps.ancestor[0] = 'svchost.exe'`}, + {expr: `ps.ancestor[l0l] = 'svchost.exe'`, err: errors.New("ps.ancestor[l0l] = 'svchost.exe'\n╭───────────^\n|\n|\n╰─────────────────── expected a valid field argument matching the pattern [0-9]+")}, } for i, tt := range tests { @@ -72,12 +76,67 @@ func TestParser(t *testing.T) { _, err := p.ParseExpr() if err == nil && tt.err != nil { t.Errorf("%d. exp=%s expected error=%v", i, tt.expr, tt.err) + } else if err != nil && tt.err != nil { + assert.EqualError(t, tt.err, err.Error()) } else if err != nil && tt.err == nil { t.Errorf("%d. exp=%s got error=%v", i, tt.expr, err) } } } +func TestParseUnaryExpr(t *testing.T) { + var tests = []struct { + expr string + ee Expr + err string + assertions func(t *testing.T, e Expr) + }{ + {"ps.name", &FieldLiteral{}, "", nil}, + {"ps.name[", &FieldLiteral{}, "expected ident, integer", nil}, + {"ps.name[svchost.exe]", &FieldLiteral{}, "expected field without argument", nil}, + {"ps.ancestor[1]", &FieldLiteral{}, "", func(t *testing.T, e Expr) { + f := e.(*FieldLiteral) + assert.Equal(t, "1", f.Arg) + }}, + {"$entry", &BareBoundVariableLiteral{}, "", nil}, + {"$entry.entropy", &BoundSegmentLiteral{}, "", func(t *testing.T, e Expr) { + s := e.(*BoundSegmentLiteral) + assert.Equal(t, fields.EntropySegment, s.Segment) + assert.Equal(t, "$entry.entropy", s.Value) + }}, + {"$entry.file.path", &BoundFieldLiteral{}, "", func(t *testing.T, e Expr) { + f := e.(*BoundFieldLiteral) + assert.Equal(t, fields.FilePath, f.Field.Field) + assert.Equal(t, "$entry.file.path", f.Value) + }}, + {"$entry.foo", nil, "expected field/segment after bound ref", nil}, + {"('a', 'b', 'c')", &ListLiteral{}, "", nil}, + {"('a', 'b', 'c'", nil, "expected ')'", nil}, + {"base(file.path)", &Function{}, "", nil}, + {"base(file.path,", &Function{}, "expected field, bound field, string, number, bool, ip, function", nil}, + } + + for _, tt := range tests { + t.Run(tt.expr, func(t *testing.T) { + p := NewParser(tt.expr) + + expr, err := p.parseUnaryExpr() + if err != nil && tt.err != "" { + require.ErrorContains(t, err, tt.err) + } + if err != nil && tt.err == "" { + assert.Fail(t, err.Error()) + } + + assert.IsType(t, tt.ee, expr) + + if tt.assertions != nil { + tt.assertions(t, expr) + } + }) + } +} + func TestExpandMacros(t *testing.T) { var tests = []struct { c *config.Filters diff --git a/pkg/filter/ql/token.go b/pkg/filter/ql/token.go index 211256bc3..5cddc60b1 100644 --- a/pkg/filter/ql/token.go +++ b/pkg/filter/ql/token.go @@ -19,7 +19,6 @@ package ql import ( - "github.com/rabbitstack/fibratus/pkg/filter/fields" "strings" ) @@ -31,9 +30,8 @@ const ( WS EOF - Field // ps.name - BoundField // $evt1.file.name - Str // 'cmd.exe' + BoundVar // $evt1.file.name + Str // 'cmd.exe' Badstr Badesc Ident @@ -74,11 +72,13 @@ const ( Gte // >= opEnd - Lparen // ( - Rparen // ) - Comma // , - Dot // . - Pipe // | + Lparen // ( + Rparen // ) + Comma // , + Dot // . + Pipe // | + LBracket // [ + RBracket // ] Seq // SEQUENCE MaxSpan // MAXSPAN @@ -105,19 +105,18 @@ var tokens = [...]string{ EOF: "EOF", WS: "WS", - Ident: "IDENT", - Field: "FIELD", - BoundField: "BOUNDFIELD", - Integer: "INTEGER", - Decimal: "DECIMAL", - Duration: "DURATION", - Str: "STRING", - Badstr: "BADSTRING", - Badesc: "BADESCAPE", - IP: "IPADDRESS", - BadIP: "BADIPADDRESS", - True: "TRUE", - False: "FALSE", + Ident: "IDENT", + BoundVar: "BOUNDVAR", + Integer: "INTEGER", + Decimal: "DECIMAL", + Duration: "DURATION", + Str: "STRING", + Badstr: "BADSTRING", + Badesc: "BADESCAPE", + IP: "IPADDRESS", + BadIP: "BADIPADDRESS", + True: "TRUE", + False: "FALSE", And: "AND", Or: "OR", @@ -147,11 +146,13 @@ var tokens = [...]string{ Gt: ">", Gte: ">=", - Lparen: "(", - Rparen: ")", - Comma: ",", - Dot: ".", - Pipe: "|", + Lparen: "(", + Rparen: ")", + Comma: ",", + Dot: ".", + Pipe: "|", + LBracket: "[", + RBracket: "]", Seq: "SEQUENCE", MaxSpan: "MAXSPAN", @@ -200,8 +201,5 @@ func lookup(id string) (token, string) { if tok, ok := keywords[strings.ToLower(id)]; ok { return tok, "" } - if tok := fields.Lookup(id); tok != "" { - return Field, id - } return Ident, id } 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. "+