Skip to content

Latest commit

 

History

History
133 lines (102 loc) · 6 KB

File metadata and controls

133 lines (102 loc) · 6 KB

NPIEP

NPIEP is a new security feature introduced by Microsoft as part of their Virtualisation Based Security (VBS). We can find this brief summary in the Hypervisor Top-Level Functional Specification.

Each of these instructions provides information about control data used by the CPU and the OS. The instructions to set this data can only be executed in kernel mode but due to historic design choices user mode software can use the read vesions. As shown below for SGDT.

In summary the instructions have the following purposes.

Instruction Name Purpose
SGDT Store Global Descriptor Table Register Stores the content of the global descriptor table register (GDTR) in the destination operand.
SIDT Store Interrupt Descriptor Table Register Stores the content the interrupt descriptor table register (IDTR) in the destination operand.
SLDT Store Local Descriptor Table Register Stores the segment selector from the local descriptor table register (LDTR) in the destination operand.
SMSW Store Machine Status Word Stores the machine status word (bits 0 through 15 of control register CR0) into the destination operand.
STR Store Task Register Stores the segment selector from the task register (TR) in the destination operand.

As you can see the information revealed all of these instructions may not be critical in all cases, but still probably shouldn't be exposed to user mode. Details on abusing the Global and Local Descriptor Tables for kernel exploitation can be found here and obviously overwriting entries in the Interrupt Descriptor Table can be great for hooking on key presses, network packets, etc. I used the below C code + asm file in visual studio to run the instructions in windows.

// InstructionProtection.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <stdlib.h>
#include <intrin.h>

extern "C" void sgdt_wrapper(void *);
extern "C" void sidt_wrapper(void *);
extern "C" unsigned long long sldt_wrapper(void);
extern "C" unsigned short smsw_wrapper(void);
extern "C" unsigned long long str_wrapper(void);

#pragma pack(1)
typedef struct _DESCRIPTOR {
	unsigned short limit;
	unsigned long long base;
} DESCRIPTOR, *PDESCRIPTOR;

int main()
{
	printf("SGDT:\n");
	PDESCRIPTOR gdtr = (PDESCRIPTOR) malloc(sizeof(DESCRIPTOR));
	memset(gdtr, 0x41, sizeof(DESCRIPTOR));
	sgdt_wrapper((void *) gdtr);
	printf("\tGDTR base: 0x%llx, limit: 0x%X\n", gdtr->base, gdtr->limit);
	printf("SIDT:\n");
	PDESCRIPTOR idtr = (PDESCRIPTOR) malloc(sizeof(DESCRIPTOR));
	memset(idtr, 0x41, sizeof(DESCRIPTOR));
	sidt_wrapper((void *) idtr);
	printf("\tIDTR base: 0x%llx, limit: 0x%X\n", idtr->base, idtr->limit);
	free(idtr);
	printf("SLDT:\n");
	unsigned long long ldtr = sldt_wrapper();
	printf("\tLDTR selector: 0x%llx\n", ldtr);
	printf("SMSW:\n");
	unsigned short cr0 = smsw_wrapper();
	printf("\tCR0.PE = %d\n", cr0 & 1);
	printf("\tCR0.MP = %d\n", (cr0 >> 1) & 1);
	printf("\tCR0.EM = %d\n", (cr0 >> 2) & 1);
	printf("\tCR0.TS = %d\n", (cr0 >> 3) & 1);
	printf("\tCR0.ET = %d\n", (cr0 >> 4) & 1);
	printf("\tCR0.NE = %d\n", (cr0 >> 5) & 1);
	printf("STR:\n");
	unsigned long long tr = str_wrapper();
	printf("\tTR segment selector: 0x%llx\n", tr);
    return 0;
}
_DATA SEGMENT
_DATA ENDS
_TEXT SEGMENT

PUBLIC sgdt_wrapper
PUBLIC sidt_wrapper
PUBLIC sldt_wrapper
PUBLIC smsw_wrapper
PUBLIC str_wrapper

sgdt_wrapper PROC
sgdt [rcx]
ret
sgdt_wrapper ENDP

sidt_wrapper PROC
sidt [rcx]
ret
sidt_wrapper ENDP

sldt_wrapper PROC
sldt rax
ret
sldt_wrapper ENDP

smsw_wrapper PROC
smsw rax
ret
smsw_wrapper ENDP

str_wrapper PROC
str rax
ret
str_wrapper ENDP

_TEXT ENDS
END

Running the resulting binary without VBS enabled, gave me the following.

I then configured and enabled HyperV followed by Device Gaurd on the test system by following this guide: Deploy Device Guard: enable virtualization-based security and using the setup powershell script that can be downloaded here: Device Guard and Credential Guard hardware readiness tool.

Once this was setup, I ran the test binary again, getting the following output.

This actually looks completely normal, but then I rebooted and the IDT and GDT addresses were identical and again and again...obviously on a normal system they aren't at static addresses. So it looks like NPIEP is enabled and working.

As a final note, it looks like Intel recently implemented an equivalent feature in hardware (some would say after far too long):

(Taken from the intel instruction set reference.)

More details pending, some intern stole my test laptop :p