Bitdefender Hypervisor Memory Introspection
slack.c
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2020 Bitdefender
3  * SPDX-License-Identifier: Apache-2.0
4  */
5 #include "slack.h"
6 #include "guests.h"
7 #include "winpe.h"
8 #include "alerts.h"
9 
10 
28 
29 
33 typedef struct _SLACK_SPACE
34 {
38 
39  union
40  {
41  struct
42  {
47  } Windows;
48  };
49 
52 
53 
54 static LIST_HEAD gSlackAllocations = LIST_HEAD_INIT(gSlackAllocations);
55 
56 #define for_each_slack(_var_name) list_for_each(gSlackAllocations, SLACK_SPACE, _var_name)
57 
58 
59 static INTSTATUS
61  _In_ QWORD VirtualAddress,
62  _In_ DWORD Size,
63  _In_ BYTE Value
64  )
74 {
77  KERNEL_DRIVER *pDriver = NULL;
78 
79  memzero(pEvent, sizeof(*pEvent));
80 
83  pEvent->Header.MitreID = idHooking;
84 
85  pEvent->Header.Flags &= ~ALERT_FLAG_NOT_RING0;
86 
89 
90  pDriver = IntDriverFindByAddress(VirtualAddress);
91  if (pDriver)
92  {
94  {
95  IntAlertFillWinKmModule(pDriver, &pEvent->Originator.Module);
96  }
97  else if (gGuest.OSType == introGuestLinux)
98  {
99  IntAlertFillLixKmModule(pDriver, &pEvent->Originator.Module);
100  }
101  }
102 
103  pEvent->Header.CpuContext.Valid = FALSE;
105 
106  pEvent->BaseAddress = VirtualAddress;
107  pEvent->VirtualAddress = VirtualAddress;
108  pEvent->Size = Size;
109 
110  pEvent->WriteInfo.Size = Size;
111  memcpy(pEvent->WriteInfo.NewValue, &Value, sizeof(BYTE));
112 
113  status = IntNotifyIntroEvent(introEventIntegrityViolation, pEvent, sizeof(*pEvent));
114  if (!INT_SUCCESS(status))
115  {
116  WARNING("[WARNING] IntNotifyIntroEvent failed: 0x%08x\n", status);
117  }
118 
119  return INT_STATUS_SUCCESS;
120 }
121 
122 
123 static INTSTATUS
125  _In_ BOOLEAN Pageable,
127  _In_ DWORD Size,
128  _Out_ QWORD *Buffer,
129  _In_opt_ QWORD SecHint
130  )
165 {
166  INTSTATUS status;
167  IMAGE_SECTION_HEADER sec = { 0 };
168  INTRO_PE_INFO peInfo = { 0 };
169  BYTE *moduleBuffer = NULL;
170  DWORD bufferSize = 0;
171 
172 
173  if (ModuleBase == gGuest.KernelVa)
174  {
175  moduleBuffer = gWinGuest->KernelBuffer;
176  bufferSize = gWinGuest->KernelBufferSize;
177  }
178 
179  status = IntPeValidateHeader(ModuleBase, moduleBuffer, bufferSize, &peInfo, gGuest.Mm.SystemCr3);
180  if (!INT_SUCCESS(status))
181  {
182  ERROR("[ERROR] IntPeValidateHeader failed with status: 0x%08x\n", status);
183  return status;
184  }
185 
186  // Get the base address of the section headers and set an EPT hook on every section that it's not writable.
187  // The idea is that this way, we will protect the code, IAT, EAT with one shot, since both the IAT & EAT are
188  // placed by the compiler inside a read-only section.
189 
190  // Parse the section headers, in order to find some empty space.
191  for (DWORD i = 0; i < peInfo.NumberOfSections; i++)
192  {
193  if (moduleBuffer != NULL && peInfo.SectionOffset + sizeof(IMAGE_SECTION_HEADER) * (i + 1ull) < bufferSize)
194  {
195  sec = *(IMAGE_SECTION_HEADER *)(moduleBuffer + peInfo.SectionOffset + sizeof(IMAGE_SECTION_HEADER) * i);
196  }
197  else
198  {
199  status = IntKernVirtMemRead(ModuleBase + peInfo.SectionOffset + sizeof(IMAGE_SECTION_HEADER) * i,
200  sizeof(IMAGE_SECTION_HEADER), &sec, NULL);
201  if (!INT_SUCCESS(status))
202  {
203  ERROR("[ERROR] Failed reading IMAGE_SECTION_HEADER %d for module 0x%016llx: 0x%08x\n",
204  i, ModuleBase, status);
205  return status;
206  }
207  }
208 
209  if ((0 != SecHint) && (0 != memcmp(&SecHint, sec.Name, 8)))
210  {
211  continue;
212  }
213 
214  // The section where we will place the hook must NOT be writable, discardable or pageable.
215  if ((sec.Characteristics & IMAGE_SCN_MEM_WRITE) ||
218  (!(sec.Characteristics & IMAGE_SCN_MEM_NOT_PAGED) && !Pageable) ||
219  ((sec.Characteristics & IMAGE_SCN_MEM_NOT_PAGED) && Pageable))
220  {
221  continue;
222  }
223 
224  // INITKDBG is overwritten at some times by kdbg... We really don't want to store our code there.
225  if ((memcmp(sec.Name, "INITKDBG", 8) == 0))
226  {
227  continue;
228  }
229 
230  // Ignore page-aligned sections.
231  if (sec.Misc.VirtualSize % PAGE_SIZE == 0)
232  {
233  continue;
234  }
235 
236  if (PAGE_REMAINING(sec.Misc.VirtualSize) >= Size)
237  {
238  // We found a suitable section; we must validate that it won't overlap with an already set hook
239  DWORD totalUsedSpace;
240  DWORD totalSpace;
241  DWORD maxOffset;
242 
243  totalUsedSpace = 0;
244  maxOffset = 0;
245  totalSpace = PAGE_REMAINING(sec.Misc.VirtualSize);
246 
247  for_each_slack(pSlack)
248  {
249  if ((pSlack->Windows.Section == i) && (pSlack->ModuleBase == ModuleBase))
250  {
251  if (pSlack->Windows.SectionOffset > maxOffset)
252  {
253  maxOffset = pSlack->Windows.SectionOffset;
254 
255  totalUsedSpace = maxOffset - sec.Misc.VirtualSize + pSlack->AllocationSize;
256  }
257  }
258  }
259 
260  // Make sure enough space still remains. Also, make sure the slack buffer is filled with zeros.
261  if (totalSpace - totalUsedSpace >= Size)
262  {
263  SLACK_SPACE *pSlack;
264  QWORD gva = ModuleBase + sec.VirtualAddress + sec.Misc.VirtualSize + totalUsedSpace, j;
265 
266  // The Size parameter is validated by the caller.
267  BYTE *buf = HpAllocWithTag(Size, IC_TAG_ALLOC);
268  if (NULL == buf)
269  {
271  }
272 
273  status = IntKernVirtMemRead(gva, Size, buf, NULL);
274  if (!INT_SUCCESS(status))
275  {
277  ERROR("[ERROR] IntKernVirtMemRead failed GVA 0x%016llx: 0x%08x\n", gva, status);
278  return status;
279  }
280 
281  for (j = 0; j < Size; j++)
282  {
283  if (0 != buf[j])
284  {
285  IntSlackSendIntegrityAlert(gva, Size, buf[j]);
286 
287  ERROR("[ERROR] Slack buffer not 0-filled! 0x%016llx\n", gva + j);
288  IntDumpBuffer(buf, gva, Size, 8, 1, TRUE, TRUE);
291  }
292  }
293 
295 
296  pSlack = HpAllocWithTag(sizeof(*pSlack), IC_TAG_SLKE);
297  if (NULL == pSlack)
298  {
300  }
301 
302  TRACE("[SLACK] Found %d bytes of space, used %d bytes, in section %d, "
303  "at offset %08x in module 0x%016llx\n", totalSpace, totalUsedSpace, i,
304  sec.VirtualAddress + sec.Misc.VirtualSize, ModuleBase);
305 
306  pSlack->Gva = gva;
307  pSlack->AllocationSize = Size;
308  pSlack->ModuleBase = ModuleBase;
309  pSlack->Windows.AllocationOffset = (sec.Misc.VirtualSize & PAGE_OFFSET) + totalUsedSpace;
310  pSlack->Windows.Section = i;
311  pSlack->Windows.SectionOffset = sec.Misc.VirtualSize + totalUsedSpace;
312  pSlack->Windows.SectionSize = sec.Misc.VirtualSize;
313 
314  InsertTailList(&gSlackAllocations, &pSlack->Link);
315 
316  *Buffer = pSlack->Gva;
317 
318  return INT_STATUS_SUCCESS;
319  }
320  }
321  }
322 
324 }
325 
326 
327 static INTSTATUS
329  _In_ DWORD Size,
330  _Out_ QWORD *Buffer
331  )
345 {
346  INTSTATUS status;
347  QWORD gva = gGuest.KernelVa;
348  QWORD maxGva = gGuest.KernelVa + (2 * PAGE_SIZE);
349  BYTE opcode = LIX_FIELD(Info, HasSlackInt3) ? 0xCC : 0x90;
350 
351  //
352  // If we have other allocations, don't bother to parse pages inside the kernel, just skip right after the
353  // last one. The NOPs are in a contiguous big block, so no need for any complicated logic.
354  //
355  if (!IsListEmpty(&gSlackAllocations))
356  {
357  SLACK_SPACE *pSlack = CONTAINING_RECORD(gSlackAllocations.Blink, SLACK_SPACE, Link);
358 
359  gva = pSlack->Gva + pSlack->AllocationSize;
360  }
361 
362  for (; gva < maxGva; gva += PAGE_REMAINING(gva))
363  {
364  BYTE *p;
365  DWORD maxOffset;
366 
367  // A limitation for now: the allocation has to be fully in one page
368  if (Size > PAGE_REMAINING(gva))
369  {
370  continue;
371  }
372 
373  maxOffset = PAGE_REMAINING(gva) - Size;
374 
375  status = IntVirtMemMap(gva, PAGE_REMAINING(gva), gGuest.Mm.SystemCr3, 0, &p);
376  if (!INT_SUCCESS(status))
377  {
378  ERROR("[ERROR] IntVirtMemMap failed for %llx: 0x%08x\n", gva, status);
379  return status;
380  }
381 
382  for (DWORD offset = 0; offset < maxOffset; offset++)
383  {
384  DWORD foundSize = 0;
385 
386  // Skip until the first NOP/INT3
387  while (offset < maxOffset && (p[offset] != opcode))
388  {
389  offset++;
390  }
391 
392  // p[maxOffset + Size] last valid is [maxOffset + Size - 1]
393  // offset goes from 0 -> maxOffset so last valid is maxOffset - 1
394  // foundSize goes from 0 -> Size so last valid is Size - 1
395  // worst case: p[maxOffset - 1 + Size - 1] = p[maxOffset + Size - 2] which is in the buffer still
396 
397  while ((foundSize < Size) && (p[offset + foundSize] == opcode))
398  {
399  foundSize++;
400  }
401 
402  if (foundSize == Size)
403  {
404  // Found a suitable area filled with NOPs, let's just use that
405  SLACK_SPACE *pSlack = HpAllocWithTag(sizeof(*pSlack), IC_TAG_SLKE);
406  if (NULL == pSlack)
407  {
409  }
410 
411  pSlack->Gva = gva + offset;
412  pSlack->AllocationSize = Size;
413  pSlack->ModuleBase = gGuest.KernelVa;
414 
415  TRACE("[SLACK] Found %d bytes of space at 0x%016llx\n", foundSize, pSlack->Gva);
416 
417  InsertTailList(&gSlackAllocations, &pSlack->Link);
418 
419  *Buffer = pSlack->Gva;
420 
421  IntVirtMemUnmap(&p);
422 
423  return INT_STATUS_SUCCESS;
424  }
425  }
426 
427  IntVirtMemUnmap(&p);
428  }
429 
430  IntSlackSendIntegrityAlert(gva, Size, 0);
431 
433 }
434 
435 
436 INTSTATUS
439  _In_ BOOLEAN Pageable,
440  _In_ DWORD Size,
441  _Out_ QWORD *Buffer,
442  _In_opt_ QWORD SecHint
443  )
463 {
464  INTSTATUS status;
465 
466  if (0 == Size)
467  {
469  }
470 
471  if (NULL == Buffer)
472  {
474  }
475 
477  {
478  if (0 == ModuleBase)
479  {
481  }
482 
483  status = IntSlackAllocWindows(Pageable, ModuleBase, Size, Buffer, SecHint);
484  }
485  else if (introGuestLinux == gGuest.OSType)
486  {
487  status = IntSlackAllocLinux(Size, Buffer);
488  }
489  else
490  {
491  status = INT_STATUS_NOT_SUPPORTED;
492  }
493 
494  return status;
495 }
496 
497 
498 INTSTATUS
500  _In_ QWORD Buffer
501  )
513 {
514  if (0 == Buffer)
515  {
517  }
518 
519  for_each_slack(pSlack)
520  {
521  if (pSlack->Gva == Buffer)
522  {
523  RemoveEntryList(&pSlack->Link);
524 
526 
527  return INT_STATUS_SUCCESS;
528  }
529  }
530 
531  return INT_STATUS_NOT_FOUND;
532 }
533 
534 
535 void
537  void
538  )
542 {
543  for_each_slack(pSlack)
544  {
545  RemoveEntryList(&pSlack->Link);
546 
548  }
549 }
#define IMAGE_SCN_MEM_EXECUTE
Definition: winpe.h:472
#define _In_opt_
Definition: intro_sal.h:16
#define _Out_
Definition: intro_sal.h:22
_Bool BOOLEAN
Definition: intro_types.h:58
#define CONTAINING_RECORD(List, Type, Member)
Definition: introlists.h:36
INTSTATUS IntVirtMemUnmap(void **HostPtr)
Unmaps a memory range previously mapped with IntVirtMemMap.
Definition: introcore.c:2234
INTSTATUS IntSlackFree(QWORD Buffer)
Free slack space.
Definition: slack.c:499
DWORD Size
The size of the access.
Definition: intro_types.h:982
uint8_t BYTE
Definition: intro_types.h:47
WINDOWS_GUEST * gWinGuest
Global variable holding the state of a Windows guest.
Definition: winguest.c:37
#define _In_
Definition: intro_sal.h:21
MITRE_ID MitreID
The Mitre ID that corresponds to this attack.
Definition: intro_types.h:1199
QWORD SystemCr3
The Cr3 used to map the kernel.
Definition: guests.h:211
#define INT_STATUS_SUCCESS
Definition: introstatus.h:54
#define PAGE_REMAINING(addr)
Definition: pgtable.h:163
QWORD NewValue[8]
The written value. Only the first Size bytes are valid.
Definition: intro_types.h:981
Event structure for integrity violations on monitored structures.
Definition: intro_types.h:1572
DWORD AllocationSize
The number of bytes allocated.
Definition: slack.c:50
#define IMAGE_SCN_MEM_WRITE
Definition: winpe.h:474
#define INT_SUCCESS(Status)
Definition: introstatus.h:42
static BOOLEAN IsListEmpty(const LIST_ENTRY *ListHead)
Definition: introlists.h:78
#define for_each_slack(_var_name)
Definition: slack.c:56
QWORD Flags
A combination of ALERT_FLAG_* values describing the alert.
Definition: intro_types.h:1198
QWORD SectionOffset
Offset of the first section header.
Definition: winpe.h:602
INTRO_OBJECT_TYPE Type
Definition: intro_types.h:1589
#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
struct _EVENT_INTEGRITY_VIOLATION::@304 Victim
DWORD SectionOffset
The offset inside the section of the allocation.
Definition: slack.c:44
#define ERROR(fmt,...)
Definition: glue.h:62
#define HpAllocWithTag(Len, Tag)
Definition: glue.h:516
int INTSTATUS
The status data type.
Definition: introstatus.h:24
#define INT_STATUS_NOT_FOUND
Definition: introstatus.h:284
void IntSlackUninit(void)
Uninit the slack system. Must be called only during uninit.
Definition: slack.c:536
INTRO_GUEST_TYPE OSType
The type of the guest.
Definition: guests.h:278
void IntAlertFillCpuContext(BOOLEAN CopyInstruction, INTRO_CPUCTX *CpuContext)
Fills the current CPU context for an alert.
Definition: alerts.c:492
struct _SLACK_SPACE * PSLACK_SPACE
struct _SLACK_SPACE SLACK_SPACE
Describes a kernel driver.
Definition: drivers.h:30
void IntAlertFillVersionInfo(INTRO_VIOLATION_HEADER *Header)
Fills version information for an alert.
Definition: alerts.c:327
INTRO_ACTION_REASON Reason
The reason for which Action was taken.
Definition: intro_types.h:1195
GENERIC_ALERT gAlert
Global alert buffer.
Definition: alerts.c:27
void IntAlertFillLixKmModule(const KERNEL_DRIVER *Driver, INTRO_MODULE *EventModule)
Saves information about a kernel module inside an alert.
Definition: alerts.c:1235
DWORD Size
The size of the modified memory area.
Definition: intro_types.h:1618
static INTSTATUS IntSlackSendIntegrityAlert(QWORD VirtualAddress, DWORD Size, BYTE Value)
Sends an integrity alert if the slack buffer not 0-filled/NOP-filled.
Definition: slack.c:60
INTRO_CPUCTX CpuContext
The context of the CPU that triggered the alert.
Definition: intro_types.h:1196
INTSTATUS IntNotifyIntroEvent(INTRO_EVENT_TYPE EventClass, void *Param, size_t EventSize)
Notifies the integrator about an introspection alert.
Definition: glue.c:1042
QWORD Gva
The guest virtual address of the actual allocation.
Definition: slack.c:37
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
INTRO_MODULE Module
The module that modified the monitored region.
Definition: intro_types.h:1578
QWORD VirtualAddress
The guest virtual address which was modified.
Definition: intro_types.h:1616
#define TRUE
Definition: intro_types.h:30
#define INT_STATUS_INVALID_PARAMETER_4
Definition: introstatus.h:71
UINT32 VirtualAddress
Definition: winpe.h:85
INTRO_VIOLATION_HEADER Header
The alert header.
Definition: intro_types.h:1574
QWORD NumberOfSections
Number of sections.
Definition: winpe.h:603
#define LIX_FIELD(Structure, Field)
Macro used to access fields inside the LIX_OPAQUE_FIELDS structure.
Definition: lixguest.h:429
TIMER_FRIENDLY void IntDumpBuffer(const void *Buffer, QWORD Gva, DWORD Length, DWORD RowLength, DWORD ElementLength, BOOLEAN LogHeader, BOOLEAN DumpAscii)
This function dumps a given buffer in a user friendly format.
Definition: dumper.c:48
#define TRACE(fmt,...)
Definition: glue.h:58
#define HpFreeAndNullWithTag(Add, Tag)
Definition: glue.h:517
INTSTATUS IntSlackAlloc(QWORD ModuleBase, BOOLEAN Pageable, DWORD Size, QWORD *Buffer, QWORD SecHint)
Allocate slack inside the guest.
Definition: slack.c:437
QWORD KernelVa
The guest virtual address at which the kernel image.
Definition: guests.h:283
union _IMAGE_SECTION_HEADER::@214 Misc
static void InsertTailList(LIST_ENTRY *ListHead, LIST_ENTRY *Entry)
Definition: introlists.h:135
#define WARNING(fmt,...)
Definition: glue.h:60
#define ALERT_FLAG_NOT_RING0
If set, the alert was triggered in ring 1, 2 or 3.
Definition: intro_types.h:674
Hooking.
Definition: intro_types.h:1154
#define PAGE_SIZE
Definition: common.h:70
DWORD AllocationOffset
The allocation offset, within the last page of the section.
Definition: slack.c:46
QWORD ModuleBase
The module base used for the allocation.
Definition: slack.c:36
uint32_t DWORD
Definition: intro_types.h:49
UINT32 VirtualSize
Definition: winpe.h:83
DWORD KernelBufferSize
The size of the KernelBuffer.
Definition: winguest.h:852
static LIST_HEAD gSlackAllocations
Definition: slack.c:54
UINT32 Characteristics
Definition: winpe.h:92
static INTSTATUS IntSlackAllocLinux(DWORD Size, QWORD *Buffer)
Allocate slack space on Linux.
Definition: slack.c:328
INTSTATUS IntPeValidateHeader(QWORD ImageBase, BYTE *ImageBaseBuffer, DWORD ImageBaseBufferSize, INTRO_PE_INFO *PeInfo, QWORD Cr3)
Validates a PE header.
Definition: winpe.c:131
BOOLEAN Valid
Set to True if the information in the structure is valid, False otherwise.
Definition: intro_types.h:965
DWORD SectionSize
The size of the section.
Definition: slack.c:45
INTRO_WRITE_INFO WriteInfo
Definition: intro_types.h:1607
__must_check INTSTATUS IntVirtMemMap(QWORD Gva, DWORD Length, QWORD Cr3, DWORD Flags, void **HostPtr)
Maps a guest virtual memory range inside Introcore virtual address space.
Definition: introcore.c:2134
struct _SLACK_SPACE::@273::@275 Windows
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
The slack space is not 0-filled/NOP-filled.
Definition: intro_types.h:270
#define IC_TAG_ALLOC
Memory allocation.
Definition: memtags.h:28
#define IMAGE_SCN_MEM_DISCARDABLE
Definition: winpe.h:468
EVENT_INTEGRITY_VIOLATION Integrity
Definition: alerts.h:23
struct _EVENT_INTEGRITY_VIOLATION::@302 Originator
INTRO_ACTION Action
The action that was taken as the result of this alert.
Definition: intro_types.h:1194
INTSTATUS IntKernVirtMemRead(QWORD KernelGva, DWORD Length, void *Buffer, DWORD *RetLength)
Reads data from a guest kernel virtual memory range.
Definition: introcore.c:674
KERNEL_DRIVER * IntDriverFindByAddress(QWORD Gva)
Returns the driver in which Gva resides.
Definition: drivers.c:164
QWORD BaseAddress
The guest virtual address at which the monitored integrity region starts.
Definition: intro_types.h:1614
#define LIST_HEAD_INIT(Name)
Definition: introlists.h:39
BYTE * KernelBuffer
A buffer containing the entire kernel image.
Definition: winguest.h:851
#define IC_TAG_SLKE
Slack space entry.
Definition: memtags.h:75
#define INT_STATUS_INVALID_PARAMETER_1
Definition: introstatus.h:62
#define INT_STATUS_NOT_SUPPORTED
Definition: introstatus.h:287
struct _LIST_ENTRY * Blink
Definition: introlists.h:25
UINT8 Name[IMAGE_SIZEOF_SHORT_NAME]
Definition: winpe.h:79
Sent for integrity violation alerts. See EVENT_INTEGRITY_VIOLATION.
Definition: intro_types.h:92
static INTSTATUS IntSlackAllocWindows(BOOLEAN Pageable, QWORD ModuleBase, DWORD Size, QWORD *Buffer, QWORD SecHint)
Allocate memory inside the guest.
Definition: slack.c:124
void IntAlertFillWinKmModule(const KERNEL_DRIVER *Driver, INTRO_MODULE *EventModule)
Saves kernel module information inside an alert.
Definition: alerts.c:617
LIST_ENTRY Link
List entry element.
Definition: slack.c:35
#define IMAGE_SCN_MEM_NOT_PAGED
Definition: winpe.h:470
DWORD Section
The section index (zero based) inside the module.
Definition: slack.c:43
#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