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 
9 
27 
28 
32 typedef struct _SLACK_SPACE
33 {
37 
38  union
39  {
40  struct
41  {
46  } Windows;
47  };
48 
51 
52 
53 static LIST_HEAD gSlackAllocations = LIST_HEAD_INIT(gSlackAllocations);
54 
55 #define for_each_slack(_var_name) list_for_each(gSlackAllocations, SLACK_SPACE, _var_name)
56 
57 
58 
59 static INTSTATUS
61  _In_ BOOLEAN Pageable,
63  _In_ DWORD Size,
64  _Out_ QWORD *Buffer,
65  _In_opt_ QWORD SecHint
66  )
101 {
102  INTSTATUS status;
103  IMAGE_SECTION_HEADER sec = { 0 };
104  INTRO_PE_INFO peInfo = { 0 };
105  BYTE *moduleBuffer = NULL;
106  DWORD bufferSize = 0;
107 
108 
109  if (ModuleBase == gGuest.KernelVa)
110  {
111  moduleBuffer = gWinGuest->KernelBuffer;
112  bufferSize = gWinGuest->KernelBufferSize;
113  }
114 
115  status = IntPeValidateHeader(ModuleBase, moduleBuffer, bufferSize, &peInfo, gGuest.Mm.SystemCr3);
116  if (!INT_SUCCESS(status))
117  {
118  ERROR("[ERROR] IntPeValidateHeader failed with status: 0x%08x\n", status);
119  return status;
120  }
121 
122  // Get the base address of the section headers and set an EPT hook on every section that it's not writable.
123  // The idea is that this way, we will protect the code, IAT, EAT with one shot, since both the IAT & EAT are
124  // placed by the compiler inside a read-only section.
125 
126  // Parse the section headers, in order to find some empty space.
127  for (DWORD i = 0; i < peInfo.NumberOfSections; i++)
128  {
129  if (moduleBuffer != NULL && peInfo.SectionOffset + sizeof(IMAGE_SECTION_HEADER) * (i + 1ull) < bufferSize)
130  {
131  sec = *(IMAGE_SECTION_HEADER *)(moduleBuffer + peInfo.SectionOffset + sizeof(IMAGE_SECTION_HEADER) * i);
132  }
133  else
134  {
135  status = IntKernVirtMemRead(ModuleBase + peInfo.SectionOffset + sizeof(IMAGE_SECTION_HEADER) * i,
136  sizeof(IMAGE_SECTION_HEADER), &sec, NULL);
137  if (!INT_SUCCESS(status))
138  {
139  ERROR("[ERROR] Failed reading IMAGE_SECTION_HEADER %d for module 0x%016llx: 0x%08x\n",
140  i, ModuleBase, status);
141  return status;
142  }
143  }
144 
145  if ((0 != SecHint) && (0 != memcmp(&SecHint, sec.Name, 8)))
146  {
147  continue;
148  }
149 
150  // The section where we will place the hook must NOT be writable, discardable or pageable.
151  if ((sec.Characteristics & IMAGE_SCN_MEM_WRITE) ||
154  (!(sec.Characteristics & IMAGE_SCN_MEM_NOT_PAGED) && !Pageable) ||
155  ((sec.Characteristics & IMAGE_SCN_MEM_NOT_PAGED) && Pageable))
156  {
157  continue;
158  }
159 
160  // INITKDBG is overwritten at some times by kdbg... We really don't want to store our code there.
161  if ((memcmp(sec.Name, "INITKDBG", 8) == 0))
162  {
163  continue;
164  }
165 
166  // Ignore page-aligned sections.
167  if (sec.Misc.VirtualSize % PAGE_SIZE == 0)
168  {
169  continue;
170  }
171 
172  if (PAGE_REMAINING(sec.Misc.VirtualSize) >= Size)
173  {
174  // We found a suitable section; we must validate that it won't overlap with an already set hook
175  DWORD totalUsedSpace;
176  DWORD totalSpace;
177  DWORD maxOffset;
178 
179  totalUsedSpace = 0;
180  maxOffset = 0;
181  totalSpace = PAGE_REMAINING(sec.Misc.VirtualSize);
182 
183  for_each_slack(pSlack)
184  {
185  if ((pSlack->Windows.Section == i) && (pSlack->ModuleBase == ModuleBase))
186  {
187  if (pSlack->Windows.SectionOffset > maxOffset)
188  {
189  maxOffset = pSlack->Windows.SectionOffset;
190 
191  totalUsedSpace = maxOffset - sec.Misc.VirtualSize + pSlack->AllocationSize;
192  }
193  }
194  }
195 
196  // Make sure enough space still remains. Also, make sure the slack buffer is filled with zeros.
197  if (totalSpace - totalUsedSpace >= Size)
198  {
199  SLACK_SPACE *pSlack;
200  QWORD gva = ModuleBase + sec.VirtualAddress + sec.Misc.VirtualSize + totalUsedSpace, j;
201 
202  // The Size parameter is validated by the caller.
203  BYTE *buf = HpAllocWithTag(Size, IC_TAG_ALLOC);
204  if (NULL == buf)
205  {
207  }
208 
209  status = IntKernVirtMemRead(gva, Size, buf, NULL);
210  if (!INT_SUCCESS(status))
211  {
213  ERROR("[ERROR] IntKernVirtMemRead failed GVA 0x%016llx: 0x%08x\n", gva, status);
214  return status;
215  }
216 
217  for (j = 0; j < Size; j++)
218  {
219  if (0 != buf[j])
220  {
221  ERROR("[ERROR] Slack buffer not 0-filled! 0x%016llx\n", gva + j);
222  IntDumpBuffer(buf, gva, Size, 8, 1, TRUE, TRUE);
225  }
226  }
227 
229 
230  pSlack = HpAllocWithTag(sizeof(*pSlack), IC_TAG_SLKE);
231  if (NULL == pSlack)
232  {
234  }
235 
236  TRACE("[SLACK] Found %d bytes of space, used %d bytes, in section %d, "
237  "at offset %08x in module 0x%016llx\n", totalSpace, totalUsedSpace, i,
238  sec.VirtualAddress + sec.Misc.VirtualSize, ModuleBase);
239 
240  pSlack->Gva = gva;
241  pSlack->AllocationSize = Size;
242  pSlack->ModuleBase = ModuleBase;
243  pSlack->Windows.AllocationOffset = (sec.Misc.VirtualSize & PAGE_OFFSET) + totalUsedSpace;
244  pSlack->Windows.Section = i;
245  pSlack->Windows.SectionOffset = sec.Misc.VirtualSize + totalUsedSpace;
246  pSlack->Windows.SectionSize = sec.Misc.VirtualSize;
247 
248  InsertTailList(&gSlackAllocations, &pSlack->Link);
249 
250  *Buffer = pSlack->Gva;
251 
252  return INT_STATUS_SUCCESS;
253  }
254  }
255  }
256 
258 }
259 
260 
261 static INTSTATUS
263  _In_ DWORD Size,
264  _Out_ QWORD *Buffer
265  )
279 {
280  INTSTATUS status;
281  QWORD gva = gGuest.KernelVa;
282  QWORD maxGva = gGuest.KernelVa + (2 * PAGE_SIZE);
283 
284  //
285  // If we have other allocations, don't bother to parse pages inside the kernel, just skip right after the
286  // last one. The NOPs are in a contiguous big block, so no need for any complicated logic.
287  //
288  if (!IsListEmpty(&gSlackAllocations))
289  {
290  SLACK_SPACE *pSlack = CONTAINING_RECORD(gSlackAllocations.Blink, SLACK_SPACE, Link);
291 
292  gva = pSlack->Gva + pSlack->AllocationSize;
293  }
294 
295  for (; gva < maxGva; gva += PAGE_REMAINING(gva))
296  {
297  BYTE *p;
298  DWORD maxOffset;
299 
300  // A limitation for now: the allocation has to be fully in one page
301  if (Size > PAGE_REMAINING(gva))
302  {
303  continue;
304  }
305 
306  maxOffset = PAGE_REMAINING(gva) - Size;
307 
308  status = IntVirtMemMap(gva, PAGE_REMAINING(gva), gGuest.Mm.SystemCr3, 0, &p);
309  if (!INT_SUCCESS(status))
310  {
311  ERROR("[ERROR] IntVirtMemMap failed for %llx: 0x%08x\n", gva, status);
312  return status;
313  }
314 
315  for (DWORD offset = 0; offset < maxOffset; offset++)
316  {
317  DWORD foundSize = 0;
318 
319  // Skip until the first NOP
320  while (offset < maxOffset && (p[offset] != 0x90))
321  {
322  offset++;
323  }
324 
325  // p[maxOffset + Size] last valid is [maxOffset + Size - 1]
326  // offset goes from 0 -> maxOffset so last valid is maxOffset - 1
327  // foundSize goes from 0 -> Size so last valid is Size - 1
328  // worst case: p[maxOffset - 1 + Size - 1] = p[maxOffset + Size - 2] which is in the buffer still
329  while ((foundSize < Size) && (p[offset + foundSize] == 0x90))
330  {
331  foundSize++;
332  }
333 
334  if (foundSize == Size)
335  {
336  // Found a suitable area filled with NOPs, let's just use that
337  SLACK_SPACE *pSlack = HpAllocWithTag(sizeof(*pSlack), IC_TAG_SLKE);
338  if (NULL == pSlack)
339  {
341  }
342 
343  pSlack->Gva = gva + offset;
344  pSlack->AllocationSize = Size;
345  pSlack->ModuleBase = gGuest.KernelVa;
346 
347  TRACE("[SLACK] Found %d bytes of space at 0x%016llx\n", foundSize, pSlack->Gva);
348 
349  InsertTailList(&gSlackAllocations, &pSlack->Link);
350 
351  *Buffer = pSlack->Gva;
352 
353  IntVirtMemUnmap(&p);
354 
355  return INT_STATUS_SUCCESS;
356  }
357  }
358 
359  IntVirtMemUnmap(&p);
360  }
361 
363 }
364 
365 
366 INTSTATUS
369  _In_ BOOLEAN Pageable,
370  _In_ DWORD Size,
371  _Out_ QWORD *Buffer,
372  _In_opt_ QWORD SecHint
373  )
393 {
394  INTSTATUS status;
395 
396  if (0 == Size)
397  {
399  }
400 
401  if (NULL == Buffer)
402  {
404  }
405 
407  {
408  if (0 == ModuleBase)
409  {
411  }
412 
413  status = IntSlackAllocWindows(Pageable, ModuleBase, Size, Buffer, SecHint);
414  }
415  else if (introGuestLinux == gGuest.OSType)
416  {
417  status = IntSlackAllocLinux(Size, Buffer);
418  }
419  else
420  {
421  status = INT_STATUS_NOT_SUPPORTED;
422  }
423 
424  return status;
425 }
426 
427 
428 INTSTATUS
430  _In_ QWORD Buffer
431  )
443 {
444  if (0 == Buffer)
445  {
447  }
448 
449  for_each_slack(pSlack)
450  {
451  if (pSlack->Gva == Buffer)
452  {
453  RemoveEntryList(&pSlack->Link);
454 
456 
457  return INT_STATUS_SUCCESS;
458  }
459  }
460 
461  return INT_STATUS_NOT_FOUND;
462 }
463 
464 
465 void
467  void
468  )
472 {
473  for_each_slack(pSlack)
474  {
475  RemoveEntryList(&pSlack->Link);
476 
478  }
479 }
#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:429
uint8_t BYTE
Definition: intro_types.h:47
WINDOWS_GUEST * gWinGuest
Global variable holding the state of a Windows guest.
Definition: winguest.c:35
#define _In_
Definition: intro_sal.h:21
QWORD SystemCr3
The Cr3 used to map the kernel.
Definition: guests.h:207
#define INT_STATUS_SUCCESS
Definition: introstatus.h:54
#define PAGE_REMAINING(addr)
Definition: pgtable.h:163
DWORD AllocationSize
The number of bytes allocated.
Definition: slack.c:49
#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:55
QWORD SectionOffset
Offset of the first section header.
Definition: winpe.h:602
#define PAGE_OFFSET
Definition: pgtable.h:32
DWORD SectionOffset
The offset inside the section of the allocation.
Definition: slack.c:43
#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:466
INTRO_GUEST_TYPE OSType
The type of the guest.
Definition: guests.h:274
struct _SLACK_SPACE * PSLACK_SPACE
struct _SLACK_SPACE SLACK_SPACE
QWORD Gva
The guest virtual address of the actual allocation.
Definition: slack.c:36
static BOOLEAN RemoveEntryList(LIST_ENTRY *Entry)
Definition: introlists.h:87
unsigned long long QWORD
Definition: intro_types.h:53
union _IMAGE_SECTION_HEADER::@209 Misc
#define TRUE
Definition: intro_types.h:30
#define INT_STATUS_INVALID_PARAMETER_4
Definition: introstatus.h:71
UINT32 VirtualAddress
Definition: winpe.h:85
QWORD NumberOfSections
Number of sections.
Definition: winpe.h:603
#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:367
QWORD KernelVa
The guest virtual address at which the kernel image.
Definition: guests.h:279
static void InsertTailList(LIST_ENTRY *ListHead, LIST_ENTRY *Entry)
Definition: introlists.h:135
#define PAGE_SIZE
Definition: common.h:53
struct _SLACK_SPACE::@266::@268 Windows
DWORD AllocationOffset
The allocation offset, within the last page of the section.
Definition: slack.c:45
QWORD ModuleBase
The module base used for the allocation.
Definition: slack.c:35
uint32_t DWORD
Definition: intro_types.h:49
UINT32 VirtualSize
Definition: winpe.h:83
DWORD KernelBufferSize
The size of the KernelBuffer.
Definition: winguest.h:838
static LIST_HEAD gSlackAllocations
Definition: slack.c:53
UINT32 Characteristics
Definition: winpe.h:92
static INTSTATUS IntSlackAllocLinux(DWORD Size, QWORD *Buffer)
Allocate slack space on Linux.
Definition: slack.c:262
INTSTATUS IntPeValidateHeader(QWORD ImageBase, BYTE *ImageBaseBuffer, DWORD ImageBaseBufferSize, INTRO_PE_INFO *PeInfo, QWORD Cr3)
Validates a PE header.
Definition: winpe.c:131
DWORD SectionSize
The size of the section.
Definition: slack.c:44
__must_check INTSTATUS IntVirtMemMap(QWORD Gva, DWORD Length, QWORD Cr3, DWORD Flags, void **HostPtr)
Maps a guest virtual memory range inside Introcore virtual address space.
Definition: introcore.c:2134
MM Mm
Guest memory information, such as paging mode, system Cr3 value, etc.
Definition: guests.h:370
GUEST_STATE gGuest
The current guest state.
Definition: guests.c:48
#define IC_TAG_ALLOC
Memory allocation.
Definition: memtags.h:28
#define IMAGE_SCN_MEM_DISCARDABLE
Definition: winpe.h:468
TIMER_FRIENDLY void IntDumpBuffer(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
INTSTATUS IntKernVirtMemRead(QWORD KernelGva, DWORD Length, void *Buffer, DWORD *RetLength)
Reads data from a guest kernel virtual memory range.
Definition: introcore.c:674
#define LIST_HEAD_INIT(Name)
Definition: introlists.h:39
BYTE * KernelBuffer
A buffer containing the entire kernel image.
Definition: winguest.h:837
#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
static INTSTATUS IntSlackAllocWindows(BOOLEAN Pageable, QWORD ModuleBase, DWORD Size, QWORD *Buffer, QWORD SecHint)
Allocate memory inside the guest.
Definition: slack.c:60
LIST_ENTRY Link
List entry element.
Definition: slack.c:34
#define IMAGE_SCN_MEM_NOT_PAGED
Definition: winpe.h:470
DWORD Section
The section index (zero based) inside the module.
Definition: slack.c:42
#define INT_STATUS_INSUFFICIENT_RESOURCES
Definition: introstatus.h:281
#define INT_STATUS_INVALID_PARAMETER_3
Definition: introstatus.h:68