From 9e3526620195da970e3c723190e9311fdb32d44f Mon Sep 17 00:00:00 2001 From: rabbitstack Date: Thu, 30 Jan 2025 18:55:30 +0100 Subject: [PATCH] feat(filter): Add additional callstack fields The new filter fields allow accessing the attributes of the last user/kernel space frame. Additionally, symbols/module filter fields are modified to accept the frame index and only retrieve the symbol/module name of the accessed frame. The callstack segments are added to access signature info of the iterated frame. --- pkg/filter/accessor_windows.go | 196 ++++++++++++++++++---------- pkg/filter/fields/fields.go | 12 ++ pkg/filter/fields/fields_windows.go | 167 ++++++++++++++---------- pkg/filter/filter_test.go | 33 ++++- pkg/filter/ql/function.go | 52 ++++++++ pkg/filter/util.go | 107 +++++++++++++++ pkg/kevent/callstack.go | 46 ++++++- pkg/kevent/callstack_test.go | 36 +++-- pkg/symbolize/symbolizer.go | 2 + pkg/util/va/address.go | 1 + 10 files changed, 494 insertions(+), 158 deletions(-) create mode 100644 pkg/filter/util.go diff --git a/pkg/filter/accessor_windows.go b/pkg/filter/accessor_windows.go index 74b47289c..a642b212e 100644 --- a/pkg/filter/accessor_windows.go +++ b/pkg/filter/accessor_windows.go @@ -26,7 +26,6 @@ import ( "github.com/rabbitstack/fibratus/pkg/network" psnap "github.com/rabbitstack/fibratus/pkg/ps" "github.com/rabbitstack/fibratus/pkg/util/cmdline" - "github.com/rabbitstack/fibratus/pkg/util/loldrivers" "github.com/rabbitstack/fibratus/pkg/util/signature" "net" "path/filepath" @@ -539,73 +538,167 @@ func newThreadAccessor() Accessor { return &threadAccessor{} } -func (t *threadAccessor) Get(f Field, kevt *kevent.Kevent) (kparams.Value, error) { +func (t *threadAccessor) Get(f Field, e *kevent.Kevent) (kparams.Value, error) { switch f.Name { case fields.ThreadBasePrio: - return kevt.Kparams.GetUint8(kparams.BasePrio) + return e.Kparams.GetUint8(kparams.BasePrio) case fields.ThreadIOPrio: - return kevt.Kparams.GetUint8(kparams.IOPrio) + return e.Kparams.GetUint8(kparams.IOPrio) case fields.ThreadPagePrio: - return kevt.Kparams.GetUint8(kparams.PagePrio) + return e.Kparams.GetUint8(kparams.PagePrio) case fields.ThreadKstackBase: - return kevt.GetParamAsString(kparams.KstackBase), nil + return e.GetParamAsString(kparams.KstackBase), nil case fields.ThreadKstackLimit: - return kevt.GetParamAsString(kparams.KstackLimit), nil + return e.GetParamAsString(kparams.KstackLimit), nil case fields.ThreadUstackBase: - return kevt.GetParamAsString(kparams.UstackBase), nil + return e.GetParamAsString(kparams.UstackBase), nil case fields.ThreadUstackLimit: - return kevt.GetParamAsString(kparams.UstackLimit), nil + return e.GetParamAsString(kparams.UstackLimit), nil case fields.ThreadEntrypoint, fields.ThreadStartAddress: - return kevt.GetParamAsString(kparams.StartAddress), nil + return e.GetParamAsString(kparams.StartAddress), nil case fields.ThreadPID: - return kevt.Kparams.GetUint32(kparams.ProcessID) + return e.Kparams.GetUint32(kparams.ProcessID) case fields.ThreadTEB: - return kevt.GetParamAsString(kparams.TEB), nil + return e.GetParamAsString(kparams.TEB), nil case fields.ThreadAccessMask: - if kevt.Type != ktypes.OpenThread { + if e.Type != ktypes.OpenThread { return nil, nil } - return kevt.Kparams.GetString(kparams.DesiredAccess) + return e.Kparams.GetString(kparams.DesiredAccess) case fields.ThreadAccessMaskNames: - if kevt.Type != ktypes.OpenThread { + if e.Type != ktypes.OpenThread { return nil, nil } - return kevt.GetFlagsAsSlice(kparams.DesiredAccess), nil + return e.GetFlagsAsSlice(kparams.DesiredAccess), nil case fields.ThreadAccessStatus: - if kevt.Type != ktypes.OpenThread { + if e.Type != ktypes.OpenThread { return nil, nil } - return kevt.GetParamAsString(kparams.NTStatus), nil + return e.GetParamAsString(kparams.NTStatus), nil case fields.ThreadCallstackSummary: - return kevt.Callstack.Summary(), nil + return e.Callstack.Summary(), nil case fields.ThreadCallstackDetail: - return kevt.Callstack.String(), nil + return e.Callstack.String(), nil case fields.ThreadCallstackModules: - return kevt.Callstack.Modules(), nil + // return the module at the given frame level + if f.Arg != "" { + n, err := strconv.Atoi(f.Arg) + if err != nil { + return nil, err + } + + if n > e.Callstack.Depth() { + return "", nil + } + + return e.Callstack.FrameAt(n).Module, nil + } + + return e.Callstack.Modules(), nil case fields.ThreadCallstackSymbols: - return kevt.Callstack.Symbols(), nil + // return the symbol at the given frame level + if f.Arg != "" { + n, err := strconv.Atoi(f.Arg) + if err != nil { + return nil, err + } + + if n > e.Callstack.Depth() { + return "", nil + } + + return e.Callstack.FrameAt(n).Symbol, nil + } + + return e.Callstack.Symbols(), nil case fields.ThreadCallstackAllocationSizes: - return kevt.Callstack.AllocationSizes(kevt.PID), nil + return e.Callstack.AllocationSizes(e.PID), nil case fields.ThreadCallstackProtections: - return kevt.Callstack.Protections(kevt.PID), nil + return e.Callstack.Protections(e.PID), nil case fields.ThreadCallstackCallsiteLeadingAssembly: - return kevt.Callstack.CallsiteInsns(kevt.PID, true), nil + return e.Callstack.CallsiteInsns(e.PID, true), nil case fields.ThreadCallstackCallsiteTrailingAssembly: - return kevt.Callstack.CallsiteInsns(kevt.PID, false), nil + return e.Callstack.CallsiteInsns(e.PID, false), nil case fields.ThreadCallstackIsUnbacked: - return kevt.Callstack.ContainsUnbacked(), nil + return e.Callstack.ContainsUnbacked(), nil case fields.ThreadCallstack: - return kevt.Callstack, nil + return e.Callstack, nil case fields.ThreadStartAddressSymbol: - if kevt.Type != ktypes.CreateThread { + if e.Type != ktypes.CreateThread { return nil, nil } - return kevt.GetParamAsString(kparams.StartAddressSymbol), nil + return e.GetParamAsString(kparams.StartAddressSymbol), nil case fields.ThreadStartAddressModule: - if kevt.Type != ktypes.CreateThread { + if e.Type != ktypes.CreateThread { return nil, nil } - return kevt.GetParamAsString(kparams.StartAddressModule), nil + return e.GetParamAsString(kparams.StartAddressModule), nil + case fields.ThreadCallstackAddresses: + return e.Callstack.Addresses(), nil + case fields.ThreadCallstackFinalUserModuleName, fields.ThreadCallstackFinalUserModulePath: + frame := e.Callstack.FinalUserFrame() + if frame != nil { + if f.Name == fields.ThreadCallstackFinalUserModuleName { + return filepath.Base(frame.Module), nil + } + return frame.Module, nil + } + return nil, nil + case fields.ThreadCallstackFinalUserSymbolName: + frame := e.Callstack.FinalUserFrame() + if frame != nil { + return frame.Symbol, nil + } + return nil, nil + case fields.ThreadCallstackFinalKernelModuleName, fields.ThreadCallstackFinalKernelModulePath: + frame := e.Callstack.FinalKernelFrame() + if frame != nil { + if f.Name == fields.ThreadCallstackFinalKernelModuleName { + return filepath.Base(frame.Module), nil + } + return frame.Module, nil + } + return nil, nil + case fields.ThreadCallstackFinalKernelSymbolName: + frame := e.Callstack.FinalKernelFrame() + if frame != nil { + return frame.Symbol, nil + } + return nil, nil + case fields.ThreadCallstackFinalUserModuleSignatureIsSigned, fields.ThreadCallstackFinalUserModuleSignatureIsTrusted: + frame := e.Callstack.FinalUserFrame() + if frame == nil || (frame != nil && frame.ModuleAddress.IsZero()) { + return nil, nil + } + + sign := getSignature(frame.ModuleAddress, frame.Module, false) + if sign == nil { + return nil, nil + } + + if f.Name == fields.ThreadCallstackFinalUserModuleSignatureIsSigned { + return sign.IsSigned(), nil + } + + return sign.IsTrusted(), nil + case fields.ThreadCallstackFinalUserModuleSignatureCertIssuer, fields.ThreadCallstackFinalUserModuleSignatureCertSubject: + frame := e.Callstack.FinalUserFrame() + if frame == nil || (frame != nil && frame.ModuleAddress.IsZero()) { + return nil, nil + } + + sign := getSignature(frame.ModuleAddress, frame.Module, true) + if sign == nil { + return nil, nil + } + + if sign.HasCertificate() && f.Name == fields.ThreadCallstackFinalUserModuleSignatureCertIssuer { + return sign.Cert.Issuer, nil + } + + if sign.HasCertificate() { + return sign.Cert.Subject, nil + } } return nil, nil @@ -1244,42 +1337,3 @@ func (*dnsAccessor) Get(f Field, kevt *kevent.Kevent) (kparams.Value, error) { 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 - } - ok, driver := loldrivers.GetClient().MatchHash(filename) - if !ok { - return nil, nil - } - if (f == fields.FileIsDriverVulnerable || f == fields.ImageIsDriverVulnerable) && driver.IsVulnerable { - return true, nil - } - if (f == fields.FileIsDriverMalicious || f == fields.ImageIsDriverMalicious) && driver.IsMalicious { - return true, nil - } - return false, nil -} - -// initLOLDriversClient initializes the loldrivers client if the filter expression -// contains any of the relevant fields. -func initLOLDriversClient(flds []Field) { - for _, f := range flds { - 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/fields/fields.go b/pkg/filter/fields/fields.go index 07544d370..717f7525a 100644 --- a/pkg/filter/fields/fields.go +++ b/pkg/filter/fields/fields.go @@ -21,6 +21,7 @@ package fields import ( "github.com/rabbitstack/fibratus/pkg/kevent/kparams" "sort" + "unicode" ) // FieldInfo is the field metadata descriptor. @@ -33,6 +34,17 @@ type FieldInfo struct { Argument *Argument } +// isNumber is the field argument validation function that +// returns true if all characters are digits. +var isNumber = func(s string) bool { + for _, c := range s { + if !unicode.IsNumber(c) { + return false + } + } + return true +} + // Argument defines field argument information. type Argument struct { // Optional indicates if the argument is optional. diff --git a/pkg/filter/fields/fields_windows.go b/pkg/filter/fields/fields_windows.go index 4ca583bf6..83d4dceeb 100644 --- a/pkg/filter/fields/fields_windows.go +++ b/pkg/filter/fields/fields_windows.go @@ -216,6 +216,28 @@ const ( ThreadStartAddressSymbol Field = "thread.start_address.symbol" // ThreadStartAddressModule represents the module corresponding to the thread start address ThreadStartAddressModule Field = "thread.start_address.module" + // ThreadCallstackAddresses represents the all callstack return addresses + ThreadCallstackAddresses Field = "thread.callstack.addresses" + // ThreadCallstackFinalUserModuleName represents the final user space stack frame module name + ThreadCallstackFinalUserModuleName Field = "thread.callstack.final_user_module.name" + // ThreadCallstackFinalUserModulePath represents the final user space stack frame module path + ThreadCallstackFinalUserModulePath Field = "thread.callstack.final_user_module.path" + // ThreadCallstackFinalUserSymbolName represents the final user space stack frame symbol name + ThreadCallstackFinalUserSymbolName Field = "thread.callstack.final_user_symbol.name" + // ThreadCallstackFinalKernelModuleName represents the final kernel space stack frame module name + ThreadCallstackFinalKernelModuleName Field = "thread.callstack.final_kernel_module.name" + // ThreadCallstackFinalKernelModulePath represents the final kernel space stack frame module name + ThreadCallstackFinalKernelModulePath Field = "thread.callstack.final_kernel_module.path" + // ThreadCallstackFinalKernelSymbolName represents the final kernel space stack frame symbol name + ThreadCallstackFinalKernelSymbolName Field = "thread.callstack.final_kernel_symbol.name" + // ThreadCallstackFinalUserModuleSignatureIsSigned represents the signature status of the final user space stack frame module + ThreadCallstackFinalUserModuleSignatureIsSigned Field = "thread.callstack.final_user_module.signature.is_signed" + // ThreadCallstackFinalUserModuleSignatureIsTrusted represents the trust status of the final user space stack frame module signature + ThreadCallstackFinalUserModuleSignatureIsTrusted Field = "thread.callstack.final_user_module.signature.is_trusted" + // ThreadCallstackFinalUserModuleSignatureCertIssuer represents the final user space stack frame module certificate issuer + ThreadCallstackFinalUserModuleSignatureCertIssuer Field = "thread.callstack.final_user_module.signature.cert.issuer" + // ThreadCallstackFinalUserModuleSignatureCertSubject represents the final user space stack frame module certificate subject + ThreadCallstackFinalUserModuleSignatureCertSubject Field = "thread.callstack.final_user_module.signature.cert.subject" // PeNumSections represents the number of sections PeNumSections Field = "pe.nsections" @@ -566,40 +588,49 @@ const ( IsUnbackedSegment Segment = "is_unbacked" CallsiteLeadingAssemblySegment Segment = "callsite_leading_assembly" CallsiteTrailingAssemblySegment Segment = "callsite_trailing_assembly" + + ModuleSignatureIsSignedSegment Segment = "module.signature.is_signed" + ModuleSignatureIsTrustedSegment Segment = "module.signature.is_trusted" + ModuleSignatureCertIssuerSegment Segment = "module.signature.cert.issuer" + ModuleSignatureCertSubjectSegment Segment = "module.signature.cert.subject" ) var segments = map[Segment]bool{ - NameSegment: true, - PathSegment: true, - TypeSegment: true, - EntropySegment: true, - SizeSegment: true, - MD5Segment: true, - AddressSegment: true, - ChecksumSegment: true, - PIDSegment: true, - CmdlineSegment: true, - ExeSegment: true, - ArgsSegment: true, - CwdSegment: true, - SIDSegment: true, - SessionIDSegment: true, - UsernameSegment: true, - DomainSegment: true, - TidSegment: true, - StartAddressSegment: true, - UserStackBaseSegment: true, - UserStackLimitSegment: true, - KernelStackBaseSegment: true, - KernelStackLimitSegment: true, - OffsetSegment: true, - SymbolSegment: true, - ModuleSegment: true, - AllocationSizeSegment: true, - ProtectionSegment: true, - IsUnbackedSegment: true, - CallsiteLeadingAssemblySegment: true, - CallsiteTrailingAssemblySegment: true, + NameSegment: true, + PathSegment: true, + TypeSegment: true, + EntropySegment: true, + SizeSegment: true, + MD5Segment: true, + AddressSegment: true, + ChecksumSegment: true, + PIDSegment: true, + CmdlineSegment: true, + ExeSegment: true, + ArgsSegment: true, + CwdSegment: true, + SIDSegment: true, + SessionIDSegment: true, + UsernameSegment: true, + DomainSegment: true, + TidSegment: true, + StartAddressSegment: true, + UserStackBaseSegment: true, + UserStackLimitSegment: true, + KernelStackBaseSegment: true, + KernelStackLimitSegment: true, + OffsetSegment: true, + SymbolSegment: true, + ModuleSegment: true, + AllocationSizeSegment: true, + ProtectionSegment: true, + IsUnbackedSegment: true, + CallsiteLeadingAssemblySegment: true, + CallsiteTrailingAssemblySegment: true, + ModuleSignatureIsSignedSegment: true, + ModuleSignatureIsTrustedSegment: true, + ModuleSignatureCertIssuerSegment: true, + ModuleSignatureCertSubjectSegment: true, } var allowedSegments = map[Field][]Segment{ @@ -608,7 +639,7 @@ var allowedSegments = map[Field][]Segment{ PsModules: {PathSegment, NameSegment, AddressSegment, SizeSegment, ChecksumSegment}, PsMmaps: {AddressSegment, TypeSegment, AddressSegment, SizeSegment, ProtectionSegment, PathSegment}, PeSections: {NameSegment, SizeSegment, EntropySegment, MD5Segment}, - ThreadCallstack: {AddressSegment, OffsetSegment, SymbolSegment, ModuleSegment, AllocationSizeSegment, ProtectionSegment, IsUnbackedSegment, CallsiteLeadingAssemblySegment, CallsiteTrailingAssemblySegment}, + ThreadCallstack: {AddressSegment, OffsetSegment, SymbolSegment, ModuleSegment, AllocationSizeSegment, ProtectionSegment, IsUnbackedSegment, CallsiteLeadingAssemblySegment, CallsiteTrailingAssemblySegment, ModuleSignatureIsSignedSegment, ModuleSignatureIsTrustedSegment, ModuleSignatureCertIssuerSegment, ModuleSignatureCertSubjectSegment}, } func (s Segment) IsEntropy() bool { return s == EntropySegment } @@ -769,40 +800,44 @@ var fields = map[Field]FieldInfo{ 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}, - ThreadStartAddressSymbol: {ThreadStartAddressSymbol, "thread start address symbol", kparams.UnicodeString, []string{"thread.start_address.symbol = 'LoadImage'"}, nil, nil}, - ThreadStartAddressModule: {ThreadStartAddressModule, "thread start address module", kparams.UnicodeString, []string{"thread.start_address.module endswith 'kernel32.dll'"}, 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: isNumber}}, + + 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}, + ThreadStartAddressSymbol: {ThreadStartAddressSymbol, "thread start address symbol", kparams.UnicodeString, []string{"thread.start_address.symbol = 'LoadImage'"}, nil, nil}, + ThreadStartAddressModule: {ThreadStartAddressModule, "thread start address module", kparams.UnicodeString, []string{"thread.start_address.module endswith 'kernel32.dll'"}, 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')", "base(thread.callstack.modules[7]) = 'ntdll.dll'"}, nil, &Argument{Optional: true, Pattern: "[0-9]+", ValidationFunc: isNumber}}, + ThreadCallstackSymbols: {ThreadCallstackSymbols, "list of symbols comprising the callstack", kparams.Slice, []string{"thread.callstack.symbols in ('ntdll.dll!NtCreateProcess')", "thread.callstack.symbols[3] = 'ntdll!NtCreateProcess'"}, nil, &Argument{Optional: true, Pattern: "[0-9]+", ValidationFunc: isNumber}}, + 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}, + ThreadCallstackAddresses: {ThreadCallstackAddresses, "list of all stack return addresses", kparams.Slice, []string{"thread.callstack.addresses in ('7ffb5c1d0396')"}, nil, nil}, + ThreadCallstackFinalUserModuleName: {ThreadCallstackFinalUserModuleName, "final user space stack frame module name", kparams.UnicodeString, []string{"thread.callstack.final_user_module.name != 'ntdll.dll'"}, nil, nil}, + ThreadCallstackFinalUserModulePath: {ThreadCallstackFinalUserModulePath, "final user space stack frame module path", kparams.UnicodeString, []string{"thread.callstack.final_user_module.path imatches '?:\\Windows\\System32\\ntdll.dll'"}, nil, nil}, + ThreadCallstackFinalUserSymbolName: {ThreadCallstackFinalUserSymbolName, "final user space stack symbol name", kparams.UnicodeString, []string{"thread.callstack.final_user_symbol.name imatches 'CreateProcess*'"}, nil, nil}, + ThreadCallstackFinalKernelModuleName: {ThreadCallstackFinalKernelModuleName, "final kernel space stack frame module name", kparams.UnicodeString, []string{"thread.callstack.final_kernel_module.name = 'FLTMGR.SYS'"}, nil, nil}, + ThreadCallstackFinalKernelModulePath: {ThreadCallstackFinalKernelModulePath, "final kernel space stack frame module path", kparams.UnicodeString, []string{"thread.callstack.final_kernel_module.path imatches '?:\\WINDOWS\\System32\\drivers\\FLTMGR.SYS'"}, nil, nil}, + ThreadCallstackFinalKernelSymbolName: {ThreadCallstackFinalKernelSymbolName, "final kernel space stack symbol name", kparams.UnicodeString, []string{"thread.callstack.final_kernel_symbol.name = 'FltGetStreamContext'"}, nil, nil}, + ThreadCallstackFinalUserModuleSignatureIsSigned: {ThreadCallstackFinalUserModuleSignatureIsSigned, "signature status of the final user space stack frame module", kparams.Bool, []string{"thread.callstack.final_user_module.signature.is_signed = true"}, nil, nil}, + ThreadCallstackFinalUserModuleSignatureIsTrusted: {ThreadCallstackFinalUserModuleSignatureIsTrusted, "signature trust status of the final user space stack frame module", kparams.Bool, []string{"thread.callstack.final_user_module.signature.is_trusted = true"}, nil, nil}, + ThreadCallstackFinalUserModuleSignatureCertIssuer: {ThreadCallstackFinalUserModuleSignatureCertIssuer, "final user space stack frame module signature certificate issuer", kparams.UnicodeString, []string{"thread.callstack.final_user_module.signature.cert.issuer imatches '*Microsoft Corporation*'"}, nil, nil}, + ThreadCallstackFinalUserModuleSignatureCertSubject: {ThreadCallstackFinalUserModuleSignatureCertSubject, "final user space stack frame module signature certificate subject", kparams.UnicodeString, []string{"thread.callstack.final_user_module.signature.cert.subject imatches '*Microsoft Windows*'"}, 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}, diff --git a/pkg/filter/filter_test.go b/pkg/filter/filter_test.go index ed4f7ee3b..b69250fcb 100644 --- a/pkg/filter/filter_test.go +++ b/pkg/filter/filter_test.go @@ -350,9 +350,17 @@ func TestThreadFilter(t *testing.T) { PS: &pstypes.PS{ Name: "svchost.exe", Envs: map[string]string{"ALLUSERSPROFILE": "C:\\ProgramData", "OS": "Windows_NT", "ProgramFiles(x86)": "C:\\Program Files (x86)"}, + Modules: []pstypes.Module{ + {Name: "C:\\Windows\\System32\\kernel32.dll", Size: 2312354, Checksum: 23123343, BaseAddress: va.Address(0x7ffb5c1d0396), DefaultBaseAddress: va.Address(0x7ffb5c1d0396)}, + {Name: "C:\\Windows\\System32\\user32.dll", Size: 32212354, Checksum: 33123343, BaseAddress: va.Address(0x7ffb313953b2), DefaultBaseAddress: va.Address(0x7ffb313953b2)}, + }, }, } + // append the module signature + cert := &sys.Cert{Subject: "US, Washington, Redmond, Microsoft Corporation, Microsoft Windows", Issuer: "US, Washington, Redmond, Microsoft Corporation, Microsoft Windows Production PCA 2011"} + signature.GetSignatures().PutSignature(0x7ffb5c1d0396, &signature.Signature{Filename: "C:\\Windows\\System32\\kernel32.dll", Level: 4, Type: 1, Cert: cert}) + // simulate unbacked RWX frame base, err := windows.VirtualAlloc(0, 1024, windows.MEM_COMMIT|windows.MEM_RESERVE, windows.PAGE_EXECUTE_READWRITE) require.NoError(t, err) @@ -374,7 +382,7 @@ func TestThreadFilter(t *testing.T) { kevt.Callstack.PushFrame(kevent.Frame{PID: kevt.PID, Addr: 0x7ffb313853b2, Offset: 0x10a, Symbol: "Java_java_lang_ProcessImpl_create", Module: "C:\\Program Files\\JetBrains\\GoLand 2021.2.3\\jbr\\bin\\java.dll"}) kevt.Callstack.PushFrame(kevent.Frame{PID: kevt.PID, Addr: 0x7ffb3138592e, Offset: 0x3a2, Symbol: "Java_java_lang_ProcessImpl_waitForTimeoutInterruptibly", Module: "C:\\Program Files\\JetBrains\\GoLand 2021.2.3\\jbr\\bin\\java.dll"}) kevt.Callstack.PushFrame(kevent.Frame{PID: kevt.PID, Addr: 0x7ffb5d8e61f4, Offset: 0x54, Symbol: "CreateProcessW", Module: "C:\\WINDOWS\\System32\\KERNEL32.DLL"}) - kevt.Callstack.PushFrame(kevent.Frame{PID: kevt.PID, Addr: 0x7ffb5c1d0396, Offset: 0x66, Symbol: "CreateProcessW", Module: "C:\\WINDOWS\\System32\\KERNELBASE.dll"}) + kevt.Callstack.PushFrame(kevent.Frame{PID: kevt.PID, Addr: 0x7ffb5c1d0396, ModuleAddress: 0x7ffb5c1d0396, Offset: 0x66, Symbol: "CreateProcessW", Module: "C:\\WINDOWS\\System32\\KERNELBASE.dll"}) kevt.Callstack.PushFrame(kevent.Frame{PID: kevt.PID, Addr: 0xfffff8072ebc1f6f, Offset: 0x4ef, Symbol: "FltRequestFileInfoOnCreateCompletion", Module: "C:\\WINDOWS\\System32\\drivers\\FLTMGR.SYS"}) kevt.Callstack.PushFrame(kevent.Frame{PID: kevt.PID, Addr: 0xfffff8072eb8961b, Offset: 0x20cb, Symbol: "FltGetStreamContext", Module: "C:\\WINDOWS\\System32\\drivers\\FLTMGR.SYS"}) @@ -389,17 +397,33 @@ func TestThreadFilter(t *testing.T) { {`thread.kstack.limit = 'ffffc307810cf000'`, true}, {`thread.start_address = '7ffe2557ff80'`, true}, {`thread.teb_address = '8f30893000'`, true}, + {`thread.start_address.symbol = 'LoadImage'`, true}, + {`thread.start_address.module = 'C:\\Windows\\System32\\kernel32.dll'`, true}, {`thread.callstack.summary = 'KERNELBASE.dll|KERNEL32.DLL|java.dll|unbacked'`, true}, {`thread.callstack.detail icontains 'C:\\WINDOWS\\System32\\KERNELBASE.dll!CreateProcessW+0x66'`, true}, {`thread.callstack.modules in ('C:\\WINDOWS\\System32\\KERNELBASE.dll', 'C:\\Program Files\\JetBrains\\GoLand 2021.2.3\\jbr\\bin\\java.dll')`, true}, + {`thread.callstack.modules[5] = 'C:\\WINDOWS\\System32\\KERNELBASE.dll'`, true}, + {`thread.callstack.modules[7] = 'C:\\WINDOWS\\System32\\drivers\\FLTMGR.SYS'`, true}, + {`thread.callstack.modules[8] = ''`, true}, {`thread.callstack.symbols imatches ('KERNELBASE.dll!CreateProcess*', 'Java_java_lang_ProcessImpl_create')`, true}, + {`thread.callstack.symbols[2] = 'Java_java_lang_ProcessImpl_create'`, true}, + {`thread.callstack.symbols[8] = ''`, true}, {`thread.callstack.protections in ('RWX')`, true}, {`thread.callstack.allocation_sizes > 0`, false}, {`length(thread.callstack.callsite_leading_assembly) > 0`, true}, {`thread.callstack.callsite_trailing_assembly matches ('*mov r10, rcx|mov eax, 0x*|syscall*')`, true}, {`thread.callstack.is_unbacked`, true}, - {`thread.start_address.symbol = 'LoadImage'`, true}, - {`thread.start_address.module = 'C:\\Windows\\System32\\kernel32.dll'`, true}, + {`thread.callstack.addresses intersects ('7ffb5d8e61f4', 'fffff8072eb8961b')`, true}, + {`thread.callstack.final_user_module.name = 'KERNELBASE.dll'`, true}, + {`thread.callstack.final_user_module.path = 'C:\\WINDOWS\\System32\\KERNELBASE.dll'`, true}, + {`thread.callstack.final_user_symbol.name = 'CreateProcessW'`, true}, + {`thread.callstack.final_kernel_module.name = 'FLTMGR.SYS'`, true}, + {`thread.callstack.final_kernel_module.path = 'C:\\WINDOWS\\System32\\drivers\\FLTMGR.SYS'`, true}, + {`thread.callstack.final_kernel_symbol.name = 'FltGetStreamContext'`, true}, + {`thread.callstack.final_user_module.signature.is_signed = true`, true}, + {`thread.callstack.final_user_module.signature.is_trusted = true`, true}, + {`thread.callstack.final_user_module.signature.cert.issuer imatches '*Microsoft Corporation*'`, true}, + {`thread.callstack.final_user_module.signature.cert.subject imatches '*Microsoft Windows*'`, true}, {`foreach(thread._callstack, $frame, $frame.address = '2638e59e0a5' or $frame.address = '7ffb5c1d0396')`, true}, {`foreach(thread._callstack, $frame, $frame.address = 'fffff8072ebc1f6f' or $frame.address = 'fffff8072eb8961b')`, true}, @@ -414,6 +438,9 @@ func TestThreadFilter(t *testing.T) { {`foreach(thread._callstack, $frame, $frame.allocation_size = 0)`, true}, {`foreach(thread._callstack, $frame, $frame.protection = 'RWX')`, true}, {`foreach(thread._callstack, $frame, $frame.callsite_trailing_assembly matches '*mov r10, rcx|mov eax, 0x*|syscall*' and $frame.module = 'unbacked')`, true}, + {`foreach(thread._callstack, $frame, $frame.module.signature.is_signed and $frame.module.signature.is_trusted)`, true}, + {`foreach(thread._callstack, $frame, $frame.module.signature.cert.issuer imatches '*Microsoft Corporation*')`, true}, + {`foreach(thread._callstack, $frame, $frame.module.signature.cert.subject imatches '*Microsoft Windows*')`, true}, } for i, tt := range tests { diff --git a/pkg/filter/ql/function.go b/pkg/filter/ql/function.go index b2f7d24fe..2890c1058 100644 --- a/pkg/filter/ql/function.go +++ b/pkg/filter/ql/function.go @@ -24,6 +24,7 @@ import ( "github.com/rabbitstack/fibratus/pkg/kevent" "github.com/rabbitstack/fibratus/pkg/pe" pstypes "github.com/rabbitstack/fibratus/pkg/ps/types" + "github.com/rabbitstack/fibratus/pkg/util/signature" "golang.org/x/sys/windows" "maps" "path/filepath" @@ -558,6 +559,57 @@ func (f *Foreach) callstackMapValuer(segments []*BoundSegmentLiteral, frame keve valuer[key] = frame.CallsiteAssembly(proc, false) case fields.CallsiteLeadingAssemblySegment: valuer[key] = frame.CallsiteAssembly(proc, true) + case fields.ModuleSignatureIsSignedSegment, fields.ModuleSignatureIsTrustedSegment, + fields.ModuleSignatureCertIssuerSegment, fields.ModuleSignatureCertSubjectSegment: + + if frame.ModuleAddress.IsZero() { + continue + } + + segment := seg.Segment + sign := signature.GetSignatures().GetSignature(frame.ModuleAddress.Uint64()) + if sign == nil && frame.Module != "" { + // register signature if not present in the cache + var err error + sign = &signature.Signature{Filename: frame.Module} + sign.Type, sign.Level, err = sign.Check() + if err != nil { + continue + } + + if sign.IsSigned() { + sign.Verify() + } + + if segment == fields.ModuleSignatureCertIssuerSegment || segment == fields.ModuleSignatureCertSubjectSegment { + if err := sign.ParseCertificate(); err != nil { + continue + } + } + + signature.GetSignatures().PutSignature(frame.ModuleAddress.Uint64(), sign) + } + + switch segment { + case fields.ModuleSignatureIsSignedSegment: + valuer[key] = sign.IsSigned() + case fields.ModuleSignatureIsTrustedSegment: + valuer[key] = sign.IsTrusted() + case fields.ModuleSignatureCertIssuerSegment: + if err := sign.ParseCertificate(); err != nil { + continue + } + if sign.HasCertificate() { + valuer[key] = sign.Cert.Issuer + } + case fields.ModuleSignatureCertSubjectSegment: + if err := sign.ParseCertificate(); err != nil { + continue + } + if sign.HasCertificate() { + valuer[key] = sign.Cert.Subject + } + } } } return valuer diff --git a/pkg/filter/util.go b/pkg/filter/util.go new file mode 100644 index 000000000..8c44fe02e --- /dev/null +++ b/pkg/filter/util.go @@ -0,0 +1,107 @@ +/* + * Copyright 2021-present by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package filter + +import ( + "github.com/rabbitstack/fibratus/pkg/filter/fields" + "github.com/rabbitstack/fibratus/pkg/kevent" + "github.com/rabbitstack/fibratus/pkg/kevent/kparams" + "github.com/rabbitstack/fibratus/pkg/kevent/ktypes" + "github.com/rabbitstack/fibratus/pkg/util/loldrivers" + "github.com/rabbitstack/fibratus/pkg/util/signature" + "github.com/rabbitstack/fibratus/pkg/util/va" + "path/filepath" +) + +// isLOLDriver interacts with the loldrivers client to determine +// whether the loaded/dropped driver is malicious or vulnerable. +func isLOLDriver(f fields.Field, e *kevent.Kevent) (kparams.Value, error) { + var filename string + + if e.Category == ktypes.File { + filename = e.GetParamAsString(kparams.FilePath) + } else { + filename = e.GetParamAsString(kparams.ImagePath) + } + + isDriver := filepath.Ext(filename) == ".sys" || e.Kparams.TryGetBool(kparams.FileIsDriver) + if !isDriver { + return nil, nil + } + ok, driver := loldrivers.GetClient().MatchHash(filename) + if !ok { + return nil, nil + } + if (f == fields.FileIsDriverVulnerable || f == fields.ImageIsDriverVulnerable) && driver.IsVulnerable { + return true, nil + } + if (f == fields.FileIsDriverMalicious || f == fields.ImageIsDriverMalicious) && driver.IsMalicious { + return true, nil + } + return false, nil +} + +// initLOLDriversClient initializes the loldrivers client if the filter expression +// contains any of the relevant fields. +func initLOLDriversClient(flds []Field) { + for _, f := range flds { + if f.Name == fields.FileIsDriverVulnerable || f.Name == fields.FileIsDriverMalicious || + f.Name == fields.ImageIsDriverVulnerable || f.Name == fields.ImageIsDriverMalicious { + loldrivers.InitClient(loldrivers.WithAsyncDownload()) + } + } +} + +// getSignature tries to find the module signature mapped to the given address. +// If the signature is not found in the cache, then a fresh signature instance +// is created and verified. +func getSignature(addr va.Address, filename string, parseCert bool) *signature.Signature { + sign := signature.GetSignatures().GetSignature(addr.Uint64()) + if sign != nil { + if parseCert { + err := sign.ParseCertificate() + if err != nil { + certErrors.Add(1) + } + } + return sign + } + + var err error + sign = &signature.Signature{Filename: filename} + sign.Type, sign.Level, err = sign.Check() + if err != nil { + signatureErrors.Add(1) + } + + if sign.IsSigned() { + sign.Verify() + } + + if parseCert { + err = sign.ParseCertificate() + if err != nil { + certErrors.Add(1) + } + } + + signature.GetSignatures().PutSignature(addr.Uint64(), sign) + + return sign +} diff --git a/pkg/kevent/callstack.go b/pkg/kevent/callstack.go index 97c58337b..5396868c5 100644 --- a/pkg/kevent/callstack.go +++ b/pkg/kevent/callstack.go @@ -53,11 +53,12 @@ var _, _, buildNumber = windows.RtlGetNtVersionNumbers() // Frame describes a single stack frame. type Frame struct { - PID uint32 // pid owning thread's stack - Addr va.Address // return address - Offset uint64 // symbol offset - Symbol string // symbol name - Module string // module name + PID uint32 // pid owning thread's stack + Addr va.Address // return address + Offset uint64 // symbol offset + Symbol string // symbol name + Module string // module name + ModuleAddress va.Address // module base address } // IsUnbacked returns true if this frame is originated @@ -183,6 +184,32 @@ func (s *Callstack) Depth() int { return len(*s) } // IsEmpty returns true if the callstack has no frames. func (s *Callstack) IsEmpty() bool { return s.Depth() == 0 } +// FinalUserFrame returns the final user space frame. +func (s *Callstack) FinalUserFrame() *Frame { + var i int + if s.IsEmpty() { + return nil + } + + for ; i < s.Depth()-1 && !(*s)[i].Addr.InSystemRange(); i++ { + } + i-- + + if i > 0 && i < s.Depth()-1 { + return &(*s)[i] + } + + return nil +} + +// FinalKernelFrame returns the final kernel space frame. +func (s *Callstack) FinalKernelFrame() *Frame { + if s.IsEmpty() { + return nil + } + return &(*s)[s.Depth()-1] +} + // Summary returns a sequence of non-repeated module names. func (s Callstack) Summary() string { var b strings.Builder @@ -272,6 +299,15 @@ func (s Callstack) ContainsUnbacked() bool { return false } +// Addresses returns stack retrun addresses. +func (s Callstack) Addresses() []string { + addrs := make([]string, len(s)) + for i, frame := range s { + addrs[i] = frame.Addr.String() + } + return addrs +} + // Modules returns all modules comprising the thread stack. func (s Callstack) Modules() []string { mods := make([]string, len(s)) diff --git a/pkg/kevent/callstack_test.go b/pkg/kevent/callstack_test.go index 656bd056b..5a30ba4ec 100644 --- a/pkg/kevent/callstack_test.go +++ b/pkg/kevent/callstack_test.go @@ -24,30 +24,25 @@ import ( "github.com/rabbitstack/fibratus/pkg/kevent/ktypes" "github.com/rabbitstack/fibratus/pkg/util/va" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "testing" "time" ) func TestCallstack(t *testing.T) { e := &Kevent{ - Type: ktypes.CreateFile, + Type: ktypes.CreateProcess, Tid: 2484, PID: 859, CPU: 1, Seq: 2, - Name: "CreateFile", + Name: "CreateProcess", Timestamp: time.Now(), - Category: ktypes.File, - Kparams: Kparams{ - kparams.FileObject: {Name: kparams.FileObject, Type: kparams.Uint64, Value: uint64(12456738026482168384)}, - kparams.FilePath: {Name: kparams.FilePath, Type: kparams.UnicodeString, Value: "C:\\Windows\\system32\\user32.dll"}, - kparams.FileType: {Name: kparams.FileType, Type: kparams.AnsiString, Value: "file"}, - kparams.FileOperation: {Name: kparams.FileOperation, Type: kparams.Enum, Value: uint32(1), Enum: fs.FileCreateDispositions}, - }, + Category: ktypes.Process, } - e.Callstack.Init(6) - assert.Equal(t, 6, cap(e.Callstack)) + e.Callstack.Init(9) + assert.Equal(t, 9, cap(e.Callstack)) e.Callstack.PushFrame(Frame{Addr: 0x2638e59e0a5, Offset: 0, Symbol: "?", Module: "unbacked"}) e.Callstack.PushFrame(Frame{Addr: 0x7ffb313853b2, Offset: 0x10a, Symbol: "Java_java_lang_ProcessImpl_create", Module: "C:\\Program Files\\JetBrains\\GoLand 2021.2.3\\jbr\\bin\\java.dll"}) @@ -55,11 +50,26 @@ func TestCallstack(t *testing.T) { e.Callstack.PushFrame(Frame{Addr: 0x7ffb5c1d0396, Offset: 0x61, Symbol: "CreateProcessW", Module: "C:\\WINDOWS\\System32\\KERNELBASE.dll"}) e.Callstack.PushFrame(Frame{Addr: 0x7ffb5d8e61f4, Offset: 0x54, Symbol: "CreateProcessW", Module: "C:\\WINDOWS\\System32\\KERNEL32.DLL"}) e.Callstack.PushFrame(Frame{Addr: 0x7ffb5c1d0396, Offset: 0x66, Symbol: "CreateProcessW", Module: "C:\\WINDOWS\\System32\\KERNELBASE.dll"}) + e.Callstack.PushFrame(Frame{Addr: 0xfffff8015662a605, Offset: 0x9125, Symbol: "setjmpex", Module: "C:\\WINDOWS\\system32\\ntoskrnl.exe"}) + e.Callstack.PushFrame(Frame{Addr: 0xfffff801568e9c33, Offset: 0x2ef3, Symbol: "LpcRequestPort", Module: "C:\\WINDOWS\\system32\\ntoskrnl.exe"}) + e.Callstack.PushFrame(Frame{Addr: 0xfffff8015690b644, Offset: 0x45b4, Symbol: "ObDeleteCapturedInsertInfo", Module: "C:\\WINDOWS\\system32\\ntoskrnl.exe"}) assert.True(t, e.Callstack.ContainsUnbacked()) - assert.Equal(t, 6, e.Callstack.Depth()) - assert.Equal(t, "0x7ffb5c1d0396 C:\\WINDOWS\\System32\\KERNELBASE.dll!CreateProcessW+0x66|0x7ffb5d8e61f4 C:\\WINDOWS\\System32\\KERNEL32.DLL!CreateProcessW+0x54|0x7ffb5c1d0396 C:\\WINDOWS\\System32\\KERNELBASE.dll!CreateProcessW+0x61|0x7ffb3138592e C:\\Program Files\\JetBrains\\GoLand 2021.2.3\\jbr\\bin\\java.dll!Java_java_lang_ProcessImpl_waitForTimeoutInterruptibly+0x3a2|0x7ffb313853b2 C:\\Program Files\\JetBrains\\GoLand 2021.2.3\\jbr\\bin\\java.dll!Java_java_lang_ProcessImpl_create+0x10a|0x2638e59e0a5 unbacked!?", e.Callstack.String()) + assert.Equal(t, 9, e.Callstack.Depth()) + assert.Equal(t, "0xfffff8015690b644 C:\\WINDOWS\\system32\\ntoskrnl.exe!ObDeleteCapturedInsertInfo+0x45b4|0xfffff801568e9c33 C:\\WINDOWS\\system32\\ntoskrnl.exe!LpcRequestPort+0x2ef3|0xfffff8015662a605 C:\\WINDOWS\\system32\\ntoskrnl.exe!setjmpex+0x9125|0x7ffb5c1d0396 C:\\WINDOWS\\System32\\KERNELBASE.dll!CreateProcessW+0x66|0x7ffb5d8e61f4 C:\\WINDOWS\\System32\\KERNEL32.DLL!CreateProcessW+0x54|0x7ffb5c1d0396 C:\\WINDOWS\\System32\\KERNELBASE.dll!CreateProcessW+0x61|0x7ffb3138592e C:\\Program Files\\JetBrains\\GoLand 2021.2.3\\jbr\\bin\\java.dll!Java_java_lang_ProcessImpl_waitForTimeoutInterruptibly+0x3a2|0x7ffb313853b2 C:\\Program Files\\JetBrains\\GoLand 2021.2.3\\jbr\\bin\\java.dll!Java_java_lang_ProcessImpl_create+0x10a|0x2638e59e0a5 unbacked!?", e.Callstack.String()) assert.Equal(t, "KERNELBASE.dll|KERNEL32.DLL|KERNELBASE.dll|java.dll|unbacked", e.Callstack.Summary()) + + uframe := e.Callstack.FinalUserFrame() + require.NotNil(t, uframe) + assert.Equal(t, "7ffb5c1d0396", uframe.Addr.String()) + assert.Equal(t, "CreateProcessW", uframe.Symbol) + assert.Equal(t, "C:\\WINDOWS\\System32\\KERNELBASE.dll", uframe.Module) + + kframe := e.Callstack.FinalKernelFrame() + require.NotNil(t, kframe) + assert.Equal(t, "fffff8015690b644", kframe.Addr.String()) + assert.Equal(t, "ObDeleteCapturedInsertInfo", kframe.Symbol) + assert.Equal(t, "C:\\WINDOWS\\system32\\ntoskrnl.exe", kframe.Module) } func TestCallstackDecorator(t *testing.T) { diff --git a/pkg/symbolize/symbolizer.go b/pkg/symbolize/symbolizer.go index 34cad4d22..1edc3505f 100644 --- a/pkg/symbolize/symbolizer.go +++ b/pkg/symbolize/symbolizer.go @@ -453,6 +453,7 @@ func (s *Symbolizer) produceFrame(addr va.Address, e *kevent.Kevent, fast, looku mod := e.PS.FindModuleByVa(addr) if mod != nil { frame.Module = mod.Name + frame.ModuleAddress = mod.BaseAddress } if lookupExport { frame.Symbol = s.resolveSymbolFromExportDirectory(addr, mod) @@ -470,6 +471,7 @@ func (s *Symbolizer) produceFrame(addr va.Address, e *kevent.Kevent, fast, looku } if mod != nil { frame.Module = mod.Name + frame.ModuleAddress = mod.BaseAddress m, ok := s.mods[mod.BaseAddress] peOK := true if !ok { diff --git a/pkg/util/va/address.go b/pkg/util/va/address.go index 32593d051..bbbf508b7 100644 --- a/pkg/util/va/address.go +++ b/pkg/util/va/address.go @@ -27,6 +27,7 @@ type Address uint64 func (a Address) String() string { return strconv.FormatUint(uint64(a), 16) } func (a Address) Uint64() uint64 { return uint64(a) } func (a Address) Uintptr() uintptr { return uintptr(a) } +func (a Address) IsZero() bool { return a == 0 } // Inc increments the address by given offset. func (a Address) Inc(offset uint64) Address {