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  return TRUE;
136  }
137 
138  if (!!(Options & THS_CHECK_MEMTABLES) && IntMtblIsPtrInReloc(stackValue, ptrStackValue, &tableGva))
139  {
140  WARNING("[SAFENESS] Stack value @ %016llx (= %016llx) points inside mem table for %016llx\n",
141  stackPtr + i, stackValue, tableGva);
142  return TRUE;
143  }
144 
145  if (!!(Options & THS_CHECK_TRAMPOLINE) && IntAgentIsPtrInTrampoline(stackValue, ptrStackValue))
146  {
147  WARNING("[SAFENESS] Stack value @ %016llx (= %016llx) points inside agent trampoline\n",
148  stackPtr + i, stackValue);
149  return TRUE;
150  }
151 
152  if (!!(Options & THS_CHECK_PTFILTER) && IntPtiIsPtrInAgent(stackValue, ptrStackValue))
153  {
154  WARNING("[SAFENESS] Stack value @ %016llx (= %016llx) points inside PT Filter\n",
155  stackPtr + i, stackValue);
156  return TRUE;
157  }
158 
159  if (!!(Options & THS_CHECK_VEFILTER) && IntVeIsPtrInAgent(stackValue, ptrStackValue))
160  {
161  WARNING("[SAFENESS] Stack value @ %016llx (= %016llx) points inside the #VE Agent\n",
162  stackPtr + i, stackValue);
163  return TRUE;
164  }
165 
166  if (!!(Options & THS_CHECK_SWAPGS) && IntSwapgsIsPtrInHandler(stackValue, ptrStackValue, &gadget))
167  {
168  WARNING("[SAFENESS] Stack value @ %016llx (= %016llx) points inside a SWAPGS gadget at 0x%016llx\n",
169  stackPtr + i, stackValue, gadget);
170  return TRUE;
171  }
172  }
173 
174  IntVirtMemUnmap(&pStack);
175  stackPtr += toCheck; // skip all that we checked
176  StackFrameStart += toCheck;
177  }
178 
179  return FALSE;
180 }
181 
182 
183 static BOOLEAN
185  _In_ const IG_ARCH_REGS *Registers,
186  _In_ QWORD Options
187  )
197 {
198  DETOUR_TAG detTag;
199  QWORD tableGva, gadget;
200 
201  if (!!(Options & THS_CHECK_DETOURS) && IntDetIsPtrInHandler(Registers->Rip, ptrLiveRip, &detTag))
202  {
203  WARNING("[SAFENESS] Live RIP %016llx points inside detour %d\n", Registers->Rip, detTag);
204  return TRUE;
205  }
206 
207  if (!!(Options & THS_CHECK_MEMTABLES) && IntMtblIsPtrInReloc(Registers->Rip, ptrLiveRip, &tableGva))
208  {
209  WARNING("[SAFENESS] Live RIP %016llx points inside mem table for %016llx\n", Registers->Rip, tableGva);
210  return TRUE;
211  }
212 
213  if (!!(Options & THS_CHECK_TRAMPOLINE) && IntAgentIsPtrInTrampoline(Registers->Rip, ptrLiveRip))
214  {
215  WARNING("[SAFENESS] Live RIP %016llx points inside agent trampoline\n", Registers->Rip);
216  return TRUE;
217  }
218 
219  if (!!(Options & THS_CHECK_PTFILTER) && IntPtiIsPtrInAgent(Registers->Rip, ptrLiveRip))
220  {
221  WARNING("[SAFENESS] Live RIP %016llx points inside PT Filter\n", Registers->Rip);
222 
223  IntDumpGva(Registers->Rip, 16, Registers->Cr3);
224  return TRUE;
225  }
226 
227  if (!!(Options & THS_CHECK_VEFILTER) && IntVeIsPtrInAgent(Registers->Rip, ptrLiveRip))
228  {
229  WARNING("[SAFENESS] Live RIP %016llx points inside the #VE Agent\n", Registers->Rip);
230  return TRUE;
231  }
232 
233  if (!!(Options & THS_CHECK_SWAPGS) && IntSwapgsIsPtrInHandler(Registers->Rip, ptrLiveRip, &gadget))
234  {
235  WARNING("[SAFENESS] Live RIP 0x%016llx points inside SWAPGS gadget at 0x%016llx\n", Registers->Rip, gadget);
236  return TRUE;
237  }
238 
239  return FALSE;
240 }
241 
242 
243 static INTSTATUS
245  _In_ QWORD StackFrameStart,
246  _In_opt_ QWORD StackFrameEnd
247  )
256 {
257  BYTE *pStack = NULL;
258  QWORD stackPtr = StackFrameStart;
259 
260  if (StackFrameStart % gGuest.WordSize != 0)
261  {
263  }
264 
265  if ((gGuest.OSType == introGuestWindows && !IS_KERNEL_POINTER_WIN(gGuest.Guest64, StackFrameStart)) ||
266  (gGuest.OSType == introGuestLinux && !IS_KERNEL_POINTER_LIX(StackFrameStart)))
267  {
269  }
270 
271  if (StackFrameEnd == 0)
272  {
273  StackFrameEnd = StackFrameStart + IntThrGetStackSize(StackFrameStart);
274  }
275 
276  if (StackFrameStart > StackFrameEnd)
277  {
278  WARNING("[WARNING] Found a start stack pointer value (0x%llx) greater than the end value (0x%llx).",
279  StackFrameStart, StackFrameEnd);
280  return FALSE;
281  }
282 
283  while (StackFrameStart < StackFrameEnd)
284  {
285  DWORD toCheck = (DWORD)(MIN(PAGE_REMAINING(stackPtr), StackFrameEnd - StackFrameStart));
286  INTSTATUS status;
287 
288  status = IntVirtMemMap(stackPtr, toCheck, gGuest.Mm.SystemCr3, 0, &pStack);
289  if (!INT_SUCCESS(status))
290  {
291  break;
292  }
293 
294  for (DWORD i = 0; i < toCheck; i += gGuest.WordSize)
295  {
296  QWORD stackValue, newValue;
297 
298  if (gGuest.Guest64)
299  {
300  stackValue = *(QWORD *)(pStack + i);
301  }
302  else
303  {
304  stackValue = *(DWORD *)(pStack + i);
305  }
306 
307  newValue = IntDetRelocatePtrIfNeeded(stackValue);
308  if (newValue == stackValue)
309  {
310  newValue = IntSwapgsRelocatePtrIfNeeded(stackValue);
311  }
312 
313  if (newValue != stackValue)
314  {
315  // IMPORTANT: If the return is at the beginning of a function, we can leave it there, and it will just
316  // go through VMCALL.
317  WARNING("[WARNING] Moving stack value (@ 0x%016llx) from 0x%016llx to 0x%016llx\n",
318  (stackPtr & PAGE_MASK) + i, stackValue, newValue);
319 
320  if (((QWORD)(pStack + i - gGuest.WordSize) & PAGE_MASK) == ((QWORD)pStack & PAGE_MASK))
321  {
322  LOG("[SAFENESS] @ 0x%016llx we have: 0x%016llx\n", (stackPtr & PAGE_MASK) + i - gGuest.WordSize,
323  gGuest.Guest64 ? * (QWORD *)(pStack + i - 8) : * (DWORD *)(pStack + i - 4));
324  }
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 (gGuest.Guest64)
333  {
334  *(QWORD *)(pStack + i) = newValue;
335  }
336  else
337  {
338  *(DWORD *)(pStack + i) = newValue & 0xffffffff;
339  }
340  }
341  }
342 
343  IntVirtMemUnmap(&pStack);
344  stackPtr += toCheck; // skip all that we checked
345  StackFrameStart += toCheck;
346  }
347 
348  return INT_STATUS_SUCCESS;
349 }
350 
351 
352 static INTSTATUS
354  _In_ IG_ARCH_REGS *Registers,
355  _In_ DWORD CpuNumber
356  )
365 {
366  INTSTATUS status;
367  QWORD newRip;
368 
369  if (NULL == Registers)
370  {
372  }
373 
374  newRip = IntDetRelocatePtrIfNeeded(Registers->Rip);
375  if (newRip == Registers->Rip)
376  {
377  newRip = IntSwapgsRelocatePtrIfNeeded(Registers->Rip);
378  }
379  if (newRip != Registers->Rip)
380  {
381  WARNING("[WARNING] Moving live RIP from 0x%016llx 0x%016llx\n", Registers->Rip, newRip);
382 
383  Registers->Rip = FIX_GUEST_POINTER(gGuest.Guest64, newRip);
384 
385  status = IntSetGprs(CpuNumber, Registers);
386  if (!INT_SUCCESS(status))
387  {
388  ERROR("[ERROR] Failed moving the RIP of the running CPU %d to the new address: 0x%08x\n",
389  CpuNumber, status);
390  return status;
391  }
392  }
393 
394  return INT_STATUS_SUCCESS;
395 }
396 
397 
398 static INTSTATUS
400  _In_ QWORD Ethread,
401  _In_ QWORD Options
402  )
419 {
420  BYTE *pThread;
421  UCHAR state, waitReason;
422  QWORD currentStack;
423  INTSTATUS status;
424 
425  if (!IS_KERNEL_POINTER_WIN(gGuest.Guest64, Ethread))
426  {
428  }
429 
430  if ((Ethread & PAGE_OFFSET) + WIN_KM_FIELD(Thread, WaitReason) > PAGE_SIZE)
431  {
432  WARNING("[WARNING] Ethread 0x%016llx is too high in page!\n", Ethread);
434  }
435 
436  status = IntVirtMemMap(Ethread, PAGE_REMAINING(Ethread), gGuest.Mm.SystemCr3, 0, &pThread);
437  if (!INT_SUCCESS(status))
438  {
439  ERROR("[ERROR] IntVirtMemMap failed for VA 0x%016llx: 0x%08x\n", Ethread, status);
440  return status;
441  }
442 
443  state = *(pThread + WIN_KM_FIELD(Thread, State));
444  waitReason = *(pThread + WIN_KM_FIELD(Thread, WaitReason));
445  if (gGuest.Guest64)
446  {
447  currentStack = *(QWORD *)(pThread + WIN_KM_FIELD(Thread, KernelStack));
448  }
449  else
450  {
451  currentStack = *(DWORD *)(pThread + WIN_KM_FIELD(Thread, KernelStack));
452  }
453 
454  IntVirtMemUnmap(&pThread);
455 
456  if (state == Running)
457  {
458  LOG("[SAFENESS] Thread 0x%016llx is running, will examine it later\n", Ethread);
460  }
461  else if (state == Terminated)
462  {
463  LOG("[SAFENESS] Terminated thread 0x%016llx\n", Ethread);
465  }
466 
467  if (WrDispatchInt != waitReason && WrQuantumEnd != waitReason)
468  {
470  }
471 
472  LOG("[SAFENESS] We have a WAITING thread to examine at 0x%016llx! Reason: %d, State: %d\n",
473  Ethread, waitReason, state);
474 
475  if (!IS_KERNEL_POINTER_WIN(gGuest.Guest64, currentStack) || // the stack is in kernel
476  0 != (currentStack % gGuest.WordSize)) // the stack is aligned
477  {
478  WARNING("[WARNING] Thread %016llx has stack at %016llx. Thread was TERMINATED!\n",
479  Ethread, currentStack);
481  }
482 
483  if (!!(Options & THS_CHECK_ONLY))
484  {
485  if (IntThrSafeIsStackPtrInIntro(currentStack, 0, Options, 0))
486  {
489  }
490  }
491  else
492  {
493  status = IntThrSafeMoveReturn(currentStack, 0);
494  if (!INT_SUCCESS(status))
495  {
496  ERROR("[ERROR] IntThrSafeMoveReturn failed for stack %016llx: %08x\n", currentStack, status);
497  }
498  }
499 
500  return status;
501 }
502 
503 
504 static INTSTATUS
506  _In_ QWORD TaskStruct,
507  _In_ QWORD Options
508  )
520 {
521  INTSTATUS status;
522  DWORD taskFlags;
523  QWORD currentStack;
524 
525  status = IntKernVirtMemFetchDword(TaskStruct + LIX_FIELD(TaskStruct, Flags), &taskFlags);
526  if (!INT_SUCCESS(status))
527  {
528  ERROR("[ERROR] Failed getting the task's flags from %llx: 0x%08x.\n",
529  TaskStruct + LIX_FIELD(TaskStruct, Flags), status);
530  return status;
531  }
532 
533  if (taskFlags & (PF_EXITPIDONE | PF_EXITING))
534  {
535  LOG("[SAFENESS] Ignoring task %llx which is dying: %08x\n", TaskStruct, taskFlags);
537  }
538 
539  // task->thread_struct.sp
540  currentStack = TaskStruct + gLixGuest->OsSpecificFields.ThreadStructOffset + LIX_FIELD(TaskStruct, ThreadStructSp);
541 
542  status = IntKernVirtMemFetchQword(currentStack, &currentStack);
543  if (!INT_SUCCESS(status))
544  {
545  ERROR("[ERROR] IntKernVirtMemFetchQword failed for %llx with status: 0x%08x\n", currentStack, status);
546 
547  status = IntKernVirtMemFetchQword(TaskStruct + LIX_FIELD(TaskStruct, Stack), &currentStack);
548  if (!INT_SUCCESS(status))
549  {
550  ERROR("[ERROR] Failed getting the task's stack from %llx: 0x%08x\n",
551  TaskStruct + LIX_FIELD(TaskStruct, Stack), status);
552  return status;
553  }
554  }
555 
556  if (!IS_KERNEL_POINTER_LIX(currentStack))
557  {
558  WARNING("[WARNING] Task %llx has current stack %llx, with flags %08x\n",
559  TaskStruct, currentStack, taskFlags);
560 
562  }
563 
564  currentStack = ALIGN_UP(currentStack, 8);
565 
566  if (!!(Options & THS_CHECK_ONLY))
567  {
568  if (IntThrSafeIsStackPtrInIntro(currentStack, 0, Options, TaskStruct))
569  {
572  }
573  }
574  else
575  {
576  status = IntThrSafeMoveReturn(currentStack, 0);
577  if (!INT_SUCCESS(status))
578  {
579  ERROR("[ERROR] IntThrSafeMoveReturn failed for stack 0x%016llx: 0x%08x\n", currentStack, status);
580  }
581  }
582 
583  return status;
584 }
585 
586 
587 static INTSTATUS
589  _In_ DWORD CpuNumber,
590  _Out_ QWORD *Stack
591  )
602 {
603  DWORD stackOffset;
604  INTSTATUS status;
605 
606  LIX_TASK_OBJECT *pTask = IntLixTaskGetCurrent(CpuNumber);
607  if (NULL == pTask)
608  {
609  return INT_STATUS_NOT_FOUND;
610  }
611 
612  stackOffset = LIX_FIELD(TaskStruct, Stack);
613 
614  status = IntKernVirtMemFetchQword(pTask->Gva + stackOffset, Stack);
615  if (!INT_SUCCESS(status))
616  {
617  return status;
618  }
619 
620  return INT_STATUS_SUCCESS;
621 }
622 
623 
624 static INTSTATUS
626  _In_ DWORD CpuNumber,
627  _Out_ QWORD *CurrentStack,
628  _Out_ QWORD *StackBase,
629  _Out_ QWORD *StackLimit
630  )
643 {
644  QWORD currentThread;
645  INTSTATUS status;
646 
647  status = IntWinThrGetCurrentThread(CpuNumber, &currentThread);
648  if (!INT_SUCCESS(status))
649  {
650  WARNING("[WARNING] IntWinThrGetCurrentThread failed: 0x%08x\n", status);
651  return status;
652  }
653 
654  status = IntKernVirtMemRead(currentThread + WIN_KM_FIELD(Thread, KernelStack),
656  CurrentStack,
657  NULL);
658  if (!INT_SUCCESS(status))
659  {
660  LOG("[ERROR] IntKernVirtMemRead failed for (Ethread: %016llx, KernelStack: 0x%x): 0x%08x\n",
661  currentThread, WIN_KM_FIELD(Thread, KernelStack), status);
662  return status;
663  }
664 
665  status = IntKernVirtMemRead(currentThread + WIN_KM_FIELD(Thread, StackBase),
667  StackBase,
668  NULL);
669  if (!INT_SUCCESS(status))
670  {
671  LOG("[ERROR] IntKernVirtMemRead failed for (Ethread: %016llx, StackBase: 0x%x): 0x%08x\n",
672  currentThread, WIN_KM_FIELD(Thread, StackBase), status);
673  return status;
674  }
675 
676  status = IntKernVirtMemRead(currentThread + WIN_KM_FIELD(Thread, StackLimit),
678  StackLimit,
679  NULL);
680  if (!INT_SUCCESS(status))
681  {
682  LOG("[ERROR] IntKernVirtMemRead failed for (Ethread: %016llx, StackLimit: 0x%x): 0x%08x\n",
683  currentThread, WIN_KM_FIELD(Thread, StackLimit), status);
684  return status;
685  }
686 
687  return INT_STATUS_SUCCESS;
688 }
689 
690 
691 static INTSTATUS
693  _In_ DWORD Cpu,
694  _In_ const IG_ARCH_REGS *Regs,
695  _In_ QWORD Options
696  )
709 {
710  INTSTATUS status;
711  QWORD toCheck[2];
712  QWORD currentStack;
713  const QWORD maxSize = IntThrGetStackSize(0);
714 
715  status = IntThrSafeLixGetCurrentStack(Cpu, &currentStack);
716  if (!INT_SUCCESS(status))
717  {
719  }
720 
721  LOG("[SAFENESS] CPU %u has current stack = 0x%016llx and RSP = 0x%016llx\n", Cpu, currentStack, Regs->Rsp);
722 
723  if (Regs->Rsp < currentStack)
724  {
725  toCheck[0] = Regs->Rsp;
726  toCheck[1] = currentStack;
727  }
728  else
729  {
730  toCheck[0] = currentStack;
731  toCheck[1] = Regs->Rsp;
732  }
733 
734  // Skip close values
735  if (toCheck[1] - toCheck[0] < maxSize)
736  {
737  // Keep the lowest one
738  toCheck[1] = 0;
739  }
740 
741  for (DWORD p = 0; p < ARRAYSIZE(toCheck); p++)
742  {
743  const QWORD ptr = toCheck[p];
744  const DWORD size = IntThrGetStackSize(ptr);
745 
746  if (!IS_KERNEL_POINTER_LIX(ptr))
747  {
748  continue;
749  }
750 
751  LOG("[SAFENESS] Will check stack %016llx with RIP %016llx on CPU %u\n", ptr, Regs->Rip, Cpu);
752 
753  if (!!(Options & THS_CHECK_ONLY))
754  {
755  if (IntThrSafeIsStackPtrInIntro(ptr, ptr + size, Options, 0))
756  {
758  }
759  }
760  else
761  {
762  status = IntThrSafeMoveReturn(ptr, ptr + size);
763  if (!INT_SUCCESS(status))
764  {
765  ERROR("[ERROR] IntThrSafeMoveReturn failed for stack %016llx for CPU %u: 0x%08x\n", ptr, Cpu, status);
766  return status;
767  }
768  }
769  }
770 
771  return INT_STATUS_SUCCESS;
772 }
773 
774 
775 static INTSTATUS
777  _In_ DWORD Cpu,
778  _In_ const IG_ARCH_REGS *Regs,
779  _In_ QWORD Options
780  )
793 {
794  INTSTATUS status;
795  QWORD stackBase, stackLimit, stackSavedInEthread, low, high;
796  BOOLEAN lowestPtrChecked = FALSE;
797  QWORD toCheck[3];
798 
799  // Make them 0 because on 32 bits we'll end up with garbage in the high-part.
800  stackSavedInEthread = stackLimit = stackBase = 0;
801 
802  status = IntThrSafeWinGetCurrentStack(Cpu, &stackSavedInEthread, &stackBase, &stackLimit);
803  if (!INT_SUCCESS(status))
804  {
805  ERROR("[ERROR] IntThrSafeWinGetCurrentStack failed on CPU %u: 0x%08x\n", Cpu, status);
806  return status;
807  }
808 
809  LOG("[SAFENESS] Active thread on CPU %u has stack @ 0x%016llx with base = 0x%016llx "
810  "limit = 0x%016llx RSP = 0x%016llx and RBP = 0x%016llx\n",
811  Cpu, stackSavedInEthread, stackBase, stackLimit, Regs->Rsp, Regs->Rbp);
812 
813  toCheck[0] = Regs->Rsp;
814  toCheck[1] = stackSavedInEthread;
815  toCheck[2] = gGuest.Guest64 ? 0 : Regs->Rbp;
816 
817  UtilSortQwords(toCheck, ARRAYSIZE(toCheck));
818 
819  // Is there even a chance for the stack base and limit to be reversed?
820  if (stackBase > stackLimit)
821  {
822  low = stackLimit;
823  high = stackBase;
824  }
825  else
826  {
827  low = stackBase;
828  high = stackLimit;
829  }
830 
831  for (DWORD p = 0; p < ARRAYSIZE(toCheck); p++)
832  {
833  DWORD size;
834  const QWORD ptr = toCheck[p];
836  {
837  continue;
838  }
839 
840  if (!lowestPtrChecked && IN_RANGE(ptr, low, high))
841  {
842  // ptr is the lowest, non-zero, stack location that is on the known ETHREAD stack, so check everything
843  // between ptr and stack base
844  size = (DWORD)((high & PAGE_MASK) - (ptr & PAGE_MASK));
845  lowestPtrChecked = TRUE;
846  }
847  else if (IN_RANGE(ptr, low, high))
848  {
849  // ptr is on the known ETHREAD stack, but was already checked at a previous step, skip it
850  continue;
851  }
852  else
853  {
854  // ptr is not on the known ETHREAD stack (most likely an interrupt changed the stack), so check it
855  // using a "close enough" page count approximation
856  LOG("[SAFENESS] Stack pointer 0x%016llx is not on the known Ethread stack [0x%016llx, 0x%016llx)!\n",
857  ptr, low, high);
858  size = IntThrGetStackSize(ptr);
859  }
860 
861  if (!!(Options & THS_CHECK_ONLY))
862  {
863  if (IntThrSafeIsStackPtrInIntro(ptr, ptr + size, Options, 0))
864  {
866  }
867  }
868  else
869  {
870  status = IntThrSafeMoveReturn(ptr, ptr + size);
871  if (!INT_SUCCESS(status) && ptr != Regs->Rbp)
872  {
873  ERROR("[ERROR] IntThrSafeMoveReturn failed for stack %016llx for CPU %u: 0x%08x\n", ptr, Cpu, status);
874  return status;
875  }
876  }
877  }
878 
879  return INT_STATUS_SUCCESS;
880 }
881 
882 
883 static INTSTATUS
885  _In_ QWORD Options
886  )
897 {
898  INTSTATUS status;
899 
900  for (DWORD c = 0; c < gGuest.CpuCount; c++)
901  {
902  IG_ARCH_REGS regs;
903 
904  status = IntGetGprs(c, &regs);
905  if (!INT_SUCCESS(status))
906  {
907  ERROR("[ERROR] IntGetGprs failed for CPU %u: 0x%08x\n", c, status);
908  continue;
909  }
910 
911  if (0 == regs.Cr3)
912  {
913  LOG("[CPU %u] Is inactive, will not check anything!\n", c);
914  continue;
915  }
916 
919  {
920  TRACE("[INFO] CPU %u, RIP 0x%016llx is in user-mode... We can safely ignore it\n", c, regs.Rip);
921  continue;
922  }
923 
924  TRACE("[INFO] Cpu %u, RIP 0x%016llx, will continue checking\n", c, regs.Rip);
925 
926  if (!!(Options & THS_CHECK_ONLY))
927  {
928  if (IntThrSafeIsLiveRIPInIntro(&regs, Options))
929  {
930  WARNING("[SAFENESS] IntThrSafeIsLiveRIPInIntro failed for CPU %u\n", c);
932  }
933  }
934  else
935  {
936  status = IntThrSafeMoveRip(&regs, c);
937  if (!INT_SUCCESS(status))
938  {
939  ERROR("[ERROR] Failed moving RIP %016llx on CPU %u: 0x%08x\n", regs.Rip, c, status);
940  }
941  }
942 
944  {
945  status = IntThrSafeWinInspectRunningThreadOnCpu(c, &regs, Options);
946  if (!INT_SUCCESS(status))
947  {
948  ERROR("[ERROR] IntThrSafeWinInspectRunningThreadOnCpu failed for CPU %u: 0x%08x\n", c, status);
949  return status;
950  }
951  }
952  else if (introGuestLinux == gGuest.OSType)
953  {
955 
956  status = IntThrSafeLixInspectRunningThreadOnCpu(c, &regs, Options);
957  if (!INT_SUCCESS(status))
958  {
959  ERROR("[ERROR] IntThrSafeLixInspectRunningThreadOnCpu failed for CPU %u: 0x%08x\n", c, status);
960  return status;
961  }
962  }
963  }
964 
965  return INT_STATUS_SUCCESS;
966 }
967 
968 
969 static INTSTATUS
971  _In_ QWORD Eprocess,
972  _In_ QWORD Options
973  )
974 {
976 }
977 
978 
979 INTSTATUS
981  _In_ QWORD Options
982  )
997 {
998  INTSTATUS status;
999 
1000  // Assume, for now, that we can unload
1001  gSafeToUnload = TRUE;
1002 
1003  if (!gGuest.GuestInitialized)
1004  {
1005  // Introspection was still initializing, so it's safe to unload.
1006  return INT_STATUS_SUCCESS;
1007  }
1008 
1009  status = IntThrSafeInspectRunningThreads(Options);
1010  if (!!(Options & THS_CHECK_ONLY) && status == INT_STATUS_CANNOT_UNLOAD)
1011  {
1012  gSafeToUnload = FALSE;
1013  return status;
1014  }
1015  else if (!INT_SUCCESS(status))
1016  {
1017  ERROR("[ERROR] IntThrSafeInspectRunningThreads failed: 0x%08x\n", status);
1018  return status;
1019  }
1020 
1021  // Will iterate all threads in the guest and will set #gSafeToUnload to TRUE if
1022  // a thread returns to our detours
1024  {
1025  // We won't have any hooks in guest or other stuff until we didn't read the kernel, so it's safe to
1026  // unload.
1027  if (0 != gWinGuest->RemainingSections)
1028  {
1029  return INT_STATUS_SUCCESS;
1030  }
1031 
1033  }
1034  else if (gGuest.OSType == introGuestLinux)
1035  {
1037  }
1038  else
1039  {
1040  return INT_STATUS_NOT_SUPPORTED;
1041  }
1042 
1043  if (status == INT_STATUS_NOT_INITIALIZED)
1044  {
1045  gSafeToUnload = FALSE;
1046  }
1047  else if (!INT_SUCCESS(status))
1048  {
1049  ERROR("[ERROR] Failed iterating processes and threads: 0x%08x\n", status);
1050  return status;
1051  }
1052 
1053  if (!!(Options & THS_CHECK_ONLY) && !gSafeToUnload)
1054  {
1055  return INT_STATUS_CANNOT_UNLOAD;
1056  }
1057 
1058  return status;
1059 }
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:576
#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:35
#define _In_
Definition: intro_sal.h:21
QWORD SystemCr3
The Cr3 used to map the kernel.
Definition: guests.h:207
#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:412
#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:274
#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:3762
#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:286
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:289
#define LIX_FIELD(Structure, Field)
Macro used to access fields inside the LIX_OPAQUE_FIELDS structure.
Definition: lixguest.h:426
#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:1874
INTSTATUS IntWinProcIterateGuestProcesses(PFUNC_IterateListCallback Callback, QWORD Aux)
Iterates the in-guest process list and calls Callback for each entry.
Definition: winprocesshp.c:428
BYTE WordSize
Guest word size. Will be 4 for 32-bit guests and 8 for 64-bit guests.
Definition: guests.h:363
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:275
#define PAGE_SIZE
Definition: common.h:53
#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:726
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:370
INTSTATUS IntLixTaskGetCurrentTaskStruct(DWORD CpuNumber, QWORD *TaskStruct)
Reads the guest virtual address of the task currently running on a CPU.
Definition: lixprocess.c:793
GUEST_STATE gGuest
The current guest state.
Definition: guests.c:48
#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:249
#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:840
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:57
LIX_TASK_OBJECT * IntLixTaskGetCurrent(DWORD CpuNumber)
Finds the task that is currently running on the given CPU.
Definition: lixprocess.c:856
QWORD IntDetRelocatePtrIfNeeded(QWORD Ptr)
Returns the new value Ptr should have if it is currently pointing inside a relocated prologue...
Definition: detours.c:1942
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:29
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