Skip to content

Commit 1fb7918

Browse files
committed
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.
1 parent a94d08a commit 1fb7918

File tree

3 files changed

+91
-15
lines changed

3 files changed

+91
-15
lines changed

pkg/callstack/callstack.go

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -169,19 +169,28 @@ func (s *Callstack) Depth() int { return len(*s) }
169169
// IsEmpty returns true if the callstack has no frames.
170170
func (s *Callstack) IsEmpty() bool { return s.Depth() == 0 }
171171

172-
// FinalUserFrame returns the final user space frame.
172+
// FinalUserFrame returns the final frame that corresponds
173+
// to the user code execution. That usually translates to
174+
// the last frame before ntdll or kernel32 modules.
173175
func (s *Callstack) FinalUserFrame() *Frame {
174-
var i int
175176
if s.IsEmpty() {
176177
return nil
177178
}
178179

179-
for ; i < s.Depth()-1 && !(*s)[i].Addr.InSystemRange(); i++ {
180+
var n int
181+
for n = s.Depth() - 1; n > 0; n-- {
182+
f := (*s)[n]
183+
if f.Addr.InSystemRange() {
184+
continue
185+
}
186+
mod := filepath.Base(strings.ToLower(f.Module))
187+
if mod != "ntdll.dll" && mod != "kernel32.dll" && mod != "kernelbase.dll" {
188+
break
189+
}
180190
}
181-
i--
182191

183-
if i > 0 && i < s.Depth()-1 {
184-
return &(*s)[i]
192+
if n >= 0 && n < s.Depth()-1 {
193+
return &(*s)[n]
185194
}
186195

187196
return nil

pkg/callstack/callstack_test.go

Lines changed: 69 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,79 @@ func TestCallstack(t *testing.T) {
4747

4848
uframe := callstack.FinalUserFrame()
4949
require.NotNil(t, uframe)
50-
assert.Equal(t, "7ffb5c1d0396", uframe.Addr.String())
51-
assert.Equal(t, "CreateProcessW", uframe.Symbol)
52-
assert.Equal(t, "C:\\WINDOWS\\System32\\KERNELBASE.dll", uframe.Module)
50+
assert.Equal(t, "7ffb3138592e", uframe.Addr.String())
51+
assert.Equal(t, "Java_java_lang_ProcessImpl_waitForTimeoutInterruptibly", uframe.Symbol)
52+
assert.Equal(t, "C:\\Program Files\\JetBrains\\GoLand 2021.2.3\\jbr\\bin\\java.dll", uframe.Module)
5353

5454
kframe := callstack.FinalKernelFrame()
5555
require.NotNil(t, kframe)
5656
assert.Equal(t, "fffff8015690b644", kframe.Addr.String())
5757
assert.Equal(t, "ObDeleteCapturedInsertInfo", kframe.Symbol)
5858
assert.Equal(t, "C:\\WINDOWS\\system32\\ntoskrnl.exe", kframe.Module)
5959
}
60+
61+
func TestCallstackFinalUserFrame(t *testing.T) {
62+
var tests = []struct {
63+
callstack Callstack
64+
expectedMod string
65+
expectedSym string
66+
}{
67+
{callstack: callstackFromFrames(
68+
Frame{Addr: 0xf259de, Module: unbacked, Symbol: "?"},
69+
Frame{Addr: 0x7ffe4fda6e3b, Module: "C:\\Windows\\System32\\KernelBase.dll", Symbol: "SetThreadContext"},
70+
Frame{Addr: 0x7ffe52942b24, Module: "C:\\Windows\\System32\\ntdll.dll", Symbol: "ZwSetContextThread"},
71+
Frame{Addr: 0xfffff807e228c555, Module: "C:\\WINDOWS\\system32\\ntoskrnl.exe", Symbol: "setjmpex"},
72+
Frame{Addr: 0xfffff807e264805c, Module: "C:\\WINDOWS\\system32\\ntoskrnl.exe", Symbol: "ObOpenObjectByPointerWithTag"}),
73+
expectedMod: "unbacked",
74+
expectedSym: "?",
75+
},
76+
{callstack: callstackFromFrames(
77+
Frame{Addr: 0x7ffff0f3bf6c, Module: "C:\\Windows\\System32\\ntdll.dll", Symbol: "RtlUserThreadStart"},
78+
Frame{Addr: 0x7ffff03ee8d7, Module: "C:\\Windows\\System32\\ntdll.dll", Symbol: "BaseThreadInitThunk"},
79+
Frame{Addr: 0x7ffff0ee5f13, Module: "C:\\Windows\\System32\\ntdll.dll", Symbol: "TpCallbackMayRunLong"},
80+
Frame{Addr: 0x7ffff0c78788, Module: "C:\\Windows\\System32\\rpcrt4.dll", Symbol: "RpcGetBufferWithObject"},
81+
Frame{Addr: 0x7ffff0c797e3, Module: "C:\\Windows\\System32\\rpcrt4.dll", Symbol: "RpcImpersonateClient"},
82+
Frame{Addr: 0x7fffee58d16a, Module: "C:\\Windows\\System32\\KernelBase.dll", Symbol: "CreateProcessInternalW"},
83+
Frame{Addr: 0x7ffff0fe1204, Module: "C:\\Windows\\System32\\ntdll.dll", Symbol: "ZwCreateUserProcess"}),
84+
expectedMod: "C:\\Windows\\System32\\rpcrt4.dll",
85+
expectedSym: "RpcImpersonateClient",
86+
},
87+
{callstack: callstackFromFrames(
88+
Frame{Addr: 0x7fffa7e3bf6c, Module: "C:\\Windows\\System32\\ntdll.dll", Symbol: "RtlUserThreadStart"},
89+
Frame{Addr: 0x7fffa60de8d7, Module: "C:\\Windows\\System32\\ntdll.dll", Symbol: "BaseThreadInitThunk"},
90+
Frame{Addr: 0x7ff6163cfc68, Module: "C:\\Program Files\\Mozilla Firefox\\firefox.exe", Symbol: "TargetCreateThread"},
91+
Frame{Addr: 0x7fffee58d16a, Module: "C:\\Windows\\System32\\ntdll.dll", Symbol: "ZwMapViewOfSection"},
92+
Frame{Addr: 0xfffff8028deeed1d, Module: "C:\\WINDOWS\\system32\\ntoskrnl.exe", Symbol: "NtMapViewOfSection"}),
93+
expectedMod: "C:\\Program Files\\Mozilla Firefox\\firefox.exe",
94+
expectedSym: "TargetCreateThread",
95+
},
96+
{callstack: callstackFromFrames(
97+
Frame{Addr: 0x7fffa7e3bf6c, Module: "C:\\Windows\\System32\\ntdll.dll", Symbol: "RtlUserThreadStart"},
98+
Frame{Addr: 0x7fffa60de8d7, Module: "C:\\Windows\\System32\\ntdll.dll", Symbol: "BaseThreadInitThunk"},
99+
Frame{Addr: 0x7ffff0c78788, Module: "C:\\Windows\\System32\\rpcrt4.dll", Symbol: "NdrServerCallNdr64"},
100+
Frame{Addr: 0x7ffff0c574ed, Module: "C:\\Windows\\System32\\rpcrt4.dll", Symbol: "NdrStubCall2"},
101+
Frame{Addr: 0x7ffff03fb090, Module: "C:\\Windows\\System32\\kernel32.dll", Symbol: "CreateProcessInternalW"},
102+
Frame{Addr: 0x7fffee58a923, Module: "C:\\Windows\\System32\\kernel32.dll", Symbol: "CreateProcessAsUserW"},
103+
Frame{Addr: 0x7ffff0fe1204, Module: "C:\\Windows\\System32\\ntdll.dll", Symbol: "ZwCreateUserProcess"}),
104+
expectedMod: "C:\\Windows\\System32\\rpcrt4.dll",
105+
expectedSym: "NdrStubCall2",
106+
},
107+
}
108+
109+
for _, tt := range tests {
110+
t.Run(tt.expectedMod+"!"+tt.expectedSym, func(t *testing.T) {
111+
f := tt.callstack.FinalUserFrame()
112+
require.NotNil(t, f)
113+
assert.Equal(t, tt.expectedMod, f.Module)
114+
assert.Equal(t, tt.expectedSym, f.Symbol)
115+
})
116+
}
117+
}
118+
119+
func callstackFromFrames(frames ...Frame) Callstack {
120+
var c Callstack
121+
for _, frame := range frames {
122+
c.PushFrame(frame)
123+
}
124+
return c
125+
}

pkg/filter/filter_test.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -355,13 +355,14 @@ func TestThreadFilter(t *testing.T) {
355355
Modules: []pstypes.Module{
356356
{Name: "C:\\Windows\\System32\\kernel32.dll", Size: 2312354, Checksum: 23123343, BaseAddress: va.Address(0x7ffb5c1d0396), DefaultBaseAddress: va.Address(0x7ffb5c1d0396)},
357357
{Name: "C:\\Windows\\System32\\user32.dll", Size: 32212354, Checksum: 33123343, BaseAddress: va.Address(0x7ffb313953b2), DefaultBaseAddress: va.Address(0x7ffb313953b2)},
358+
{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)},
358359
},
359360
},
360361
}
361362

362363
// append the module signature
363364
cert := &sys.Cert{Subject: "US, Washington, Redmond, Microsoft Corporation, Microsoft Windows", Issuer: "US, Washington, Redmond, Microsoft Corporation, Microsoft Windows Production PCA 2011"}
364-
signature.GetSignatures().PutSignature(0x7ffb5c1d0396, &signature.Signature{Filename: "C:\\Windows\\System32\\kernel32.dll", Level: 4, Type: 1, Cert: cert})
365+
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})
365366

366367
// simulate unbacked RWX frame
367368
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) {
382383
kevt.Callstack.PushFrame(callstack.Frame{PID: kevt.PID, Addr: 0x2638e59e0a5, Offset: 0, Symbol: "?", Module: "unbacked"})
383384
kevt.Callstack.PushFrame(callstack.Frame{PID: kevt.PID, Addr: va.Address(base), Offset: 0, Symbol: "?", Module: "unbacked"})
384385
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"})
385-
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"})
386+
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"})
386387
kevt.Callstack.PushFrame(callstack.Frame{PID: kevt.PID, Addr: 0x7ffb5d8e61f4, Offset: 0x54, Symbol: "CreateProcessW", Module: "C:\\WINDOWS\\System32\\KERNEL32.DLL"})
387-
kevt.Callstack.PushFrame(callstack.Frame{PID: kevt.PID, Addr: 0x7ffb5c1d0396, ModuleAddress: 0x7ffb5c1d0396, Offset: 0x66, Symbol: "CreateProcessW", Module: "C:\\WINDOWS\\System32\\KERNELBASE.dll"})
388+
kevt.Callstack.PushFrame(callstack.Frame{PID: kevt.PID, Addr: 0x7ffb5c1d0396, Offset: 0x66, Symbol: "CreateProcessW", Module: "C:\\WINDOWS\\System32\\KERNELBASE.dll"})
388389
kevt.Callstack.PushFrame(callstack.Frame{PID: kevt.PID, Addr: 0xfffff8072ebc1f6f, Offset: 0x4ef, Symbol: "FltRequestFileInfoOnCreateCompletion", Module: "C:\\WINDOWS\\System32\\drivers\\FLTMGR.SYS"})
389390
kevt.Callstack.PushFrame(callstack.Frame{PID: kevt.PID, Addr: 0xfffff8072eb8961b, Offset: 0x20cb, Symbol: "FltGetStreamContext", Module: "C:\\WINDOWS\\System32\\drivers\\FLTMGR.SYS"})
390391

@@ -416,9 +417,9 @@ func TestThreadFilter(t *testing.T) {
416417
{`thread.callstack.callsite_trailing_assembly matches ('*mov r10, rcx|mov eax, 0x*|syscall*')`, true},
417418
{`thread.callstack.is_unbacked`, true},
418419
{`thread.callstack.addresses intersects ('7ffb5d8e61f4', 'fffff8072eb8961b')`, true},
419-
{`thread.callstack.final_user_module.name = 'KERNELBASE.dll'`, true},
420-
{`thread.callstack.final_user_module.path = 'C:\\WINDOWS\\System32\\KERNELBASE.dll'`, true},
421-
{`thread.callstack.final_user_symbol.name = 'CreateProcessW'`, true},
420+
{`thread.callstack.final_user_module.name = 'java.dll'`, true},
421+
{`thread.callstack.final_user_module.path = 'C:\\Program Files\\JetBrains\\GoLand 2021.2.3\\jbr\\bin\\java.dll'`, true},
422+
{`thread.callstack.final_user_symbol.name = 'Java_java_lang_ProcessImpl_waitForTimeoutInterruptibly'`, true},
422423
{`thread.callstack.final_kernel_module.name = 'FLTMGR.SYS'`, true},
423424
{`thread.callstack.final_kernel_module.path = 'C:\\WINDOWS\\System32\\drivers\\FLTMGR.SYS'`, true},
424425
{`thread.callstack.final_kernel_symbol.name = 'FltGetStreamContext'`, true},

0 commit comments

Comments
 (0)