Bitdefender Hypervisor Memory Introspection
swapgs.c
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2020 Bitdefender
3  * SPDX-License-Identifier: Apache-2.0
4  */
5 #include "swapgs.h"
6 #include "guests.h"
7 #include "icache.h"
8 #include "memcloak.h"
9 #include "slack.h"
10 #include "winpe.h"
11 
29 
30 
34 typedef struct _SWAPGS_HANDLER
35 {
41  BYTE Handler[32];
43  void *Cloak;
45 
50 typedef struct _SWAPGS_GADGET
51 {
55  void *Cloak;
58 
61 
64 
67 
68 
69 static SWAPGS_HANDLER *
72  _In_ BYTE Instruction[16],
73  _In_ BYTE Length
74  )
94 {
95  INTSTATUS status;
96  LIST_ENTRY *list;
97 
98  // First, search for an already-installed handler.
99  list = gHandlers.Flink;
100  while (list != &gHandlers)
101  {
103 
104  list = list->Flink;
105 
106  if ((hnd->Section == Section) && (hnd->InstructionLength == Length) &&
107  (0 == memcmp(Instruction, hnd->Instruction, Length)))
108  {
109  return hnd;
110  }
111  }
112 
113  // No match found, allocate a fresh handler.
114  SWAPGS_HANDLER *hnd = HpAllocWithTag(sizeof(*hnd), IC_TAG_SGDH);
115  if (NULL == hnd)
116  {
117  ERROR("[ERROR] HpAllocWithTag failed!\n");
118  return 0;
119  }
120 
121  InsertTailList(&gHandlers, &hnd->Link);
122 
123  hnd->Section = Section;
124  hnd->InstructionLength = Length;
125  memcpy(hnd->Instruction, Instruction, Length);
126 
127  // Store the LFENCE.
128  hnd->Handler[hnd->HandlerLength++] = 0x0F;
129  hnd->Handler[hnd->HandlerLength++] = 0xAE;
130  hnd->Handler[hnd->HandlerLength++] = 0xE8;
131 
132  // Store the original instruction.
133  memcpy(hnd->Handler + hnd->HandlerLength, Instruction, Length);
134 
135  hnd->HandlerLength += Length;
136 
137  // Store a PUSHFQ.
138  hnd->Handler[hnd->HandlerLength++] = 0x9C;
139 
140  // Store an "ADD [rsp + 8], x", in order to RET exactly after the modified instruction.
141  hnd->Handler[hnd->HandlerLength++] = 0x48;
142  hnd->Handler[hnd->HandlerLength++] = 0x83;
143  hnd->Handler[hnd->HandlerLength++] = 0x44;
144  hnd->Handler[hnd->HandlerLength++] = 0x24;
145  hnd->Handler[hnd->HandlerLength++] = 0x08;
146  hnd->Handler[hnd->HandlerLength++] = Length - 5;
147 
148  // Store a POPFQ.
149  hnd->Handler[hnd->HandlerLength++] = 0x9D;
150 
151  hnd->Handler[hnd->HandlerLength++] = 0xC3;
152 
153  // Allocate slack space for the handler.
154  status = IntSlackAlloc(gGuest.KernelVa, FALSE, hnd->HandlerLength, &hnd->Slack, Section);
155  if (!INT_SUCCESS(status))
156  {
157  ERROR("[ERROR] IntSlackAlloc failed: 0x%08x\n", status);
158  return 0;
159  }
160 
161  // Store the handler & cloak it.
163  NULL, hnd->Handler, NULL, &hnd->Cloak);
164  if (!INT_SUCCESS(status))
165  {
166  ERROR("[ERROR] IntMemClkCloakRegion failed: 0x%08x\n", status);
167  return 0;
168  }
169 
170  // All good, return the freshly allocated slack.
171  return hnd;
172 }
173 
174 
178 typedef enum _SWAPGS_SSTATE
179 {
185 } SWAPGS_SSTATE;
186 
187 
188 
189 INTSTATUS
191  void
192  )
217 {
218  INTSTATUS status;
219  DWORD i, count;
220  CHAR secname[9];
221  QWORD secid, seqstart, seqoffset;
222  SWAPGS_SSTATE state;
223  PBYTE pSectionBuffer = NULL;
224  IMAGE_DOS_HEADER *pDos;
225  IMAGE_FILE_HEADER *pFileHeader;
226  DWORD secheadersRva;
227 
229  {
231  }
232 
233  if (!gGuest.KptiActive)
234  {
236  }
237 
238  if (!gGuest.Guest64)
239  {
241  }
242 
243  IntPauseVcpus();
244 
245  // Parse the kernel and find all the code gadgets that match this template.
247 
249 
250  pFileHeader = (IMAGE_FILE_HEADER *)(gWinGuest->KernelBuffer + pDos->e_lfanew + 4);
251 
252  secheadersRva = pDos->e_lfanew + 4 + sizeof(IMAGE_FILE_HEADER) + pFileHeader->SizeOfOptionalHeader;
253 
254  for (DWORD seci = 0; seci < pFileHeader->NumberOfSections; seci++)
255  {
256  const IMAGE_SECTION_HEADER *pSec = (const IMAGE_SECTION_HEADER *)(gWinGuest->KernelBuffer + secheadersRva +
257  sizeof(IMAGE_SECTION_HEADER) * seci);
258 
259  // Make sure the section is ok.
260  if (pSec->VirtualAddress >= gGuest.KernelSize ||
262  {
263  ERROR("[ERROR] The section seems to point outside the kernel image!\n");
265  goto cleanup_and_exit;
266  }
267 
268  // We only parse executable, non-paged, non-discardable sections.
269  if (0 == (pSec->Characteristics & IMAGE_SCN_MEM_EXECUTE) ||
270  0 == (pSec->Characteristics & IMAGE_SCN_MEM_NOT_PAGED) ||
272  {
273  continue;
274  }
275 
276  // Skip all non-.text and non-KVASCODE sections.
277  if (0 != memcmp(pSec->Name, ".text", 5) && 0 != memcmp(pSec->Name, "KVASCODE", 8))
278  {
279  continue;
280  }
281 
282  count = 0;
283 
284  memcpy(secname, pSec->Name, 8);
285 
286  if (0 == memcmp(pSec->Name, "KVASCODE", 8))
287  {
288  // KVASCODE gadgets must be relocated inside the same section, otherwise we may end up branching from
289  // this section in another one which isn't mapped due to KPTI.
290  memcpy(&secid, pSec->Name, 8);
291  }
292  else
293  {
294  // Not KVASCODE, any executable non-paged section will do.
295  secid = 0;
296  }
297 
298  secname[8] = 0;
299  i = 0;
300  seqstart = 0;
301  seqoffset = 0;
302  state = swapgsSstateNone;
303 
304  TRACE("[SWAPGS] Parsing section '%s', virtual-size 0x%08x...\n", secname, pSec->Misc.VirtualSize);
305 
306  pSectionBuffer = gWinGuest->KernelBuffer + pSec->VirtualAddress;
307 
308  while (i < pSec->Misc.VirtualSize)
309  {
310  INSTRUX instrux;
311  QWORD offset = i;
312  QWORD target = gGuest.KernelVa + pSec->VirtualAddress + offset;
313  DWORD reloffs = 0;
314  NDSTATUS ndstatus;
315 
316  ndstatus = NdDecodeEx(&instrux, pSectionBuffer + offset, pSec->Misc.VirtualSize - i, ND_CODE_64, ND_DATA_64);
317  if (!ND_SUCCESS(ndstatus))
318  {
319  i += 1;
320  continue;
321  }
322 
323  if (instrux.Seg == ND_PREFIX_G2_SEG_GS && state == swapgsSstateSwapgs &&
324  !ND_IS_OP_REG(&instrux.Operands[0], ND_REG_GPR, 8, NDR_RSP))
325  {
326  // We found a GS based addressing after SWAPGS. Make sure it does NOT modify RSP, or else, we won't be
327  // able to RET from the handler!
328  // Also, if the following instruction is a serializing instruction, this will just reset the entire
329  // state, as we won't have to mitigate anything.
331  }
332  else if (instrux.Instruction == ND_INS_SWAPGS && (state == swapgsSstateJcc || state == swapgsSstatePush))
333  {
334  // We have a SWAPGS after either a conditional branch or some PUSHes.
335  state = swapgsSstateSwapgs;
336  }
337  else if (instrux.Instruction == ND_INS_PUSH && (state == swapgsSstateJcc || state == swapgsSstatePush))
338  {
339  // PUSH after PUSH or conditional branch.
340  state = swapgsSstatePush;
341  }
342  else if (instrux.Instruction == ND_INS_Jcc && state == swapgsSstateNone)
343  {
344  // Found the conditional branch!
345  seqstart = target;
346  seqoffset = offset;
347  state = swapgsSstateJcc;
348  }
349  else
350  {
351  // Nothing interesting, reset the state machine.
352  state = swapgsSstateNone;
353  }
354 
355  if (swapgsSstateGsBasedAccess == state)
356  {
357  BYTE call[16] =
358  {
359  0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
360  0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
361  };
362 
363  LOG("[SWAPGS] Found vulnerable sequence at 0x%016llx, offset 0x%llx, context "
364  "%02x %02x %02x %02x %02x %02x %02x %02x\n",
365  seqstart, seqoffset,
366  pSectionBuffer[seqoffset + 0],
367  pSectionBuffer[seqoffset + 1],
368  pSectionBuffer[seqoffset + 2],
369  pSectionBuffer[seqoffset + 3],
370  pSectionBuffer[seqoffset + 4],
371  pSectionBuffer[seqoffset + 5],
372  pSectionBuffer[seqoffset + 6],
373  pSectionBuffer[seqoffset + 7]
374  );
375 
376  // Get a handler.
377  SWAPGS_HANDLER *handler = IntSwapgsInstallHandler(secid, instrux.InstructionBytes, instrux.Length);
378  if (NULL == handler)
379  {
380  ERROR("[ERROR] Failed installing a handler!\n");
381  status = INT_STATUS_NOT_SUPPORTED;
382  goto cleanup_and_exit;
383  }
384 
385  // Allocate a new gadget object.
386  SWAPGS_GADGET *gadget = HpAllocWithTag(sizeof(*gadget), IC_TAG_SGDG);
387  if (NULL == gadget)
388  {
389  ERROR("[ERROR] HpAllocWithTag failed!\n");
391  goto cleanup_and_exit;
392  }
393 
394  InsertTailList(&gGadgets, &gadget->Link);
395 
396  // Found a SWAPGS gadget, patch it out!
397  gadget->Gla = target;
398  gadget->Length = instrux.Length;
399  gadget->Handler = handler;
400 
401  // Note: the vulnerable sequences are always of the form "Jcc/SWAPGS", which are 5 bytes long.
402  reloffs = (DWORD)(handler->Slack - target - 5);
403 
404  // Build a "CALL gadget" instruction.
405  call[0] = 0xE8; // CALL opcode
406  call[1] = (reloffs >> 0) & 0xFF;
407  call[2] = (reloffs >> 8) & 0xFF;
408  call[3] = (reloffs >> 16) & 0xFF;
409  call[4] = (reloffs >> 24) & 0xFF;
410 
411  // Intercept the instruction.
412  status = IntMemClkCloakRegion(gadget->Gla, 0, instrux.Length, MEMCLOAK_OPT_APPLY_PATCH,
413  pSectionBuffer + offset,
414  call, NULL, &gadget->Cloak);
415  if (!INT_SUCCESS(status))
416  {
417  ERROR("[ERROR] IntMemClkCloakRegion failed: 0x%08x\n", status);
418  goto cleanup_and_exit;
419  }
420 
422 
423  count++;
424  }
425 
426  i += instrux.Length;
427  }
428 
429  TRACE("[SWAPGS] Patched %d instructions in section '%s'!\n", count, secname);
430  }
431 
432  status = INT_STATUS_SUCCESS;
433 
434  gMitigated = TRUE;
435 
436 cleanup_and_exit:
437  IntResumeVcpus();
438 
440 
441  return status;
442 }
443 
444 
445 void
447  void
448  )
454 {
455  LIST_ENTRY *list;
456 
457  list = gGadgets.Flink;
458  while (list != &gGadgets)
459  {
461 
462  list = list->Flink;
463 
464  // Remove the CALL.
465  if (NULL != gadget->Cloak)
466  {
468 
469  gadget->Cloak = NULL;
470  }
471 
472  RemoveEntryList(&gadget->Link);
473 
475  }
476 
477  list = gHandlers.Flink;
478  while (list != &gHandlers)
479  {
481 
482  list = list->Flink;
483 
484  if (NULL != handler->Cloak)
485  {
487 
488  handler->Cloak = NULL;
489  }
490 
491  RemoveEntryList(&handler->Link);
492 
494  }
495 
496  gMitigated = FALSE;
497 }
498 
499 
500 void
502  void
503  )
507 {
508  LIST_ENTRY *list;
509 
510  list = gGadgets.Flink;
511  while (list != &gGadgets)
512  {
514 
515  list = list->Flink;
516 
517  if (NULL != gadget->Cloak)
518  {
520 
521  gadget->Cloak = NULL;
522 
523  // Flush the icache entry.
525  }
526  }
527 }
528 
529 
530 BOOLEAN
532  _In_ QWORD Ptr,
533  _In_ THS_PTR_TYPE Type,
534  _Out_opt_ QWORD *Gadget
535  )
545 {
546  LIST_ENTRY *list;
547 
548  list = gHandlers.Flink;
549  while (list != &gHandlers)
550  {
552 
553  list = list->Flink;
554 
555  if (Ptr >= handler->Slack && Ptr < handler->Slack + handler->HandlerLength)
556  {
557  WARNING("[WARNING] Found %s ptr 0x%016llx in SWAPGS handler: slack addr 0x%016llx, size %x\n",
558  Type == ptrLiveRip ? "live RIP" : "stack value", Ptr, handler->Slack, handler->HandlerLength);
559 
560  if (NULL != Gadget)
561  {
562  *Gadget = handler->Slack;
563  }
564 
565  return TRUE;
566  }
567  }
568 
569  if (NULL != Gadget)
570  {
571  *Gadget = 0;
572  }
573 
574  return FALSE;
575 }
576 
577 
578 QWORD
580  _In_ QWORD Ptr
581  )
589 {
590  LIST_ENTRY *list;
591 
592  list = gGadgets.Flink;
593  while (list != &gGadgets)
594  {
596  list = list->Flink;
597 
598  // We can check past the beginning.
599  if (Ptr > gadget->Gla && Ptr < gadget->Gla + gadget->Length)
600  {
601  return gadget->Handler->Slack + (Ptr - gadget->Gla);
602  }
603  }
604 
605  // No modification, return it as it is.
606  return Ptr;
607 }
#define IMAGE_SCN_MEM_EXECUTE
Definition: winpe.h:472
UINT16 NumberOfSections
Definition: winpe.h:65
_Bool BOOLEAN
Definition: intro_types.h:58
#define CONTAINING_RECORD(List, Type, Member)
Definition: introlists.h:36
struct _IMAGE_FILE_HEADER IMAGE_FILE_HEADER
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
#define INT_STATUS_SUCCESS
Definition: introstatus.h:54
LIST_HEAD gGadgets
List of all the hooked gadgets.
Definition: swapgs.c:60
#define STATS_EXIT(id)
Definition: stats.h:160
DWORD KernelSize
The size of the kernel.
Definition: guests.h:284
struct _LIST_ENTRY * Flink
Definition: introlists.h:20
#define IC_ANY_VAS
Definition: icache.h:86
A conditional jump was found.
Definition: swapgs.c:181
struct _SWAPGS_HANDLER SWAPGS_HANDLER
#define INT_SUCCESS(Status)
Definition: introstatus.h:42
INTSTATUS IntResumeVcpus(void)
Resumes the VCPUs previously paused with IntPauseVcpus.
Definition: introcore.c:2355
LIST_HEAD gHandlers
List of all distinct handlers.
Definition: swapgs.c:63
INTSTATUS IntSwapgsStartMitigation(void)
Scan the kernel for vulnerable SWAPGS gadgets, and mitigate CVE-2019-1125, when such gadgets are foun...
Definition: swapgs.c:190
QWORD Gla
Linear address of the instrumented SWAPGS gadget.
Definition: swapgs.c:53
#define INT_STATUS_NOT_NEEDED_HINT
Definition: introstatus.h:317
#define ERROR(fmt,...)
Definition: glue.h:62
A GS based addressing instruction was found. This is where we start instrumenting.
Definition: swapgs.c:184
#define HpAllocWithTag(Len, Tag)
Definition: glue.h:516
BOOLEAN IntSwapgsIsPtrInHandler(QWORD Ptr, THS_PTR_TYPE Type, QWORD *Gadget)
Check if a pointer points inside a SWAPGS handler.
Definition: swapgs.c:531
int INTSTATUS
The status data type.
Definition: introstatus.h:24
enum _SWAPGS_SSTATE SWAPGS_SSTATE
BYTE Handler[32]
Handler code.
Definition: swapgs.c:41
QWORD IntSwapgsRelocatePtrIfNeeded(QWORD Ptr)
Relocate a pointer if it points inside a SWAPGS gadget, and make it point inside the installed handle...
Definition: swapgs.c:579
INTSTATUS IntPauseVcpus(void)
Pauses all the guest VCPUs.
Definition: introcore.c:2320
void * Cloak
Cloak handle to the handler.
Definition: swapgs.c:43
Will write the contents of the patched data inside the guest.
Definition: memcloak.h:54
INTSTATUS IntSlackAlloc(QWORD ModuleBase, BOOLEAN Pageable, DWORD Size, QWORD *Buffer, QWORD SecHint)
Allocate slack inside the guest.
Definition: slack.c:437
#define LOG(fmt,...)
Definition: glue.h:61
BOOLEAN KptiActive
True if KPTI is enabled on this guest, False if it is not.
Definition: guests.h:291
BYTE Instruction[16]
The instruction that has been replaced by a jump.
Definition: swapgs.c:39
void IntSwapgsUninit(void)
Uninit the SWAPGS mitigation.
Definition: swapgs.c:446
SWAPGS_HANDLER * Handler
SWAPGS handler.
Definition: swapgs.c:56
#define _Out_opt_
Definition: intro_sal.h:30
#define STATS_ENTER(id)
Definition: stats.h:153
uint8_t * PBYTE
Definition: intro_types.h:47
static BOOLEAN RemoveEntryList(LIST_ENTRY *Entry)
Definition: introlists.h:87
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
QWORD Current
The currently used options.
Definition: guests.h:236
LIST_ENTRY Link
List entry element.
Definition: swapgs.c:36
LIST_ENTRY Link
List entry element.
Definition: swapgs.c:52
#define TRUE
Definition: intro_types.h:30
UINT32 VirtualAddress
Definition: winpe.h:85
BOOLEAN gMitigated
TRUE if we mitigated the SWAPGS issue.
Definition: swapgs.c:66
#define HpFreeAndNullWithTag(Add, Tag)
Definition: glue.h:517
#define TRACE(fmt,...)
Definition: glue.h:58
#define INT_STATUS_INVALID_INTERNAL_STATE
Definition: introstatus.h:272
INTSTATUS IntMemClkCloakRegion(QWORD VirtualAddress, QWORD Cr3, DWORD Size, DWORD Options, PBYTE OriginalData, PBYTE PatchedData, PFUNC_IntMemCloakWriteHandle WriteHandler, void **CloakHandle)
Hides a memory zone from the guest.
Definition: memcloak.c:548
_SWAPGS_SSTATE
Definition: swapgs.c:178
QWORD KernelVa
The guest virtual address at which the kernel image.
Definition: guests.h:283
INTSTATUS IntIcFlushAddress(PINS_CACHE Cache, QWORD Gva, QWORD Cr3)
Flush entries cached from a given address.
Definition: icache.c:597
INT32 e_lfanew
Definition: winpe.h:58
union _IMAGE_SECTION_HEADER::@214 Misc
No state, just started scanning.
Definition: swapgs.c:180
The RIP of a thread.
static void InsertTailList(LIST_ENTRY *ListHead, LIST_ENTRY *Entry)
Definition: introlists.h:135
A PUSH was found.
Definition: swapgs.c:182
#define WARNING(fmt,...)
Definition: glue.h:60
BYTE HandlerLength
Handler code length.
Definition: swapgs.c:42
void * InstructionCache
The currently used instructions cache.
Definition: guests.h:404
uint32_t DWORD
Definition: intro_types.h:49
UINT32 VirtualSize
Definition: winpe.h:83
struct _SWAPGS_GADGET SWAPGS_GADGET
UINT32 Characteristics
Definition: winpe.h:92
QWORD Section
NT section of the handler.
Definition: swapgs.c:38
Measures the instruction search done for the SWAPGS protection.
Definition: stats.h:63
GUEST_STATE gGuest
The current guest state.
Definition: guests.c:50
static SWAPGS_HANDLER * IntSwapgsInstallHandler(QWORD Section, BYTE Instruction[16], BYTE Length)
Install a handler for a given instruction.
Definition: swapgs.c:70
THS_PTR_TYPE
The type of pointer to be checked.
#define IMAGE_SCN_MEM_DISCARDABLE
Definition: winpe.h:468
The SWAPGS instruction was found.
Definition: swapgs.c:183
#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_SGDG
SWAPGS gadget.
Definition: memtags.h:126
#define INTRO_OPT_PROT_KM_SWAPGS
Enable SWAPGS (CVE-2019-1125) mitigation.
Definition: intro_types.h:502
void IntSwapgsDisable(void)
Disable SWAPGS mitigations. Must be used only for PrepareUninit.
Definition: swapgs.c:501
#define INT_STATUS_NOT_SUPPORTED
Definition: introstatus.h:287
#define IC_TAG_SGDH
SWAPGS handler.
Definition: memtags.h:127
INTSTATUS IntMemClkUncloakRegion(void *CloakHandle, DWORD Options)
Removes a cloak region, making the original memory contents available again to the guest...
Definition: memcloak.c:970
UINT8 Name[IMAGE_SIZEOF_SHORT_NAME]
Definition: winpe.h:79
BYTE Length
Length of the instrumented SWAPS gadget.
Definition: swapgs.c:54
QWORD Slack
Slack address where the handler has been allocated.
Definition: swapgs.c:37
char CHAR
Definition: intro_types.h:56
BYTE InstructionLength
Length of the replaced instruction.
Definition: swapgs.c:40
#define IMAGE_SCN_MEM_NOT_PAGED
Definition: winpe.h:470
void * Cloak
Cloak handle.
Definition: swapgs.c:55
INTRO_PROT_OPTIONS CoreOptions
The activation and protection options for this guest.
Definition: guests.h:271
UINT16 SizeOfOptionalHeader
Definition: winpe.h:69
#define FALSE
Definition: intro_types.h:34
#define INT_STATUS_INSUFFICIENT_RESOURCES
Definition: introstatus.h:281