From 1fb79187eef13b891ea0f9bd822ab5c26c02dcee Mon Sep 17 00:00:00 2001 From: rabbitstack Date: Thu, 3 Apr 2025 01:24:26 +0200 Subject: [PATCH 1/2] fix(callstack): Rework final user frame heuristics Change the heuristics for obtaining the final user code stack frame by looking for the presence of modules not referencing Win32 or Native (ntdll) layers that are intermediary modules for transitioning into kernel land. --- pkg/callstack/callstack.go | 21 +++++++--- pkg/callstack/callstack_test.go | 72 +++++++++++++++++++++++++++++++-- pkg/filter/filter_test.go | 13 +++--- 3 files changed, 91 insertions(+), 15 deletions(-) diff --git a/pkg/callstack/callstack.go b/pkg/callstack/callstack.go index 051db7c58..a60ae1b4d 100644 --- a/pkg/callstack/callstack.go +++ b/pkg/callstack/callstack.go @@ -169,19 +169,28 @@ 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. +// FinalUserFrame returns the final frame that corresponds +// to the user code execution. That usually translates to +// the last frame before ntdll or kernel32 modules. func (s *Callstack) FinalUserFrame() *Frame { - var i int if s.IsEmpty() { return nil } - for ; i < s.Depth()-1 && !(*s)[i].Addr.InSystemRange(); i++ { + var n int + for n = s.Depth() - 1; n > 0; n-- { + f := (*s)[n] + if f.Addr.InSystemRange() { + continue + } + mod := filepath.Base(strings.ToLower(f.Module)) + if mod != "ntdll.dll" && mod != "kernel32.dll" && mod != "kernelbase.dll" { + break + } } - i-- - if i > 0 && i < s.Depth()-1 { - return &(*s)[i] + if n >= 0 && n < s.Depth()-1 { + return &(*s)[n] } return nil diff --git a/pkg/callstack/callstack_test.go b/pkg/callstack/callstack_test.go index 664fc9909..5ac3e4b48 100644 --- a/pkg/callstack/callstack_test.go +++ b/pkg/callstack/callstack_test.go @@ -47,9 +47,9 @@ func TestCallstack(t *testing.T) { uframe := 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) + assert.Equal(t, "7ffb3138592e", uframe.Addr.String()) + assert.Equal(t, "Java_java_lang_ProcessImpl_waitForTimeoutInterruptibly", uframe.Symbol) + assert.Equal(t, "C:\\Program Files\\JetBrains\\GoLand 2021.2.3\\jbr\\bin\\java.dll", uframe.Module) kframe := callstack.FinalKernelFrame() require.NotNil(t, kframe) @@ -57,3 +57,69 @@ func TestCallstack(t *testing.T) { assert.Equal(t, "ObDeleteCapturedInsertInfo", kframe.Symbol) assert.Equal(t, "C:\\WINDOWS\\system32\\ntoskrnl.exe", kframe.Module) } + +func TestCallstackFinalUserFrame(t *testing.T) { + var tests = []struct { + callstack Callstack + expectedMod string + expectedSym string + }{ + {callstack: callstackFromFrames( + Frame{Addr: 0xf259de, Module: unbacked, Symbol: "?"}, + Frame{Addr: 0x7ffe4fda6e3b, Module: "C:\\Windows\\System32\\KernelBase.dll", Symbol: "SetThreadContext"}, + Frame{Addr: 0x7ffe52942b24, Module: "C:\\Windows\\System32\\ntdll.dll", Symbol: "ZwSetContextThread"}, + Frame{Addr: 0xfffff807e228c555, Module: "C:\\WINDOWS\\system32\\ntoskrnl.exe", Symbol: "setjmpex"}, + Frame{Addr: 0xfffff807e264805c, Module: "C:\\WINDOWS\\system32\\ntoskrnl.exe", Symbol: "ObOpenObjectByPointerWithTag"}), + expectedMod: "unbacked", + expectedSym: "?", + }, + {callstack: callstackFromFrames( + Frame{Addr: 0x7ffff0f3bf6c, Module: "C:\\Windows\\System32\\ntdll.dll", Symbol: "RtlUserThreadStart"}, + Frame{Addr: 0x7ffff03ee8d7, Module: "C:\\Windows\\System32\\ntdll.dll", Symbol: "BaseThreadInitThunk"}, + Frame{Addr: 0x7ffff0ee5f13, Module: "C:\\Windows\\System32\\ntdll.dll", Symbol: "TpCallbackMayRunLong"}, + Frame{Addr: 0x7ffff0c78788, Module: "C:\\Windows\\System32\\rpcrt4.dll", Symbol: "RpcGetBufferWithObject"}, + Frame{Addr: 0x7ffff0c797e3, Module: "C:\\Windows\\System32\\rpcrt4.dll", Symbol: "RpcImpersonateClient"}, + Frame{Addr: 0x7fffee58d16a, Module: "C:\\Windows\\System32\\KernelBase.dll", Symbol: "CreateProcessInternalW"}, + Frame{Addr: 0x7ffff0fe1204, Module: "C:\\Windows\\System32\\ntdll.dll", Symbol: "ZwCreateUserProcess"}), + expectedMod: "C:\\Windows\\System32\\rpcrt4.dll", + expectedSym: "RpcImpersonateClient", + }, + {callstack: callstackFromFrames( + Frame{Addr: 0x7fffa7e3bf6c, Module: "C:\\Windows\\System32\\ntdll.dll", Symbol: "RtlUserThreadStart"}, + Frame{Addr: 0x7fffa60de8d7, Module: "C:\\Windows\\System32\\ntdll.dll", Symbol: "BaseThreadInitThunk"}, + Frame{Addr: 0x7ff6163cfc68, Module: "C:\\Program Files\\Mozilla Firefox\\firefox.exe", Symbol: "TargetCreateThread"}, + Frame{Addr: 0x7fffee58d16a, Module: "C:\\Windows\\System32\\ntdll.dll", Symbol: "ZwMapViewOfSection"}, + Frame{Addr: 0xfffff8028deeed1d, Module: "C:\\WINDOWS\\system32\\ntoskrnl.exe", Symbol: "NtMapViewOfSection"}), + expectedMod: "C:\\Program Files\\Mozilla Firefox\\firefox.exe", + expectedSym: "TargetCreateThread", + }, + {callstack: callstackFromFrames( + Frame{Addr: 0x7fffa7e3bf6c, Module: "C:\\Windows\\System32\\ntdll.dll", Symbol: "RtlUserThreadStart"}, + Frame{Addr: 0x7fffa60de8d7, Module: "C:\\Windows\\System32\\ntdll.dll", Symbol: "BaseThreadInitThunk"}, + Frame{Addr: 0x7ffff0c78788, Module: "C:\\Windows\\System32\\rpcrt4.dll", Symbol: "NdrServerCallNdr64"}, + Frame{Addr: 0x7ffff0c574ed, Module: "C:\\Windows\\System32\\rpcrt4.dll", Symbol: "NdrStubCall2"}, + Frame{Addr: 0x7ffff03fb090, Module: "C:\\Windows\\System32\\kernel32.dll", Symbol: "CreateProcessInternalW"}, + Frame{Addr: 0x7fffee58a923, Module: "C:\\Windows\\System32\\kernel32.dll", Symbol: "CreateProcessAsUserW"}, + Frame{Addr: 0x7ffff0fe1204, Module: "C:\\Windows\\System32\\ntdll.dll", Symbol: "ZwCreateUserProcess"}), + expectedMod: "C:\\Windows\\System32\\rpcrt4.dll", + expectedSym: "NdrStubCall2", + }, + } + + for _, tt := range tests { + t.Run(tt.expectedMod+"!"+tt.expectedSym, func(t *testing.T) { + f := tt.callstack.FinalUserFrame() + require.NotNil(t, f) + assert.Equal(t, tt.expectedMod, f.Module) + assert.Equal(t, tt.expectedSym, f.Symbol) + }) + } +} + +func callstackFromFrames(frames ...Frame) Callstack { + var c Callstack + for _, frame := range frames { + c.PushFrame(frame) + } + return c +} diff --git a/pkg/filter/filter_test.go b/pkg/filter/filter_test.go index b5cead9b3..2481e660b 100644 --- a/pkg/filter/filter_test.go +++ b/pkg/filter/filter_test.go @@ -355,13 +355,14 @@ func TestThreadFilter(t *testing.T) { 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)}, + {Name: "C:\\Program Files\\JetBrains\\GoLand 2021.2.3\\jbr\\bin\\java.dll", Size: 32212354, Checksum: 33123343, BaseAddress: va.Address(0x7ffb3138592e), DefaultBaseAddress: va.Address(0x7ffb3138592e)}, }, }, } // 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}) + signature.GetSignatures().PutSignature(0x7ffb3138592e, &signature.Signature{Filename: "C:\\Program Files\\JetBrains\\GoLand 2021.2.3\\jbr\\bin\\java.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) @@ -382,9 +383,9 @@ func TestThreadFilter(t *testing.T) { kevt.Callstack.PushFrame(callstack.Frame{PID: kevt.PID, Addr: 0x2638e59e0a5, Offset: 0, Symbol: "?", Module: "unbacked"}) kevt.Callstack.PushFrame(callstack.Frame{PID: kevt.PID, Addr: va.Address(base), Offset: 0, Symbol: "?", Module: "unbacked"}) kevt.Callstack.PushFrame(callstack.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(callstack.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(callstack.Frame{PID: kevt.PID, Addr: 0x7ffb3138592e, ModuleAddress: 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(callstack.Frame{PID: kevt.PID, Addr: 0x7ffb5d8e61f4, Offset: 0x54, Symbol: "CreateProcessW", Module: "C:\\WINDOWS\\System32\\KERNEL32.DLL"}) - kevt.Callstack.PushFrame(callstack.Frame{PID: kevt.PID, Addr: 0x7ffb5c1d0396, ModuleAddress: 0x7ffb5c1d0396, Offset: 0x66, Symbol: "CreateProcessW", Module: "C:\\WINDOWS\\System32\\KERNELBASE.dll"}) + kevt.Callstack.PushFrame(callstack.Frame{PID: kevt.PID, Addr: 0x7ffb5c1d0396, Offset: 0x66, Symbol: "CreateProcessW", Module: "C:\\WINDOWS\\System32\\KERNELBASE.dll"}) kevt.Callstack.PushFrame(callstack.Frame{PID: kevt.PID, Addr: 0xfffff8072ebc1f6f, Offset: 0x4ef, Symbol: "FltRequestFileInfoOnCreateCompletion", Module: "C:\\WINDOWS\\System32\\drivers\\FLTMGR.SYS"}) kevt.Callstack.PushFrame(callstack.Frame{PID: kevt.PID, Addr: 0xfffff8072eb8961b, Offset: 0x20cb, Symbol: "FltGetStreamContext", Module: "C:\\WINDOWS\\System32\\drivers\\FLTMGR.SYS"}) @@ -416,9 +417,9 @@ func TestThreadFilter(t *testing.T) { {`thread.callstack.callsite_trailing_assembly matches ('*mov r10, rcx|mov eax, 0x*|syscall*')`, true}, {`thread.callstack.is_unbacked`, 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_user_module.name = 'java.dll'`, true}, + {`thread.callstack.final_user_module.path = 'C:\\Program Files\\JetBrains\\GoLand 2021.2.3\\jbr\\bin\\java.dll'`, true}, + {`thread.callstack.final_user_symbol.name = 'Java_java_lang_ProcessImpl_waitForTimeoutInterruptibly'`, 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}, From 0a341562719d881603e4d1415e05a5dd3a75c1a3 Mon Sep 17 00:00:00 2001 From: rabbitstack Date: Fri, 11 Apr 2025 17:50:08 +0200 Subject: [PATCH 2/2] chore(tests): Remove redundant callstack test --- pkg/kevent/kevent_windows_test.go | 44 ------------------------------- 1 file changed, 44 deletions(-) diff --git a/pkg/kevent/kevent_windows_test.go b/pkg/kevent/kevent_windows_test.go index 8c7329cfd..4347b3649 100644 --- a/pkg/kevent/kevent_windows_test.go +++ b/pkg/kevent/kevent_windows_test.go @@ -19,7 +19,6 @@ package kevent import ( - "github.com/rabbitstack/fibratus/pkg/callstack" "github.com/rabbitstack/fibratus/pkg/fs" "github.com/rabbitstack/fibratus/pkg/kevent/kparams" "github.com/rabbitstack/fibratus/pkg/kevent/ktypes" @@ -120,46 +119,3 @@ func TestPartialKey(t *testing.T) { }) } } - -func TestCallstack(t *testing.T) { - e := &Kevent{ - Type: ktypes.CreateProcess, - Tid: 2484, - PID: 859, - CPU: 1, - Seq: 2, - Name: "CreateProcess", - Timestamp: time.Now(), - Category: ktypes.Process, - } - - e.Callstack.Init(9) - assert.Equal(t, 9, cap(e.Callstack)) - - e.Callstack.PushFrame(callstack.Frame{Addr: 0x2638e59e0a5, Offset: 0, Symbol: "?", Module: "unbacked"}) - e.Callstack.PushFrame(callstack.Frame{Addr: 0x7ffb313853b2, Offset: 0x10a, Symbol: "Java_java_lang_ProcessImpl_create", Module: "C:\\Program Files\\JetBrains\\GoLand 2021.2.3\\jbr\\bin\\java.dll"}) - e.Callstack.PushFrame(callstack.Frame{Addr: 0x7ffb3138592e, Offset: 0x3a2, Symbol: "Java_java_lang_ProcessImpl_waitForTimeoutInterruptibly", Module: "C:\\Program Files\\JetBrains\\GoLand 2021.2.3\\jbr\\bin\\java.dll"}) - e.Callstack.PushFrame(callstack.Frame{Addr: 0x7ffb5c1d0396, Offset: 0x61, Symbol: "CreateProcessW", Module: "C:\\WINDOWS\\System32\\KERNELBASE.dll"}) - e.Callstack.PushFrame(callstack.Frame{Addr: 0x7ffb5d8e61f4, Offset: 0x54, Symbol: "CreateProcessW", Module: "C:\\WINDOWS\\System32\\KERNEL32.DLL"}) - e.Callstack.PushFrame(callstack.Frame{Addr: 0x7ffb5c1d0396, Offset: 0x66, Symbol: "CreateProcessW", Module: "C:\\WINDOWS\\System32\\KERNELBASE.dll"}) - e.Callstack.PushFrame(callstack.Frame{Addr: 0xfffff8015662a605, Offset: 0x9125, Symbol: "setjmpex", Module: "C:\\WINDOWS\\system32\\ntoskrnl.exe"}) - e.Callstack.PushFrame(callstack.Frame{Addr: 0xfffff801568e9c33, Offset: 0x2ef3, Symbol: "LpcRequestPort", Module: "C:\\WINDOWS\\system32\\ntoskrnl.exe"}) - e.Callstack.PushFrame(callstack.Frame{Addr: 0xfffff8015690b644, Offset: 0x45b4, Symbol: "ObDeleteCapturedInsertInfo", Module: "C:\\WINDOWS\\system32\\ntoskrnl.exe"}) - - assert.True(t, e.Callstack.ContainsUnbacked()) - 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) -}