Bitdefender Hypervisor Memory Introspection
vasmonitor.c
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2020 Bitdefender
3  * SPDX-License-Identifier: Apache-2.0
4  */
5 #include "vasmonitor.h"
6 #include "hook.h"
7 #include "introcpu.h"
8 
22 
26 typedef struct _VAS_STATE
27 {
31 
32 static VAS_STATE gVasState = { 0 };
33 
34 
37  _In_opt_ void *Context,
38  _In_ void *Hook,
39  _In_ QWORD Address,
40  _Out_ INTRO_ACTION *Action
41  );
42 
43 static INTSTATUS
45  _In_ QWORD LinearAddress,
46  _In_ QWORD CurrentPage,
47  _In_ BYTE PagingMode,
48  _In_ BYTE Level,
49  _In_ PVAS_ROOT Root,
50  _Out_ PVAS_TABLE *Table
51  );
52 
53 static INTSTATUS
55  _In_ PVAS_TABLE Table
56  );
57 
58 
59 static INTSTATUS
61  _In_ PVAS_TABLE DataAddress,
62  _In_ QWORD DataInfo
63  )
73 {
74  UNREFERENCED_PARAMETER(DataInfo);
75 
76  if (NULL == DataAddress)
77  {
79  }
80 
81  if (NULL != DataAddress->Entries)
82  {
83  HpFreeAndNullWithTag(&DataAddress->Entries, IC_TAG_VASE);
84  }
85 
86  if (NULL != DataAddress->Tables)
87  {
88  HpFreeAndNullWithTag(&DataAddress->Tables, IC_TAG_VASP);
89  }
90 
91  HpFreeAndNullWithTag(&DataAddress, IC_TAG_VAST);
92 
93  return INT_STATUS_SUCCESS;
94 }
95 
96 
97 static QWORD
99  _In_ PVAS_TABLE Table
100  )
108 {
109  QWORD pageSize;
110 
111  if (NULL == Table)
112  {
113  return 0;
114  }
115 
116  if (Table->PagingMode == PAGING_4_LEVEL_MODE ||
117  Table->PagingMode == PAGING_5_LEVEL_MODE)
118  {
119  pageSize = (1 == Table->Level) ? PAGE_SIZE_4K : (2 == Table->Level ? PAGE_SIZE_2M : PAGE_SIZE_1G);
120  }
121  else if (Table->PagingMode == PAGING_PAE_MODE)
122  {
123  pageSize = (1 == Table->Level) ? PAGE_SIZE_4K : PAGE_SIZE_2M;
124  }
125  else
126  {
127  pageSize = (1 == Table->Level) ? PAGE_SIZE_4K : PAGE_SIZE_4M;
128  }
129 
130  return pageSize;
131 }
132 
133 
134 INTSTATUS
136  _In_opt_ void *Context,
137  _In_ void *Hook,
138  _In_ QWORD Address,
139  _Out_ INTRO_ACTION *Action
140  )
157 {
158  INTSTATUS status;
159  PVAS_TABLE pTable;
160  PVAS_TABLE_ENTRY pEntry;
161  QWORD newValue, oldValue, index, physAddr, gla;
162  BOOLEAN oldP, newP, oldPSE, newPSE;
163  BYTE sz;
164 
166 
167  if (Context == NULL)
168  {
170  }
171 
172  if (Action == NULL)
173  {
175  }
176 
177  status = INT_STATUS_SUCCESS;
178  newValue = oldValue = 0;
179 
180  // Actions are always allowed from this handler.
181  *Action = introGuestAllowed;
182 
183  pTable = (PVAS_TABLE)Context;
184 
186 
187  // Get the actual entry that just got written
188  switch (pTable->PagingMode)
189  {
190  case PAGING_5_LEVEL_MODE:
191  sz = 8;
192  index = (Address & PAGE_OFFSET) >> 3;
193  if ((pTable->Level == 5) && (index >= 256))
194  {
195  // kernel entry written, ignore.
196  goto cleanup_and_exit;
197  }
198  break;
199  case PAGING_4_LEVEL_MODE:
200  sz = 8;
201  index = (Address & PAGE_OFFSET) >> 3;
202  if ((pTable->Level == 4) && (index >= 256))
203  {
204  // kernel entry written, ignore.
205  goto cleanup_and_exit;
206  }
207  break;
208  case PAGING_PAE_MODE:
209  sz = 8;
210  if (pTable->Level == 3)
211  {
212  index = (Address & 0x1f) >> 3;
213 
214  if (index >= 2)
215  {
216  // kernel entry written, ignore.
217  goto cleanup_and_exit;
218  }
219  }
220  else
221  {
222  index = (Address & PAGE_OFFSET) >> 3;
223  }
224  break;
225  case PAGING_NORMAL_MODE:
226  sz = 4;
227  index = (Address & PAGE_OFFSET) >> 2;
228  if ((pTable->Level == 2) && (index >= 512))
229  {
230  // kernel entry written, ignore.
231  goto cleanup_and_exit;
232  }
233  break;
234  default:
236  }
237 
238  // Fetch the entry.
239  pEntry = &pTable->Entries[index];
240 
241  gla = VAS_COMPUTE_GLA(pTable->LinearAddress, index, pTable->Level, pTable->PagingMode);
242 
243  if (introGuestLinux == gGuest.OSType && 0 == gla)
244  {
245  // Ignore NULL-page mappings on Linux, since they may be mapped inside the kernel
246  // and we will have a bad time trying to track them
247  goto cleanup_and_exit;
248  }
249 
250  status = IntHookPtwProcessWrite(&pEntry->WriteState, Address, sz, &oldValue, &newValue);
251  if ((INT_STATUS_PAGE_NOT_PRESENT == status) || (INT_STATUS_NO_MAPPING_STRUCTURES == status))
252  {
253  *Action = introGuestAllowed;
254  status = INT_STATUS_SUCCESS;
255  goto cleanup_and_exit;
256  }
257  else if (!INT_SUCCESS(status))
258  {
259  QWORD cr3;
260 
261  IntCr3Read(gVcpu->Index, &cr3);
262 
263  ERROR("[ERROR] IntHookPtwProcessWrite failed at PTE %llx, CR3 %llx (current), CR3 %llx (hooked): 0x%08x\n",
264  Address, cr3, pTable->Root->Cr3, status);
265 
266  ERROR("[ERROR] Dumping the entire VASMON tables for VA space %llx...\n", pTable->Root->Cr3);
267 
268  IntVasDump(pTable->Root->Cr3);
269 
270  ERROR("[ERROR] Dumping the entire VASMON tables for VA space %llx DONE!\n", pTable->Root->Cr3);
271 
273  goto cleanup_and_exit;
274  }
275  else if ((INT_STATUS_PARTIAL_WRITE == status) || (INT_STATUS_NOT_NEEDED_HINT == status))
276  {
277  status = INT_STATUS_SUCCESS;
278  goto cleanup_and_exit;
279  }
280 
281  pTable->WriteCount++;
282 
283  physAddr = newValue & PHYS_PAGE_MASK;
284 
285  oldP = (0 != (oldValue & PD_P));
286  newP = (0 != (newValue & PD_P));
287  oldPSE = (oldP && (0 != (oldValue & PD_PS)));
288  newPSE = (newP && (0 != (newValue & PD_PS)));
289 
290  if (newP && (0 != (newValue & PT_US)) && (pTable->LinearAddress >= 0xFFFF800000000000))
291  {
292  LOG("[VASMON] Kernel page 0x%016llx is turning into user page from RIP 0x%016llx: 0x%016llx - 0x%016llx, "
293  "CR3 %llx!\n", pTable->LinearAddress, gVcpu->Regs.Rip, oldValue, newValue, pTable->Root->Cr3);
294  }
295 
296  if (newP && (0 == (newValue & PT_US)) && (pTable->LinearAddress < 0xFFFF800000000000))
297  {
298  LOG("[VASMON] User page 0x%016llx is turning into kernel page from RIP 0x%016llx: 0x%016llx - 0x%016llx, "
299  "CR3 %llx!\n", pTable->LinearAddress, gVcpu->Regs.Rip, oldValue, newValue, pTable->Root->Cr3);
300  }
301 
302  if (pTable->Level > 1)
303  {
304  // if we're at level one, than we have a page table. No mappings must be hooked/removed from this level.
305  if (oldP && !oldPSE && (!newP || newPSE))
306  {
307  // Table removed - we will also remove it.
308  if (NULL != pTable->Tables[index])
309  {
310  status = IntVasUnHookTables(pTable->Tables[index]);
311  if (!INT_SUCCESS(status))
312  {
313  ERROR("[ERROR] IntVasUnHookTable failed: 0x%08x\n", status);
314  }
315  }
316 
317  pTable->Tables[index] = NULL;
318  }
319  else if (newP && !newPSE && (!oldP || oldPSE))
320  {
321  // Table mapped - add a hook on it. We also make sure we haven't already hooked it - this may happen
322  // on Xen due to duplicate EPT violations.
323  if (NULL != pTable->Tables[index])
324  {
325  status = IntVasUnHookTables(pTable->Tables[index]);
326  if (!INT_SUCCESS(status))
327  {
328  ERROR("[ERROR] IntVasUnHookTable failed: 0x%08x\n", status);
329  }
330 
331  pTable->Tables[index] = NULL;
332  }
333 
334  status = IntVasHookTables(gla,
335  physAddr,
336  pTable->PagingMode,
337  pTable->Level - 1,
338  pTable->Root,
339  &pTable->Tables[index]);
340  if (!INT_SUCCESS(status))
341  {
342  ERROR("[ERROR] IntVasHookTables failed in root %llx: 0x%08x\n", pTable->Root->Cr3, status);
343  }
344  }
345  else if (oldP && newP && !oldPSE && !newPSE &&
346  (CLEAN_PHYS_ADDRESS64(oldValue) != CLEAN_PHYS_ADDRESS64(newValue)))
347  {
348  // Remapping table.
349  if (NULL != pTable->Tables[index])
350  {
351  status = IntVasUnHookTables(pTable->Tables[index]);
352  if (!INT_SUCCESS(status))
353  {
354  ERROR("[ERROR] IntVasUnHookTable failed: 0x%08x\n", status);
355  }
356  }
357 
358  pTable->Tables[index] = NULL;
359 
360  status = IntVasHookTables(gla,
361  physAddr,
362  pTable->PagingMode,
363  pTable->Level - 1,
364  pTable->Root,
365  &pTable->Tables[index]);
366  if (!INT_SUCCESS(status))
367  {
368  ERROR("[ERROR] IntVasHookTables failed in root %llx: 0x%08x\n", pTable->Root->Cr3, status);
369  }
370  }
371 
372  // On all other cases, a 4K, 2M, 4M or 1G is being mapped, which is uninteresting to us.
373  }
374 
375  // Invoke the VA space modification callback, if this is a leaf page being modified.
376  if (((oldValue & pTable->Root->MonitoredBits) != (newValue & pTable->Root->MonitoredBits)) &&
377  ((1 == pTable->Level) || oldPSE || newPSE))
378  {
380  void *context;
381  QWORD pageSize;
382 
383  callback = pTable->Root->Callback;
384 
385  context = pTable->Root->Context;
386 
387  pageSize = IntVasGetPageSize(pTable);
388 
389  // Invoke the callback.
390  if (NULL != callback)
391  {
392  status = callback(context, gla, oldValue, newValue, pageSize);
393  if (!INT_SUCCESS(status))
394  {
395  ERROR("[ERROR] callback failed: 0x%08x\n", status);
396  }
397  }
398  }
399 
400 cleanup_and_exit:
402 
403  return status;
404 }
405 
406 
407 static INTSTATUS
409  _In_ QWORD LinearAddress,
410  _In_ QWORD CurrentPage,
411  _In_ BYTE PagingMode,
412  _In_ BYTE Level,
413  _In_ PVAS_ROOT Root,
414  _Out_ PVAS_TABLE *Table
415  )
434 {
435  INTSTATUS status;
436  QWORD localLinearAddress;
437  DWORD i;
438  WORD entriesCount;
439  VAS_TABLE *pTable;
440 
441  if (0 == CurrentPage)
442  {
444  }
445 
446  if (0 == Level)
447  {
449  }
450 
451  if (NULL == Table)
452  {
454  }
455 
456  *Table = NULL;
457 
458  pTable = HpAllocWithTag(sizeof(*pTable), IC_TAG_VAST);
459  if (NULL == pTable)
460  {
462  }
463 
464  // Init fields.
465  pTable->Level = Level;
466  pTable->PagingMode = PagingMode;
467  pTable->Root = Root;
468  pTable->LinearAddress = LinearAddress;
469  pTable->EntriesCount = 0;
470 
471  IntPauseVcpus();
472  status = IntHookGpaSetHook(CurrentPage,
473  (PAGING_PAE_MODE == PagingMode ? (3 == Level ? 0x20 : 0x1000) : (0x1000)),
476  pTable,
477  NULL,
479  (PHOOK_GPA *)&pTable->WriteHook);
480  IntResumeVcpus();
481  if (!INT_SUCCESS(status))
482  {
483  ERROR("[ERROR] IntHookGpaSetHook failed: 0x%08x\n", status);
484 
486 
487  return status;
488  }
489 
490 
491  // Initialize the entries count.
492  if (PAGING_4_LEVEL_MODE == PagingMode ||
493  PAGING_5_LEVEL_MODE == PagingMode)
494  {
495  // Long mode paging, 512 entries on each level.
496  entriesCount = 512;
497  }
498  else if (PAGING_PAE_MODE == PagingMode)
499  {
500  if (3 == Level)
501  {
502  // PAE mode, root entry, 4 entries.
503  entriesCount = 4;
504  }
505  else
506  {
507  // PAE mode, intermediary entry, 512 entries.
508  entriesCount = 512;
509  }
510  }
511  else
512  {
513  // Normal mode, 1024 entries on all levels.
514  entriesCount = 1024;
515  }
516 
517  pTable->EntriesCount = entriesCount;
518 
519  pTable->Entries = HpAllocWithTag(sizeof(*pTable->Entries) * entriesCount, IC_TAG_VASE);
520  if (NULL == pTable->Entries)
521  {
522  IntHookGpaRemoveHook((HOOK_GPA **)&pTable->WriteHook, 0);
523 
525 
527  }
528 
529  if (Level != 1)
530  {
531  pTable->Tables = HpAllocWithTag(sizeof(*pTable->Tables) * entriesCount, IC_TAG_VASP);
532  if (NULL == pTable->Tables)
533  {
534  IntHookGpaRemoveHook((HOOK_GPA **)&pTable->WriteHook, 0);
535 
537 
539 
541  }
542  }
543  else
544  {
545  pTable->Tables = NULL;
546  }
547 
548  // Now parse the tables.
549  if (PAGING_5_LEVEL_MODE == PagingMode)
550  {
551  PQWORD pPage;
552 
553  status = IntPhysMemMap(CurrentPage, PAGE_SIZE, 0, &pPage);
554  if (!INT_SUCCESS(status))
555  {
556  ERROR("[ERROR] IntPhysMemMap failed: 0x%08x\n", status);
557  goto cleanup_and_exit;
558  }
559 
560  for (i = 0; i < (DWORD)((5 == Level) ? 256 : 512); i++)
561  {
562  localLinearAddress = VAS_COMPUTE_GLA_64(LinearAddress, (QWORD)i, Level);
563 
564  // Init current entry
565  pTable->Entries[i].WriteState.IntEntry = 0;
566  pTable->Entries[i].WriteState.CurEntry = pPage[i];
567 
568  if (NULL != pTable->Tables)
569  {
570  pTable->Tables[i] = NULL;
571  }
572 
573  if (pPage[i] & 1)
574  {
575  if (1 == Level)
576  {
577  // 4K page.
578  }
579  else if ((2 == Level) && (0 != (pPage[i] & PD_PS)))
580  {
581  // 2M page
582  }
583  else if ((3 == Level) && (0 != (pPage[i] & PDP_PS)))
584  {
585  // 1G page
586  }
587  else
588  {
589  status = IntVasHookTables(localLinearAddress,
590  pPage[i] & PHYS_PAGE_MASK,
591  PagingMode,
592  Level - 1,
593  Root,
594  &pTable->Tables[i]);
595  if (!INT_SUCCESS(status))
596  {
597  ERROR("[ERROR] IntVasHookTables failed in root %llx: 0x%08x\n", Root->Cr3, status);
598  IntPhysMemUnmap(&pPage);
599  goto cleanup_and_exit;
600  }
601  }
602  }
603  }
604 
605  IntPhysMemUnmap(&pPage);
606 
607  }
608  else if (PAGING_4_LEVEL_MODE == PagingMode)
609  {
610  PQWORD pPage;
611 
612  status = IntPhysMemMap(CurrentPage, PAGE_SIZE, 0, &pPage);
613  if (!INT_SUCCESS(status))
614  {
615  ERROR("[ERROR] IntPhysMemMap failed: 0x%08x\n", status);
616  goto cleanup_and_exit;
617  }
618 
619  for (i = 0; i < (DWORD)((4 == Level) ? 256 : 512); i++)
620  {
621  localLinearAddress = VAS_COMPUTE_GLA_64(LinearAddress, (QWORD)i, Level);
622 
623  // Init current entry
624  pTable->Entries[i].WriteState.IntEntry = 0;
625  pTable->Entries[i].WriteState.CurEntry = pPage[i];
626 
627  if (NULL != pTable->Tables)
628  {
629  pTable->Tables[i] = NULL;
630  }
631 
632  if (pPage[i] & 1)
633  {
634  if (1 == Level)
635  {
636  // 4K page.
637  }
638  else if ((2 == Level) && (0 != (pPage[i] & PD_PS)))
639  {
640  // 2M page
641  }
642  else if ((3 == Level) && (0 != (pPage[i] & PDP_PS)))
643  {
644  // 1G page
645  }
646  else
647  {
648  status = IntVasHookTables(localLinearAddress,
649  pPage[i] & PHYS_PAGE_MASK,
650  PagingMode,
651  Level - 1,
652  Root,
653  &pTable->Tables[i]);
654  if (!INT_SUCCESS(status))
655  {
656  ERROR("[ERROR] IntVasHookTables failed in root %llx: 0x%08x\n", Root->Cr3, status);
657  IntPhysMemUnmap(&pPage);
658  goto cleanup_and_exit;
659  }
660  }
661  }
662  }
663 
664  IntPhysMemUnmap(&pPage);
665 
666  }
667  else if (PAGING_PAE_MODE == PagingMode)
668  {
669  PQWORD pPage;
670 
671  if (3 != Level)
672  {
673  status = IntPhysMemMap(CurrentPage, PAGE_SIZE, 0, &pPage);
674  }
675  else
676  {
677  status = IntPhysMemMap(CurrentPage, 32, 0, &pPage);
678  }
679  if (!INT_SUCCESS(status))
680  {
681  ERROR("[ERROR] IntPhysMemMap failed: 0x%08x\n", status);
682  goto cleanup_and_exit;
683  }
684 
685  for (i = 0; i < (DWORD)((3 == Level) ? 2 : (PAGE_SIZE / 8)); i++)
686  {
687  localLinearAddress = VAS_COMPUTE_GLA_PAE(LinearAddress, (QWORD)i, Level);
688 
689  // Init current entry
690  pTable->Entries[i].WriteState.IntEntry = 0;
691  pTable->Entries[i].WriteState.CurEntry = pPage[i];
692 
693  if (NULL != pTable->Tables)
694  {
695  pTable->Tables[i] = NULL;
696  }
697 
698  if (pPage[i] & 1)
699  {
700  if (1 == Level)
701  {
702  // 4K page
703  }
704  else if ((2 == Level) && (0 != (pPage[i] & PD_PS)))
705  {
706  // 2M page
707  }
708  else
709  {
710  status = IntVasHookTables(localLinearAddress,
711  pPage[i] & PHYS_PAGE_MASK,
712  PagingMode,
713  Level - 1,
714  Root,
715  &pTable->Tables[i]);
716  if (!INT_SUCCESS(status))
717  {
718  ERROR("[ERROR] IntVasHookTables failed in root %llx: 0x%08x\n", Root->Cr3, status);
719  IntPhysMemUnmap(&pPage);
720  goto cleanup_and_exit;
721  }
722  }
723  }
724  }
725 
726  IntPhysMemUnmap(&pPage);
727 
728  }
729  else if (PAGING_NORMAL_MODE == PagingMode)
730  {
731  PDWORD pPage;
732 
733  status = IntPhysMemMap(CurrentPage, PAGE_SIZE, 0, &pPage);
734  if (!INT_SUCCESS(status))
735  {
736  ERROR("[ERROR] IntPhysMemMap failed: 0x%08x\n", status);
737  goto cleanup_and_exit;
738  }
739 
740  for (i = 0; i < (DWORD)(2 == Level ? 512 : 1024); i++)
741  {
742  localLinearAddress = VAS_COMPUTE_GLA_32(LinearAddress, (QWORD)i, Level);
743 
744  // Init current entry
745  pTable->Entries[i].WriteState.IntEntry = 0;
746  pTable->Entries[i].WriteState.CurEntry = pPage[i];
747 
748  if (NULL != pTable->Tables)
749  {
750  pTable->Tables[i] = NULL;
751  }
752 
753  if (pPage[i] & 1)
754  {
755  if (1 == Level)
756  {
757  // 4K page
758  }
759  else if ((2 == Level) && (0 != (pPage[i] & PD_PS)))
760  {
761  // 4M page
762  }
763  else
764  {
765  status = IntVasHookTables(localLinearAddress,
766  pPage[i] & PHYS_PAGE_MASK,
767  PagingMode,
768  Level - 1,
769  Root,
770  &pTable->Tables[i]);
771  if (!INT_SUCCESS(status))
772  {
773  ERROR("[ERROR] IntVasHookTables failed in root %llx: 0x%08x\n", Root->Cr3, status);
774  IntPhysMemUnmap(&pPage);
775  goto cleanup_and_exit;
776  }
777  }
778  }
779  }
780 
781  IntPhysMemUnmap(&pPage);
782 
783  }
784  else
785  {
787  }
788 
789 cleanup_and_exit:
790  *Table = pTable;
791 
792  return status;
793 }
794 
795 
796 static INTSTATUS
798  _In_ PVAS_TABLE Table
799  )
807 //
808 {
809  INTSTATUS status;
810 
811  if (NULL == Table)
812  {
814  }
815 
816  if (NULL != Table->WriteHook)
817  {
818  status = IntHookGpaRemoveHook((HOOK_GPA **)&Table->WriteHook, 0);
819  if (!INT_SUCCESS(status))
820  {
821  ERROR("[ERROR] IntHookGpaRemoveHook failed: 0x%08x\n", status);
822  }
823  }
824 
825  // Only if we have entries.
826  if (NULL != Table->Entries)
827  {
828  for (DWORD i = 0; i < Table->EntriesCount; i++)
829  {
830  // Recurse and remove child tables.
831  if ((NULL != Table->Tables) && (NULL != Table->Tables[i]))
832  {
833  status = IntVasUnHookTables(Table->Tables[i]);
834  if (!INT_SUCCESS(status))
835  {
836  ERROR("[ERROR] IntVasUnHookTable failed: 0x%08x\n", status);
837  }
838  }
839  else
840  {
841  // Remove write/exec hooks by invoking the modification callback.
843  void *context;
844  QWORD pageSize, gla;
845 
846  callback = Table->Root->Callback;
847 
848  context = Table->Root->Context;
849 
850  pageSize = IntVasGetPageSize(Table);
851 
852  gla = VAS_COMPUTE_GLA(Table->LinearAddress, i, Table->Level, Table->PagingMode);
853 
854  // We need to invoke the callback only for non-zero previous values. Otherwise, the page is
855  // not present and we need not do anything.
856  status = callback(context, gla, Table->Entries[i].WriteState.CurEntry, 0, pageSize);
857  if (!INT_SUCCESS(status))
858  {
859  ERROR("[ERROR] VAS callback failed: 0x%08x\n", status);
860  }
861  }
862  }
863  }
864 
865  status = IntVasDeleteTable(Table, 0);
866  if (!INT_SUCCESS(status))
867  {
868  ERROR("[ERROR] IntVasCleanupCallback failed: 0x%08x\n", status);
869  }
870 
871  // Done!
872  return status;
873 }
874 
875 
876 INTSTATUS
878  _In_ QWORD Cr3,
880  _In_ void *Context,
881  _In_ QWORD MonitoredBits,
882  _Out_ void **Root
883  )
902 {
903  INTSTATUS status;
904  VAS_ROOT *pVasRoot;
905 
906  if (0 == Cr3)
907  {
909  }
910 
911  if (NULL == Callback)
912  {
914  }
915 
916  if (NULL == Root)
917  {
919  }
920 
921  pVasRoot = HpAllocWithTag(sizeof(*pVasRoot), IC_TAG_VASR);
922  if (NULL == pVasRoot)
923  {
925  }
926 
927  pVasRoot->Cr3 = Cr3;
928  pVasRoot->Callback = Callback;
929  pVasRoot->Context = Context;
930  pVasRoot->MonitoredBits = MonitoredBits;
931 
933  {
934  status = IntVasHookTables(0, Cr3, PAGING_5_LEVEL_MODE, 5, pVasRoot, &pVasRoot->Table);
935  }
936  else if (PAGING_4_LEVEL_MODE == gGuest.Mm.Mode)
937  {
938  status = IntVasHookTables(0, Cr3, PAGING_4_LEVEL_MODE, 4, pVasRoot, &pVasRoot->Table);
939  }
940  else if (PAGING_PAE_MODE == gGuest.Mm.Mode)
941  {
942  status = IntVasHookTables(0, Cr3, PAGING_PAE_MODE, 3, pVasRoot, &pVasRoot->Table);
943  }
944  else if (PAGING_NORMAL_MODE == gGuest.Mm.Mode)
945  {
946  WARNING("[WARNING] The paging mode of the system is 32 bit without PAE! Protection is limited (no NX)!\n");
947 
948  status = IntVasHookTables(0, Cr3, PAGING_NORMAL_MODE, 2, pVasRoot, &pVasRoot->Table);
949  }
950  else
951  {
952  status = INT_STATUS_NOT_SUPPORTED;
953  }
954  if (!INT_SUCCESS(status))
955  {
956  ERROR("[ERROR] Failed initiating VA space monitoring for CR3 0x%016llx: 0x%08x\n", Cr3, status);
957 
958  // If it managed to hook some tables, than remove the structures.
959  if (NULL != pVasRoot->Table)
960  {
961  IntVasUnHookTables(pVasRoot->Table);
962  }
963 
964  HpFreeAndNullWithTag(&pVasRoot, IC_TAG_VASR);
965  }
966  else
967  {
968  InsertTailList(&gVasState.MonitoredSpaces, &pVasRoot->Link);
969  }
970 
971  // It will be NULL in case of failure, so it's safe.
972  *Root = pVasRoot;
973 
974  return status;
975 }
976 
977 
978 INTSTATUS
980  _In_opt_ QWORD Cr3,
981  _In_opt_ PVAS_ROOT Root
982  )
995 {
996  INTSTATUS status;
997 
998  if (NULL == Root)
999  {
1000  LIST_ENTRY *list = gVasState.MonitoredSpaces.Flink;
1001 
1002  while (list != &gVasState.MonitoredSpaces)
1003  {
1004  PVAS_ROOT pRoot = CONTAINING_RECORD(list, VAS_ROOT, Link);
1005  list = list->Flink;
1006 
1007  if (pRoot->Cr3 == Cr3)
1008  {
1009  Root = pRoot;
1010  break;
1011  }
1012  }
1013  }
1014 
1015  if (NULL == Root)
1016  {
1017  return INT_STATUS_NOT_FOUND;
1018  }
1019 
1020  status = IntVasUnHookTables(Root->Table);
1021  if (!INT_SUCCESS(status))
1022  {
1023  ERROR("[ERROR] IntVasUnHookTables failed: 0x%08x\n", status);
1024  }
1025 
1026  RemoveEntryList(&Root->Link);
1027 
1029 
1030  return INT_STATUS_SUCCESS;
1031 }
1032 
1033 
1034 static INTSTATUS
1036  _In_ PVAS_TABLE Table,
1037  _In_opt_ PVAS_TABLE_ENTRY Parent
1038  )
1047 {
1048  DWORD i;
1049  CHAR *spaces[5] = { "", " ", " ", " ", " ", };
1050 
1051  LOG(" %s level %d: CUR %llx, INT %llx, mask %x, GPA %llx, GLA %llx\n",
1052  spaces[Table->Level], Table->Level,
1053  Parent ? Parent->WriteState.CurEntry : 0,
1054  Parent ? Parent->WriteState.IntEntry : 0,
1055  Parent ? Parent->WriteState.WrittenMask : 0,
1056  ((PHOOK_GPA)Table->WriteHook)->GpaPage,
1057  Table->LinearAddress);
1058 
1059  if (NULL == Table->Tables)
1060  {
1061  return INT_STATUS_SUCCESS;
1062  }
1063 
1064  for (i = 0; i < Table->EntriesCount; i++)
1065  {
1066  if (NULL != Table->Tables[i])
1067  {
1068  IntVasDumpTables(Table->Tables[i], &Table->Entries[i]);
1069  }
1070  }
1071 
1072  return INT_STATUS_SUCCESS;
1073 }
1074 
1075 
1076 INTSTATUS
1078  _In_ QWORD Cr3
1079  )
1088 {
1089  INTSTATUS status;
1090  PVAS_ROOT pRoot;
1091  LIST_ENTRY *list;
1092 
1093  pRoot = NULL;
1094 
1095  list = gVasState.MonitoredSpaces.Flink;
1096  while (list != &gVasState.MonitoredSpaces)
1097  {
1098  pRoot = CONTAINING_RECORD(list, VAS_ROOT, Link);
1099  list = list->Flink;
1100 
1101  if (pRoot->Cr3 == Cr3)
1102  {
1103  break;
1104  }
1105 
1106  pRoot = NULL;
1107  }
1108 
1109  if (NULL == pRoot)
1110  {
1111  return INT_STATUS_NOT_FOUND;
1112  }
1113 
1114  status = IntVasDumpTables(pRoot->Table, NULL);
1115  if (!INT_SUCCESS(status))
1116  {
1117  ERROR("[ERROR] IntVasUnHookTables failed: 0x%08x\n", status);
1118  }
1119 
1120  return INT_STATUS_SUCCESS;
1121 }
1122 
1123 
1124 INTSTATUS
1126  void
1127  )
1133 {
1134  InitializeListHead(&gVasState.MonitoredSpaces);
1135 
1136  gVasState.Initialized = TRUE;
1137 
1138  return INT_STATUS_SUCCESS;
1139 }
1140 
1141 
1142 INTSTATUS
1144  void
1145  )
1152 {
1153  if (!gVasState.Initialized)
1154  {
1156  }
1157 
1158  gVasState.Initialized = FALSE;
1159 
1160  memzero(&gVasState, sizeof(gVasState));
1161 
1162  return INT_STATUS_SUCCESS;
1163 }
#define VAS_COMPUTE_GLA_PAE(Base, Index, Level)
Definition: vasmonitor.h:36
#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
DWORD WriteCount
Definition: vasmonitor.h:69
#define _Out_
Definition: intro_sal.h:22
_Bool BOOLEAN
Definition: intro_types.h:58
#define CONTAINING_RECORD(List, Type, Member)
Definition: introlists.h:36
static QWORD IntVasGetPageSize(PVAS_TABLE Table)
Computes the size of a page, given a VAS table.
Definition: vasmonitor.c:98
uint8_t BYTE
Definition: intro_types.h:47
INTSTATUS IntVasDump(QWORD Cr3)
Dump the monitored tables for the indicated Cr3.
Definition: vasmonitor.c:1077
IG_ARCH_REGS Regs
The current state of the guest registers.
Definition: guests.h:95
DWORD Index
The VCPU number.
Definition: guests.h:172
#define _In_
Definition: intro_sal.h:21
INTSTATUS IntHookGpaRemoveHook(HOOK_GPA **Hook, DWORD Flags)
Remove a GPA hook.
Definition: hook_gpa.c:738
struct _VAS_STATE * PVAS_STATE
INTSTATUS IntVasUnInit(void)
Uninit the VAS monitor state.
Definition: vasmonitor.c:1143
#define CLEAN_PHYS_ADDRESS64(x)
Definition: pgtable.h:119
#define INT_STATUS_SUCCESS
Definition: introstatus.h:54
uint16_t WORD
Definition: intro_types.h:48
#define STATS_EXIT(id)
Definition: stats.h:160
struct _LIST_ENTRY * Flink
Definition: introlists.h:20
#define VAS_COMPUTE_GLA_32(Base, Index, Level)
Definition: vasmonitor.h:37
QWORD LinearAddress
The first linear address translated by this table.
Definition: vasmonitor.h:68
QWORD IntEntry
Definition: hook_ptwh.h:21
#define INT_SUCCESS(Status)
Definition: introstatus.h:42
INTSTATUS IntResumeVcpus(void)
Resumes the VCPUs previously paused with IntPauseVcpus.
Definition: introcore.c:2355
#define PAGE_OFFSET
Definition: pgtable.h:32
BYTE PagingMode
Paging mode.
Definition: vasmonitor.h:73
#define PT_US
Definition: pgtable.h:85
Measures page table writes done by the VAS monitor.
Definition: stats.h:56
BYTE Level
The level of the current page table.
Definition: vasmonitor.h:72
#define INT_STATUS_NOT_NEEDED_HINT
Definition: introstatus.h:317
#define ERROR(fmt,...)
Definition: glue.h:62
#define IC_TAG_VASP
VAS Monitor Table Pointers array.
Definition: memtags.h:62
static INTSTATUS IntVasHookTables(QWORD LinearAddress, QWORD CurrentPage, BYTE PagingMode, BYTE Level, PVAS_ROOT Root, PVAS_TABLE *Table)
Recursively hook all the page-tables starting with the indicated page-table.
Definition: vasmonitor.c:408
#define HpAllocWithTag(Len, Tag)
Definition: glue.h:516
#define PHYS_PAGE_MASK
Definition: pgtable.h:38
int INTSTATUS
The status data type.
Definition: introstatus.h:24
LIST_ENTRY Link
List entry link.
Definition: vasmonitor.h:82
static VAS_STATE gVasState
Definition: vasmonitor.c:32
static INTSTATUS IntVasDumpTables(PVAS_TABLE Table, PVAS_TABLE_ENTRY Parent)
Dump the VAS tables.
Definition: vasmonitor.c:1035
#define INT_STATUS_NOT_FOUND
Definition: introstatus.h:284
#define VAS_COMPUTE_GLA_64(Base, Index, Level)
Definition: vasmonitor.h:35
static INTSTATUS IntVasDeleteTable(PVAS_TABLE DataAddress, QWORD DataInfo)
Delete the indicated VAS table.
Definition: vasmonitor.c:60
#define VAS_COMPUTE_GLA(Base, Index, Level, Pg)
Definition: vasmonitor.h:39
INTSTATUS IntPauseVcpus(void)
Pauses all the guest VCPUs.
Definition: introcore.c:2320
INTRO_GUEST_TYPE OSType
The type of the guest.
Definition: guests.h:278
INTSTATUS IntHookGpaSetHook(QWORD Gpa, DWORD Length, BYTE Type, PFUNC_EptViolationCallback Callback, void *Context, void *ParentHook, DWORD Flags, HOOK_GPA **Hook)
Places an EPT hook on the indicated memory range.
Definition: hook_gpa.c:193
#define LOG(fmt,...)
Definition: glue.h:61
uint32_t * PDWORD
Definition: intro_types.h:49
#define PAGE_SIZE_4M
Definition: pgtable.h:20
INTSTATUS IntVasStartMonitorVaSpace(QWORD Cr3, PFUNC_VaSpaceModificationCallback Callback, void *Context, QWORD MonitoredBits, void **Root)
Start monitoring the indicated virtual address space.
Definition: vasmonitor.c:877
struct _VAS_ROOT * Root
The root handle.
Definition: vasmonitor.h:64
HOOK_PTEWS WriteState
Write state of each page-table entry.
Definition: vasmonitor.h:55
#define HOOK_FLG_PAGING_STRUCTURE
If flag is set, the hook is set on paging structures.
Definition: hook.h:49
5-level paging
Definition: introcore.h:72
#define IC_TAG_VAST
VAS Monitor Table.
Definition: memtags.h:60
struct _VAS_TABLE * PVAS_TABLE
#define IC_TAG_VASR
VAS Root Object.
Definition: memtags.h:59
#define STATS_ENTER(id)
Definition: stats.h:153
static BOOLEAN RemoveEntryList(LIST_ENTRY *Entry)
Definition: introlists.h:87
#define memzero(a, s)
Definition: introcrt.h:35
unsigned long long QWORD
Definition: intro_types.h:53
void * Context
Optional context, will be passed to the callback.
Definition: vasmonitor.h:84
LIST_HEAD MonitoredSpaces
List of monitored virtual address spaces.
Definition: vasmonitor.c:29
#define TRUE
Definition: intro_types.h:30
#define INT_STATUS_INVALID_PARAMETER_4
Definition: introstatus.h:71
#define HpFreeAndNullWithTag(Add, Tag)
Definition: glue.h:517
#define INT_STATUS_INVALID_PARAMETER_5
Definition: introstatus.h:74
#define INT_STATUS_INVALID_INTERNAL_STATE
Definition: introstatus.h:272
static INTSTATUS IntVasUnHookTables(PVAS_TABLE Table)
Every table starting with this one will be deleted.
Definition: vasmonitor.c:797
static void InsertTailList(LIST_ENTRY *ListHead, LIST_ENTRY *Entry)
Definition: introlists.h:135
32-bit paging with PAE
Definition: introcore.h:70
void * WriteHook
The write hook handle.
Definition: vasmonitor.h:65
#define WARNING(fmt,...)
Definition: glue.h:60
PVAS_TABLE Table
This entry will contain the data associated to the PML4/PDP/PD - the first level. ...
Definition: vasmonitor.h:88
4-level paging
Definition: introcore.h:71
static void InitializeListHead(LIST_ENTRY *ListHead)
Definition: introlists.h:69
struct _VAS_TABLE ** Tables
Pointer to children tables, for each valid entry. NULL for leafs.
Definition: vasmonitor.h:67
#define PAGE_SIZE
Definition: common.h:70
PVAS_TABLE_ENTRY Entries
Children entries.
Definition: vasmonitor.h:66
#define UNREFERENCED_PARAMETER(P)
Definition: introdefs.h:29
BOOLEAN Initialized
Set once the state is initialized.
Definition: vasmonitor.c:28
uint32_t DWORD
Definition: intro_types.h:49
QWORD MonitoredBits
Monitored bits inside page-table entries.
Definition: vasmonitor.h:85
#define INT_STATUS_INVALID_PARAMETER_6
Definition: introstatus.h:77
PFUNC_VaSpaceModificationCallback Callback
Definition: vasmonitor.h:86
enum _INTRO_ACTION INTRO_ACTION
Event actions.
QWORD CurEntry
Current page-table entry value.
Definition: hook_ptwh.h:20
#define PAGE_SIZE_2M
Definition: pgtable.h:15
#define IntDbgEnterDebugger()
Definition: introcore.h:381
WORD EntriesCount
The number of entries. It can vary from 4 to 512 to 1024, depending on mode.
Definition: vasmonitor.h:71
MM Mm
Guest memory information, such as paging mode, system Cr3 value, etc.
Definition: guests.h:374
GUEST_STATE gGuest
The current guest state.
Definition: guests.c:50
INTSTATUS(* PFUNC_VaSpaceModificationCallback)(void *Context, QWORD VirtualAddress, QWORD OldEntry, QWORD NewEntry, QWORD PageSize)
Translation modification callback.
Definition: vasmonitor.h:26
INTSTATUS IntVasPageTableWriteCallback(void *Context, void *Hook, QWORD Address, INTRO_ACTION *Action)
Handle writes inside the monitored page-tables.
Definition: vasmonitor.c:135
#define INT_STATUS_PARTIAL_WRITE
Definition: introstatus.h:362
#define PAGE_SIZE_1G
Definition: pgtable.h:25
#define INT_STATUS_NO_MAPPING_STRUCTURES
Indicates that not all mapping structures of a virtual address are present.
Definition: introstatus.h:434
PAGING_MODE Mode
The paging mode used by the guest.
Definition: guests.h:221
INTSTATUS IntCr3Read(DWORD CpuNumber, QWORD *Cr3Value)
Reads the value of the guest CR3.
Definition: introcpu.c:415
#define IC_TAG_VASE
VAS Monitor Table Entries array.
Definition: memtags.h:61
__must_check INTSTATUS IntPhysMemMap(QWORD PhysAddress, DWORD Length, DWORD Flags, void **HostPtr)
Maps a guest physical address inside Introcore VA space.
Definition: glue.c:338
#define PD_PS
Definition: pgtable.h:78
#define INT_STATUS_NOT_INITIALIZED_HINT
Definition: introstatus.h:320
#define INT_STATUS_INVALID_PARAMETER_1
Definition: introstatus.h:62
#define INT_STATUS_NOT_SUPPORTED
Definition: introstatus.h:287
VCPU_STATE * gVcpu
The state of the current VCPU.
Definition: guests.c:59
struct _VAS_STATE VAS_STATE
#define PD_P
Definition: pgtable.h:71
32-bit paging
Definition: introcore.h:69
INTSTATUS IntVasStopMonitorVaSpace(QWORD Cr3, PVAS_ROOT Root)
Stops monitoring the indicated virtual address space.
Definition: vasmonitor.c:979
#define PDP_PS
Definition: pgtable.h:67
INTSTATUS IntVasInit(void)
Initialize the VAS monitor state.
Definition: vasmonitor.c:1125
QWORD Cr3
Monitored virtual address space.
Definition: vasmonitor.h:83
char CHAR
Definition: intro_types.h:56
unsigned long long * PQWORD
Definition: intro_types.h:53
Write-access hook.
Definition: glueiface.h:299
INTSTATUS IntPhysMemUnmap(void **HostPtr)
Unmaps an address previously mapped with IntPhysMemMap.
Definition: glue.c:396
#define PAGE_SIZE_4K
Definition: pgtable.h:10
#define INT_STATUS_INVALID_PARAMETER_2
Definition: introstatus.h:65
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 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