Bitdefender Hypervisor Memory Introspection
hook_pts.c
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2020 Bitdefender
3  * SPDX-License-Identifier: Apache-2.0
4  */
5 #include "hook.h"
6 #include "hook_pts.h"
7 #include "alerts.h"
8 #include "gpacache.h"
9 #include "kernvm.h"
10 
11 
83 
84 
88 typedef struct _INVOCATION_CONTEXT
89 {
93  void *Context;
100  BOOLEAN Static;
103 
104 
107 #define INVK_CTX_CACHE_SIZE 8
110 
111 // Defines the internal PTS level constants.
112 #define PTS_LEVEL_ROOT 6
113 #define PTS_LEVEL_PML5 5
114 #define PTS_LEVEL_PML4 4
115 #define PTS_LEVEL_PDP 3
116 #define PTS_LEVEL_PD 2
117 #define PTS_LEVEL_PT 1
118 
119 
120 static PHOOK_PTS_ENTRY
122  _In_ LIST_HEAD *ListHead,
123  _In_ QWORD PhysicalAddress
124  );
125 
126 static INTSTATUS
128  _In_ QWORD PtPaAddress,
129  _In_ WORD EntrySizeAndLevel,
130  _In_opt_ PHOOK_PTS_ENTRY Parent,
131  _Out_ PHOOK_PTS_ENTRY *Entry
132  );
133 
134 static INTSTATUS
136  _In_ PHOOK_PTS_ENTRY Entry,
137  _In_ QWORD OldValue,
138  _In_ QWORD NewValue
139  );
140 
141 
142 static __forceinline QWORD
144  _In_ PHOOK_PTS_ENTRY Entry
145  )
155 {
156  const QWORD pageSizeL[4] = { PAGE_SIZE_4K, PAGE_SIZE_2M, PAGE_SIZE_1G, 0 };
157  const QWORD pageSizeS[2] = { PAGE_SIZE_4K, PAGE_SIZE_4M };
158 
159  return (4 == Entry->EntrySize) ? pageSizeS[Entry->Level - 1] : pageSizeL[Entry->Level - 1];
160 }
161 
162 
163 static void
165  _In_ PLIST_HEAD List,
167  )
177 {
178  if (0 == (Context->Header.Flags & HOOK_FLG_HIGH_PRIORITY))
179  {
180  InsertTailList(List, &Context->Link);
181  }
182  else
183  {
184  // High priority - it goes at the end of the high-priority callbacks.
185  LIST_ENTRY *pivot;
186 
187  pivot = List->Flink;
188  while (pivot != List)
189  {
190  PHOOK_PTS pPts = CONTAINING_RECORD(pivot, HOOK_PTS, Link);
191 
192  if (0 == (pPts->Header.Flags & HOOK_FLG_HIGH_PRIORITY))
193  {
194  pivot = pivot->Blink;
195  break;
196  }
197 
198  pivot = pivot->Flink;
199  }
200 
201  InsertAfterList(pivot, &Context->Link);
202  }
203 }
204 
205 
206 static void
208  _In_ PHOOK_PTS_ENTRY Entry
209  )
219 {
220  LIST_ENTRY *list = Entry->ContextEntries.Flink;
221  while (list != &Entry->ContextEntries)
222  {
223  PHOOK_PTS pPts = CONTAINING_RECORD(list, HOOK_PTS, Link);
224 
225  list = list->Flink;
226 
227  pPts->OldEntry = pPts->CurEntry;
228  pPts->OldPageSize = pPts->CurPageSize;
229  pPts->CurEntry = pPts->Parent->WriteState.CurEntry;
231 
232  // Callbacks will be invoked in the exact same order they are present inside the list.
233  if (NULL != pPts->Callback)
234  {
235  INVOCATION_CONTEXT *pInvk;
236 
238  {
239  pInvk = &gInvkCtxStatic[gInvkCtxIndex];
240  gInvkCtxIndex++;
241 
242  pInvk->Static = TRUE;
243  }
244  else
245  {
246  pInvk = HpAllocWithTag(sizeof(*pInvk), IC_TAG_INVC);
247  if (NULL == pInvk)
248  {
249  continue;
250  }
251 
252  pInvk->Static = FALSE;
253  }
254 
255  pInvk->Callback = pPts->Callback;
256  pInvk->Context = pPts->Header.Context;
257  pInvk->Flags = pPts->Header.Flags;
258  pInvk->OldEntry = pPts->OldEntry;
259  pInvk->NewEntry = pPts->CurEntry;
260  pInvk->OldPageSize = pPts->OldPageSize;
261  pInvk->NewPageSize = pPts->CurPageSize;
262  pInvk->VirtualAddress = pPts->VirtualAddress;
263  pInvk->Hook = pPts;
264 
266  }
267  }
268 }
269 
270 
271 static INTSTATUS
273  _In_ LIST_HEAD *Callbacks
274  )
287 {
288  // We can now safely invoke the callbacks. If we would invoke the swap-in callbacks while holding the
289  // PTS lock, then placing or removing paged hooks would become impossible.
290  INTSTATUS status;
291  LIST_ENTRY *list;
292  BOOLEAN deny, pause;
293  PINVOCATION_CONTEXT pInvk;
294 
295  deny = pause = FALSE;
296 
297  pInvk = CONTAINING_RECORD(Callbacks->Flink, INVOCATION_CONTEXT, Link);
298  if (!!(pInvk->Flags & HOOK_FLG_HIGH_PRIORITY))
299  {
300  pause = TRUE;
301  IntPauseVcpus();
302  }
303 
304  list = Callbacks->Flink;
305  while (list != Callbacks)
306  {
307  pInvk = CONTAINING_RECORD(list, INVOCATION_CONTEXT, Link);
308 
309  list = list->Flink;
310 
311  // VERY IMPORTANT: If this PTS hook has been flagged for removal, we mustn't call the callback, as it most
312  // likely doesn't expect to be called, and resources may have already been freed, leading to use-after-free.
313  // Concrete example: we have 2 swapmem callbacks on the same page. The page gets swapped in, and the first
314  // callback is called; this callback decides to remove the transaction for the second callback, which will
315  // free both the SWAPMEM_TRANSACTION and the SWAPMEM_PAGE structure. However, if we are not careful to NOT call
316  // the second swap-in callback when the PTS hook has been removed, we will trigger the use-after-free, in that
317  // the second callback will access the SWAPMEM_PAGE or the SWAPMEM_TRANSACTION structures, even though they
318  // were freed by the first callback. The fix is trivial - simply avoid calling a PTS callback if the PTS hook
319  // is flagged for removal.
320  if (!!(pInvk->Hook->Header.Flags & (HOOK_FLG_DISABLED | HOOK_FLG_REMOVE)))
321  {
322  LOG("[PTS] Skipping calling the PTS callback for VA 0x%016llx\n", pInvk->VirtualAddress);
323  goto _skip_call;
324  }
325 
326  status = pInvk->Callback(pInvk->Context,
327  pInvk->VirtualAddress,
328  pInvk->OldEntry,
329  pInvk->NewEntry,
330  pInvk->OldPageSize,
331  pInvk->NewPageSize);
332  if (!INT_SUCCESS(status))
333  {
334  ERROR("[ERROR] Callback failed: 0x%08x\n", status);
335 
336  if (INT_STATUS_ACCESS_DENIED == status)
337  {
338  deny = TRUE;
339  }
340  }
341  else if (INT_STATUS_REMOVE_HOOK_ON_RET == status)
342  {
343  status = IntHookPtsRemoveHook((HOOK_PTS **)&pInvk->Hook, 0);
344  if (!INT_SUCCESS(status))
345  {
346  ERROR("[ERROR] IntHookPtsRemoveHook failed: 0x%08x\n", status);
347  }
348  }
349 
350 _skip_call:
351  RemoveEntryList(&pInvk->Link);
352 
353  if (!pInvk->Static)
354  {
356  }
357  }
358 
359  if (pause)
360  {
361  IntResumeVcpus();
362  }
363 
364  if (deny)
365  {
367  }
368 
369  return INT_STATUS_SUCCESS;
370 }
371 
372 
373 static INTSTATUS
375  _In_opt_ void *Context,
376  _In_ void *Hook,
377  _In_ QWORD Address,
378  _Out_ INTRO_ACTION *Action
379  )
396 {
397  INTSTATUS status;
398  PHOOK_PTS_ENTRY pPt;
399  QWORD newValue, oldValue;
400  LIST_HEAD localCallbacksList;
401  BOOLEAN exitfn;
402 
404 
405  if (Context == NULL)
406  {
408  }
409 
410  if (Action == NULL)
411  {
413  }
414 
415  status = INT_STATUS_SUCCESS;
416  newValue = oldValue = 0;
417  exitfn = FALSE;
418  InitializeListHead(&localCallbacksList);
419  gInvkCtxIndex = 0;
420 
421  *Action = introGuestAllowed;
422 
423  pPt = (PHOOK_PTS_ENTRY)Context;
424 
426 
427  // If the entry was removed in the meantime, bail out.
428  if (0 != (pPt->Header.Flags & (HOOK_FLG_DISABLED | HOOK_FLG_REMOVE)))
429  {
430  goto cleanup_and_exit;
431  }
432 
433  status = IntHookPtwProcessWrite(&pPt->WriteState, Address, pPt->EntrySize, &oldValue, &newValue);
434  if ((INT_STATUS_PAGE_NOT_PRESENT == status) || (INT_STATUS_NO_MAPPING_STRUCTURES == status))
435  {
436  *Action = introGuestAllowed;
437  status = INT_STATUS_SUCCESS;
438  exitfn = TRUE;
439  goto cleanup_and_exit;
440  }
441  else if (!INT_SUCCESS(status))
442  {
443  ERROR("[ERROR] IntHookPtwProcessWrite failed at %llx: 0x%08x\n", Address, status);
445  goto cleanup_and_exit;
446  }
447  else if ((INT_STATUS_PARTIAL_WRITE == status) || (INT_STATUS_NOT_NEEDED_HINT == status))
448  {
449  status = INT_STATUS_SUCCESS;
450  goto cleanup_and_exit;
451  }
452 
453  // Either no interesting bits were modified (P, PSE or GPA) or both the old and new values are invalid.
454  if (((oldValue & HOOK_PTS_MONITORED_BITS) == (newValue & HOOK_PTS_MONITORED_BITS)) ||
455  (0 == ((oldValue & PT_P) + (newValue & PT_P))))
456  {
457  status = INT_STATUS_SUCCESS;
458  goto cleanup_and_exit;
459  }
460 
461  gHooks->PtsHooks.CallbacksList = &localCallbacksList;
462 
463  status = IntHookPtsHandleModification(pPt, oldValue, newValue);
464  if (!INT_SUCCESS(status))
465  {
466  ERROR("[ERROR] IntHookPtsHandleModification failed: 0x%08x\n", status);
467  }
468 
469  gHooks->PtsHooks.CallbacksList = NULL;
470 
471 cleanup_and_exit:
472 
473  if (exitfn)
474  {
475  goto exit_fn;
476  }
477 
478  // We can now safely invoke the callbacks. If we would invoke the swap-in callbacks while holding the
479  // PTS lock, then placing or removing paged hooks would become impossible.
480  status = IntHookPtsInvokeCallbacks(&localCallbacksList);
481  if (!INT_SUCCESS(status))
482  {
483  ERROR("[ERROR] IntHookPtsInvokeCallbacks failed: 0x%08x\n", status);
484  }
485 
486  // If any of the swap-in callbacks returned INT_STATUS_ACCESS_DENIED, than we will block this write.
487  if (INT_STATUS_ACCESS_DENIED == status)
488  {
489  TRACE("[PTS] Callback returned INT_STATUS_ACCESS_DENIED, will block the PT write.\n");
490  *Action = introGuestNotAllowed;
491  }
492 
494 
495  status = INT_STATUS_SUCCESS;
496 
497 exit_fn:
498 
499  if ((gGuest.KernelBetaDetections) && (*Action == introGuestNotAllowed))
500  {
501  *Action = introGuestAllowed;
502  }
503 
504  return status;
505 }
506 
507 
508 static INTSTATUS
510  _In_ PHOOK_PTS_ENTRY Entry,
512  )
521 {
522  INTSTATUS status;
523 
524  // Clear the delete PT hook flag for now.
525  Entry->Header.Flags &= ~HOOK_PTS_FLG_DELETE_PT_HOOK;
526 
527  if ((NULL != Entry->PtPaHook) && Entry->PtPaHookSet)
528  {
529  // Remove the hook established on the page table entry.
530  status = IntHookPtmRemoveHook((HOOK_PTM **)&Entry->PtPaHook, HOOK_FLG_CHAIN_DELETE);
531  if (!INT_SUCCESS(status))
532  {
533  ERROR("[ERROR] IntHookPtmRemoveHook failed: 0x%08x\n", status);
534  }
535 
536  // Mark the fact that the PT entry is not hooked anymore.
537  Entry->PtPaHookSet = FALSE;
538 
539  // Mark that we have to delete this PT hook.
540  Entry->Header.Flags |= HOOK_PTS_FLG_DELETE_PT_HOOK;
541  }
542 
543  RemoveEntryList(&Entry->Link);
544 
545  switch (Entry->Level)
546  {
547  case 1:
549  break;
550  case 2:
552  break;
553  case 3:
555  break;
556  case 4:
558  break;
559  case 5:
561  break;
562  case 6:
564  break;
565  default:
566  break;
567  }
568 
569  // Update the flags.
570  Entry->Header.Flags |= (HOOK_FLG_REMOVE);
571 
572  if (Flags & HOOK_FLG_CHAIN_DELETE)
573  {
574  Entry->Header.Flags |= HOOK_FLG_CHAIN_DELETE;
575  }
576 
577  // Flag the state as being dirty.
579 
580  return INT_STATUS_SUCCESS;
581 }
582 
583 
584 static INTSTATUS
588  )
601 {
602  INTSTATUS status;
603  PHOOK_PTS_ENTRY pPt;
604  PHOOK_HEADER pChild;
605  BOOLEAN decRefCount;
606 
607  if (0 != (Hook->Header.Flags & HOOK_FLG_REMOVE))
608  {
610  }
611 
612  status = INT_STATUS_SUCCESS;
613 
614  // We have to iterate through all the parents on this current branch, and decrement ref-counts where needed.
615  pPt = Hook->Parent; // We start from the Context entry.
616  decRefCount = TRUE; // And we have to decrement the ref count for now.
617  pChild = &Hook->Header; // The child is the current PTS structure.
618 
619  // While we have more parents and the ref-count decreased...
620  while ((NULL != pPt) && (decRefCount))
621  {
622  // Decrement the current ref count.
623  pPt->RefCount--;
624 
625  if (0 == pPt->RefCount)
626  {
628  if (!INT_SUCCESS(status))
629  {
630  ERROR("[ERROR] IntHookPtsRemovePteHook failed: 0x%08x\n", status);
631  }
632 
633  // We need to mark this child in order to delete the PT hook. Since he's the last child being removed,
634  // it's going to be its responsibility to remove the PT hook.
636 
637  decRefCount = TRUE;
638  }
639  else
640  {
641  decRefCount = FALSE;
642  }
643 
644  // Advance the child.
645  pChild = &pPt->Header;
646 
647  // And go up one level to the next PD.
648  pPt = pPt->Header.ParentHook;
649  }
650 
651  // We now mark the PTS hook for removal.
652  Hook->Header.Flags |= (HOOK_FLG_REMOVE);
653 
654  if (Flags & HOOK_FLG_CHAIN_DELETE)
655  {
656  Hook->Header.Flags |= HOOK_FLG_CHAIN_DELETE;
657  }
658 
659  RemoveEntryList(&Hook->Link);
660  RemoveEntryList(&Hook->PtsLink);
661 
662  // And insert it inside the removed PF hooks list.
664 
665  // Flag the state as being dirty.
667 
668  return status;
669 }
670 
671 
672 static INTSTATUS
676  )
687 {
688  INTSTATUS status;
689 
690  if ((NULL != Hook->PtPaHook) && (0 != (Hook->Header.Flags & HOOK_PTS_FLG_DELETE_PT_HOOK)))
691  {
692  status = IntHookPtmDeleteHook(&Hook->PtPaHook, Flags);
693  if (!INT_SUCCESS(status))
694  {
695  ERROR("[ERROR] IntHookPtmDeleteHook failed: 0x%08x\n", status);
696  }
697  }
698 
699  RemoveEntryList(&Hook->Link);
700 
701  // Delete the current PD hook. All the upper level hooks have already been removed.
703 
704  return INT_STATUS_SUCCESS;
705 }
706 
707 
708 static INTSTATUS
712  )
723 {
724  INTSTATUS status;
725 
726  if (NULL == Hook)
727  {
729  }
730 
731  // We must have a valid parent & we must have the delete PD hook flag set, which basically means that we must
732  // delete the parent.
733  if ((NULL != Hook->Header.ParentHook) && (0 != (Hook->Header.Flags & HOOK_PTS_FLG_DELETE_PD_HOOK)))
734  {
735  status = IntHookPtsDeleteParents(Hook->Header.ParentHook, Flags);
736  if (!INT_SUCCESS(status))
737  {
738  ERROR("[ERROR] IntHookPtDeleteParents failed for level %d: 0x%08x\n", Hook->Level + 1, status);
739  }
740 
741  Hook->Header.ParentHook = NULL;
742  }
743 
744  // actually delete this entry.
745  status = IntHookPtsDeletePdHook(Hook, Flags);
746  if (!INT_SUCCESS(status))
747  {
748  ERROR("[ERROR] IntHookPtDeletePdHook failed: 0x%08x\n", status);
749  }
750 
751  return INT_STATUS_SUCCESS;
752 }
753 
754 
755 static INTSTATUS
759  )
770 {
771  INTSTATUS status;
772 
773  status = INT_STATUS_SUCCESS;
774 
775  // Remove all the parents, if needed.
776  if ((NULL != Hook->Parent) && (0 != (Hook->Header.Flags & HOOK_PTS_FLG_DELETE_PD_HOOK)))
777  {
778  status = IntHookPtsDeleteParents(Hook->Parent, Flags);
779  if (!INT_SUCCESS(status))
780  {
781  ERROR("[ERROR] IntHookPtsDeleteParents failed: 0x%08x\n", status);
782  }
783 
784  Hook->Parent = NULL;
785  }
786 
787  RemoveEntryList(&Hook->Link);
788 
790 
791  return status;
792 }
793 
794 
795 static PHOOK_PTS_ENTRY
797  _In_ LIST_HEAD *ListHead,
798  _In_ QWORD PhysicalAddress
799  )
808 {
809  LIST_ENTRY *list;
810  LIST_HEAD *listHead;
811 
812  listHead = ListHead;
813 
814  list = listHead->Flink;
815  while (list != listHead)
816  {
818  list = list->Flink;
819 
820  if (pPt->PtPaAddress == PhysicalAddress)
821  {
822  return pPt;
823  }
824  }
825 
826  return NULL;
827 }
828 
829 
830 static INTSTATUS
832  _In_ PHOOK_PTS_ENTRY Entry,
833  _In_ QWORD NewPtPaAddress,
834  _In_ QWORD NewPteValue
835  )
848 {
849  INTSTATUS status;
850 
851  // They are not needed here.
852  UNREFERENCED_PARAMETER(NewPtPaAddress);
853  UNREFERENCED_PARAMETER(NewPteValue);
854 
855  // If the GPA hook is set on this entry PTE, then remove it.
856  if (Entry->PtPaHookSet && (NULL != Entry->PtPaHook))
857  {
858  status = IntHookPtmRemoveHook((HOOK_PTM **)&Entry->PtPaHook, 0);
859  if (!INT_SUCCESS(status))
860  {
861  ERROR("[ERROR] IntHookPtmRemoveHook failed: 0x%08x\n", status);
862  }
863  }
864 
865  Entry->PtPaHookSet = FALSE;
866  Entry->PtPaAddress = 0;
867 
868  return INT_STATUS_SUCCESS;
869 }
870 
871 
872 static INTSTATUS
874  _In_ PHOOK_PTS_ENTRY Entry,
875  _In_ QWORD NewPtPaAddress
876  )
888 {
889  INTSTATUS status;
890 
891  // Remove the old hook, if there's one.
892  if (Entry->PtPaHookSet && (NULL != Entry->PtPaHook))
893  {
894  if (Entry->PtPaAddress != NewPtPaAddress)
895  {
896  status = IntHookPtmRemoveHook((HOOK_PTM **)&Entry->PtPaHook, 0);
897  if (!INT_SUCCESS(status))
898  {
899  ERROR("[ERROR] IntHookPtmRemoveHook failed: 0x%08x\n", status);
900  }
901  }
902  else
903  {
904  return INT_STATUS_SUCCESS;
905  }
906  }
907 
908  // Establish a new hook on this PTE.
909  status = IntHookPtmSetHook(NewPtPaAddress,
911  Entry,
912  Entry,
914  &Entry->PtPaHook);
915  if (!INT_SUCCESS(status))
916  {
917  ERROR("[ERROR] IntHookPtmSetHook failed: 0x%08x\n", status);
918  return status;
919  }
920 
921  // Update the internal flags.
922  Entry->PtPaHookSet = TRUE;
923 
924  Entry->PtPaAddress = NewPtPaAddress;
925 
926  return INT_STATUS_SUCCESS;
927 }
928 
929 
930 static INTSTATUS
932  _In_ PHOOK_PTS_ENTRY Entry,
933  _In_ QWORD NewPtPaAddress
934  )
946 {
947  INTSTATUS status;
948 
949  status = IntHookPtsDisableEntry(Entry, 0, 0);
950  if (!INT_SUCCESS(status))
951  {
952  ERROR("[ERROR] IntHookPtDisableEntry failed: 0x%08x\n", status);
953  return status;
954  }
955 
956  status = IntHookPtsEnableEntry(Entry, NewPtPaAddress);
957  if (!INT_SUCCESS(status))
958  {
959  ERROR("[ERROR] IntHookPtsEnableEntry failed: 0x%08x\n", status);
960  return status;
961  }
962 
963  return INT_STATUS_SUCCESS;
964 }
965 
966 
967 static INTSTATUS
969  _In_ PHOOK_PTS_ENTRY MergeRoot,
970  _In_ PHOOK_PTS_ENTRY Entry
971  )
985 {
986  INTSTATUS status;
987  LIST_ENTRY *list;
988 
989  // First of all, do the recursive calls.
990  list = Entry->ChildrenEntries.Flink;
991  while (list != &Entry->ChildrenEntries)
992  {
994 
995  list = list->Flink;
996 
997  // Go deeper & parse the lower level tables.
998  status = IntHookPtsMergeEntry(MergeRoot, pPt);
999  if (!INT_SUCCESS(status))
1000  {
1001  // We can safely continue in case of failure. Any error generated inside IntHookPtsMergeEntry is fatal
1002  // and would normally lead to a BugCheck.
1003  ERROR("[ERROR] IntHookPtsMergeEntry failed: 0x%08x\n", status);
1004  }
1005 
1006  // We know for sure that pPt PTE, which is a child of Entry, was removed; therefore, we can safely decrement
1007  // the ref count.
1008  if (Entry->RefCount > 0)
1009  {
1010  Entry->RefCount--;
1011  }
1012  else
1013  {
1014  ERROR("[ERROR] Entry %p has refcount 0!\n", Entry);
1017  }
1018  }
1019 
1020  // If the root is the same with the entry then we're done.
1021  if (MergeRoot == Entry)
1022  {
1023  goto done;
1024  }
1025 
1026  list = Entry->ContextEntries.Flink;
1027  while (list != &Entry->ContextEntries)
1028  {
1029  PHOOK_PTS pPts = CONTAINING_RECORD(list, HOOK_PTS, Link);
1030 
1031  list = list->Flink;
1032 
1033  // Remove this context entry from the current leaf.
1034  RemoveEntryList(&pPts->Link);
1035 
1036  // Insert this context entry inside the new leaf (the new large page)
1037  IntHookAddCallbackToList(&MergeRoot->ContextEntries, pPts);
1039 
1040  // Update the ref count of the current entry.
1041  Entry->RefCount--;
1042 
1043  // Update the ref count of the new leaf (the large page)
1044  MergeRoot->RefCount++;
1045 
1046  // And update the context parent pointer.
1047  pPts->Parent = MergeRoot;
1048  }
1049 
1050  // Right here, the current Entry refCount _must_ be zero. If it isn't, something went wrong.
1051  if (0 != Entry->RefCount)
1052  {
1053  ERROR("[ERROR] Entry %p refCount is %d after entry merging!\n", Entry, Entry->RefCount);
1056  }
1057 
1058  // We can flag this entry for removal
1059  status = IntHookPtsRemovePteHook(Entry, 0);
1060  if (!INT_SUCCESS(status))
1061  {
1062  ERROR("[ERROR] IntHookPtRemovePteHook failed: 0x%08x\n", status);
1064  return status;
1065  }
1066 
1067 done:
1068  // Done!
1069  return INT_STATUS_SUCCESS;
1070 }
1071 
1072 
1073 static INTSTATUS
1075  _In_ PHOOK_PTS_ENTRY Entry,
1076  _In_ QWORD NewPtPaAddress,
1077  _In_ QWORD NewPteValue
1078  )
1088 {
1089  UNREFERENCED_PARAMETER(Entry);
1090  UNREFERENCED_PARAMETER(NewPtPaAddress);
1091  UNREFERENCED_PARAMETER(NewPteValue);
1092 
1093  // There is nothing to do here.
1094 
1095  return INT_STATUS_SUCCESS;
1096 }
1097 
1098 
1099 static INTSTATUS
1101  _In_ PHOOK_PTS_ENTRY Entry,
1102  _In_ QWORD OldValue,
1103  _In_ QWORD NewValue
1104  )
1125 {
1126  INTSTATUS status;
1127  QWORD curValue, newValue;
1128  BOOLEAN curValid, newValid, curPse, newPse, pseChanged;
1129  LIST_ENTRY *list;
1130 
1131  // Now handle the actual modification. Update the current entry.
1132  curValue = OldValue;
1133  newValue = NewValue;
1134 
1135  curValid = (0 != (curValue & 1));
1136  newValid = (0 != (newValue & 1));
1137 
1138  // Both the old & the new values are invalid. Bail out.
1139  if (!curValid && !newValid)
1140  {
1142  }
1143 
1145 
1146  if (newValid)
1147  {
1148  if (Entry->Level != 1)
1149  {
1150  curPse = (0 != (Entry->IsPs));
1151  newPse = (0 != (newValue & PD_PS));
1152  }
1153  else
1154  {
1155  curPse = newPse = FALSE;
1156  }
1157  }
1158  else
1159  {
1160  // The entry is not valid, we don't care about the PSE.
1161  curPse = newPse = FALSE;
1162  }
1163 
1164  pseChanged = (newValid && (curPse != newPse));
1165 
1166  // Handle PSE changes and non-leaf modifications.
1167  if (newValid && ((!Entry->IsLeaf && !pseChanged) || (Entry->IsLeaf && curPse && !newPse)))
1168  {
1169  // This happens in two cases:
1170  // 1. non-leaf entry is modified (no PSE change)
1171  // 2. a large page is remapped as smaller pages
1172  //
1173  // Two important things must be handled here:
1174  // 1. Contexts that have been added while this entry was invalid - lower level page tables will be allocated and
1175  // the contexts will be propagated downwards
1176  // 2. A PTE that previously pointed to a large page now points to smaller pages - we basically have to do the
1177  // exact same thing.
1178 
1179  DWORD offsetsCount, index;
1180  QWORD offsets[8] = { 0 };
1181  PHOOK_PTS_ENTRY pPt;
1182  static PHOOK_PTS_ENTRY cache[1024]; // Used for fast lookup to already allocated entries.
1183  BOOLEAN useCache = FALSE;
1184 
1185  memzero(cache, sizeof(cache));
1186 
1187  offsetsCount = 0;
1188 
1189  // If there are no children entries, we can use the cache for faster lookup for entries which have just
1190  // been allocated.
1191  useCache = IsListEmpty(&Entry->ChildrenEntries);
1192 
1193  list = Entry->ContextEntries.Flink;
1194  while (list != &Entry->ContextEntries)
1195  {
1196  BYTE level;
1197  QWORD childPtPaAddress, offset;
1198  PHOOK_PTS pPts = CONTAINING_RECORD(list, HOOK_PTS, Link);
1199 
1200  list = list->Flink;
1201 
1202  // Split the address.
1203  IntSplitVirtualAddress(pPts->VirtualAddress, &offsetsCount, offsets);
1204 
1205  // Get this new entry PtPaAddress.
1206  level = (BYTE)(offsetsCount - Entry->Level + 1);
1207 
1208  offset = offsets[level];
1209  index = (DWORD)(offset / Entry->EntrySize);
1210 
1211  childPtPaAddress = CLEAN_PHYS_ADDRESS64(newValue) + offset;
1212 
1213  if (useCache)
1214  {
1215  pPt = cache[index];
1216  }
1217  else
1218  {
1219  pPt = IntHookPtsFindEntry(&Entry->ChildrenEntries, childPtPaAddress);
1220  }
1221  if (NULL == pPt)
1222  {
1223  // The child PT entry does not yet exist; We have to alloc it. Hook it if the PS changed, otherwise
1224  // it will be hooked when parsing all the children entries.
1225  status = IntHookPtsCreateEntry(childPtPaAddress,
1226  Entry->EntrySize | ((Entry->Level - 1) << 8), Entry, &pPt);
1227  if (!INT_SUCCESS(status))
1228  {
1229  ERROR("[ERROR] IntHookPtCreateEntry failed for %llx: 0x%08x\n", childPtPaAddress, status);
1230  continue;
1231  }
1232 
1233  cache[index] = pPt;
1234  }
1235 
1236  // For each moved context we have to decrement the Entry refCount.
1237  if (Entry->RefCount > 0)
1238  {
1239  Entry->RefCount--;
1240  }
1241  else
1242  {
1243  ERROR("[ERROR] Entry %p refCount is zero!\n", Entry);
1245  }
1246 
1247  // Remove the context from this Entry, and insert it inside the child contexts entries.
1248  RemoveEntryList(&pPts->Link);
1249 
1251 
1252  // Update the PTS hook parent.
1253  pPts->Parent = pPt;
1254 
1255  // Update the pPt ref count.
1256  pPt->RefCount++;
1257  }
1258  }
1259  else if (newValid && !curPse && newPse)
1260  {
1261  // This happens in one case:
1262  // 1. small pages are remapped as a large page
1263  // Note that if the entry is non-leaf and it was valid but now it becomes invalid, the children will simply
1264  // be marked invalid. There's no need to merge all the lower-level entries, because we don't destroy the
1265  // lower-level tree. The only cases where the tree has to be re-arranged are: small pages maps as large page,
1266  // large page mapped as small pages or non-leaf entry being mapped for the first time.
1267 
1268  status = IntHookPtsMergeEntry(Entry, Entry);
1269  if (!INT_SUCCESS(status))
1270  {
1271  ERROR("[ERROR] IntHookPtMergeEntry failed: 0x%08x\n", status);
1272  }
1273 
1274  // If we get here, the ChildrenEntries should already be empty.
1275  if (!IsListEmpty(&Entry->ChildrenEntries))
1276  {
1277  ERROR("[ERROR] Just merged entry %108p, but the ChildrenList is not empty!\n", Entry);
1279  }
1280  }
1281 
1282  // Parse the existing children.
1283  list = Entry->ChildrenEntries.Flink;
1284  while (list != &Entry->ChildrenEntries)
1285  {
1287  QWORD oldChildValue, newChildValue, newPtPaAddress;
1288 
1289  newChildValue = 0;
1290 
1291  // Fetch the new value that lies inside the new children PTE
1292  newPtPaAddress = CLEAN_PHYS_ADDRESS64(newValue) + pPt->EntryOffset;
1293 
1294  // Parse the current entry.
1295  if (!newValid)
1296  {
1297  // The entry is not valid. We must remove the hook.
1298  status = IntHookPtsDisableEntry(pPt, 0, 0);
1299  if (!INT_SUCCESS(status))
1300  {
1301  ERROR("[ERROR] IntHookPtsDisableEntry failed: 0x%08x\n", status);
1302  }
1303  }
1304  else
1305  {
1306  // The entry is now valid. Handle each separate case.
1307 
1308  if (pseChanged)
1309  {
1310  // curPse && !newPse
1311  // One 1G/4M/2M page re-mapped as smaller pages.
1312  // This is handled when collapsing contexts into new, lower-level page table entries.
1313  // Handled above
1314 
1315  // !curPse && newPse
1316  // Smaller pages re-mapped as larger pages.
1317  // Handled above
1318  }
1319  else if (!curValid)
1320  {
1321  // The entry was not valid, now it is and the PSE is the same.
1322  status = IntHookPtsEnableEntry(pPt, newPtPaAddress);
1323  if (!INT_SUCCESS(status))
1324  {
1325  ERROR("[ERROR] IntHookPtsEnableEntry failed: 0x%08x\n", status);
1326  }
1327  }
1328  else if (CLEAN_PHYS_ADDRESS64(curValue) != CLEAN_PHYS_ADDRESS64(newValue))
1329  {
1330  // The entry was valid, but the physical address changed.
1331  status = IntHookPtsRemapEntry(pPt, newPtPaAddress);
1332  if (!INT_SUCCESS(status))
1333  {
1334  ERROR("[ERROR] IntHookPtsRemapEntry failed: 0x%08x\n", status);
1335  }
1336  }
1337  else
1338  {
1339  // Control bits were modified. Ignored for now.
1340  }
1341  }
1342 
1343  // Fetch the new child value after placing the PT hooks, in order to avoid PT modifications made between
1344  // the moment where we read the value and the moment where the EPT hook is actually placed.
1345  if (newValid && !newPse)
1346  {
1347  status = IntGpaCacheFetchAndAdd(gGuest.GpaCache, newPtPaAddress, pPt->EntrySize, (PBYTE)&newChildValue);
1348  if (!INT_SUCCESS(status))
1349  {
1350  ERROR("[ERROR] IntGpaCacheFetchAndAdd failed: 0x%08x\n", status);
1351  newChildValue = 0;
1352  }
1353  }
1354 
1355  oldChildValue = pPt->WriteState.CurEntry;
1356 
1357  // Simulate a write inside the child.
1358  pPt->WriteState.CurEntry = newChildValue;
1359 
1360  // Recursive call.
1361  status = IntHookPtsHandleModification(pPt, oldChildValue, newChildValue);
1362  if (!INT_SUCCESS(status))
1363  {
1364  ERROR("[ERROR] IntHookPtsHandleModification failed: 0x%08x\n", status);
1365  }
1366 
1367  list = list->Flink;
1368  }
1369 
1370 
1371  Entry->IsValid = newValid;
1372 
1373  if (newValid)
1374  {
1375  Entry->IsPs = newPse;
1376 
1377  Entry->IsLeaf = (Entry->IsPs) || (1 == Entry->Level);
1378  }
1379 
1380 
1381  // If this is a leaf entry and we have any kind of modification between the old value and the new one, we can
1382  // invoke the registered callbacks.
1383  if (Entry->IsLeaf)
1384  {
1385  IntHookPtsCloneCallbacks(Entry);
1386  }
1387 
1389 
1390  return INT_STATUS_SUCCESS;
1391 }
1392 
1393 
1394 static __inline INTSTATUS
1396  _In_ QWORD PtPaAddress,
1397  _In_ WORD EntrySizeAndLevel,
1398  _In_opt_ PHOOK_PTS_ENTRY Parent,
1399  _Out_ PHOOK_PTS_ENTRY *Entry
1400  )
1409 {
1410  INTSTATUS status;
1411  HOOK_PTS_ENTRY *pPt;
1412  DWORD flags;
1413 
1414  status = INT_STATUS_SUCCESS;
1415  *Entry = NULL;
1416 
1417  pPt = HpAllocWithTag(sizeof(*pPt), IC_TAG_PTPT);
1418  if (NULL == pPt)
1419  {
1421  }
1422 
1423  // Initialize and fill the entry.
1426 
1427  // Initialize the header.
1428  pPt->Header.Flags = 0;
1429  pPt->Header.Context = NULL;
1430  pPt->Header.ParentHook = Parent;
1431  pPt->Header.HookType = hookTypePtsPt;
1432 
1433  pPt->EntrySize = EntrySizeAndLevel & 0xFF;
1434  pPt->Level = EntrySizeAndLevel >> 8;
1435  pPt->IsLeaf = pPt->Level == 1;
1436  pPt->PtPaHookSet = FALSE;
1437  pPt->IsValid = FALSE;
1438  pPt->IsPs = FALSE;
1439  pPt->WriteState.IntEntry = 0;
1440  pPt->WriteState.CurEntry = 0;
1441  pPt->WriteState.WrittenMask = 0;
1442  pPt->PtPaAddress = PtPaAddress;
1443  pPt->RefCount = 0;
1444  pPt->PtPaHook = NULL;
1445  pPt->EntryOffset = (DWORD)(PtPaAddress & PAGE_OFFSET);
1446 
1447  if (pPt->Level < PTS_LEVEL_ROOT)
1448  {
1449  if ((pPt->Level == 3) && (gGuest.Mm.Mode == PAGING_PAE_MODE))
1450  {
1451  // Top level mapping structure (PDPTE) on PAE paging - we only need to hook 0x20 bytes (4 entries).
1452  flags = HOOK_FLG_PAE_ROOT;
1453  }
1454  else if (((CLEAN_PHYS_ADDRESS64(PtPaAddress) != CLEAN_PHYS_ADDRESS64(gGuest.Mm.SystemCr3))) &&
1455  (((pPt->Level == 5) && (gGuest.Mm.Mode == PAGING_5_LEVEL_MODE)) ||
1456  ((pPt->Level == 4) && (gGuest.Mm.Mode == PAGING_4_LEVEL_MODE)) ||
1457  ((pPt->Level == 2) && (gGuest.Mm.Mode == PAGING_NORMAL_MODE))))
1458  {
1459  // Top level mapping inside a user process CR3 - we only need to hook the low half of the entries.
1460  flags = HOOK_FLG_PT_UM_ROOT;
1461  }
1462  else
1463  {
1464  // Generic paging structure, otherwise.
1465  flags = HOOK_FLG_PAGING_STRUCTURE;
1466  }
1467 
1468  // Place the write hook, only if it is not the root.
1469  status = IntHookPtmSetHook(pPt->PtPaAddress,
1471  pPt,
1472  pPt,
1473  flags,
1474  &pPt->PtPaHook);
1475  if (!INT_SUCCESS(status))
1476  {
1477  ERROR("[ERROR] IntHookPtmSetHook failed: 0x%08x\n", status);
1478 
1479  // And leave!
1480  goto cleanup_and_exit;
1481  }
1482 
1483  // IMPORTANT: Fetch data from the page-table page only after the hook has been set on it. Otherwise, we may
1484  // race with the guest: the page-table entries may be modified since the entry has been read and until the
1485  // table has been hooked, leading to inconsistencies.
1486 
1487  // Also, if this is not the root, fetch the current entry inside the PT.
1489  pPt->PtPaAddress,
1490  pPt->EntrySize,
1491  (PBYTE)&pPt->WriteState.CurEntry);
1492  if (!INT_SUCCESS(status))
1493  {
1494  ERROR("[ERROR] IntGpaCacheFetchAndAdd failed for 0x%016llx: 0x%08x\n", pPt->PtPaAddress, status);
1495 
1496  // And leave!
1497  goto cleanup_and_exit;
1498  }
1499 
1500  // Save the valid flag.
1501  pPt->IsValid = (0 != (pPt->WriteState.CurEntry & 1));
1502 
1503  // Save the PS flag.
1504  pPt->IsPs = (pPt->IsValid) && (pPt->Level != 1) && (0 != (pPt->WriteState.CurEntry & PD_PS));
1505 
1506  // Determine whether this is a leaf or not.
1507  pPt->IsLeaf = (pPt->Level == 1) || (pPt->IsPs);
1508 
1509  pPt->PtPaHookSet = TRUE;
1510  }
1511 
1512  if (NULL != Parent)
1513  {
1514  InsertTailList(&Parent->ChildrenEntries, &pPt->Link);
1515 
1516  Parent->RefCount++;
1517  }
1518 
1519 cleanup_and_exit:
1520  if (!INT_SUCCESS(status))
1521  {
1522  if (NULL != pPt)
1523  {
1525  }
1526  }
1527 
1528  *Entry = pPt;
1529 
1530  return status;
1531 }
1532 
1533 
1534 INTSTATUS
1536  _In_ QWORD Cr3,
1539  _In_opt_ void *Context,
1540  _In_opt_ void *Parent,
1541  _In_ DWORD Flags,
1543  )
1567 {
1568  INTSTATUS status;
1569  PHOOK_PTS pPts;
1570  QWORD pml5eAddress, pml4eAddress, pdpeAddress, pdeAddress, pteAddress;
1571  PHOOK_PTS_ENTRY root, pml5, pml4, pdp, pd, pt, pf;
1572  DWORD hid;
1573  LIST_ENTRY *list;
1574  BYTE entrySize;
1575  BOOLEAN doCleanup;
1576  PHOOK_PTS_STATE pPtsState;
1577 
1578  if (NULL == Callback)
1579  {
1581  }
1582 
1583  if (NULL == Hook)
1584  {
1586  }
1587 
1588  pPts = NULL;
1589  pml5eAddress = pml4eAddress = pdpeAddress = pdeAddress = pteAddress = 0;
1590  root = pml4 = pdp = pd = pt = pf = NULL;
1591  doCleanup = FALSE;
1592  pPtsState = &gHooks->PtsHooks;
1593 
1594  Flags &= HOOK_FLG_GLOBAL_MASK;
1595 
1596  if (0 == Cr3)
1597  {
1598  Cr3 = gGuest.Mm.SystemCr3;
1599  }
1600 
1601  if ((Cr3 != gGuest.Mm.SystemCr3) && (( gGuest.Guest64 && !!(VirtualAddress & BIT(63))) ||
1602  (!gGuest.Guest64 && !!(VirtualAddress & BIT(31)))))
1603  {
1604  ERROR("[ERROR] Kernel mapping 0x%016llx hook set inside non-system CR3 0x%016llx!\n", VirtualAddress, Cr3);
1605  return INT_STATUS_NOT_SUPPORTED;
1606  }
1607 
1608  status = INT_STATUS_SUCCESS;
1609 
1610  // Entries may be 4 bytes or 8 bytes in length. They are 4 bytes in legacy paging mode only.
1611  entrySize = (gGuest.Mm.Mode == PAGING_NORMAL_MODE ? 4 : 8);
1612 
1613  pPts = HpAllocWithTag(sizeof(*pPts), IC_TAG_PTPS);
1614  if (NULL == pPts)
1615  {
1617  }
1618 
1619  pPts->Header.Flags = Flags;
1620  pPts->Header.ParentHook = Parent;
1621  pPts->Header.Context = Context;
1622  pPts->Header.HookType = hookTypePts;
1623 
1624  pPts->Callback = Callback;
1626 
1627  pPts->Cr3 = Cr3;
1628 
1629  // Search the root. The key is the Cr3.
1630  if (PAGING_PAE_MODE == gGuest.Mm.Mode)
1631  {
1632  hid = HOOK_PT_PAE_ROOT_HASH_ID(Cr3);
1633  }
1634  else
1635  {
1636  hid = HOOK_PT_HASH_ID(Cr3);
1637  }
1638 
1639  list = gHooks->PtsHooks.HooksRootList[hid].Flink;
1640  while (list != &gHooks->PtsHooks.HooksRootList[hid])
1641  {
1642  root = CONTAINING_RECORD(list, HOOK_PTS_ENTRY, Link);
1643 
1644  list = list->Flink;
1645 
1646  if (root->PtPaAddress == Cr3)
1647  {
1648  break;
1649  }
1650 
1651  root = NULL;
1652  }
1653 
1654  // If we didn't find a root yet, create one.
1655  if (NULL == root)
1656  {
1657  // Level 6 is just a magic that indicates the root of translation.
1658  status = IntHookPtsCreateEntry(Cr3,
1659  entrySize | (PTS_LEVEL_ROOT << 8),
1660  NULL,
1661  &root);
1662  if (!INT_SUCCESS(status))
1663  {
1664  ERROR("[ERROR] IntHookPtsCreateEntry failed: 0x%08x\n", status);
1665  doCleanup = TRUE;
1666  goto cleanup_and_exit;
1667  }
1668 
1669  // Insert the root inside the roots hash table.
1671  }
1672 
1673  // Force the PF to root for now.
1674  pf = root;
1675 
1677  {
1678  pml5eAddress = (CLEAN_PHYS_ADDRESS64(Cr3)) + PML5_INDEX(VirtualAddress) * 8ull;
1679  }
1680 
1681  // Parse and hook the PML5 entry.
1682  if (0 != pml5eAddress)
1683  {
1684  // PML5 entry present, hook it if not already hooked.
1685  pml5 = IntHookPtsFindEntry(&root->ChildrenEntries, pml5eAddress);
1686  if (NULL == pml5)
1687  {
1688  status = IntHookPtsCreateEntry(pml5eAddress, entrySize | (PTS_LEVEL_PML5 << 8), root, &pml5);
1689  if (!INT_SUCCESS(status))
1690  {
1691  ERROR("[ERROR] IntHookPtsCreateEntry failed: 0x%08x\n", status);
1692  doCleanup = TRUE;
1693  goto cleanup_and_exit;
1694  }
1695  }
1696 
1697  pf = pml5;
1698  }
1699  else
1700  {
1701  pml5 = root;
1702  }
1703 
1705  {
1706  if (!!(pml5->WriteState.CurEntry & PT_P))
1707  {
1708  pml4eAddress = (CLEAN_PHYS_ADDRESS64(pml5->WriteState.CurEntry)) + PML4_INDEX(VirtualAddress) * 8ull;
1709  }
1710  }
1711  else if (PAGING_4_LEVEL_MODE == gGuest.Mm.Mode)
1712  {
1713  pml4eAddress = (CLEAN_PHYS_ADDRESS64(Cr3)) + PML4_INDEX(VirtualAddress) * 8ull;
1714  }
1715 
1716 
1717  // Parse and hook the PML4 entry.
1718  if (0 != pml4eAddress)
1719  {
1720  // PML4 entry present, hook it if not already hooked.
1721  pml4 = IntHookPtsFindEntry(&pml5->ChildrenEntries, pml4eAddress);
1722  if (NULL == pml4)
1723  {
1724  status = IntHookPtsCreateEntry(pml4eAddress, entrySize | (PTS_LEVEL_PML4 << 8), pml5, &pml4);
1725  if (!INT_SUCCESS(status))
1726  {
1727  ERROR("[ERROR] IntHookPtsCreateEntry failed: 0x%08x\n", status);
1728  doCleanup = TRUE;
1729  goto cleanup_and_exit;
1730  }
1731  }
1732 
1733  pf = pml4;
1734  }
1735  else
1736  {
1737  pml4 = root;
1738  }
1739 
1740  if (PAGING_5_LEVEL_MODE == gGuest.Mm.Mode ||
1742  {
1743  if (!!(pml4->WriteState.CurEntry & PT_P))
1744  {
1745  pdpeAddress = (CLEAN_PHYS_ADDRESS64(pml4->WriteState.CurEntry)) + PDP_INDEX(VirtualAddress) * 8ull;
1746  }
1747  }
1748  else if (PAGING_PAE_MODE == gGuest.Mm.Mode)
1749  {
1750  pdpeAddress = (CLEAN_PHYS_ADDRESS32PAE_ROOT(Cr3)) + PDPPAE_INDEX(VirtualAddress) * 8ull;
1751  }
1752 
1753 
1754  // PDP entry now.
1755  if (0 != pdpeAddress)
1756  {
1757  QWORD pdpeValue;
1758 
1759  // PDP entry present, hook it if not already hooked.
1760  pdp = IntHookPtsFindEntry(&pml4->ChildrenEntries, pdpeAddress);
1761  if (NULL == pdp)
1762  {
1763  status = IntHookPtsCreateEntry(pdpeAddress, entrySize | (PTS_LEVEL_PDP << 8), pml4, &pdp);
1764  if (!INT_SUCCESS(status))
1765  {
1766  ERROR("[ERROR] IntHookPtsCreateEntry failed: 0x%08x\n", status);
1767  doCleanup = TRUE;
1768  goto cleanup_and_exit;
1769  }
1770  }
1771 
1772  pf = pdp;
1773 
1774  pdpeValue = pdp->WriteState.CurEntry;
1775 
1776  // Check for 1G page.
1777  if ((0 != (pdpeValue & PDP_PS)) && (0 != (pdpeValue & PDP_P)))
1778  {
1779  pf->IsLeaf = TRUE;
1780  goto parse_done;
1781  }
1782  }
1783  else
1784  {
1785  pdp = root;
1786  }
1787 
1788  if (PAGING_5_LEVEL_MODE == gGuest.Mm.Mode ||
1791  {
1792  if (!!(pdp->WriteState.CurEntry & PT_P))
1793  {
1794  pdeAddress = (CLEAN_PHYS_ADDRESS64(pdp->WriteState.CurEntry)) + PD_INDEX(VirtualAddress) * 8ull;
1795  }
1796  }
1797  else if (PAGING_NORMAL_MODE == gGuest.Mm.Mode)
1798  {
1799  pdeAddress = (CLEAN_PHYS_ADDRESS32(Cr3)) + PD32_INDEX(VirtualAddress) * 4ull;
1800  }
1801 
1802 
1803  // PD entry now.
1804  if (0 != pdeAddress)
1805  {
1806  QWORD pdeValue;
1807 
1808  // PDP entry present, hook it if not already hooked.
1809  pd = IntHookPtsFindEntry(&pdp->ChildrenEntries, pdeAddress);
1810  if (NULL == pd)
1811  {
1812  status = IntHookPtsCreateEntry(pdeAddress, entrySize | (PTS_LEVEL_PD << 8), pdp, &pd);
1813  if (!INT_SUCCESS(status))
1814  {
1815  ERROR("[ERROR] IntHookPtsCreateEntry failed: 0x%08x\n", status);
1816  doCleanup = TRUE;
1817  goto cleanup_and_exit;
1818  }
1819  }
1820 
1821  pf = pd;
1822 
1823  pdeValue = pd->WriteState.CurEntry;
1824 
1825  // Check for 2M/4M large page.
1826  if ((0 != (pdeValue & PDP_PS)) && (0 != (pdeValue & PDP_P)))
1827  {
1828  pf->IsLeaf = TRUE;
1829  goto parse_done;
1830  }
1831  }
1832  else
1833  {
1834  pd = root;
1835  }
1836 
1837  if (PAGING_5_LEVEL_MODE == gGuest.Mm.Mode ||
1840  {
1841  if (!!(pd->WriteState.CurEntry & PT_P))
1842  {
1843  pteAddress = (CLEAN_PHYS_ADDRESS64(pd->WriteState.CurEntry)) + PT_INDEX(VirtualAddress) * 8ull;
1844  }
1845  }
1846  else if (PAGING_NORMAL_MODE == gGuest.Mm.Mode)
1847  {
1848  if (!!(pd->WriteState.CurEntry & PT_P))
1849  {
1850  pteAddress = (CLEAN_PHYS_ADDRESS32(pd->WriteState.CurEntry)) + PT32_INDEX(VirtualAddress) * 4ull;
1851  }
1852  }
1853 
1854 
1855  // PT entry now.
1856  if (0 != pteAddress)
1857  {
1858  // PDP entry present, hook it if not already hooked.
1859  pt = IntHookPtsFindEntry(&pd->ChildrenEntries, pteAddress);
1860  if (NULL == pt)
1861  {
1862  status = IntHookPtsCreateEntry(pteAddress, entrySize | (PTS_LEVEL_PT << 8), pd, &pt);
1863  if (!INT_SUCCESS(status))
1864  {
1865  ERROR("[ERROR] IntHookPtsCreateEntry failed: 0x%08x\n", status);
1866  doCleanup = TRUE;
1867  goto cleanup_and_exit;
1868  }
1869  }
1870 
1871  pf = pt;
1872 
1873  pf->IsLeaf = TRUE;
1874  goto parse_done;
1875  }
1876  else
1877  {
1878  pt = root;
1879  }
1880 
1881 parse_done:
1882 cleanup_and_exit:
1883 
1884  // We will now insert the current PTS hook inside the contexts list of the PF. Note that right now, we don't know
1885  // and we don't care who pf is; it might be the pml4 or it might be the pt (the structures may be incomplete).
1886  // The PTS hook will be moved up and down on the hierarchy whenever the translations are modified.
1887  if (NULL != pf)
1888  {
1889  // Add the PTS entry to the list.
1891 
1892  // Update the refcount.
1893  pf->RefCount++;
1894 
1895  // Mark the reference to the parent.
1896  pPts->Parent = pf;
1897  }
1898 
1899  // Insert the entry in the global PTS list (this needs to be done even in the case of a failure, because
1900  // IntHookPtsRemoveHookInternal will try to remove it from that list and will crash)
1901  InsertTailList(&pPtsState->HooksPtsList, &pPts->PtsLink);
1902 
1903  // Something went wrong; We need to free what we managed to allocate.
1904  if (doCleanup)
1905  {
1906  INTSTATUS status2;
1907 
1908  if (NULL != pf)
1909  {
1910  // The pf is initialized, we can remove the PTS.
1911  status2 = IntHookPtsRemoveHookInternal(pPts, 0);
1912  if (!INT_SUCCESS(status2))
1913  {
1914  ERROR("[ERROR] IntHookPtsRemoveHookInternal failed: 0x%08x\n", status2);
1915  }
1916  }
1917  else
1918  {
1919  // pf is NULL, we don't even have a root, so we just have to free the PTS descriptor.
1921  }
1922 
1923  pPts = NULL;
1924  }
1925  else if (NULL != pf)
1926  {
1927  // Store the current physical addresses and page size
1928  pPts->OldEntry = 0;
1929  pPts->OldPageSize = 0;
1930  pPts->CurEntry = pf->WriteState.CurEntry;
1931  pPts->CurPageSize = IntHookPtsGetPageSize(pf);
1932  }
1933 
1934  if (INT_SUCCESS(status))
1935  {
1936  *Hook = pPts;
1937  }
1938 
1939  return status;
1940 }
1941 
1942 
1943 INTSTATUS
1945  _Inout_ HOOK_PTS **Hook,
1946  _In_ DWORD Flags
1947  )
1960 {
1961  INTSTATUS status;
1962 
1963  if (NULL == Hook)
1964  {
1966  }
1967 
1968  if (NULL == *Hook)
1969  {
1971  }
1972 
1973  Flags &= HOOK_FLG_GLOBAL_MASK;
1974 
1975  status = IntHookPtsRemoveHookInternal(*Hook, Flags);
1976  if (!INT_SUCCESS(status))
1977  {
1978  ERROR("[ERROR] IntHookPtsRemoveHookInternal failed: 0x%08x\n", status);
1979  }
1980 
1981  // NOTE: If chain delete is requested, the caller will make sure to explicitly call delete on this hook. In this
1982  // case, don't NULL out the hook, as it's still needed, and it's not removed yet.
1983  if (!(Flags & HOOK_FLG_CHAIN_DELETE))
1984  {
1985  *Hook = NULL;
1986  }
1987 
1988  return status;
1989 }
1990 
1991 
1992 INTSTATUS
1994  _Inout_ HOOK_PTS **Hook,
1995  _In_ DWORD Flags
1996  )
2009 {
2010  INTSTATUS status;
2011 
2012  if (NULL == Hook)
2013  {
2015  }
2016 
2017  if (NULL == *Hook)
2018  {
2020  }
2021 
2022  Flags &= HOOK_FLG_GLOBAL_MASK;
2023 
2024  status = IntHookPtsDeleteHookInternal(*Hook, Flags);
2025  if (!INT_SUCCESS(status))
2026  {
2027  ERROR("[ERROR] IntHookPtsDeleteHookInternal failed: 0x%08x\n", status);
2028  }
2029 
2030  *Hook = NULL;
2031 
2032  return status;
2033 }
2034 
2035 
2036 static __inline INTSTATUS
2038  _In_ LIST_HEAD *ListHead
2039  )
2047 {
2048  INTSTATUS status;
2049  LIST_ENTRY *list;
2050 
2051  // Hooks have been removed, make sure we free them all.
2052  list = ListHead->Flink;
2053  while (list != ListHead)
2054  {
2056 
2057  list = list->Flink;
2058 
2059  if (0 != (pPt->Header.Flags & HOOK_FLG_CHAIN_DELETE))
2060  {
2061  continue;
2062  }
2063 
2064  if (0 != (pPt->Header.Flags & HOOK_FLG_REMOVE))
2065  {
2066  status = IntHookPtsDeletePdHook(pPt, 0);
2067  if (!INT_SUCCESS(status))
2068  {
2069  ERROR("[ERROR] IntHookPtsDeletePdHook failed: 0x%08x\n", status);
2070  }
2071  }
2072  else
2073  {
2074  WARNING("[WARNING] Unknown state for the hook at %p, flags %08x!\n", pPt, pPt->Header.Flags);
2075  IntEnterDebugger();
2076  }
2077  }
2078 
2079  return INT_STATUS_SUCCESS;
2080 }
2081 
2082 
2083 INTSTATUS
2085  void
2086  )
2096 {
2097  INTSTATUS status;
2098  LIST_ENTRY *list;
2099 
2101  {
2102  return INT_STATUS_SUCCESS;
2103  }
2104 
2105  // Cleanup the PTS list of hooks.
2107  while (list != &gHooks->PtsHooks.RemovedHooksPtsList)
2108  {
2109  PHOOK_PTS pPts = CONTAINING_RECORD(list, HOOK_PTS, Link);
2110  list = list->Flink;
2111 
2112  // Chain delete requested - the parent of this hook will decide when to delete it.
2113  if (0 != (pPts->Header.Flags & HOOK_FLG_CHAIN_DELETE))
2114  {
2115  continue;
2116  }
2117 
2118  // Hook is removed, we can delete it.
2119  if (0 != (pPts->Header.Flags & HOOK_FLG_REMOVE))
2120  {
2121  status = IntHookPtsDeleteHookInternal(pPts, 0);
2122  if (!INT_SUCCESS(status))
2123  {
2124  ERROR("[ERROR] IntHookPtsDeleteHookInternal failed: 0x%08x\n", status);
2125  }
2126  }
2127  else
2128  {
2129  ERROR("[ERROR] Unknown hook state for hook %p, flags %08x\n", pPts, pPts->Header.Flags);
2130  IntEnterDebugger();
2131  }
2132  }
2133 
2134  // We can try to clean-up the PTE hooks.
2136  if (!INT_SUCCESS(status))
2137  {
2138  ERROR("[ERROR] IntHookPtCleanupList failed: 0x%08x\n", status);
2139  }
2140 
2141  // We can try to clean-up the PDE hooks.
2143  if (!INT_SUCCESS(status))
2144  {
2145  ERROR("[ERROR] IntHookPtCleanupList failed: 0x%08x\n", status);
2146  }
2147 
2148  // We can try to clean-up the PDPE hooks.
2150  if (!INT_SUCCESS(status))
2151  {
2152  ERROR("[ERROR] IntHookPtCleanupList failed: 0x%08x\n", status);
2153  }
2154 
2155  // We can try to clean-up the PML4E hooks.
2157  if (!INT_SUCCESS(status))
2158  {
2159  ERROR("[ERROR] IntHookPtCleanupList failed: 0x%08x\n", status);
2160  }
2161 
2162  // We can try to clean-up the PML5E hooks.
2164  if (!INT_SUCCESS(status))
2165  {
2166  ERROR("[ERROR] IntHookPtCleanupList failed: 0x%08x\n", status);
2167  }
2168 
2169  // We can try to clean-up the Root hooks.
2171  if (!INT_SUCCESS(status))
2172  {
2173  ERROR("[ERROR] IntHookPtCleanupList failed: 0x%08x\n", status);
2174  }
2175 
2177 
2178  return status;
2179 }
2180 
2181 
2182 INTSTATUS
2184  void
2185  )
2191 {
2193 
2194  for (DWORD i = 0; i < HOOK_PT_HASH_SIZE; i++)
2195  {
2197  }
2198 
2206 
2208 
2209  gHooks->PtsHooks.CallbacksList = NULL;
2210 
2211  return INT_STATUS_SUCCESS;
2212 }
2213 
2214 
2215 INTSTATUS
2217  _In_ PHOOK_PTS_ENTRY Entry,
2218  _In_ QWORD OldValue,
2219  _In_ QWORD NewValue
2220  )
2228 {
2229  Entry->WriteState.CurEntry = OldValue;
2230 
2231  return IntHookPtsHandleModification(Entry, OldValue, NewValue);
2232 }
2233 
2234 
2235 INTSTATUS
2237  void
2238  )
2250 {
2251  if (!gGuest.PtFilterEnabled)
2252  {
2254  }
2255 
2256  if (NULL == gHooks)
2257  {
2259  }
2260 
2262 
2263  for (LIST_ENTRY *entry = gHooks->PtsHooks.HooksPtsList.Flink;
2264  entry != &gHooks->PtsHooks.HooksPtsList; entry = entry->Flink)
2265  {
2266  PHOOK_PTS pHook = CONTAINING_RECORD(entry, HOOK_PTS, PtsLink);
2267  VA_TRANSLATION tr = { 0 };
2268  QWORD changedBits;
2269  INTSTATUS status;
2270 
2271  if (0 != ((HOOK_FLG_DISABLED | HOOK_FLG_REMOVE) & pHook->Header.Flags))
2272  {
2273  // Skip disabled/removed hooks
2274  continue;
2275  }
2276 
2278  {
2279  // Don't check user mode translations
2280  // If someone has the power of changing these we already have big problems
2281  continue;
2282  }
2283 
2284  if (pHook->IntegrityCheckFailed)
2285  {
2286  // No point in checking this VA again if the integrity check already failed for it
2287  continue;
2288  }
2289 
2290  status = IntTranslateVirtualAddressEx(pHook->VirtualAddress, pHook->Cr3, 0, &tr);
2291  if (!INT_SUCCESS(status) || 0 == tr.MappingsCount)
2292  {
2293  TRACE("[ERROR] IntTranslateVirtualAddressEx failed for 0x%016llx with Cr3 0x%016llx: 0x%08x\n",
2294  pHook->VirtualAddress, pHook->Cr3, status);
2295  continue;
2296  }
2297 
2298  if ((0 == (pHook->CurEntry & PT_P)) && (0 == (tr.MappingsEntries[tr.MappingsCount - 1] & PT_P)))
2299  {
2300  // Don't check the translation if both the old and the new entry are not present
2301  continue;
2302  }
2303 
2304  changedBits = pHook->CurEntry ^ tr.MappingsEntries[tr.MappingsCount - 1];
2305  if ((0 != (HOOK_PTS_MONITORED_BITS & changedBits)) ||
2306  (pHook->CurPageSize != tr.PageSize))
2307  {
2309  EXCEPTION_KM_ORIGINATOR originator = { 0 };
2310  BOOLEAN inAgent = FALSE;
2311 
2312  // This might be a fake violation. Basically, we have a race condition: while the timer runs another
2313  // CPU might be in the filtering agent waiting for a PT write to be acknowledged by introcore.
2314 
2315  IntPauseVcpus();
2316 
2318  if (INT_STATUS_CANNOT_UNLOAD == status)
2319  {
2320  inAgent = TRUE;
2321  }
2322 
2323  IntResumeVcpus();
2324 
2325  if (inAgent)
2326  {
2327  goto stop_and_exit;
2328  }
2329 
2330  LOG("[PTS INTEGRITY] Translation modification for VA 0x%016llx in CR3 0x%016llx: old = 0x%016llx "
2331  "new = 0x%016llx old size = 0x%016llx new size = 0x%016llx\n",
2332  pHook->VirtualAddress, pHook->Cr3,
2333  pHook->CurEntry, tr.MappingsEntries[tr.MappingsCount - 1],
2334  pHook->CurPageSize, tr.PageSize);
2335 
2336  memset(pTr, 0, sizeof(EVENT_TRANSLATION_VIOLATION));
2337 
2340  pTr->Header.MitreID = idRootkit;
2341 
2342  pTr->WriteInfo.NewValue[0] = tr.MappingsEntries[tr.MappingsCount - 1];
2343  pTr->WriteInfo.OldValue[0] = pHook->CurEntry;
2344  pTr->WriteInfo.Size = 8;
2345 
2346  pTr->Victim.VirtualAddress = pHook->VirtualAddress;
2348 
2350 
2354 
2356 
2357  status = IntExceptKernelGetOriginator(&originator, 0);
2358  if (!INT_SUCCESS(status))
2359  {
2360  WARNING("[WARNING] Failed to get originator on translation violation, RIP: %llx\n",
2361  pTr->Header.CpuContext.Rip);
2362  }
2363  else
2364  {
2366  }
2367 
2369 
2370  status = IntNotifyIntroEvent(introEventTranslationViolation, pTr, sizeof(*pTr));
2371  if (!INT_SUCCESS(status))
2372  {
2373  WARNING("[WARNING] IntNotifyIntroEvent failed: 0x%08x\n", status);
2374  }
2375 
2376  pHook->IntegrityCheckFailed = TRUE;
2377  }
2378  }
2379 
2380 stop_and_exit:
2382 
2383  return INT_STATUS_SUCCESS;
2384 }
2385 
2386 
2387 static INTSTATUS
2389  _In_ HOOK_PTS_ENTRY const *Entry
2390  )
2399 {
2400  LIST_ENTRY *list;
2401  QWORD x;
2402  DWORD i;
2403 
2404  if (NULL == Entry)
2405  {
2407  }
2408 
2409  for (i = 0; i < (DWORD)(5 - Entry->Level); i++)
2410  {
2411  NLOG(" ");
2412  }
2413 
2414  IntPhysicalMemRead(Entry->PtPaAddress, Entry->EntrySize, &x, NULL);
2415 
2416  NLOG("Level %d, Entry %p, PTE at 0x%016llx, refcount %d, Cur 0x%016llx, Int 0x%016llx, "
2417  "Real 0x%016llx, IsValid %d, IsPs %d, IsLeaf %d\n",
2418  Entry->Level, Entry, Entry->PtPaAddress, Entry->RefCount, Entry->WriteState.CurEntry,
2419  Entry->WriteState.IntEntry, x, Entry->IsValid, Entry->IsPs, Entry->IsLeaf);
2420 
2421  list = Entry->ContextEntries.Flink;
2422  while (list != &Entry->ContextEntries)
2423  {
2424  PHOOK_PTS pPts = CONTAINING_RECORD(list, HOOK_PTS, Link);
2425 
2426  list = list->Flink;
2427 
2428  for (i = 0; i < (DWORD)(5 - Entry->Level); i++)
2429  {
2430  NLOG(" ");
2431  }
2432 
2433  NLOG(" -> Context %p, Flags 0x%08x, Context callback %p, callback context %p, VA 0x%016llx\n",
2434  pPts, pPts->Header.Flags, pPts->Callback, pPts->Header.Context, pPts->VirtualAddress);
2435  }
2436 
2437  list = Entry->ChildrenEntries.Flink;
2438  while (list != &Entry->ChildrenEntries)
2439  {
2441 
2442  list = list->Flink;
2443 
2445  }
2446 
2447  return INT_STATUS_SUCCESS;
2448 }
2449 
2450 
2451 void
2453  void
2454  )
2460 {
2461  LIST_ENTRY *list;
2462  DWORD i;
2463 
2464  for (i = 0; i < HOOK_PT_HASH_SIZE; i++)
2465  {
2466  list = gHooks->PtsHooks.HooksRootList[i].Flink;
2467  while (list != &gHooks->PtsHooks.HooksRootList[i])
2468  {
2470 
2471  list = list->Flink;
2472 
2473  NLOG("-------------------------------------------------------------\n");
2474  NLOG("Root 0x%016llx, with refcount %d\n", pPt->PtPaAddress, pPt->RefCount);
2476  }
2477  }
2478 }
#define INT_STATUS_ACCESS_DENIED
Definition: introstatus.h:290
#define _In_opt_
Definition: intro_sal.h:16
#define INT_STATUS_PAGE_NOT_PRESENT
Indicates that a virtual address is not present.
Definition: introstatus.h:438
INTSTATUS IntHookPtmRemoveHook(HOOK_PTM **Hook, DWORD Flags)
Remove a page-table hook handle.
Definition: hook_ptm.c:520
static void IntHookPtsCloneCallbacks(PHOOK_PTS_ENTRY Entry)
Clone a list of callbacks locally, so they can be safely invoked.
Definition: hook_pts.c:207
#define _Out_
Definition: intro_sal.h:22
_Bool BOOLEAN
Definition: intro_types.h:58
#define CONTAINING_RECORD(List, Type, Member)
Definition: introlists.h:36
LIST_HEAD RemovedHooksPdpList
List of removed page-directory pointer entry hooks.
Definition: hook_pts.h:114
void * Context
User-defined data that will be supplied to the callback.
Definition: hook.h:74
QWORD IntAlertCoreGetFlags(QWORD ProtectionFlag, INTRO_ACTION_REASON Reason)
Returns the flags for an alert.
Definition: alerts.c:366
DWORD Size
The size of the access.
Definition: intro_types.h:982
INTSTATUS IntHookPtsWriteEntry(PHOOK_PTS_ENTRY Entry, QWORD OldValue, QWORD NewValue)
Tests the translation modification handler.
Definition: hook_pts.c:2216
BYTE EntrySize
4 (32 bit paging) or 8 (PAE or 64 bit paging)
Definition: hook_pts.h:68
#define HOOK_PTS_FLG_DELETE_PT_HOOK
Definition: hook_pts.h:14
INTSTATUS IntPhysicalMemRead(QWORD PhysicalAddress, DWORD Length, void *Buffer, DWORD *RetLength)
Reads data from a guest physical memory range, but only for a single page.
Definition: introcore.c:721
#define THS_CHECK_PTFILTER
Will check if any RIP is inside the PT filter agent.
uint8_t BYTE
Definition: intro_types.h:47
static DWORD gInvkCtxIndex
Definition: hook_pts.c:109
LIST_ENTRY Link
List element.
Definition: hook_pts.h:94
QWORD OldEntry
Previous page-table entry.
Definition: hook_pts.h:91
#define PDP_P
Definition: pgtable.h:61
#define _In_
Definition: intro_sal.h:21
LIST_HEAD HooksRootList[HOOK_PT_HASH_SIZE]
Hash of monitored virtual address spaces.
Definition: hook_pts.h:109
MITRE_ID MitreID
The Mitre ID that corresponds to this attack.
Definition: intro_types.h:1199
#define CLEAN_PHYS_ADDRESS64(x)
Definition: pgtable.h:119
QWORD SystemCr3
The Cr3 used to map the kernel.
Definition: guests.h:211
#define INT_STATUS_SUCCESS
Definition: introstatus.h:54
struct _INVOCATION_CONTEXT * PINVOCATION_CONTEXT
BOOLEAN IsLeaf
Definition: hook_pts.h:64
#define BIT(x)
Definition: common.h:68
static INTSTATUS IntHookPtsRemoveHookInternal(PHOOK_PTS Hook, DWORD Flags)
Remove a PTS hook.
Definition: hook_pts.c:585
uint16_t WORD
Definition: intro_types.h:48
QWORD NewValue[8]
The written value. Only the first Size bytes are valid.
Definition: intro_types.h:981
LIST_ENTRY PtsLink
Link inside the HooksPtsList.
Definition: hook_pts.h:95
Measures page tables integrity checks.
Definition: stats.h:59
#define STATS_EXIT(id)
Definition: stats.h:160
#define IntEnterDebugger()
Definition: introcore.h:373
LIST_HEAD ChildrenEntries
Children entries. Will be empty for leafs. Each entry is a HOOK_PTS_ENTRY.
Definition: hook_pts.h:72
struct _LIST_ENTRY * Flink
Definition: introlists.h:20
QWORD IntEntry
Definition: hook_ptwh.h:21
struct _HOOK_PTS_ENTRY * PHOOK_PTS_ENTRY
#define IC_TAG_PTPT
PTS Page Table hook.
Definition: memtags.h:63
#define INT_SUCCESS(Status)
Definition: introstatus.h:42
static INTSTATUS IntHookPtsDeleteParents(PHOOK_PTS_ENTRY Hook, DWORD Flags)
Permanently deletes all PTM hooks of a page-table entry hook.
Definition: hook_pts.c:709
#define HOOK_FLG_DISABLED
If flag is set, the hook is disabled, therefore ignored on EPT violations.
Definition: hook.h:46
static BOOLEAN IsListEmpty(const LIST_ENTRY *ListHead)
Definition: introlists.h:78
#define PTS_LEVEL_PDP
Definition: hook_pts.c:115
static INTSTATUS IntHookPtsInvokeCallbacks(LIST_HEAD *Callbacks)
Invoke all the callbacks from a given list.
Definition: hook_pts.c:272
PHOOK_PTS Hook
The PTS hook associated with the modified address.
Definition: hook_pts.c:91
QWORD Flags
A combination of ALERT_FLAG_* values describing the alert.
Definition: intro_types.h:1198
INTSTATUS IntResumeVcpus(void)
Resumes the VCPUs previously paused with IntPauseVcpus.
Definition: introcore.c:2355
#define PD_INDEX(a)
Definition: pgtable.h:97
QWORD OldPageSize
Previous page size.
Definition: hook_pts.h:93
#define PT32_INDEX(a)
Definition: pgtable.h:108
#define PAGE_OFFSET
Definition: pgtable.h:32
The action was not allowed because there was no reason to allow it.
Definition: intro_types.h:183
BOOLEAN KernelBetaDetections
True if the kernel protection is in beta (log-only) mode.
Definition: guests.h:303
DWORD WrittenMask
Bit mask indicating which bytes inside the page-table entry have been written.
Definition: hook_ptwh.h:23
INTRO_VIOLATION_HEADER Header
The alert header.
Definition: intro_types.h:1542
QWORD NewPageSize
New page size.
Definition: hook_pts.c:98
QWORD Cr3
Virtual address space where the address is monitored.
Definition: hook_pts.h:88
#define HOOK_PT_HASH_ID(x)
Definition: hook_pts.h:22
struct _EVENT_TRANSLATION_VIOLATION::@301 Victim
#define PTS_LEVEL_PT
Definition: hook_pts.c:117
INTSTATUS IntHookPtsSetHook(QWORD Cr3, QWORD VirtualAddress, PFUNC_SwapCallback Callback, void *Context, void *Parent, DWORD Flags, PHOOK_PTS *Hook)
Start monitoring translation modifications for the given VirtualAddress.
Definition: hook_pts.c:1535
#define INT_STATUS_NOT_NEEDED_HINT
Definition: introstatus.h:317
#define ERROR(fmt,...)
Definition: glue.h:62
LIST_HEAD RemovedHooksPml4List
List of removed PML4 entry hooks.
Definition: hook_pts.h:115
static INTSTATUS IntHookPtsEnableEntry(PHOOK_PTS_ENTRY Entry, QWORD NewPtPaAddress)
Enable a page-table entry hook.
Definition: hook_pts.c:873
KERNEL_DRIVER * Driver
The driver that&#39;s modifying the memory.
Definition: exceptions.h:949
#define HpAllocWithTag(Len, Tag)
Definition: glue.h:516
BOOLEAN IsPs
True if this entry is a page size extension, and points to a 2M/4M/1G page.
Definition: hook_pts.h:67
int INTSTATUS
The status data type.
Definition: introstatus.h:24
#define PTS_LEVEL_PD
Definition: hook_pts.c:116
QWORD VirtualAddress
The Virtual Address whose translation is being modified.
Definition: intro_types.h:1552
HOOK_STATE * gHooks
Global hooks state.
Definition: hook.c:8
static INVOCATION_CONTEXT gInvkCtxStatic[INVK_CTX_CACHE_SIZE]
Definition: hook_pts.c:108
Event structure for illegal paging-structures modifications.
Definition: intro_types.h:1540
QWORD Rip
The value of the guest RIP register when the event was generated.
Definition: intro_types.h:968
Rootkit.
Definition: intro_types.h:1144
Describes a kernel-mode originator.
Definition: exceptions.h:943
#define HOOK_PT_PAE_ROOT_HASH_ID(x)
Definition: hook_pts.h:23
static INTSTATUS IntHookPtsCreateEntry(QWORD PtPaAddress, WORD EntrySizeAndLevel, PHOOK_PTS_ENTRY Parent, PHOOK_PTS_ENTRY *Entry)
Creates a new page-table entry hook structure.
Definition: hook_pts.c:1395
INTSTATUS IntPauseVcpus(void)
Pauses all the guest VCPUs.
Definition: introcore.c:2320
EVENT_TRANSLATION_VIOLATION Translation
Definition: alerts.h:22
void IntAlertFillCpuContext(BOOLEAN CopyInstruction, INTRO_CPUCTX *CpuContext)
Fills the current CPU context for an alert.
Definition: alerts.c:492
static INTSTATUS IntHookPtsRemapEntry(PHOOK_PTS_ENTRY Entry, QWORD NewPtPaAddress)
Remap a page-table entry to a new value.
Definition: hook_pts.c:931
Measures page table entries writes.
Definition: stats.h:47
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 PML4_INDEX(a)
Definition: pgtable.h:95
LIST_HEAD HooksPtsList
List of swap hooks.
Definition: hook_pts.h:108
#define PTS_LEVEL_PML5
Definition: hook_pts.c:113
static QWORD IntHookPtsGetPageSize(PHOOK_PTS_ENTRY Entry)
Computes the page size of a PTS entry.
Definition: hook_pts.c:143
#define LOG(fmt,...)
Definition: glue.h:61
QWORD CurPageSize
Current page size.
Definition: hook_pts.h:92
#define PAGE_SIZE_4M
Definition: pgtable.h:20
void IntAlertFillVersionInfo(INTRO_VIOLATION_HEADER *Header)
Fills version information for an alert.
Definition: alerts.c:327
#define HOOK_FLG_HIGH_PRIORITY
If flag is set, the callback associated to this hook will have a higher priority than the others...
Definition: hook.h:54
#define HOOK_FLG_PT_UM_ROOT
If flag is set, the hook is set on the root paging structure, and only the low, user-mode entires are...
Definition: hook.h:52
static void IntHookAddCallbackToList(PLIST_HEAD List, PHOOK_PTS Context)
Adds a callback to the provided list.
Definition: hook_pts.c:164
static __inline INTSTATUS IntHookPtsCleanupList(LIST_HEAD *ListHead)
Commits a list of page-table entry hooks.
Definition: hook_pts.c:2037
TRANS_VIOLATION_TYPE ViolationType
Definition: intro_types.h:1562
INTRO_ACTION_REASON Reason
The reason for which Action was taken.
Definition: intro_types.h:1195
#define PTS_LEVEL_ROOT
Definition: hook_pts.c:112
#define HOOK_FLG_PAGING_STRUCTURE
If flag is set, the hook is set on paging structures.
Definition: hook.h:49
BOOLEAN IntegrityCheckFailed
True if integrity checks failed on this translation.
Definition: hook_pts.h:98
BYTE HookType
The type of the hook structure (see _HOOK_TYPE)
Definition: hook.h:68
DWORD MappingsCount
The number of entries inside the MappingsTrace and MappingsEntries arrays.
Definition: introcore.h:123
#define ALERT_FLAG_BETA
If set, the alert is a BETA alert. No action was taken.
Definition: intro_types.h:671
GENERIC_ALERT gAlert
Global alert buffer.
Definition: alerts.c:27
INTSTATUS IntSplitVirtualAddress(QWORD VirtualAddress, DWORD *OffsetsCount, QWORD *OffsetsTrace)
Split a linear address into page-table indexes.
Definition: kernvm.c:12
BOOLEAN Static
True if this entry was not dynamically allocated (it doesn&#39;t have to be freed).
Definition: hook_pts.c:101
#define _Inout_
Definition: intro_sal.h:20
#define INT_STATUS_CANNOT_UNLOAD
Indicates that Introcore can not unload in a safely manner.
Definition: introstatus.h:450
#define HOOK_FLG_GLOBAL_MASK
Global flags must be defined here and must be handled by each hooks layer (even if it ignores them...
Definition: hook.h:35
BOOLEAN PtFilterEnabled
If True, the in-guest PT filter is enabled and deployed.
Definition: guests.h:338
5-level paging
Definition: introcore.h:72
#define CLEAN_PHYS_ADDRESS32PAE_ROOT(x)
Definition: pgtable.h:140
#define PDP_INDEX(a)
Definition: pgtable.h:96
#define STATS_ENTER(id)
Definition: stats.h:153
INTRO_CPUCTX CpuContext
The context of the CPU that triggered the alert.
Definition: intro_types.h:1196
uint8_t * PBYTE
Definition: intro_types.h:47
QWORD MappingsEntries[MAX_TRANSLATION_DEPTH]
Contains the entry in which paging table.
Definition: introcore.h:115
WORD EntryOffset
Entry offset inside the monitored page-table.
Definition: hook_pts.h:75
INTSTATUS IntNotifyIntroEvent(INTRO_EVENT_TYPE EventClass, void *Param, size_t EventSize)
Notifies the integrator about an introspection alert.
Definition: glue.c:1042
QWORD NewEntry
New PT entry.
Definition: hook_pts.c:96
static BOOLEAN RemoveEntryList(LIST_ENTRY *Entry)
Definition: introlists.h:87
#define memzero(a, s)
Definition: introcrt.h:35
#define PT_P
Definition: pgtable.h:83
INTSTATUS IntGpaCacheFetchAndAdd(PGPA_CACHE Cache, QWORD Gpa, DWORD Size, PBYTE Buffer)
Fetch data from a cached entry, or add it to the cache, of not already present.
Definition: gpacache.c:508
BOOLEAN Guest64
True if this is a 64-bit guest, False if it is a 32-bit guest.
Definition: guests.h:290
unsigned long long QWORD
Definition: intro_types.h:53
LIST_HEAD RemovedHooksPtList
List of removed page-table entry hooks.
Definition: hook_pts.h:112
QWORD CurEntry
Current page-table entry.
Definition: hook_pts.h:90
void * ParentHook
The parent hook. For a GPA hook, for example, a GVA hook or a PagedHook will be the parent hook...
Definition: hook.h:73
#define HOOK_FLG_CHAIN_DELETE
If flag is set, then we won&#39;t remove the hook on commit phase; we&#39;ll let the parent hook handle the d...
Definition: hook.h:48
PHOOK_PTM PtPaHook
Definition: hook_pts.h:60
void * GpaCache
The currently used GPA cache.
Definition: guests.h:403
#define TRUE
Definition: intro_types.h:30
#define INT_STATUS_INVALID_PARAMETER_4
Definition: introstatus.h:71
INTSTATUS IntHookPtmDeleteHook(HOOK_PTM **Hook, DWORD Flags)
Permanently delete a page-table hook handle.
Definition: hook_ptm.c:643
INTRO_WRITE_INFO WriteInfo
The original and new address to which VirtualAddress translates.
Definition: intro_types.h:1560
#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 HOOK_PTS_FLG_DELETE_PD_HOOK
Definition: hook_pts.h:15
HOOK_HEADER Header
Hook header - must be present for every hook.
Definition: hook_pts.h:59
#define TRACE(fmt,...)
Definition: glue.h:58
#define HpFreeAndNullWithTag(Add, Tag)
Definition: glue.h:517
HOOK_HEADER Header
Hook header - must be present for every hook.
Definition: hook_pts.h:87
#define PML5_INDEX(a)
Definition: pgtable.h:94
#define INT_STATUS_INVALID_INTERNAL_STATE
Definition: introstatus.h:272
Sent for virtual address translation alerts. See EVENT_TRANSLATION_VIOLATION.
Definition: intro_types.h:94
INTSTATUS(* PFUNC_SwapCallback)(void *Context, QWORD VirtualAddress, QWORD OldEntry, QWORD NewEntry, QWORD OldPageSize, QWORD NewPageSize)
Callback invoked on translation modifications.
Definition: hook_pts.h:41
static void InsertAfterList(LIST_ENTRY *Pivot, LIST_ENTRY *Item)
Definition: introlists.h:169
void IntAlertFillWinProcessCurrent(INTRO_PROCESS *EventProcess)
Saves information about the current Windows process inside an alert.
Definition: alerts.c:781
Measures page table writes that are actually relevant for Introcore.
Definition: stats.h:48
static void InsertTailList(LIST_ENTRY *ListHead, LIST_ENTRY *Entry)
Definition: introlists.h:135
DWORD RefCount
Number of references.
Definition: hook_pts.h:74
INTSTATUS IntTranslateVirtualAddressEx(QWORD Gva, QWORD Cr3, DWORD Flags, VA_TRANSLATION *Translation)
Translates a guest virtual address to a guest physical address.
Definition: introcore.c:1863
QWORD PtPaAddress
Physical address of the PT/PD/PDP/PML4/PML5 entry associated to this particular page.
Definition: hook_pts.h:62
32-bit paging with PAE
Definition: introcore.h:70
INTSTATUS IntHookPtsRemoveHook(HOOK_PTS **Hook, DWORD Flags)
Remove a PTS hook.
Definition: hook_pts.c:1944
#define WARNING(fmt,...)
Definition: glue.h:60
BOOLEAN HooksRemoved
True if any hook has been removed.
Definition: hook_pts.h:117
4-level paging
Definition: introcore.h:71
static INTSTATUS IntHookPtsWriteCallback(void *Context, void *Hook, QWORD Address, INTRO_ACTION *Action)
Page-table modification handler.
Definition: hook_pts.c:374
LIST_HEAD ContextEntries
The actual contexts. Each context will be a HOOK_PTS structure.
Definition: hook_pts.h:73
static INTSTATUS IntHookPtsDisableEntry(PHOOK_PTS_ENTRY Entry, QWORD NewPtPaAddress, QWORD NewPteValue)
Disable a page-table entry hook.
Definition: hook_pts.c:831
static void InitializeListHead(LIST_ENTRY *ListHead)
Definition: introlists.h:69
INTSTATUS IntHookPtsDeleteHook(HOOK_PTS **Hook, DWORD Flags)
Permanently delete the PTS hook.
Definition: hook_pts.c:1993
LIST_HEAD RemovedHooksPtsList
List of removed PTS entries.
Definition: hook_pts.h:111
#define UNREFERENCED_PARAMETER(P)
Definition: introdefs.h:29
#define PT_INDEX(a)
Definition: pgtable.h:98
QWORD OldEntry
Old PT entry.
Definition: hook_pts.c:95
#define __forceinline
Definition: introtypes.h:61
#define IC_TAG_INVC
Invocation context.
Definition: memtags.h:83
uint32_t DWORD
Definition: intro_types.h:49
INTSTATUS IntHookPtsCheckIntegrity(void)
Checks the integrity of the existing page-table hooks. Used for debugging the PT filter.
Definition: hook_pts.c:2236
#define CLEAN_PHYS_ADDRESS32(x)
Definition: pgtable.h:126
enum _INTRO_ACTION INTRO_ACTION
Event actions.
QWORD CurEntry
Current page-table entry value.
Definition: hook_ptwh.h:20
INTSTATUS IntHookPtsCommitHooks(void)
Commit all PTS hook modifications.
Definition: hook_pts.c:2084
PHOOK_PTS_ENTRY Parent
The leaf page-table entry hook associated with this address.
Definition: hook_pts.h:96
QWORD OldValue[8]
The original value. Only the first Size bytes are valid.
Definition: intro_types.h:980
BOOLEAN PtPaHookSet
True if a hook is placed on the PT entry.
Definition: hook_pts.h:63
#define PAGE_SIZE_2M
Definition: pgtable.h:15
#define PDPPAE_INDEX(a)
Definition: pgtable.h:102
#define IntDbgEnterDebugger()
Definition: introcore.h:381
static INTSTATUS IntHookPtsDumpPtsEntry(HOOK_PTS_ENTRY const *Entry)
Prints a HOOK_PTS_ENTRY structure.
Definition: hook_pts.c:2388
PFUNC_SwapCallback Callback
Swap callback.
Definition: hook_pts.h:97
MM Mm
Guest memory information, such as paging mode, system Cr3 value, etc.
Definition: guests.h:374
INTRO_MODULE Module
The module that modified the translation.
Definition: intro_types.h:1546
GUEST_STATE gGuest
The current guest state.
Definition: guests.c:50
void IntHookPtsDump(void)
Prints all the page table hooks.
Definition: hook_pts.c:2452
BOOLEAN IsValid
This referrers to the entry contained by this PTE. If true, it points to a valid table.
Definition: hook_pts.h:66
#define PTS_LEVEL_PML4
Definition: hook_pts.c:114
PFUNC_EptViolationCallback Callback
Write callback to be called for the modification.
Definition: hook_ptm.c:17
LIST_HEAD RemovedHooksPdList
List of removed page-directory entry hooks.
Definition: hook_pts.h:113
#define ALERT_FLAG_FEEDBACK_ONLY
If set, the alert is a feedback only alert.
Definition: intro_types.h:683
INTSTATUS IntHookPtsInit(void)
Initializes the PTS hooks system.
Definition: hook_pts.c:2183
BYTE Level
Page table level (1 - PT, 5 - PML5)
Definition: hook_pts.h:69
struct _INVOCATION_CONTEXT INVOCATION_CONTEXT
QWORD PageSize
The page size used for this translation.
Definition: introcore.h:121
#define INT_STATUS_PARTIAL_WRITE
Definition: introstatus.h:362
#define PAGE_SIZE_1G
Definition: pgtable.h:25
LIST_HEAD RemovedHooksPml5List
List of removed PML5 entry hooks.
Definition: hook_pts.h:116
#define THS_CHECK_ONLY
Will check for safeness, without moving any RIP or stack value.
#define IC_TAG_PTPS
PTS Page Hook Context.
Definition: memtags.h:64
struct _EXCEPTION_KM_ORIGINATOR::@64 Original
INTRO_ACTION Action
The action that was taken as the result of this alert.
Definition: intro_types.h:1194
#define INT_STATUS_NO_MAPPING_STRUCTURES
Indicates that not all mapping structures of a virtual address are present.
Definition: introstatus.h:434
HOOK_PTEWS WriteState
Write state.
Definition: hook_pts.h:70
LIST_ENTRY Link
List element entry.
Definition: hook_ptm.c:15
#define NLOG(fmt,...)
Definition: glue.h:43
PAGING_MODE Mode
The paging mode used by the guest.
Definition: guests.h:221
#define PD32_INDEX(a)
Definition: pgtable.h:107
LIST_HEAD * CallbacksList
List of callbacks.
Definition: hook_pts.h:107
#define HOOK_PTS_MONITORED_BITS
Definition: hook_pts.h:19
HOOK_PTS_STATE PtsHooks
PTS hooks state (public page-table monitoring).
Definition: hook.h:95
static INTSTATUS IntHookPtsDeleteHookInternal(PHOOK_PTS Hook, DWORD Flags)
Permanently deletes a PTS hook.
Definition: hook_pts.c:756
#define PD_PS
Definition: pgtable.h:78
#define INT_STATUS_NOT_INITIALIZED_HINT
Definition: introstatus.h:320
Encapsulates information about a virtual to physical memory translation.
Definition: introcore.h:102
static INTSTATUS IntHookPtsRemovePteHook(PHOOK_PTS_ENTRY Entry, DWORD Flags)
Remove a page table entry hook.
Definition: hook_pts.c:509
struct _EVENT_TRANSLATION_VIOLATION::@300 Originator
#define INT_STATUS_INVALID_PARAMETER_1
Definition: introstatus.h:62
#define INT_STATUS_NOT_SUPPORTED
Definition: introstatus.h:287
INTRO_PROCESS CurrentProcess
The current process.
Definition: intro_types.h:1197
struct _LIST_ENTRY * Blink
Definition: introlists.h:25
QWORD OldPageSize
Old page size.
Definition: hook_pts.c:97
LIST_ENTRY Link
Link inside the containing list.
Definition: hook_pts.h:71
INTSTATUS IntHookPtmSetHook(QWORD Address, PFUNC_EptViolationCallback Callback, void *Context, void *ParentHook, DWORD Flags, PHOOK_PTM *Hook)
Set a hook on a page-table.
Definition: hook_ptm.c:325
PFUNC_SwapCallback Callback
The swap callback.
Definition: hook_pts.c:92
32-bit paging
Definition: introcore.h:69
Used by page-table hooks.
Definition: hook.h:19
void * Context
Context to be passed to the Callback.
Definition: hook_ptm.c:16
QWORD VirtualAddress
Virtual address whose translation is being modified.
Definition: hook_pts.c:94
#define PDP_PS
Definition: pgtable.h:67
DWORD Flags
Generic flags. Check out EPT Hook flags.
Definition: hook.h:67
static INTSTATUS IntHookPtsHandleModification(PHOOK_PTS_ENTRY Entry, QWORD OldValue, QWORD NewValue)
Handle a modification inside a page-table entry.
Definition: hook_pts.c:1100
#define INVK_CTX_CACHE_SIZE
Definition: hook_pts.c:107
LIST_HEAD RemovedHooksRootList
List of removed root entries.
Definition: hook_pts.h:110
static INTSTATUS IntHookPtsMergeEntry(PHOOK_PTS_ENTRY MergeRoot, PHOOK_PTS_ENTRY Entry)
Merge multiple entries into a single one.
Definition: hook_pts.c:968
void IntAlertFillWinKmModule(const KERNEL_DRIVER *Driver, INTRO_MODULE *EventModule)
Saves kernel module information inside an alert.
Definition: alerts.c:617
#define INT_STATUS_REMOVE_HOOK_ON_RET
Can be used by hook callbacks in order to signal that the hook should be removed. ...
Definition: introstatus.h:343
static INTSTATUS IntHookPtsControlEntry(PHOOK_PTS_ENTRY Entry, QWORD NewPtPaAddress, QWORD NewPteValue)
Handle control bits modifications inside a page-table entry.
Definition: hook_pts.c:1074
QWORD VirtualAddress
The monitored virtual address.
Definition: hook_pts.h:89
static PHOOK_PTS_ENTRY IntHookPtsFindEntry(LIST_HEAD *ListHead, QWORD PhysicalAddress)
Finds an already existing page-table entry hook on a given physical address.
Definition: hook_pts.c:796
#define PAGE_SIZE_4K
Definition: pgtable.h:10
INTSTATUS IntExceptKernelGetOriginator(EXCEPTION_KM_ORIGINATOR *Originator, DWORD Options)
This function is used to get the information about the kernel-mode originator.
#define HOOK_PT_HASH_SIZE
Definition: hook_pts.h:21
static INTSTATUS IntHookPtsDeletePdHook(PHOOK_PTS_ENTRY Hook, DWORD Flags)
Permanently deletes a page-table entry hook.
Definition: hook_pts.c:673
Used by an internal page monitored using PTS.
Definition: hook.h:20
#define HOOK_FLG_REMOVE
If flag is set, the hook has been removed, and waits the next commit to be actually deleted...
Definition: hook.h:44
#define HOOK_FLG_PAE_ROOT
Definition: hook.h:50
INTSTATUS IntHookPtwProcessWrite(PHOOK_PTEWS WriteState, QWORD Address, BYTE EntrySize, QWORD *OldValue, QWORD *NewValue)
Processes a page-table write, returning the old and the new page-table entry value.
Definition: hook_ptwh.c:149
#define INT_STATUS_INVALID_PARAMETER_7
Definition: introstatus.h:80
A translation was modified without us intercepting it. This points to a bug in Introcore.
Definition: intro_types.h:1533
#define FALSE
Definition: intro_types.h:34
#define INT_STATUS_INSUFFICIENT_RESOURCES
Definition: introstatus.h:281
#define INT_STATUS_INVALID_PARAMETER_3
Definition: introstatus.h:68