Bitdefender Hypervisor Memory Introspection
thread_safeness.c
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2020 Bitdefender
3  * SPDX-License-Identifier: Apache-2.0
4  */
5 #include "thread_safeness.h"
6 #include "guests.h"
7 #include "introcpu.h"
8 #include "lixprocess.h"
9 #include "memtables.h"
10 #include "ptfilter.h"
11 #include "utils.h"
12 #include "vecore.h"
13 #include "winprocesshp.h"
14 #include "winthread.h"
15 #include "swapgs.h"
16 
17 
18 #define KSTACK_PAGE_COUNT_X86 8
19 #define KSTACK_PAGE_COUNT_X64 16
20 
22 
23 
24 static __forceinline DWORD
26  _In_opt_ QWORD Rsp
27  )
28 {
30  {
32  }
33 
34  QWORD stackBase = Rsp & (~((QWORD)LIX_FIELD(Info, ThreadSize) - 1));
35 
36  return MIN((DWORD)(LIX_FIELD(Info, ThreadSize)), (DWORD)(LIX_FIELD(Info, ThreadSize) - (Rsp - stackBase)));
37 }
38 
39 
40 static BOOLEAN
42  _In_ QWORD StackFrameStart,
43  _In_opt_ QWORD StackFrameEnd,
44  _In_ QWORD Options,
45  _In_ QWORD ProcessGva
46  )
59 {
60  INTSTATUS status;
61  QWORD stackPtr = StackFrameStart;
62  BYTE *pStack = NULL;
63 
64  if (StackFrameStart % gGuest.WordSize != 0)
65  {
66  return FALSE;
67  }
68 
69  if ((gGuest.OSType == introGuestWindows && !IS_KERNEL_POINTER_WIN(gGuest.Guest64, StackFrameStart)) ||
70  (gGuest.OSType == introGuestLinux && !IS_KERNEL_POINTER_LIX(StackFrameStart)))
71  {
72  return FALSE;
73  }
74 
75  if (StackFrameEnd == 0)
76  {
77  StackFrameEnd = StackFrameStart + IntThrGetStackSize(StackFrameStart);
78  }
79 
80  if (StackFrameStart > StackFrameEnd)
81  {
82  WARNING("[WARNING] Found a start stack pointer value (0x%llx) greater than the end value (0x%llx).",
83  StackFrameStart, StackFrameEnd);
84  return FALSE;
85  }
86 
88  {
89  for (DWORD index = 0; index < gGuest.CpuCount; index++)
90  {
91  if (ProcessGva == gVcpu->LixProcessGva)
92  {
93  TRACE("[SAFENESS] Ignore running task 0x%llx ... \n", gVcpu->LixProcessGva);
94  return FALSE;
95  }
96  }
97  }
98 
99  while (StackFrameStart < StackFrameEnd)
100  {
101  DWORD toCheck = (DWORD)(MIN(PAGE_REMAINING(stackPtr), StackFrameEnd - StackFrameStart));
102 
103  // For the first page, stackPtr might not be page aligned, so align it before mapping it
104  status = IntVirtMemMap(stackPtr, toCheck, gGuest.Mm.SystemCr3, 0, &pStack);
105  if (!INT_SUCCESS(status))
106  {
108  {
109  WARNING("[WARNING] Failed to map the stack page %llx : %08x\n",
110  stackPtr & PAGE_MASK, status);
111  }
112 
113  break;
114  }
115 
116  for (DWORD i = 0; i < toCheck; i += gGuest.WordSize)
117  {
118  QWORD stackValue;
119  DETOUR_TAG detTag;
120  QWORD tableGva, gadget;
121 
122  if (gGuest.Guest64)
123  {
124  stackValue = *(QWORD *)(pStack + i);
125  }
126  else
127  {
128  stackValue = *(DWORD *)(pStack + i);
129  }
130 
131  if (!!(Options & THS_CHECK_DETOURS) && IntDetIsPtrInHandler(stackValue, ptrStackValue, &detTag))
132  {
133  WARNING("[SAFENESS] Stack value @ %016llx (= %016llx) points inside detour %d\n",
134  stackPtr + i, stackValue, detTag);
135  IntVirtMemUnmap(&pStack);
136  return TRUE;
137  }
138 
139  if (!!(Options & THS_CHECK_MEMTABLES) && IntMtblIsPtrInReloc(stackValue, ptrStackValue, &tableGva))
140  {
141  WARNING("[SAFENESS] Stack value @ %016llx (= %016llx) points inside mem table for %016llx\n",
142  stackPtr + i, stackValue, tableGva);
143  IntVirtMemUnmap(&pStack);
144  return TRUE;
145  }
146 
147  if (!!(Options & THS_CHECK_TRAMPOLINE) && IntAgentIsPtrInTrampoline(stackValue, ptrStackValue))
148  {
149  WARNING("[SAFENESS] Stack value @ %016llx (= %016llx) points inside agent trampoline\n",
150  stackPtr + i, stackValue);
151  IntVirtMemUnmap(&pStack);
152  return TRUE;
153  }
154 
155  if (!!(Options & THS_CHECK_PTFILTER) && IntPtiIsPtrInAgent(stackValue, ptrStackValue))
156  {
157  WARNING("[SAFENESS] Stack value @ %016llx (= %016llx) points inside PT Filter\n",
158  stackPtr + i, stackValue);
159  IntVirtMemUnmap(&pStack);
160  return TRUE;
161  }
162 
163  if (!!(Options & THS_CHECK_VEFILTER) && IntVeIsPtrInAgent(stackValue, ptrStackValue))
164  {
165  WARNING("[SAFENESS] Stack value @ %016llx (= %016llx) points inside the #VE Agent\n",
166  stackPtr + i, stackValue);
167  IntVirtMemUnmap(&pStack);
168  return TRUE;
169  }
170 
171  if (!!(Options & THS_CHECK_SWAPGS) && IntSwapgsIsPtrInHandler(stackValue, ptrStackValue, &gadget))
172  {
173  WARNING("[SAFENESS] Stack value @ %016llx (= %016llx) points inside a SWAPGS gadget at 0x%016llx\n",
174  stackPtr + i, stackValue, gadget);
175  IntVirtMemUnmap(&pStack);
176  return TRUE;
177  }
178  }
179 
180  IntVirtMemUnmap(&pStack);
181  stackPtr += toCheck; // skip all that we checked
182  StackFrameStart += toCheck;
183  }
184 
185  return FALSE;
186 }
187 
188 
189 static BOOLEAN
191  _In_ const IG_ARCH_REGS *Registers,
192  _In_ QWORD Options
193  )
203 {
204  DETOUR_TAG detTag;
205  QWORD tableGva, gadget;
206 
207  if (!!(Options & THS_CHECK_DETOURS) && IntDetIsPtrInHandler(Registers->Rip, ptrLiveRip, &detTag))
208  {
209  WARNING("[SAFENESS] Live RIP %016llx points inside detour %d\n", Registers->Rip, detTag);
210  return TRUE;
211  }
212 
213  if (!!(Options & THS_CHECK_MEMTABLES) && IntMtblIsPtrInReloc(Registers->Rip, ptrLiveRip, &tableGva))
214  {
215  WARNING("[SAFENESS] Live RIP %016llx points inside mem table for %016llx\n", Registers->Rip, tableGva);
216  return TRUE;
217  }
218 
219  if (!!(Options & THS_CHECK_TRAMPOLINE) && IntAgentIsPtrInTrampoline(Registers->Rip, ptrLiveRip))
220  {
221  WARNING("[SAFENESS] Live RIP %016llx points inside agent trampoline\n", Registers->Rip);
222  return TRUE;
223  }
224 
225  if (!!(Options & THS_CHECK_PTFILTER) && IntPtiIsPtrInAgent(Registers->Rip, ptrLiveRip))
226  {
227  WARNING("[SAFENESS] Live RIP %016llx points inside PT Filter\n", Registers->Rip);
228 
229  IntDumpGva(Registers->Rip, 16, Registers->Cr3);
230  return TRUE;
231  }
232 
233  if (!!(Options & THS_CHECK_VEFILTER) && IntVeIsPtrInAgent(Registers->Rip, ptrLiveRip))
234  {
235  WARNING("[SAFENESS] Live RIP %016llx points inside the #VE Agent\n", Registers->Rip);
236  return TRUE;
237  }
238 
239  if (!!(Options & THS_CHECK_SWAPGS) && IntSwapgsIsPtrInHandler(Registers->Rip, ptrLiveRip, &gadget))
240  {
241  WARNING("[SAFENESS] Live RIP 0x%016llx points inside SWAPGS gadget at 0x%016llx\n", Registers->Rip, gadget);
242  return TRUE;
243  }
244 
245  return FALSE;
246 }
247 
248 
249 static INTSTATUS
251  _In_ QWORD StackFrameStart,
252  _In_opt_ QWORD StackFrameEnd
253  )
262 {
263  BYTE *pStack = NULL;
264  QWORD stackPtr = StackFrameStart;
265 
266  if (StackFrameStart % gGuest.WordSize != 0)
267  {
269  }
270 
271  if ((gGuest.OSType == introGuestWindows && !IS_KERNEL_POINTER_WIN(gGuest.Guest64, StackFrameStart)) ||
272  (gGuest.OSType == introGuestLinux && !IS_KERNEL_POINTER_LIX(StackFrameStart)))
273  {
275  }
276 
277  if (StackFrameEnd == 0)
278  {
279  StackFrameEnd = StackFrameStart + IntThrGetStackSize(StackFrameStart);
280  }
281 
282  if (StackFrameStart > StackFrameEnd)
283  {
284  WARNING("[WARNING] Found a start stack pointer value (0x%llx) greater than the end value (0x%llx).",
285  StackFrameStart, StackFrameEnd);
286  return FALSE;
287  }
288 
289  while (StackFrameStart < StackFrameEnd)
290  {
291  DWORD toCheck = (DWORD)(MIN(PAGE_REMAINING(stackPtr), StackFrameEnd - StackFrameStart));
292  INTSTATUS status;
293 
294  status = IntVirtMemMap(stackPtr, toCheck, gGuest.Mm.SystemCr3, 0, &pStack);
295  if (!INT_SUCCESS(status))
296  {
297  break;
298  }
299 
300  for (DWORD i = 0; i < toCheck; i += gGuest.WordSize)
301  {
302  QWORD stackValue, newValue;
303 
304  if (gGuest.Guest64)
305  {
306  stackValue = *(QWORD *)(pStack + i);
307  }
308  else
309  {
310  stackValue = *(DWORD *)(pStack + i);
311  }
312 
313  newValue = IntDetRelocatePtrIfNeeded(stackValue);
314  if (newValue == stackValue)
315  {
316  newValue = IntSwapgsRelocatePtrIfNeeded(stackValue);
317  }
318 
319  if (newValue != stackValue)
320  {
321  // IMPORTANT: If the return is at the beginning of a function, we can leave it there, and it will just
322  // go through VMCALL.
323  WARNING("[WARNING] Moving stack value (@ 0x%016llx) from 0x%016llx to 0x%016llx\n",
324  (stackPtr & PAGE_MASK) + i, stackValue, newValue);
325 
326  if (((QWORD)(pStack + i - gGuest.WordSize) & PAGE_MASK) == ((QWORD)pStack & PAGE_MASK))
327  {
328  LOG("[SAFENESS] @ 0x%016llx we have: 0x%016llx\n", (stackPtr & PAGE_MASK) + i - gGuest.WordSize,
329  gGuest.Guest64 ? * (QWORD *)(pStack + i - 8) : * (DWORD *)(pStack + i - 4));
330  }
331 
332  if (((QWORD)(pStack + i + gGuest.WordSize) & PAGE_MASK) == ((QWORD)pStack & PAGE_MASK))
333  {
334  LOG("[SAFENESS] @ 0x%016llx we have: 0x%016llx\n", (stackPtr & PAGE_MASK) + i + gGuest.WordSize,
335  gGuest.Guest64 ? * (QWORD *)(pStack + i + 8) : * (DWORD *)(pStack + i + 4));
336  }
337 
338  if (gGuest.Guest64)
339  {
340  *(QWORD *)(pStack + i) = newValue;
341  }
342  else
343  {
344  *(DWORD *)(pStack + i) = newValue & 0xffffffff;
345  }
346  }
347  }
348 
349  IntVirtMemUnmap(&pStack);
350  stackPtr += toCheck; // skip all that we checked
351  StackFrameStart += toCheck;
352  }
353 
354  return INT_STATUS_SUCCESS;
355 }
356 
357 
358 static INTSTATUS
360  _In_ IG_ARCH_REGS *Registers,
361  _In_ DWORD CpuNumber
362  )
371 {
372  INTSTATUS status;
373  QWORD newRip;
374 
375  if (NULL == Registers)
376  {
378  }
379 
380  newRip = IntDetRelocatePtrIfNeeded(Registers->Rip);
381  if (newRip == Registers->Rip)
382  {
383  newRip = IntSwapgsRelocatePtrIfNeeded(Registers->Rip);
384  }
385  if (newRip != Registers->Rip)
386  {
387  WARNING("[WARNING] Moving live RIP from 0x%016llx 0x%016llx\n", Registers->Rip, newRip);
388 
389  Registers->Rip = FIX_GUEST_POINTER(gGuest.Guest64, newRip);
390 
391  status = IntSetGprs(CpuNumber, Registers);
392  if (!INT_SUCCESS(status))
393  {
394  ERROR("[ERROR] Failed moving the RIP of the running CPU %d to the new address: 0x%08x\n",
395  CpuNumber, status);
396  return status;
397  }
398  }
399 
400  return INT_STATUS_SUCCESS;
401 }
402 
403 
404 static INTSTATUS
406  _In_ QWORD Ethread,
407  _In_ QWORD Options
408  )
425 {
426  BYTE *pThread;
427  UCHAR state, waitReason;
428  QWORD currentStack;
429  INTSTATUS status;
430 
431  if (!IS_KERNEL_POINTER_WIN(gGuest.Guest64, Ethread))
432  {
434  }
435 
436  if ((Ethread & PAGE_OFFSET) + WIN_KM_FIELD(Thread, WaitReason) > PAGE_SIZE)
437  {
438  WARNING("[WARNING] Ethread 0x%016llx is too high in page!\n", Ethread);
440  }
441 
442  status = IntVirtMemMap(Ethread, PAGE_REMAINING(Ethread), gGuest.Mm.SystemCr3, 0, &pThread);
443  if (!INT_SUCCESS(status))
444  {
445  ERROR("[ERROR] IntVirtMemMap failed for VA 0x%016llx: 0x%08x\n", Ethread, status);
446  return status;
447  }
448 
449  state = *(pThread + WIN_KM_FIELD(Thread, State));
450  waitReason = *(pThread + WIN_KM_FIELD(Thread, WaitReason));
451  if (gGuest.Guest64)
452  {
453  currentStack = *(QWORD *)(pThread + WIN_KM_FIELD(Thread, KernelStack));
454  }
455  else
456  {
457  currentStack = *(DWORD *)(pThread + WIN_KM_FIELD(Thread, KernelStack));
458  }
459 
460  IntVirtMemUnmap(&pThread);
461 
462  if (state == Running)
463  {
464  LOG("[SAFENESS] Thread 0x%016llx is running, will examine it later\n", Ethread);
466  }
467  else if (state == Terminated)
468  {
469  LOG("[SAFENESS] Terminated thread 0x%016llx\n", Ethread);
471  }
472 
473  if (WrDispatchInt != waitReason && WrQuantumEnd != waitReason)
474  {
476  }
477 
478  LOG("[SAFENESS] We have a WAITING thread to examine at 0x%016llx! Reason: %d, State: %d\n",
479  Ethread, waitReason, state);
480 
481  if (!IS_KERNEL_POINTER_WIN(gGuest.Guest64, currentStack) || // the stack is in kernel
482  0 != (currentStack % gGuest.WordSize)) // the stack is aligned
483  {
484  WARNING("[WARNING] Thread %016llx has stack at %016llx. Thread was TERMINATED!\n",
485  Ethread, currentStack);
487  }
488 
489  if (!!(Options & THS_CHECK_ONLY))
490  {
491  if (IntThrSafeIsStackPtrInIntro(currentStack, 0, Options, 0))
492  {
495  }
496  }
497  else
498  {
499  status = IntThrSafeMoveReturn(currentStack, 0);
500  if (!INT_SUCCESS(status))
501  {
502  ERROR("[ERROR] IntThrSafeMoveReturn failed for stack %016llx: %08x\n", currentStack, status);
503  }
504  }
505 
506  return status;
507 }
508 
509 
510 static INTSTATUS
512  _In_ QWORD TaskStruct,
513  _In_ QWORD Options
514  )
526 {
527  INTSTATUS status;
528  DWORD taskFlags;
529  QWORD currentStack;
530 
531  status = IntKernVirtMemFetchDword(TaskStruct + LIX_FIELD(TaskStruct, Flags), &taskFlags);
532  if (!INT_SUCCESS(status))
533  {
534  ERROR("[ERROR] Failed getting the task's flags from %llx: 0x%08x.\n",
535  TaskStruct + LIX_FIELD(TaskStruct, Flags), status);
536  return status;
537  }
538 
539  if (taskFlags & (PF_EXITPIDONE | PF_EXITING))
540  {
541  LOG("[SAFENESS] Ignoring task %llx which is dying: %08x\n", TaskStruct, taskFlags);
543  }
544 
545  // task->thread_struct.sp
546  currentStack = TaskStruct + gLixGuest->OsSpecificFields.ThreadStructOffset + LIX_FIELD(TaskStruct, ThreadStructSp);
547 
548  status = IntKernVirtMemFetchQword(currentStack, &currentStack);
549  if (!INT_SUCCESS(status))
550  {
551  ERROR("[ERROR] IntKernVirtMemFetchQword failed for %llx with status: 0x%08x\n", currentStack, status);
552 
553  status = IntKernVirtMemFetchQword(TaskStruct + LIX_FIELD(TaskStruct, Stack), &currentStack);
554  if (!INT_SUCCESS(status))
555  {
556  ERROR("[ERROR] Failed getting the task's stack from %llx: 0x%08x\n",
557  TaskStruct + LIX_FIELD(TaskStruct, Stack), status);
558  return status;
559  }
560  }
561 
562  if (!IS_KERNEL_POINTER_LIX(currentStack))
563  {
564  WARNING("[WARNING] Task %llx has current stack %llx, with flags %08x\n",
565  TaskStruct, currentStack, taskFlags);
566 
568  }
569 
570  currentStack = ALIGN_UP(currentStack, 8);
571 
572  if (!!(Options & THS_CHECK_ONLY))
573  {
574  if (IntThrSafeIsStackPtrInIntro(currentStack, 0, Options, TaskStruct))
575  {
578  }
579  }
580  else
581  {
582  status = IntThrSafeMoveReturn(currentStack, 0);
583  if (!INT_SUCCESS(status))
584  {
585  ERROR("[ERROR] IntThrSafeMoveReturn failed for stack 0x%016llx: 0x%08x\n", currentStack, status);
586  }
587  }
588 
589  return status;
590 }
591 
592 
593 static INTSTATUS
595  _In_ DWORD CpuNumber,
596  _Out_ QWORD *Stack
597  )
608 {
609  DWORD stackOffset;
610  INTSTATUS status;
611 
612  LIX_TASK_OBJECT *pTask = IntLixTaskGetCurrent(CpuNumber);
613  if (NULL == pTask)
614  {
615  return INT_STATUS_NOT_FOUND;
616  }
617 
618  stackOffset = LIX_FIELD(TaskStruct, Stack);
619 
620  status = IntKernVirtMemFetchQword(pTask->Gva + stackOffset, Stack);
621  if (!INT_SUCCESS(status))
622  {
623  return status;
624  }
625 
626  return INT_STATUS_SUCCESS;
627 }
628 
629 
630 static INTSTATUS
632  _In_ DWORD CpuNumber,
633  _Out_ QWORD *CurrentStack,
634  _Out_ QWORD *StackBase,
635  _Out_ QWORD *StackLimit
636  )
649 {
650  QWORD currentThread;
651  INTSTATUS status;
652 
653  status = IntWinThrGetCurrentThread(CpuNumber, &currentThread);
654  if (!INT_SUCCESS(status))
655  {
656  WARNING("[WARNING] IntWinThrGetCurrentThread failed: 0x%08x\n", status);
657  return status;
658  }
659 
660  status = IntKernVirtMemRead(currentThread + WIN_KM_FIELD(Thread, KernelStack),
662  CurrentStack,
663  NULL);
664  if (!INT_SUCCESS(status))
665  {
666  LOG("[ERROR] IntKernVirtMemRead failed for (Ethread: %016llx, KernelStack: 0x%x): 0x%08x\n",
667  currentThread, WIN_KM_FIELD(Thread, KernelStack), status);
668  return status;
669  }
670 
671  status = IntKernVirtMemRead(currentThread + WIN_KM_FIELD(Thread, StackBase),
673  StackBase,
674  NULL);
675  if (!INT_SUCCESS(status))
676  {
677  LOG("[ERROR] IntKernVirtMemRead failed for (Ethread: %016llx, StackBase: 0x%x): 0x%08x\n",
678  currentThread, WIN_KM_FIELD(Thread, StackBase), status);
679  return status;
680  }
681 
682  status = IntKernVirtMemRead(currentThread + WIN_KM_FIELD(Thread, StackLimit),
684  StackLimit,
685  NULL);
686  if (!INT_SUCCESS(status))
687  {
688  LOG("[ERROR] IntKernVirtMemRead failed for (Ethread: %016llx, StackLimit: 0x%x): 0x%08x\n",
689  currentThread, WIN_KM_FIELD(Thread, StackLimit), status);
690  return status;
691  }
692 
693  return INT_STATUS_SUCCESS;
694 }
695 
696 
697 static INTSTATUS
699  _In_ DWORD Cpu,
700  _In_ const IG_ARCH_REGS *Regs,
701  _In_ QWORD Options
702  )
715 {
716  INTSTATUS status;
717  QWORD toCheck[2];
718  QWORD currentStack;
719  const QWORD maxSize = IntThrGetStackSize(0);
720 
721  status = IntThrSafeLixGetCurrentStack(Cpu, &currentStack);
722  if (!INT_SUCCESS(status))
723  {
725  }
726 
727  LOG("[SAFENESS] CPU %u has current stack = 0x%016llx and RSP = 0x%016llx\n", Cpu, currentStack, Regs->Rsp);
728 
729  if (Regs->Rsp < currentStack)
730  {
731  toCheck[0] = Regs->Rsp;
732  toCheck[1] = currentStack;
733  }
734  else
735  {
736  toCheck[0] = currentStack;
737  toCheck[1] = Regs->Rsp;
738  }
739 
740  // Skip close values
741  if (toCheck[1] - toCheck[0] < maxSize)
742  {
743  // Keep the lowest one
744  toCheck[1] = 0;
745  }
746 
747  for (DWORD p = 0; p < ARRAYSIZE(toCheck); p++)
748  {
749  const QWORD ptr = toCheck[p];
750  const DWORD size = IntThrGetStackSize(ptr);
751 
752  if (!IS_KERNEL_POINTER_LIX(ptr))
753  {
754  continue;
755  }
756 
757  LOG("[SAFENESS] Will check stack %016llx with RIP %016llx on CPU %u\n", ptr, Regs->Rip, Cpu);
758 
759  if (!!(Options & THS_CHECK_ONLY))
760  {
761  if (IntThrSafeIsStackPtrInIntro(ptr, ptr + size, Options, 0))
762  {
764  }
765  }
766  else
767  {
768  status = IntThrSafeMoveReturn(ptr, ptr + size);
769  if (!INT_SUCCESS(status))
770  {
771  ERROR("[ERROR] IntThrSafeMoveReturn failed for stack %016llx for CPU %u: 0x%08x\n", ptr, Cpu, status);
772  return status;
773  }
774  }
775  }
776 
777  return INT_STATUS_SUCCESS;
778 }
779 
780 
781 static INTSTATUS
783  _In_ DWORD Cpu,
784  _In_ const IG_ARCH_REGS *Regs,
785  _In_ QWORD Options
786  )
799 {
800  INTSTATUS status;
801  QWORD stackBase, stackLimit, stackSavedInEthread, low, high;
802  BOOLEAN lowestPtrChecked = FALSE;
803  QWORD toCheck[3];
804 
805  // Make them 0 because on 32 bits we'll end up with garbage in the high-part.
806  stackSavedInEthread = stackLimit = stackBase = 0;
807 
808  status = IntThrSafeWinGetCurrentStack(Cpu, &stackSavedInEthread, &stackBase, &stackLimit);
809  if (!INT_SUCCESS(status))
810  {
811  ERROR("[ERROR] IntThrSafeWinGetCurrentStack failed on CPU %u: 0x%08x\n", Cpu, status);
812  return status;
813  }
814 
815  LOG("[SAFENESS] Active thread on CPU %u has stack @ 0x%016llx with base = 0x%016llx "
816  "limit = 0x%016llx RSP = 0x%016llx and RBP = 0x%016llx\n",
817  Cpu, stackSavedInEthread, stackBase, stackLimit, Regs->Rsp, Regs->Rbp);
818 
819  toCheck[0] = Regs->Rsp;
820  toCheck[1] = stackSavedInEthread;
821  toCheck[2] = gGuest.Guest64 ? 0 : Regs->Rbp;
822 
823  UtilSortQwords(toCheck, ARRAYSIZE(toCheck));
824 
825  // Is there even a chance for the stack base and limit to be reversed?
826  if (stackBase > stackLimit)
827  {
828  low = stackLimit;
829  high = stackBase;
830  }
831  else
832  {
833  low = stackBase;
834  high = stackLimit;
835  }
836 
837  for (DWORD p = 0; p < ARRAYSIZE(toCheck); p++)
838  {
839  DWORD size;
840  const QWORD ptr = toCheck[p];
842  {
843  continue;
844  }
845 
846  if (!lowestPtrChecked && IN_RANGE(ptr, low, high))
847  {
848  // ptr is the lowest, non-zero, stack location that is on the known ETHREAD stack, so check everything
849  // between ptr and stack base
850  size = (DWORD)((high & PAGE_MASK) - (ptr & PAGE_MASK));
851  lowestPtrChecked = TRUE;
852  }
853  else if (IN_RANGE(ptr, low, high))
854  {
855  // ptr is on the known ETHREAD stack, but was already checked at a previous step, skip it
856  continue;
857  }
858  else
859  {
860  // ptr is not on the known ETHREAD stack (most likely an interrupt changed the stack), so check it
861  // using a "close enough" page count approximation
862  LOG("[SAFENESS] Stack pointer 0x%016llx is not on the known Ethread stack [0x%016llx, 0x%016llx)!\n",
863  ptr, low, high);
864  size = IntThrGetStackSize(ptr);
865  }
866 
867  if (!!(Options & THS_CHECK_ONLY))
868  {
869  if (IntThrSafeIsStackPtrInIntro(ptr, ptr + size, Options, 0))
870  {
872  }
873  }
874  else
875  {
876  status = IntThrSafeMoveReturn(ptr, ptr + size);
877  if (!INT_SUCCESS(status) && ptr != Regs->Rbp)
878  {
879  ERROR("[ERROR] IntThrSafeMoveReturn failed for stack %016llx for CPU %u: 0x%08x\n", ptr, Cpu, status);
880  return status;
881  }
882  }
883  }
884 
885  return INT_STATUS_SUCCESS;
886 }
887 
888 
889 static INTSTATUS
891  _In_ QWORD Options
892  )
903 {
904  INTSTATUS status;
905 
906  for (DWORD c = 0; c < gGuest.CpuCount; c++)
907  {
908  IG_ARCH_REGS regs;
909 
910  status = IntGetGprs(c, &regs);
911  if (!INT_SUCCESS(status))
912  {
913  ERROR("[ERROR] IntGetGprs failed for CPU %u: 0x%08x\n", c, status);
914  continue;
915  }
916 
917  if (0 == regs.Cr3)
918  {
919  LOG("[CPU %u] Is inactive, will not check anything!\n", c);
920  continue;
921  }
922 
925  {
926  TRACE("[INFO] CPU %u, RIP 0x%016llx is in user-mode... We can safely ignore it\n", c, regs.Rip);
927  continue;
928  }
929 
930  TRACE("[INFO] Cpu %u, RIP 0x%016llx, will continue checking\n", c, regs.Rip);
931 
932  if (!!(Options & THS_CHECK_ONLY))
933  {
934  if (IntThrSafeIsLiveRIPInIntro(&regs, Options))
935  {
936  WARNING("[SAFENESS] IntThrSafeIsLiveRIPInIntro failed for CPU %u\n", c);
938  }
939  }
940  else
941  {
942  status = IntThrSafeMoveRip(&regs, c);
943  if (!INT_SUCCESS(status))
944  {
945  ERROR("[ERROR] Failed moving RIP %016llx on CPU %u: 0x%08x\n", regs.Rip, c, status);
946  }
947  }
948 
950  {
951  status = IntThrSafeWinInspectRunningThreadOnCpu(c, &regs, Options);
952  if (!INT_SUCCESS(status))
953  {
954  ERROR("[ERROR] IntThrSafeWinInspectRunningThreadOnCpu failed for CPU %u: 0x%08x\n", c, status);
955  return status;
956  }
957  }
958  else if (introGuestLinux == gGuest.OSType)
959  {
961 
962  status = IntThrSafeLixInspectRunningThreadOnCpu(c, &regs, Options);
963  if (!INT_SUCCESS(status))
964  {
965  ERROR("[ERROR] IntThrSafeLixInspectRunningThreadOnCpu failed for CPU %u: 0x%08x\n", c, status);
966  return status;
967  }
968  }
969  }
970 
971  return INT_STATUS_SUCCESS;
972 }
973 
974 
975 static INTSTATUS
977  _In_ QWORD Eprocess,
978  _In_ QWORD Options
979  )
980 {
982 }
983 
984 
985 INTSTATUS
987  _In_ QWORD Options
988  )
1003 {
1004  INTSTATUS status;
1005 
1006  // Assume, for now, that we can unload
1007  gSafeToUnload = TRUE;
1008 
1009  if (!gGuest.GuestInitialized)
1010  {
1011  // Introspection was still initializing, so it's safe to unload.
1012  return INT_STATUS_SUCCESS;
1013  }
1014 
1015  status = IntThrSafeInspectRunningThreads(Options);
1016  if (!!(Options & THS_CHECK_ONLY) && status == INT_STATUS_CANNOT_UNLOAD)
1017  {
1018  gSafeToUnload = FALSE;
1019  return status;
1020  }
1021  else if (!INT_SUCCESS(status))
1022  {
1023  ERROR("[ERROR] IntThrSafeInspectRunningThreads failed: 0x%08x\n", status);
1024  return status;
1025  }
1026 
1027  // Will iterate all threads in the guest and will set #gSafeToUnload to TRUE if
1028  // a thread returns to our detours
1030  {
1031  // We won't have any hooks in guest or other stuff until we didn't read the kernel, so it's safe to
1032  // unload.
1033  if (0 != gWinGuest->RemainingSections)
1034  {
1035  return INT_STATUS_SUCCESS;
1036  }
1037 
1039  }
1040  else if (gGuest.OSType == introGuestLinux)
1041  {
1043  }
1044  else
1045  {
1046  return INT_STATUS_NOT_SUPPORTED;
1047  }
1048 
1049  if (status == INT_STATUS_NOT_INITIALIZED)
1050  {
1051  gSafeToUnload = FALSE;
1052  }
1053  else if (!INT_SUCCESS(status))
1054  {
1055  ERROR("[ERROR] Failed iterating processes and threads: 0x%08x\n", status);
1056  return status;
1057  }
1058 
1059  if (!!(Options & THS_CHECK_ONLY) && !gSafeToUnload)
1060  {
1061  return INT_STATUS_CANNOT_UNLOAD;
1062  }
1063 
1064  return status;
1065 }
BOOLEAN IntPtiIsPtrInAgent(QWORD Ptr, THS_PTR_TYPE Type)
Check if an address points inside the PT filter. Ignore non-executable sections when doing so...
Definition: ptfilter.c:1813
#define THS_CHECK_SWAPGS
Will check if any RIP is inside a mitigated SWAPGS gadget.
static INTSTATUS IntThrSafeMoveRip(IG_ARCH_REGS *Registers, DWORD CpuNumber)
Will check if it is safe for Introcore to modify the RIP value.
#define _In_opt_
Definition: intro_sal.h:16
LIX_OPAQUE_FIELDS OsSpecificFields
OS-dependent and specific information.
Definition: lixguest.h:579
#define _Out_
Definition: intro_sal.h:22
_Bool BOOLEAN
Definition: intro_types.h:58
INTSTATUS IntVirtMemUnmap(void **HostPtr)
Unmaps a memory range previously mapped with IntVirtMemMap.
Definition: introcore.c:2234
#define ALIGN_UP(x, a)
Definition: introdefs.h:164
#define THS_CHECK_PTFILTER
Will check if any RIP is inside the PT filter agent.
uint8_t BYTE
Definition: intro_types.h:47
static INTSTATUS IntThrSafeWinInspectWaitingFromGuestList(QWORD Eprocess, QWORD Options)
WINDOWS_GUEST * gWinGuest
Global variable holding the state of a Windows guest.
Definition: winguest.c:37
#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
INTSTATUS IntThrSafeCheckThreads(QWORD Options)
Checks if any of the guest threads have their RIP or have any stack pointers pointing to regions of c...
#define PAGE_REMAINING(addr)
Definition: pgtable.h:163
BOOLEAN IntMtblIsPtrInReloc(QWORD Ptr, THS_PTR_TYPE Type, QWORD *Table)
Check if the given pointer is inside a mem-table relocation handler.
Definition: memtables.c:596
INTSTATUS IntGetGprs(DWORD CpuNumber, PIG_ARCH_REGS Regs)
Get the current guest GPR state.
Definition: introcpu.c:827
static BOOLEAN gSafeToUnload
QWORD Gva
The guest virtual address of the task_struct.
Definition: lixprocess.h:42
#define INT_SUCCESS(Status)
Definition: introstatus.h:42
INTSTATUS IntWinThrGetCurrentThread(DWORD CpuNumber, QWORD *EthreadAddress)
Get the ETHREAD structure address of the thread currently running on the given CPU.
Definition: winthread.c:26
void UtilSortQwords(PQWORD Array, const DWORD NumberOfElements)
Definition: utils.c:384
#define PAGE_OFFSET
Definition: pgtable.h:32
#define ARRAYSIZE(A)
Definition: introdefs.h:101
#define PF_EXITPIDONE
Definition: lixddefs.h:122
#define INT_STATUS_NOT_NEEDED_HINT
Definition: introstatus.h:317
#define ERROR(fmt,...)
Definition: glue.h:62
int INTSTATUS
The status data type.
Definition: introstatus.h:24
#define INT_STATUS_NOT_FOUND
Definition: introstatus.h:284
DWORD ThreadStructOffset
The offset of the thread_struct from task_struct.
Definition: lixguest.h:415
#define KSTACK_PAGE_COUNT_X86
Stack page count to check on 32-bit systems.
INTRO_GUEST_TYPE OSType
The type of the guest.
Definition: guests.h:278
#define MIN(a, b)
Definition: introdefs.h:146
#define LOG(fmt,...)
Definition: glue.h:61
#define INT_STATUS_BREAK_ITERATION
Can be used by iteration callbacks to break the iteration early.
Definition: introstatus.h:374
BOOLEAN IntSwapgsIsPtrInHandler(QWORD Ptr, THS_PTR_TYPE Type, QWORD *Gadget)
Check if a pointer points inside a SWAPGS handler.
Definition: swapgs.c:531
A stack value.
Exposes the functions used to provide Windows Threads related support.
static INTSTATUS IntThrSafeMoveReturn(QWORD StackFrameStart, QWORD StackFrameEnd)
Will check if it is safe for Introcore to move the return value on the stack.
INTSTATUS IntLixTaskIterateGuestTasks(PFUNC_IterateListCallback Callback, QWORD Aux)
Iterates the guest process list and calls the provided callback for each process and thread found...
Definition: lixprocess.c:3799
#define INT_STATUS_CANNOT_UNLOAD
Indicates that Introcore can not unload in a safely manner.
Definition: introstatus.h:450
static INTSTATUS IntThrSafeWinInspectWaitingThread(QWORD Ethread, QWORD Options)
Inspects a waiting thread from a Windows guest.
INTSTATUS IntKernVirtMemFetchDword(QWORD GuestVirtualAddress, DWORD *Data)
Reads 4 bytes from the guest kernel memory.
Definition: introcore.c:829
#define IS_KERNEL_POINTER_LIX(p)
Definition: lixguest.h:11
#define INT_STATUS_NOT_INITIALIZED
Definition: introstatus.h:266
INTSTATUS IntKernVirtMemFetchQword(QWORD GuestVirtualAddress, QWORD *Data)
Reads 8 bytes from the guest kernel memory.
Definition: introcore.c:811
static INTSTATUS IntThrSafeLixInspectRunningThreadOnCpu(DWORD Cpu, const IG_ARCH_REGS *Regs, QWORD Options)
Inspects the running threads of a VCPU.
#define IN_RANGE(x, start, end)
Definition: introdefs.h:167
BOOLEAN Guest64
True if this is a 64-bit guest, False if it is a 32-bit guest.
Definition: guests.h:290
QWORD IntSwapgsRelocatePtrIfNeeded(QWORD Ptr)
Relocate a pointer if it points inside a SWAPGS gadget, and make it point inside the installed handle...
Definition: swapgs.c:579
unsigned long long QWORD
Definition: intro_types.h:53
#define TRUE
Definition: intro_types.h:30
BOOLEAN IntAgentIsPtrInTrampoline(QWORD Ptr, THS_PTR_TYPE Type)
Check if the provided pointer points inside the Windows trampoline code.
Definition: agent.c:180
#define IS_KERNEL_POINTER_WIN(is64, p)
Checks if a guest virtual address resides inside the Windows kernel address space.
Definition: wddefs.h:76
BOOLEAN GuestInitialized
True if the OS-specific portion has been initialized.
Definition: guests.h:293
#define LIX_FIELD(Structure, Field)
Macro used to access fields inside the LIX_OPAQUE_FIELDS structure.
Definition: lixguest.h:429
#define TRACE(fmt,...)
Definition: glue.h:58
unsigned char UCHAR
Definition: intro_types.h:55
BOOLEAN IntDetIsPtrInHandler(QWORD Ptr, THS_PTR_TYPE Type, DETOUR_TAG *Tag)
Checks if a guest pointer is inside a detour handler.
Definition: detours.c:1980
INTSTATUS IntWinProcIterateGuestProcesses(PFUNC_IterateListCallback Callback, QWORD Aux)
Iterates the in-guest process list and calls Callback for each entry.
Definition: winprocesshp.c:501
BYTE WordSize
Guest word size. Will be 4 for 32-bit guests and 8 for 64-bit guests.
Definition: guests.h:367
The RIP of a thread.
static BOOLEAN IntThrSafeIsLiveRIPInIntro(const IG_ARCH_REGS *Registers, QWORD Options)
Checks if the RIP on one of the guests VCPU points inside an Introcore owned code section...
#define WARNING(fmt,...)
Definition: glue.h:60
DWORD CpuCount
The number of logical CPUs.
Definition: guests.h:279
#define PAGE_SIZE
Definition: common.h:70
#define PF_EXITING
Definition: lixddefs.h:121
#define __forceinline
Definition: introtypes.h:61
#define WIN_KM_FIELD(Structure, Field)
Macro used to access kernel mode fields inside the WIN_OPAQUE_FIELDS structure.
Definition: winguest.h:740
uint32_t DWORD
Definition: intro_types.h:49
static DWORD IntThrGetStackSize(QWORD Rsp)
#define THS_CHECK_DETOURS
Will check if any RIP is inside detours.
static INTSTATUS IntThrSafeLixInspectWaitingThread(QWORD TaskStruct, QWORD Options)
Inspects a waiting thread from a Linux guest.
static INTSTATUS IntThrSafeWinInspectRunningThreadOnCpu(DWORD Cpu, const IG_ARCH_REGS *Regs, QWORD Options)
Inspects the running threads of a VCPU.
#define THS_CHECK_VEFILTER
Will check if any RIP is inside the VE filter agent.
#define KSTACK_PAGE_COUNT_X64
Stack page count to check on 64-bit systems.
BOOLEAN IntVeIsPtrInAgent(QWORD Ptr, THS_PTR_TYPE Type)
Check if an address points inside the VE agent.
Definition: vecore.c:2214
INTSTATUS IntSetGprs(DWORD CpuNumber, PIG_ARCH_REGS Regs)
Sets the values of the guest GPRs.
Definition: introcpu.c:905
__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
INTSTATUS IntLixTaskGetCurrentTaskStruct(DWORD CpuNumber, QWORD *TaskStruct)
Reads the guest virtual address of the task currently running on a CPU.
Definition: lixprocess.c:795
GUEST_STATE gGuest
The current guest state.
Definition: guests.c:50
#define FIX_GUEST_POINTER(is64, x)
Masks the unused part of a Windows guest virtual address.
Definition: wddefs.h:87
TIMER_FRIENDLY void IntDumpGva(QWORD Gva, DWORD Length, QWORD Cr3)
This function is a wrapper over IntDumpGvaEx (it uses RowLength = 16, ElementLength = 1...
Definition: dumper.c:273
#define THS_CHECK_ONLY
Will check for safeness, without moving any RIP or stack value.
DWORD RemainingSections
The number of kernel sections not yet read into KernelBuffer.
Definition: winguest.h:854
INTSTATUS IntKernVirtMemRead(QWORD KernelGva, DWORD Length, void *Buffer, DWORD *RetLength)
Reads data from a guest kernel virtual memory range.
Definition: introcore.c:674
static BOOLEAN IntThrSafeIsStackPtrInIntro(QWORD StackFrameStart, QWORD StackFrameEnd, QWORD Options, QWORD ProcessGva)
Checks if a pointer from the stack points to a section of code injected or modified by Introcore insi...
#define THS_CHECK_TRAMPOLINE
Will check if any RIP is inside the agent loader.
static INTSTATUS IntThrSafeWinGetCurrentStack(DWORD CpuNumber, QWORD *CurrentStack, QWORD *StackBase, QWORD *StackLimit)
Get the current stack values for a VCPU for a Windows guest.
#define INT_STATUS_INVALID_PARAMETER_1
Definition: introstatus.h:62
#define INT_STATUS_NOT_SUPPORTED
Definition: introstatus.h:287
VCPU_STATE * gVcpu
The state of the current VCPU.
Definition: guests.c:59
LIX_TASK_OBJECT * IntLixTaskGetCurrent(DWORD CpuNumber)
Finds the task that is currently running on the given CPU.
Definition: lixprocess.c:858
QWORD IntDetRelocatePtrIfNeeded(QWORD Ptr)
Returns the new value Ptr should have if it is currently pointing inside a relocated prologue...
Definition: detours.c:2048
Holds register state.
Definition: glueiface.h:30
static INTSTATUS IntThrSafeInspectRunningThreads(QWORD Options)
Inspects the currently running threads.
QWORD LixProcessGva
The guest virtual address of the running task on the current vCPU (valid only for Linux / thread safe...
Definition: guests.h:166
DETOUR_TAG
Unique tag used to identify a detour.
Definition: detours.h:119
INTSTATUS IntWinThrIterateThreads(QWORD Eprocess, PFUNC_IterateListCallback Callback, QWORD Aux)
Iterate all the threads of the given process and invoke the callback for each one of them...
Definition: winthread.c:96
#define THS_CHECK_MEMTABLES
Will check if any RIP is inside memtables.
#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
static INTSTATUS IntThrSafeLixGetCurrentStack(DWORD CpuNumber, QWORD *Stack)
Get the current stack value for a VCPU for a Linux guest.
#define FALSE
Definition: intro_types.h:34