Skip to content

fix(callstack): Rework final user frame heuristics #482

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 15 additions & 6 deletions pkg/callstack/callstack.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
72 changes: 69 additions & 3 deletions pkg/callstack/callstack_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,79 @@ 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)
assert.Equal(t, "fffff8015690b644", kframe.Addr.String())
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
}
13 changes: 7 additions & 6 deletions pkg/filter/filter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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"})

Expand Down Expand Up @@ -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},
Expand Down
44 changes: 0 additions & 44 deletions pkg/kevent/kevent_windows_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
}