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