Bitdefender Hypervisor Memory Introspection
lixstack.c
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2020 Bitdefender
3  * SPDX-License-Identifier: Apache-2.0
4  */
5 #include "lixstack.h"
6 #include "drivers.h"
7 #include "guests.h"
8 #include "lixmm.h"
9 #include "lixfiles.h"
10 #include "lixksym.h"
11 
12 
15  _In_opt_ QWORD Cr3,
16  _In_ QWORD Stack,
17  _In_ QWORD Rip,
18  _In_ DWORD MaxNumberOfTraces,
19  _In_ QWORD Flags,
20  _Inout_ STACK_TRACE *StackTrace
21  )
43 {
44  INTSTATUS status;
45  BOOLEAN remap;
46  QWORD stackFrame;
47  PBYTE pOriginalStackMap, pStack;
48  QWORD currentRip;
49  QWORD interationsTry;
50  QWORD cr3;
51  DWORD searchLimit;
52 
54 
55  if (!IS_KERNEL_POINTER_WIN(TRUE, Stack) || Stack % 8 != 0)
56  {
58  }
59 
60  if (NULL == StackTrace || NULL == StackTrace->Traces)
61  {
63  }
64 
65  memzero(StackTrace->Traces, MaxNumberOfTraces * sizeof(STACK_ELEMENT));
66 
67  StackTrace->NumberOfTraces = 0;
68  pStack = pOriginalStackMap = NULL;
69  StackTrace->StartRip = Rip;
70  stackFrame = Stack;
71  currentRip = Rip;
72  interationsTry = MaxNumberOfTraces * 2ull;
73  remap = TRUE;
74  status = INT_STATUS_NOT_FOUND;
75  cr3 = Cr3 != 0 ? Cr3 : gGuest.Mm.SystemCr3;
76 
77  while (StackTrace->NumberOfTraces < MaxNumberOfTraces && interationsTry-- > 0)
78  {
79  QWORD nextStackFrame, currentStackFrame, retAddress;
80  KERNEL_DRIVER *pRetMod = NULL;
81 
82  if (remap)
83  {
84  status = IntVirtMemMap(stackFrame & PAGE_MASK, PAGE_SIZE, cr3, 0, &pOriginalStackMap);
85  if (!INT_SUCCESS(status))
86  {
87  ERROR("[ERROR] Failed mapping VA 0x%016llx to host: 0x%08x\n", stackFrame, status);
88  goto leave;
89  }
90 
91  pStack = pOriginalStackMap + (stackFrame & PAGE_OFFSET);
92  remap = FALSE;
93  }
94 
95  //
96  // If we are at offset 0xff8 (the return address is at 8),
97  // then we don't need to map this page but the next one
98  //
99  if (((stackFrame + 8) & PAGE_MASK) != (stackFrame & PAGE_MASK))
100  {
101  status = IntKernVirtMemFetchQword(stackFrame + 8, &retAddress);
102  if (!INT_SUCCESS(status))
103  {
104  ERROR("[ERROR] Failed getting the return address for stack frame 0x%016llx: 0x%08x\n",
105  stackFrame, status);
106  goto leave;
107  }
108  }
109  else
110  {
111  retAddress = *(QWORD *)(pStack + 8);
112  }
113 
114  // go to the next if this value is an address on the stack or not a kernel pointer
115  if (!IS_KERNEL_POINTER_LIX(retAddress) || retAddress > 0xfffffffffffff000)
116  {
117  goto _next_stack_frame;
118  }
119 
120  if (0 == (Flags & STACK_FLG_FAST_GET))
121  {
122  pRetMod = IntDriverFindByAddress(retAddress);
123  }
124 
125  if (StackTrace->NumberOfTraces > 0)
126  {
127  if (StackTrace->Traces[StackTrace->NumberOfTraces - 1].ReturnAddress == retAddress)
128  {
129  // We remained in the same stack trace (will loop infinitely...)
130  break;
131  }
132  }
133 
134  StackTrace->Traces[StackTrace->NumberOfTraces].ReturnAddress = retAddress;
135  StackTrace->Traces[StackTrace->NumberOfTraces].ReturnModule = pRetMod;
136  StackTrace->Traces[StackTrace->NumberOfTraces].RetAddrPointer = stackFrame + gGuest.WordSize;
137  StackTrace->Traces[StackTrace->NumberOfTraces].CurrentRip = currentRip;
138  StackTrace->NumberOfTraces++;
139 
140  currentRip = retAddress;
141 
142 _next_stack_frame:
143  // Get the previous stack frame @ rbp
144  nextStackFrame = *(QWORD *)pStack;
145 
146  currentStackFrame = stackFrame;
147 
148  //
149  // Stack limit is somewhere around 4KB. If this fails then we most probably have a function that doesn't
150  // respect the RBP as frame-pointer convention so do a little back-search for a pointer on the stack (where
151  // the original stack value was saved). Worst-case we get to the previous stack-frame and will get out after
152  // this.
153  //
154  searchLimit = 256;
155  while ((nextStackFrame - currentStackFrame >= 4 * PAGE_SIZE) ||
156  !IS_KERNEL_POINTER_WIN(TRUE, nextStackFrame) ||
157  (nextStackFrame % 8 != 0))
158  {
159  pStack -= 8;
160  stackFrame -= 8;
161 
162  // If we got in the previous page, do the remapping
163  if (((size_t)pStack & PAGE_MASK) != ((size_t)pOriginalStackMap & PAGE_MASK))
164  {
165  searchLimit--;
166  if (0 == searchLimit)
167  {
168  goto leave;
169  }
170 
171  IntVirtMemUnmap(&pOriginalStackMap);
172 
173  status = IntVirtMemMap(stackFrame & PAGE_MASK, PAGE_SIZE, cr3, 0, &pOriginalStackMap);
174  if (!INT_SUCCESS(status))
175  {
176  if (StackTrace->NumberOfTraces > 0)
177  {
178  status = INT_STATUS_SUCCESS;
179  }
180  else
181  {
182  TRACE("[WARNING] Got to the beginning of stack and no frame was found. Status = 0x%08x\n",
183  status);
184  }
185 
186  goto leave;
187  }
188 
189  pStack = pOriginalStackMap + (stackFrame & PAGE_OFFSET);
190  }
191 
192  nextStackFrame = *(QWORD *)pStack;
193  }
194 
195  if (nextStackFrame == currentStackFrame || nextStackFrame % 8 != 0)
196  {
197  // if it's the same stack frame or an unaligned value we have nothing else to do
198  goto leave;
199  }
200 
201  if ((nextStackFrame & PAGE_MASK) != (stackFrame & PAGE_MASK))
202  {
203  remap = TRUE;
204  IntVirtMemUnmap(&pOriginalStackMap);
205  }
206 
207  // save the next pointer, and search again from there
208  stackFrame = nextStackFrame;
209  pStack = pOriginalStackMap + (stackFrame & PAGE_OFFSET);
210  }
211 
212  status = INT_STATUS_SUCCESS; // if we get here we are successful
213 
214 leave:
215  if (NULL != pOriginalStackMap)
216  {
217  IntVirtMemUnmap(&pOriginalStackMap);
218  }
219 
220  return status;
221 }
222 
223 
224 INTSTATUS
226  _In_opt_ QWORD Cr3,
227  _In_ PIG_ARCH_REGS Registers,
228  _In_ DWORD MaxNumberOfTraces,
229  _In_ QWORD Flags,
230  _Inout_ STACK_TRACE *StackTrace
231  )
248 {
249  QWORD stackFrame;
250 
251  stackFrame = Registers->Rbp;
252 
253  if (0 == Cr3)
254  {
255  Cr3 = gGuest.Mm.SystemCr3;
256  }
257 
258  //
259  // If RBP doesn't point to a valid stack, then we can only get one return address.
260  // Let's hope it's enough!
261  //
262  if ((stackFrame > Registers->Rsp &&
263  stackFrame - Registers->Rsp > 32 * PAGE_SIZE) ||
264  (stackFrame < Registers->Rsp &&
265  Registers->Rsp - stackFrame > 32 * PAGE_SIZE))
266  {
267  INTSTATUS status;
268 
269  memzero(StackTrace->Traces, MaxNumberOfTraces * sizeof(STACK_ELEMENT));
270 
271  stackFrame = Registers->Rsp;
272 
273  StackTrace->NumberOfTraces = 1;
274 
275  status = IntKernVirtMemFetchQword(stackFrame, &StackTrace->Traces[0].ReturnAddress);
276  if (!INT_SUCCESS(status))
277  {
278  ERROR("[ERROR] RSP 0x%016llx is not present: %08x\n", stackFrame, status);
279  return status;
280  }
281 
282  StackTrace->Traces[0].ReturnModule = IntDriverFindByAddress(StackTrace->Traces[0].ReturnAddress);
283 
284  return INT_STATUS_SUCCESS;
285  }
286 
287  return IntLixStackTraceGet(Cr3, stackFrame, Registers->Rip, MaxNumberOfTraces, Flags, StackTrace);
288 }
289 
290 
291 void
293  _In_ DWORD MaxTraces
294  )
305 {
306  INTSTATUS status;
307  CHAR funcName[MAX_FUNC_NAME];
308  DWORD trace = 0;
309  QWORD rsp = gVcpu->Regs.Rsp;
310 
311  do
312  {
313  DWORD size = PAGE_REMAINING(rsp);
314  QWORD *pStack;
315 
316  status = IntVirtMemMap(rsp, size, gGuest.Mm.SystemCr3, 0, &pStack);
317  if (!INT_SUCCESS(status))
318  {
319  return;
320  }
321 
322  for (DWORD i = 0; i < size / 8; i++)
323  {
324  KERNEL_DRIVER *pMod;
325 
326  if (!IS_KERNEL_POINTER_LIX(pStack[i]))
327  {
328  continue;
329  }
330 
331  pMod = IntDriverFindByAddress(pStack[i]);
332  if (NULL == pMod)
333  {
334  continue;
335  }
336 
337  // if it is a kernel function, we can dump its name
338  if (pMod == gGuest.KernelDriver)
339  {
341  {
342  continue;
343  }
344 
345  status = IntKsymFindByAddress(pStack[i], sizeof(funcName), funcName, NULL, NULL);
346  if (INT_SUCCESS(status))
347  {
348  LOG("[STACK-TRACE] <0x%llx> %s\n", pStack[i], funcName);
349  }
350  else
351  {
352  if ((pMod->Lix.Initialized ||
353  !IN_RANGE_LEN(pStack[i], pMod->Lix.InitLayout.Base, pMod->Lix.InitLayout.TextSize)) &&
354  !IN_RANGE_LEN(pStack[i], pMod->Lix.CoreLayout.Base, pMod->Lix.CoreLayout.TextSize))
355  {
356  continue;
357  }
358 
359  LOG("[STACK-TRACE] <0x%llx> (symbol not found)\n", pStack[i]);
360  }
361  }
362  else
363  {
364  LOG("[STACK-TRACE] <0x%llx> (symbol not found) in mod %s\n", pStack[i], (char *)pMod->Name);
365  }
366 
367  if (++trace >= MaxTraces)
368  {
369  break;
370  }
371  }
372 
373  IntVirtMemUnmap(&pStack);
374 
375  rsp += size;
376  } while (trace < MaxTraces);
377 }
378 
379 
380 void
382  _In_ LIX_TASK_OBJECT *Task
383  )
393 {
394  INTSTATUS status;
395  LIX_TRAP_FRAME trapFrame;
396  LIX_VMA crtVma;
397  LIX_VMA stackVma;
398  CHAR *pFileName = NULL;
399 
400  status = IntLixTaskGetTrapFrame(Task, &trapFrame);
401  if (!INT_SUCCESS(status))
402  {
403  ERROR("[ERROR] IntLixTaskGetUmTrapFrame failed: %08x\n", status);
404  return;
405  }
406 
407  status = IntLixMmFetchVma(Task, trapFrame.Rip, &crtVma);
408  if (!INT_SUCCESS(status))
409  {
410  ERROR("[ERROR] IntLixMmFetchVad failed for RIP %llx: %08x\n", trapFrame.Rip, status);
411  return;
412  }
413 
414  status = IntLixGetFileName(crtVma.File, &pFileName, NULL, NULL);
415  if (!INT_SUCCESS(status))
416  {
417  ERROR("[ERROR] IntLixGetFileName failed with status: %x\n", status);
418  }
419 
420  status = IntLixMmFetchVma(Task, trapFrame.Rsp, &stackVma);
421  if (!INT_SUCCESS(status))
422  {
423  ERROR("[ERROR] IntLixMmFetchVad failed for RIP %llx: %08x\n", trapFrame.Rsp, status);
424  return;
425  }
426 
427  TRACE("[UM STACK-TRACE] Task : '%s' (%d/%d) -> '%s' RIP: 0x%llx",
428  Task->ProcName, Task->Pid, Task->Tgid, pFileName != NULL ? pFileName : "<no file>", trapFrame.Rip);
429  TRACE("[UM STACK-TRACE] Stack [0x%llx - 0x%llx] - Stack pointer: 0x%llx\n", stackVma.Start, stackVma.End,
430  trapFrame.Rsp);
431 
432  if (pFileName != NULL)
433  {
434  HpFreeAndNullWithTag(&pFileName, IC_TAG_NAME);
435  }
436 
437  IntDisasmGva(trapFrame.Rip - 0x20, 0x40);
438 
439  IntDumpLixUmTrapFrame(&trapFrame);
440 
441  for (QWORD crtRsp = trapFrame.Rsp; crtRsp < stackVma.End; crtRsp += 8)
442  {
443  LIX_VMA tmpVma;
444  CHAR *pTmpFile = NULL;
445  QWORD value = 0;
446 
447  status = IntVirtMemFetchQword(crtRsp, Task->Cr3, &value);
448  if (!INT_SUCCESS(status))
449  {
450  continue;
451  }
452 
453  status = IntLixMmFetchVma(Task, value, &tmpVma);
454  if (!INT_SUCCESS(status))
455  {
456  continue;
457  }
458 
459  if (tmpVma.Flags & VM_EXEC)
460  {
461  IntLixGetFileName(tmpVma.File, &pTmpFile, NULL, NULL);
462 
463  LOG("[UM STACK-TRACE] Return = 0x%016llx : Stack = 0x%016llx, File '%s'",
464  value, crtRsp, pTmpFile == NULL ? "<invalid file>" : pTmpFile);
465  }
466 
467  if (pTmpFile != NULL)
468  {
469  HpFreeAndNullWithTag(&pTmpFile, IC_TAG_NAME);
470  }
471  }
472 }
473 
474 
#define _In_opt_
Definition: intro_sal.h:16
_Bool BOOLEAN
Definition: intro_types.h:58
#define STACK_FLG_FAST_GET
Flag that tells to only get return addresses (no drivers).
Definition: guest_stack.h:22
INTSTATUS IntVirtMemUnmap(void **HostPtr)
Unmaps a memory range previously mapped with IntVirtMemMap.
Definition: introcore.c:2234
IG_ARCH_REGS Regs
The current state of the guest registers.
Definition: guests.h:95
#define _In_
Definition: intro_sal.h:21
QWORD SystemCr3
The Cr3 used to map the kernel.
Definition: guests.h:211
#define INT_STATUS_SUCCESS
Definition: introstatus.h:54
#define PAGE_REMAINING(addr)
Definition: pgtable.h:163
#define MAX_FUNC_NAME
The maximum number of characters allowed for a function name.
Definition: lixstack.h:10
void IntDumpLixUmTrapFrame(LIX_TRAP_FRAME *TrapFrame)
This function dumps a Linux UM trap frame.
Definition: dumper.c:741
INTSTATUS IntKsymFindByAddress(QWORD Gva, DWORD Length, char *SymName, QWORD *SymStart, QWORD *SymEnd)
Finds the symbol which is located at the given address.
Definition: lixksym.c:1283
#define INT_SUCCESS(Status)
Definition: introstatus.h:42
INTSTATUS IntVirtMemFetchQword(QWORD GuestVirtualAddress, QWORD Cr3, QWORD *Data)
Reads 8 bytes from the guest memory.
Definition: introcore.c:866
#define PAGE_OFFSET
Definition: pgtable.h:32
Structure that describes a stack trace element.
Definition: guest_stack.h:25
#define ERROR(fmt,...)
Definition: glue.h:62
void IntLixDumpStacktrace(DWORD MaxTraces)
Logs a Kernel stack backtrace.
Definition: lixstack.c:292
int INTSTATUS
The status data type.
Definition: introstatus.h:24
QWORD CodeEnd
The guest virtual address where the code ends.
Definition: lixguest.h:498
QWORD CodeStart
The guest virtual address where the code starts.
Definition: lixguest.h:497
INTSTATUS IntLixStackTraceGetReg(QWORD Cr3, PIG_ARCH_REGS Registers, DWORD MaxNumberOfTraces, QWORD Flags, STACK_TRACE *StackTrace)
Retrieves a Kernel stack backtrace based on the register values.
Definition: lixstack.c:225
LIX_MODULE_LAYOUT CoreLayout
The layout of the core section.
Definition: lixmodule.h:40
LIX_MODULE_LAYOUT InitLayout
The layout of the init section.
Definition: lixmodule.h:39
#define INT_STATUS_NOT_FOUND
Definition: introstatus.h:284
QWORD Start
Start of the memory described by the VMA.
Definition: lixmm.h:19
#define LOG(fmt,...)
Definition: glue.h:61
Describes a kernel driver.
Definition: drivers.h:30
void IntLixStackDumpUmStackTrace(LIX_TASK_OBJECT *Task)
Logs the libraries found in the user mode stacktrace.
Definition: lixstack.c:381
INTSTATUS IntLixStackTraceGet(QWORD Cr3, QWORD Stack, QWORD Rip, DWORD MaxNumberOfTraces, QWORD Flags, STACK_TRACE *StackTrace)
Retrieves a Kernel stack trace.
Definition: lixstack.c:14
#define _Inout_
Definition: intro_sal.h:20
INTSTATUS IntLixMmFetchVma(LIX_TASK_OBJECT *Task, QWORD Address, LIX_VMA *Vma)
Retrieve information about a VMA structure containing a user mode address.
Definition: lixmm.c:581
#define IS_KERNEL_POINTER_LIX(p)
Definition: lixguest.h:11
INTSTATUS IntKernVirtMemFetchQword(QWORD GuestVirtualAddress, QWORD *Data)
Reads 8 bytes from the guest kernel memory.
Definition: introcore.c:811
struct _LINUX_GUEST::@126 Layout
uint8_t * PBYTE
Definition: intro_types.h:47
#define IN_RANGE(x, start, end)
Definition: introdefs.h:167
#define memzero(a, s)
Definition: introcrt.h:35
QWORD End
End of the memory described by the VMA.
Definition: lixmm.h:20
unsigned long long QWORD
Definition: intro_types.h:53
#define IN_RANGE_LEN(x, start, len)
Definition: introdefs.h:175
#define TRUE
Definition: intro_types.h:30
#define INT_STATUS_INVALID_PARAMETER_4
Definition: introstatus.h:71
#define IS_KERNEL_POINTER_WIN(is64, p)
Checks if a guest virtual address resides inside the Windows kernel address space.
Definition: wddefs.h:76
#define HpFreeAndNullWithTag(Add, Tag)
Definition: glue.h:517
#define TRACE(fmt,...)
Definition: glue.h:58
#define VM_EXEC
Definition: lixddefs.h:43
void * Name
The name of the driver.
Definition: drivers.h:54
BYTE WordSize
Guest word size. Will be 4 for 32-bit guests and 8 for 64-bit guests.
Definition: guests.h:367
LIX_KERNEL_MODULE Lix
Valid only for Linux guests.
Definition: drivers.h:71
#define PAGE_SIZE
Definition: common.h:70
#define UNREFERENCED_PARAMETER(P)
Definition: introdefs.h:29
uint32_t DWORD
Definition: intro_types.h:49
QWORD Flags
Flags for the VMA.
Definition: lixmm.h:25
INTSTATUS IntLixTaskGetTrapFrame(const LIX_TASK_OBJECT *Task, LIX_TRAP_FRAME *TrapFrame)
Retrieves the trap frame for a Linux task.
Definition: lixprocess.c:1098
#define IC_TAG_NAME
Object name.
Definition: memtags.h:56
INTSTATUS IntLixGetFileName(QWORD FileStruct, char **FileName, DWORD *NameLength, QWORD *DentryGva)
Gets the file-name that corresponds to the provided FileStruct (guest virtual address).
Definition: lixfiles.c:565
__must_check INTSTATUS IntVirtMemMap(QWORD Gva, DWORD Length, QWORD Cr3, DWORD Flags, void **HostPtr)
Maps a guest virtual memory range inside Introcore virtual address space.
Definition: introcore.c:2134
MM Mm
Guest memory information, such as paging mode, system Cr3 value, etc.
Definition: guests.h:374
GUEST_STATE gGuest
The current guest state.
Definition: guests.c:50
Definition: lixmm.h:14
KERNEL_DRIVER * IntDriverFindByAddress(QWORD Gva)
Returns the driver in which Gva resides.
Definition: drivers.c:164
KERNEL_DRIVER * KernelDriver
Points to the driver object that describes the kernel image.
Definition: guests.h:385
VCPU_STATE * gVcpu
The state of the current VCPU.
Definition: guests.c:59
BOOLEAN Initialized
This means that the init section is discarded.
Definition: lixmodule.h:45
QWORD Base
The base GVA of the section.
Definition: lixmodule.h:19
Holds register state.
Definition: glueiface.h:30
void IntDisasmGva(QWORD Gva, DWORD Length)
This function disassembles a code buffer (given its GVA) and then dumps the instructions (textual dis...
Definition: dumper.c:432
Structure that describes a stack trace.
Definition: guest_stack.h:42
char CHAR
Definition: intro_types.h:56
#define PAGE_MASK
Definition: pgtable.h:35
#define INT_STATUS_INVALID_PARAMETER_2
Definition: introstatus.h:65
LINUX_GUEST * gLixGuest
Global variable holding the state of a Linux guest.
Definition: lixguest.c:30
QWORD File
The Gva of the file this VMA maps to. Can be 0 which means this VMA is not a memory mapped file...
Definition: lixmm.h:23
#define FALSE
Definition: intro_types.h:34
DWORD TextSize
The size of the .text (code usually).
Definition: lixmodule.h:21