Bitdefender Hypervisor Memory Introspection
memtables.c File Reference

Implements instrumentation for switch-case table access instructions. More...

#include "memtables.h"
#include "guests.h"
#include "icache.h"
#include "memcloak.h"
#include "ptfilter.h"
#include "slack.h"

Go to the source code of this file.

Functions

static INTSTATUS IntMtblRemoveEntry (PMEM_TABLE_RELOC Reloc)
 Removes a mem-table entry. More...
 
static INTSTATUS IntMtblPatchInstruction (PMEM_TABLE_RELOC Reloc, PINSTRUX Instrux, PIG_ARCH_REGS Regs)
 Relocate and instrument a switch-case load instruction. More...
 
INTSTATUS IntMtblCheckAccess (void)
 Check if the current instruction is like a switch-case table access instruction. More...
 
BOOLEAN IntMtblIsPtrInReloc (QWORD Ptr, THS_PTR_TYPE Type, QWORD *Table)
 Check if the given pointer is inside a mem-table relocation handler. More...
 
void IntMtblDisable (void)
 Disables mem-table instructions instrumentation. More...
 
BOOLEAN IntMtblInsRelocated (QWORD Rip)
 Check if the instruction at the provided RIP is instrumented. More...
 
INTSTATUS IntMtblRemoveAgentEntries (void)
 Removes only the mem-table entries that were relocated inside the PT filter. More...
 
INTSTATUS IntMtblUninit (void)
 Completely uninit the mem-tables, removing all the handlers from the NT slack space. More...
 

Variables

static LIST_HEAD gMemTables = LIST_HEAD_INIT(gMemTables)
 List of memtables. More...
 

Detailed Description

Implements instrumentation for switch-case table access instructions.

Mem-tables is a module used to instrument switch-case tables that are inserted by the compiler inside code pages. Sometimes, these switch-case tables end up being placed inside a page of memory which also contains a hooked API; pages which contain hooked APIs are monitored via EPT against reads, in order to hide the hooks from patch-guard, but if switch-case tables are also present inside those pages, a very high number of read EPT violations will be generated, leading to a very high performance impact. The way we mitigate this is by relocating all such instructions into the slack space of the NT image, and by replacing the memory access instruction with a sequence of instructions that load immediate values instead (the immediate values are the values that would normally be loaded from memory).

Definition in file memtables.c.

Function Documentation

◆ IntMtblCheckAccess()

INTSTATUS IntMtblCheckAccess ( void  )

Check if the current instruction is like a switch-case table access instruction.

This function checks if the current instruction (pointed by the RIP on the current VCPU) looks like an instruction which loads switch-case offset from a code-page. We look after the following features: 0. The instruction must be a MOV instruction;

  1. The instruction must have exactly 2 operands;
  2. First operand must be a register;
  3. Second operand must be memory;
  4. SIB addressing must be used, with a non-zero index;
  5. The memory access must be read;
  6. Both operands must be 4 bytes;
  7. The instruction must be at least 5 bytes long (in order to accommodate a relative jump);
  8. The read linear address must point inside the NT image;
  9. The read linear address must not point inside the SSDT; If such a candidate instruction is found, a new entry is allocated for it (or an existing entry is searched). Once we identify the proper entry, we increment the number times the instruction triggered a memory read, and once it exceeds 50 hits, we will try to instrument it using the IntMtblPatchInstruction function. If instrumenting the instruction fails, we flag the entry as being ignored, and we won't try to instrument it again. NOTE: This function is called on read EPT violations that take place on addresses for which we don't have a registered hook/callback.
Return values
INT_STATUS_SUCCESSOn success.
INT_STATUS_NOT_NEEDED_HINTIf there's no need to instrument the instruction.
INT_STATUS_INSUFFICIENT_RESOURCESIf a memory alloc fails.

Definition at line 401 of file memtables.c.

Referenced by IntHandleMemAccess().

◆ IntMtblDisable()

void IntMtblDisable ( void  )

Disables mem-table instructions instrumentation.

This function will remove all the hooks placed on mem-table like instructions, thus disabling the instrumentation. Note that the handlers will still remain, and if we have pointers still pointing there, nothing bad will happen. This function should be called only when preparing for uninit.

Definition at line 644 of file memtables.c.

Referenced by IntGuestPrepareUninit().

◆ IntMtblInsRelocated()

BOOLEAN IntMtblInsRelocated ( QWORD  Rip)

Check if the instruction at the provided RIP is instrumented.

Parameters
[in]RipThe RIP to be checked.
Return values
TRUEif the RIP contains an instrumented instruction, FALSE otherwise.

Definition at line 677 of file memtables.c.

Referenced by IntHandleEptViolation().

◆ IntMtblIsPtrInReloc()

BOOLEAN IntMtblIsPtrInReloc ( QWORD  Ptr,
THS_PTR_TYPE  Type,
QWORD Table 
)

Check if the given pointer is inside a mem-table relocation handler.

Parameters
[in]PtrThe pointer to be checked.
[in]TypePointer type - stack value or live RIP.
[out]TableOptional address to the relocation table, if any is found.
Return values
TRUEIf the pointer points within a relocation handler, FALSE otherwise.

Definition at line 596 of file memtables.c.

Referenced by IntThrSafeIsLiveRIPInIntro(), and IntThrSafeIsStackPtrInIntro().

◆ IntMtblPatchInstruction()

static INTSTATUS IntMtblPatchInstruction ( PMEM_TABLE_RELOC  Reloc,
PINSTRUX  Instrux,
PIG_ARCH_REGS  Regs 
)
static

Relocate and instrument a switch-case load instruction.

This function will re-write a switch-case load instruction using sequences of instruction which do not access memory (the page which is already read hooked, in particular). The rewriting algorithm relies on loading immediate values into the destination register instead of directly accessing the memory. Each instrumented instruction will contain a header, and a variable size region, with one entry for each value inside the switch-case table. Given a switch-case load instruction, say "MOV ecx, dword ptr [rdx+rax*4+0x4ea91c]", this is how we rewrite it:

  1. We store the header 1a. Store a "PUSHFQ" - in order to preserve the flags. 1b. Store a "PUSH rcx" - note that we push the destination register from the relocated instruction. 1c. Store a "PUSH rax" - note that we push the index register from the memory operand. 1d. Store a "MOV [rsp+8], slack_addr_low" and "MOV [rsp+12],slack_addr_high", in order to overwrite the previously saved destination register with the slack address of our immediate table. slack_addr will point to the first immediate block, imm0. 1e. Store a "IMUL rax, rax, 11" - we multiply the index register by the size of each immediate block. 1f. Store a "ADD [rsp+8], rax" - we add the newly calculated offset to the destination on the stack. 1g. Store a "POP rax" - restore the index register. 1h. Store a "POP rcx" - this will load the address we wish to jump to in rcx. 1i. Store a "MOV [rsp-8], 0" - this removes the pointer that might still point to us (thread-safeness) 1j. Store a "POPFQ" - restore the flags. 1k. Store a "JMP rcx" - jumps to the block of instructions that stores the desired immediate in ecx.
  2. We store an array of immediate blocks, which basically load the memory values into the destination register, as immediate values; for each value k inside the switch-case table: 2a. Store a "MOV ecx, immk" - immk is the value located in the switch-case table at index k 2b. Store a "IRETQ" or a "JMP back" - both jump back to the instruction following the instrumented instruction, and resume normal execution, with the desired switch-case table loaded into the proper destination register, but without accessing memory

For example, let's assume the following:

  • we instrument instruction "MOV ecx, dword ptr [rdx+rax*4+0x4ea91c]", which lies at 0xFFFF800012340000
  • the instruction following this instruction is a "JMP [rdx+rcx]", which lies at 0xFFFF800012340007
  • switch-case table has 4 elements: 0x1000, 0x2000, 0x3000 and 0x4000
  • the slack space allocated for this handle is at address 0xFFFF800056780000
  • rax is 1 The entire instrumented block would look like this: 0xFFFF800056780000: PUSHFQ PUSH rcx PUSH rax MOV [rsp + 8], 0x5678002F ; 0xFFFF800056780000 slack address + the size of the header, which is 0x2F MOV [rsp + 12], 0xFFFF8000 IMUL rax, rax, 11 ; rax will now be 11 ADD [rsp + 8], rax ; we now have on the stack 0xFFFF80005678002F + 0xB = 0xFFFF80005678003A POP rax POP rcx ; ecx will now be 0xFFFF80005678003A MOV [rsp - 8], 0 ; overwrite what used to be the 0xFFFF80005678003A value; this is needed in ; order to avoid false-positives in the thread-safeness, which might see this ; still pointing inside our slack space, and thus consider unload to be unsafe POPFQ JMP rcx ; this jumps to 0xFFFF80005678003A, which is block 1 0x0000BDBDCECE002F: ; Block 0, loads the first switch-case value, as immediate MOV ecx, 0x1000 JMP 0xFFFF800012340007 0x0000BDBDCECE003A: ; Block 1, loads the second switch-case value, as immediate MOV ecx, 0x2000 JMP 0xFFFF800012340007 0x0000BDBDCECE0045: ; Block 2 MOV ecx, 0x3000 JMP 0xFFFF800012340007 0x0000BDBDCECE0050: ; Block 3 MOV ecx, 0x3000 JMP 0xFFFF800012340007 The MOV instruction at address 0xFFFF800012340000 will be replaced with a "JMP 0xFFFF800056780000" instruction.

NOTE: the instruction has already been validated by the caller, so there's no need to do any check here. In order to see what validations/checks we do before we decide to relocate such an instruction, take a look at IntMtblCheckAccess. NOTE: this code will be written inside the guest while the VCPUs are paused. NOTE: after modifying the instruction, its entry inside the instruction-cache must be invalidated; if we don't do this, another VCPU that might have generated an exit from the same instruction might try to instrument it again, as decoding the current instruction would still see the old MOV cached, instead of seeing the newly patched JMP.

Parameters
[in,out]RelocThe mem-table relocation structure.
[in]InstruxDecoded instruction to be instrumented.
[in]RegsThe general purpose registers.
Return values
INT_STATUS_SUCCESSOn success.
INT_STATUS_NOT_SUPPORTEDIf the switch-case table has more than 16 entries.

Definition at line 70 of file memtables.c.

Referenced by IntMtblCheckAccess().

◆ IntMtblRemoveAgentEntries()

INTSTATUS IntMtblRemoveAgentEntries ( void  )

Removes only the mem-table entries that were relocated inside the PT filter.

When using the PT filter, many mem-table instructions may need to be instrumented. Since the NT sections slack space is very scarce, we will use, in that case, the PT filter itself in order to accommodate the relocated instructions. However, when the PT filter is unloaded, we also must stop instrumenting the instructions that were relocated inside of it.

Return values
INT_STATUS_SUCCESSOn success.

Definition at line 707 of file memtables.c.

Referenced by IntPtiDisableFiltering().

◆ IntMtblRemoveEntry()

static INTSTATUS IntMtblRemoveEntry ( PMEM_TABLE_RELOC  Reloc)
static

Removes a mem-table entry.

This function completely removed a mem-table entry, by removing the hook established on the instrumented instruction, and by removing the handling code injected inside the NT slack space.

Parameters
[in]RelocMem-table relocation to be removed.
Return values
INT_STATUS_SUCCESSOn success.

Definition at line 32 of file memtables.c.

Referenced by IntMtblPatchInstruction(), IntMtblRemoveAgentEntries(), and IntMtblUninit().

◆ IntMtblUninit()

INTSTATUS IntMtblUninit ( void  )

Completely uninit the mem-tables, removing all the handlers from the NT slack space.

This function must be called only during uninit, and only after thread-safeness was employed, in order to make sure no live RIPs or saved RIPs point inside a handler.

Return values
INT_STATUS_SUCCESSOn success.

Definition at line 745 of file memtables.c.

Referenced by IntGuestUninit().

Variable Documentation

◆ gMemTables

LIST_HEAD gMemTables = LIST_HEAD_INIT(gMemTables)
static

List of memtables.

Definition at line 12 of file memtables.c.