Bitdefender Hypervisor Memory Introspection
rtlpvirtualunwind.c
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2020 Bitdefender
3  * SPDX-License-Identifier: Apache-2.0
4  */
5 #include "rtlpvirtualunwind.h"
6 #include "guests.h"
7 #include "introcpu.h"
8 
9 
11 
12 
15  void
16  )
66 {
67  INTSTATUS status;
68  QWORD detourAddr, readAddr, byteAddr, cmpAddr, cmpValue;
69  DWORD size;
70  DETOUR_TAG tag;
71  BYTE byteValue;
72  QWORD e;
73 
75  {
77  }
78 
79  // Ignore anything coming outside the kernel image. This happens when PatchGuard triggers read faults. Also, ignore
80  // reads that don't hit inside the kernel image.
83  {
85  }
86 
87  // If the read didn't came from one of our handlers, bail out.
88  status = IntDetGetAddrAndTag(gVcpu->Regs.Rip, &detourAddr, &size, &tag);
89  if (!INT_SUCCESS(status))
90  {
91  return status;
92  }
93 
94 
95  // NOTE: It's perfectly safe to compare only the low 32 bit of the base register, since we restrict this
96  // optimization for the kernel image only.
97 
98  switch (tag)
99  {
101  // mov al, [rcx]
102  e = __rdtsc() % 12;
103  readAddr = gVcpu->Regs.Rcx;
104  cmpValue = gVcpu->Regs.Rcx;
105  byteAddr = detourAddr + 1 + (e * 10) + 1;
106  cmpAddr = detourAddr + 1 + (e * 10) + 4;
107  break;
108 
110  // mov al, [rcx+1]
111  e = __rdtsc() % 12;
112  readAddr = gVcpu->Regs.Rcx + 1;
113  cmpValue = gVcpu->Regs.Rcx;
114  byteAddr = detourAddr + 1 + (e * 10ull) + 1;
115  cmpAddr = detourAddr + 1 + (e * 10ull) + 4;
116  break;
117 
119  // mov dl, [rcx]
120  e = __rdtsc() % 12;
121  readAddr = gVcpu->Regs.Rcx;
122  cmpValue = gVcpu->Regs.Rcx;
123  byteAddr = detourAddr + 1 + (e * 10ull) + 1;
124  cmpAddr = detourAddr + 1 + (e * 10ull) + 4;
125  break;
126 
128  // mov al, [rbp+1]
129  e = __rdtsc() % 12;
130  readAddr = gVcpu->Regs.Rbp + 1;
131  cmpValue = gVcpu->Regs.Rbp;
132  byteAddr = detourAddr + 1 + (e * 10) + 1;
133  cmpAddr = detourAddr + 1 + (e * 10) + 4;
134  break;
135 
137  // cmp byte ptr[rbp + 0], 48h
138  e = __rdtsc() % 8;
139  readAddr = gVcpu->Regs.Rbp;
140  cmpValue = gVcpu->Regs.Rbp;
141  byteAddr = detourAddr + 2 + (e * 16) + 9;
142  cmpAddr = detourAddr + 2 + (e * 16) + 2;
143  break;
144 
146  // mov al, [rbp + 0]
147  e = __rdtsc() % 12;
148  readAddr = gVcpu->Regs.Rbp;
149  cmpValue = gVcpu->Regs.Rbp;
150  byteAddr = detourAddr + 1 + (e * 10) + 1;
151  cmpAddr = detourAddr + 1 + (e * 10) + 4;
152  break;
153 
155  // cmp byte ptr[rbp + 1], 8Dh
156  e = __rdtsc() % 8;
157  readAddr = gVcpu->Regs.Rbp + 1;
158  cmpValue = gVcpu->Regs.Rbp;
159  byteAddr = detourAddr + 2 + (e * 16) + 9;
160  cmpAddr = detourAddr + 2 + (e * 16) + 2;
161  break;
162 
164  // cmp byte ptr[rcx + 1], 0FFh
165  e = __rdtsc() % 8;
166  readAddr = gVcpu->Regs.Rcx + 1;
167  cmpValue = gVcpu->Regs.Rcx;
168  byteAddr = detourAddr + 2 + (e * 16) + 9;
169  cmpAddr = detourAddr + 2 + (e * 16) + 2;
170  break;
171 
172  default:
173  return INT_STATUS_NOT_FOUND;
174  }
175 
176  IntPauseVcpus();
177 
179 
180  for (DWORD i = 0; i < gGuest.CpuCount; i++)
181  {
182  QWORD rip;
183 
184  // Ignore the current VCPU, since it obviously points inside a handler.
185  if (gVcpu->Index == i)
186  {
187  continue;
188  }
189 
190  status = IntRipRead(i, &rip);
191  if (!INT_SUCCESS(status))
192  {
193  ERROR("[ERROR] IntRipRead failed: 0x%08x\n", status);
194  continue;
195  }
196 
197  if (rip >= detourAddr && rip < detourAddr + size)
198  {
200  }
201  }
202 
204  {
205  TRACE("[RTLPVIRTUALUNWIND] A rip seems to be inside our relocs, bailing out for now...\n");
207  goto resume_and_exit;
208  }
209 
210  // Read the byte value.
211  status = IntKernVirtMemRead(readAddr, 1, &byteValue, NULL);
212  if (!INT_SUCCESS(status))
213  {
214  ERROR("[ERROR] IntKernVirtMemRead failed for %llx: 0x%08x\n", readAddr, status);
215  goto resume_and_exit;
216  }
217 
218  // Patch the immediate in the instruction.
219  status = IntKernVirtMemWrite(byteAddr, 1, &byteValue);
220  if (!INT_SUCCESS(status))
221  {
222  ERROR("[ERROR] IntKernVirtMemWrite failed for %llx: 0x%08x\n", byteAddr, status);
223  goto resume_and_exit;
224  }
225 
226  // Patch the accessed target address.
227  status = IntKernVirtMemWrite(cmpAddr, 4, &cmpValue);
228  if (!INT_SUCCESS(status))
229  {
230  ERROR("[ERROR] IntKernVirtMemWrite failed for %llx: 0x%08x\n", cmpAddr, status);
231  goto resume_and_exit;
232  }
233 
234  status = INT_STATUS_SUCCESS;
235 
236  TRACE("[RTLPVIRTUALUNWIND] Successfully patched detour with tag %d, entry %llu, IF = %d\n",
237  tag - detTagRtlVirtualUnwind1, e, (gVcpu->Regs.Flags & NDR_RFLAG_IF) ? 1 : 0);
238 
239 resume_and_exit:
240  IntResumeVcpus();
241 
242  return status;
243 }
_Bool BOOLEAN
Definition: intro_types.h:58
uint8_t BYTE
Definition: intro_types.h:47
INTSTATUS IntKernVirtMemWrite(QWORD KernelGva, DWORD Length, void *Buffer)
Writes data to a guest kernel virtual memory range.
Definition: introcore.c:699
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 INT_STATUS_SUCCESS
Definition: introstatus.h:54
DWORD KernelSize
The size of the kernel.
Definition: guests.h:284
#define INT_SUCCESS(Status)
Definition: introstatus.h:42
INTSTATUS IntResumeVcpus(void)
Resumes the VCPUs previously paused with IntPauseVcpus.
Definition: introcore.c:2355
#define INT_STATUS_NOT_NEEDED_HINT
Definition: introstatus.h:317
#define ERROR(fmt,...)
Definition: glue.h:62
int INTSTATUS
The status data type.
Definition: introstatus.h:24
DWORD OSVersion
Os version.
Definition: guests.h:281
#define INT_STATUS_NOT_FOUND
Definition: introstatus.h:284
INTSTATUS IntRipRead(DWORD CpuNumber, QWORD *Rip)
Reads the value of the guest RIP.
Definition: introcpu.c:49
QWORD Flags
Definition: glueiface.h:49
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 IntDetGetAddrAndTag(QWORD Ptr, QWORD *Address, DWORD *Size, DETOUR_TAG *Tag)
Checks if Ptr is inside a detour handler and returns the detour&#39;s handler address, size and tag.
Definition: detours.c:2074
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
#define TRUE
Definition: intro_types.h:30
#define TRACE(fmt,...)
Definition: glue.h:58
INTSTATUS IntRtlpVirtualUnwindCheckAccess(void)
Check if a memory read operation was issued by RtlpVirtualUnwind or friends and update the cache...
QWORD KernelVa
The guest virtual address at which the kernel image.
Definition: guests.h:283
BOOLEAN gRipInsideRtlpVirtualUnwindReloc
DWORD CpuCount
The number of logical CPUs.
Definition: guests.h:279
uint32_t DWORD
Definition: intro_types.h:49
static uint64_t __rdtsc(void)
Definition: intrinsics.h:306
GUEST_STATE gGuest
The current guest state.
Definition: guests.c:50
INTSTATUS IntKernVirtMemRead(QWORD KernelGva, DWORD Length, void *Buffer, DWORD *RetLength)
Reads data from a guest kernel virtual memory range.
Definition: introcore.c:674
VCPU_STATE * gVcpu
The state of the current VCPU.
Definition: guests.c:59
DETOUR_TAG
Unique tag used to identify a detour.
Definition: detours.h:119
QWORD Gla
The accessed guest virtual address. Valid only for EPT exits.
Definition: guests.h:102
#define FALSE
Definition: intro_types.h:34