Patchguard: Detection of Hypervisor Based Introspection [P2]

No Errata For U!

If you haven’t already, read Part 1 which outlines three neat tricks used by Patchguard.

KiErrata420Present

The LSTAR MSR can be intercepted using a hypervisor to trap on reads and writes. It is the most common and efficient way to hook syscalls in most modern x86 operating systems. However contrary to what I’ve read online, this unfortunately comes at the cost of many potential detection vectors for the hypervisor if not properly dealt with. Using a few clever tricks in privileged code, we can reliably determine if a hook on the LSTAR MSR is present or not, that is, if proper precautions have not already been implemented in the hypervisor. Starting in Windows 10 1903 build 18362, Microsoft added several LSTAR hook detection techniques.

One of the simpler LSTAR hook detections was not given the meme “errata” name, perhaps it was not good enough ūüôĀ – so let’s call it KiErrata420Present (possibly not that far off from what Microsoft calls it internally?).

I have outlined the detection below:

KiErrata420Present:
        cli                             ; disable interrupts
        mov     r9d, 0C0000082h         ;
        mov     ecx, r9d                ;
        rdmsr                           ; read LSTAR MSR value
        shl     rdx, 32                 ;
        or      rax, rdx                ; store LSTAR value in rax
        lea     rdx, [rdi+87Ah]         ; store temp LSTAR value in rdx read from pg context
        mov     rbx, rax                ; rbx = original LSTAR value
        mov     rax, rdx                ; rax = temp LSTAR value
        shr     rdx, 32                 ;
        wrmsr                           ; write temporary LSTAR MSR value
        mov     r14d, 20000h            ;
        lea     rax, [rdi+87Ch]         ; rax = stub to execute syscall
        mov     rsi, 0A3A03F5891C8B4E8h ; rsi = constant to obfuscate pg context pointer
        test    [rdi+994h], r14d        ; test if should store pg check data?
        jnz     short trigger_syscall   ; if nz, skip tracing

        mov     r8, gs:KPCR.CurrentPrcb ;
        lea     rdx, [rdi+rsi]          ;
        mov     rcx, [rdi+4C0h]         ;
        mov     [rcx], rdx              ;
        mov     rcx, [rdi+4C8h]         ; store pg check related data
        mov     [rcx], r8               ;
        mov     rcx, [rdi+4D0h]         ;
        mov     [rcx], r9               ;
        mov     rcx, [rdi+4D8h]         ;
        mov     qword ptr [rcx], 112h   ;

trigger_syscall:
        call    KeGuardDispatchICall    ; dispatch call to syscall instruction stub

        test    [rdi+994h], r14d        ; test if pg check should be traced?
        jnz     short restore_lstar     ; if nz, skip tracing

        mov     rax, [rdi+4C0h]         ;
        mov     [rax], rsi              ;
        mov     rax, [rdi+4C8h]         ;
        mov     [rax], r13              ; wipe pg check related data
        mov     rax, [rdi+4D0h]         ;
        mov     [rax], r13              ;
        mov     rax, [rdi+4D8h]         ;
        mov     [rax], r13              ;

restore_lstar:
        mov     rdx, rbx                ; restore original LSTAR value
        mov     rax, rbx                ;
        shr     rdx, 32                 ;
        mov     ecx, 0C0000082h         ;
        wrmsr                           ; write original LSTAR MSR value
        sti                             ; reenable interrupts

This check is indeed very simple. It temporarily overwrites the system’s LSTAR MSR value with its own temporary syscall handler, and restores the original LSTAR MSR value afterwards. How do I know this for sure? Let’s dig in further to find out.

First off let’s figure out what temporary value is written to the LSTAR MSR:

lea     rdx, [rdi+87Ah]         ; store temp LSTAR value in rdx read from pg context
mov     rbx, rax                ; rbx = original LSTAR value
mov     rax, rdx                ; rax = temp LSTAR value
shr     rdx, 32                 ;

As we can see the temporary LSTAR value written is the address at RDI+0x87A. Knowing a little about the patchguard callback we know that the RDI register holds a temporary addresses of the current “patchguard context”. Using this knowledge, we can easily determine where the context+0x87A is written to in the patchguard initialization routine:

mov     byte ptr [r14+87Ah], 0C3h ; store RET instruction

Great, this is the opcode of the return instruction, which is very interesting!

Next let’s figure out what this call is at KeGuardDispatchICall. As you may already know, KeGuardDispatchICall works by branching to an instruction pointer given in RAX. So let’s check out where RAX comes from then:

lea     rax, [rdi+87Ch]         ; rax = stub to execute syscall

Last step, determine where context+0x87C is written to in the patchguard initialization routine:

mov     eax, 050Fh
mov     [r14+87Ch], ax ; store SYSCALL instruction

Whats the meaning of this 050Fh we see? Why that is the SYSCALL instruction opcode! I think we already know what is happening now. But let’s simplify this a little bit more using some pseudocode:

_disable();
OriginalSyscall64 = __readmsr(MSR_LSTAR);
__writemsr(MSR_LSTAR, &PgContext->DummySyscallHandler); // C3 -> ret
KeGuardDispatchICall(&PgContext->Syscall); // 0F 05 -> syscall
__writemsr(MSR_LSTAR, OriginalSyscall64);
_enable();

Neat! It simply executes the SYSCALL instruction and then immediately returns from the handler.

This is very effective against most hypervisors utilizing LSTAR hooks and is even better at annoying hypervisors that do their best to prevent the guest from tampering with the LSTAR MSR. In many naive LSTAR MSR hook implementations, developers will simply disallow writes to the LSTAR MSR altogether, which will in turn cause a fault in this case because the context is not setup before executing the syscall in this situation. An example of such an implementation is Hyperbone’s LSTAR MSR hook.

This becomes a frustrating issue for the hypervisor developer. They should fret however since there is a rather simple solution to this simple problem for the developer of a hypervisor. The solution is to let the guest overwrite the LSTAR MSR, and effectively shadow the original.

Well then that means the guest can just force us to unhook???

Yes, you’d be right. However, we can restore our hook afterwards in this case and in almost every other case, unless the guest creates their own syscall hook implementation themselves. It is unfortunate for them that in Windows, patchguard has a separate check for asserting the value of the LSTAR MSR is not tampered with. Therefore, realistically no piece of guest software is going to permanently overwrite your precious LSTAR MSR on Windows unless they have disabled patchguard, which is entirely possible, but also very easy to catch. Besides, these circumstances can all be monitored in the VMM and circumvented as needed.

For this case specifically we can circumvent this detection as such:

VMM_EVENT_STATUS
HVAPI
VmmHandleMsrRead(
    _In_ PVIRTUAL_CPU Vcpu
    )
{
    // ...

    //
    // Hide our LSTAR syscall hook handler address.
    //
    case MSR_LSTAR:
        if (Vcpu->OriginalLSTAR) {
            MsrValue = Vcpu->OriginalLSTAR;
        } else {
            MsrValue = __readmsr(MSR_LSTAR);
        }
        break;

    // ...
}

VMM_EVENT_STATUS
HVAPI
VmmHandleMsrWrite(
    _In_ PVIRTUAL_CPU Vcpu
    )
{
    // ...

    //
    // Let the guest overwrite our hook to avoid possible detection.
    //
    // If and only if the guest is writing the original LSTAR, we replace
    // the MSR value with the hook LSTAR value.
    //
    // N.B. We do this to get around one of PatchGuard's syscall hook
    //      detections which works like this:
    //
    //  _disable();
    //  OriginalSyscall64 = __readmsr(MSR_LSTAR);
    //  __writemsr(MSR_LSTAR, &PgCtx->PgSyscallDummy); // C3 -> ret
    //  KeGuardDispatchICall(&PgCtx->SyscallOpcode1); // 0F 05 -> syscall
    //  __writemsr(MSR_LSTAR, OriginalSyscall64);
    //  _enable();
    //
    case MSR_LSTAR:
        if (MsrValue == Vcpu->OriginalLSTAR) {
            MsrValue = Vcpu->HookLSTAR;
        }
        __writemsr(MSR_LSTAR, MsrValue);
        break;

    // ...
}
We effectively solve this problem completely by shadowing the original LSTAR value on reads and writes to the LSTAR MSR.

KiErrata1337Present

Using a bit of critical thinking, I came up with my own rather deviant LSTAR detection using some tricks I found derived from Patchguard. I call this one KiErrata1337Present, shamelessly derived from Microsoft’s meme “errata” naming scheme for their other cool patchguard checks.

Those who have looked into modern 64-bit system call handlers in Linux and/or Windows may have noticed they start and (sometimes) end with the SWAPGS instruction. The SWAPGS instruction exchanges the current GS base register (IA32_GS_BASE) value with the kernel GS base register value contained in MSR address C0000102H (IA32_KERNEL_GS_BASE).

The instructions immediately following the SWAPGS instruction in the syscall handler is a segmented MOV instruction. Here’s a peek of KiSystemCall64:

KiSystemCall64 proc near
        swapgs                                  ; swap GS base with IA32_KERNEL_GS_BASE
        mov     gs:KPCR.UserRsp, rsp            ; store user mode stack in processor control region
        mov     rsp, gs:KPCR.Prcb.RspBase       ; set the kernel stack from processor control region

Cool, knowing these couple details we know we can mess with the GS base to cause a page fault (#PF) inside the syscall handler. Wait, what?

WTF why would you want to purposely page fault???

You’d be sane thinking this. The reason we want to fault inside the syscall handler is so that we can read the REAL RIP of the syscall handler. This is a very important detail!

Alright, let’s try purposely generating a a page fault (#PF) then:

KiErrata1337Present:
        swapgs                                  ; swapgs to emulate coming from user mode

        mov     ecx 0C0000102h                  ;
        xor     eax, eax                        ; set KERNEL_GS_BASE MSR to zero
        xor     edx, edx                        ;
        wrmsr                                   ;

        syscall                                 ; execute the syscall instruction to trigger fault

        ret

Boom! We page faulted – that’s a good thing by the way!

However, there are a couple problems right off the top: the original page fault handler in Windows is just going to BLOW up and bugcheck, and if we don’t restore the original GS base and kernel GS base values, the operating system is also going to BLOW up in the next context switch. So we need to temporarily hook the interrupt descriptor table (IDT), and back up the GS bases, too easy!

Steps to temporarily hook the interrupt descriptor table IDT are as follows:

  1. Disable interrupts
  2. Save the original IDT
  3. Load our temporary IDT
  4. Do your thang
  5. Restore original IDT
  6. Re-enable interrupts

Here is some pseudo code implementing the above steps with the page fault #PF exception hook we need:

TempIdtr.Limit = sizeof(TempIdt) - 1;
TempIdtr.Base = (UINT64)&TempIdt[0];
for (IdtEntry in KPCR->IdtBase)
    TempIdt[i] = IdtEntry; // Fill in temporary IDT

_disable();             // Disable interrupts
__sidt(&OriginalIdtr);  // Backup original IDT
__lidt(&TempIdtr);      // Load our temporary hook IDT

// Hook page fault handler.
TempIdt[PF] = PageFaultHookHandler;

// Trigger syscall that will purposely page fault!
KiErrata1337Present();  // This must be lean enough not to timeout watchdog!

__lidt(&OriginalIdtr);  // Restore the original IDT.
_enable();              // Re-enable interrupts.

Our page fault handler doesn’t do anything but return from the interrupt for now as we test. We do this using the IRET instruction. Please read about the IRET instruction if you are not already familiar with it as it is very important you understand it for later on when we actually create the detection out of all this!

Here is our boring page fault hook handler:

PageFaultHookHandler:
        add     rsp, 8                  ; skip fault code on stack
        iretq                           ; return from interrupt

Now that we have a hook setup on the page fault handler, let’s fix our KiErrata1337Present routine to backup and restore the original GS bases:

KiErrata1337Present:
        mov     ecx, 0C0000101h         ; read original GS_BASE MSR
        rdmsr                           ;
        push    rdx                     ; backup original GS_BASE MSR
        push    rax                     ;
        mov     ecx, 0C0000102h         ; read original KERNEL_GS_BASE MSR
        rdmsr                           ;
        push    rdx                     ; backup original KERNEL_GS_BASE MSR
        push    rax                     ;

        swapgs                          ; swapgs to emulate coming from user mode

        xor     eax, eax                ;
        xor     edx, edx                ; set KERNEL_GS_BASE MSR to zero
        wrmsr                           ;

        syscall                         ; execute syscall instruction which executes swapgs immediately

        mov     ecx, 0C0000102h         ;
        pop     rax                     ;
        pop     rdx                     ; restore original KERNEL_GS_BASE MSR
        wrmsr                           ;
        mov     ecx, 0C0000101h         ;
        pop     rax                     ;
        pop     rdx                     ; restore original GS_BASE MSR
        wrmsr                           ;

        ret                             ; return back to caller

It works! Now for the juicy detection!

Like I mentioned before, the entire reason we want to cause a fault in the syscall handler is so that we can read the RIP from the machine trap frame upon faulting.¬† That part is easy. But how do we jump back to our KiErrata1337Present routine if we are in the page fault handler? Well, lucky for us, the SYSCALL instruction saves a return address for us which is actually intended for it’s counterpart SYSRET. When the SYSCALL instruction executes, it will store the address of the next instruction in the RCX register.

We can see the SYSCALL instruction operates as such:

RCX¬†‚Üź¬†RIP;¬†(*¬†Will¬†contain¬†address¬†of¬†next¬†instruction¬†*)
RIP¬†‚Üź¬†IA32_LSTAR;
R11¬†‚Üź¬†RFLAGS;
RFLAGS¬†‚Üź¬†RFLAGS¬†AND¬†NOT(IA32_FMASK);
// .... memes

So how do we return? Simple, We override the RIP address on the machine frame. Hopefully you understand how IRET works now if you weren’t already familiar with it, because now is where we use it’s operation to wrap up our detection. Let’s be clever and get two birds stoned at once by using the XCHG instruction:

PageFaultHookHandler:
        add     rsp, 8                  ; skip fault code on stack
        xchg    qword [rsp], rcx        ; xchg trap frame RIP with syscall return address in RCX
        iretq                           ; return from interrupt

We use the XCHG instruction to our advantage to exchange the syscall return address in RCX, with the RIP in the trap frame. This allows us to effectively store the REAL syscall handler address in RCX and still branch back to the instruction immediately after our SYSCALL instruction.

That’s pretty much it. That was a nutty one, wasn’t it? Putting it all together looks something¬† like this:

// detect.c

VOID
DoTheThing(
    VOID
    )
{
    KIDTENTRY64 TempIdt[19];
    X64_DESCRIPTOR TempIdtr;
    PVOID SyscallHandler;

    TempIdtr.Limit = sizeof(TempIdt) - 1;
    TempIdtr.Base = (UINT64)&TempIdt[0];
    RtlCopyMemory(TempIdt, KeGetPcr()->IdtBase, TempIdtr.Limit + 1);

    _disable();             // Disable interrupts
    __sidt(&OriginalIdtr);  // Backup original IDT
    __lidt(&TempIdtr);      // Load our temporary hook IDT

    // Hook page fault handler.
    TempIdt[X86_TRAP_PF].OffsetLow = (UINT16)(UINTN)PageFaultHookHandler;
    TempIdt[X86_TRAP_PF].OffsetMiddle = (UINT16)((UINTN)PageFaultHookHandler >> 16);
    TempIdt[X86_TRAP_PF].OffsetHigh = (UINT32)((UINTN)PageFaultHookHandler >> 32);

    // Trigger syscall that will purposely page fault!
    SyscallHandler = KiErrata1337Present();

    __lidt(&OriginalIdtr);  // Restore the original IDT.
    _enable();              // Re-enable interrupts.

    LOG_INFO("REAL SYSCALL Handler = 0x%p", SyscallHandler);
}
; detect.asm

PageFaultHookHandler:
        add     rsp, 8                  ; skip fault code on stack
        xchg    qword [rsp], rcx        ; xchg trap frame RIP with syscall return address in RCX
        iretq

KiErrata1337Present:
        push    rbx                     ; backup RBX which is to be clobbered

        mov     ecx, 0C0000101h         ; read original GS_BASE MSR
        rdmsr                           ;
        push    rdx                     ; backup original GS_BASE MSR
        push    rax                     ;
        mov     ecx, 0C0000102h         ; read original KERNEL_GS_BASE MSR
        rdmsr                           ;
        push    rdx                     ; backup original KERNEL_GS_BASE MSR
        push    rax                     ;

        swapgs                          ; swapgs to emulate coming from user mode

        xor     eax, eax                ;
        xor     edx, edx                ; set KERNEL_GS_BASE MSR to zero
        wrmsr                           ;

        syscall                         ; execute syscall instruction which executes swapgs immediately
        mov     rbx, rcx                ; store result syscall handler address in RBX for now

        mov     ecx, 0C0000102h         ;
        pop     rax                     ;
        pop     rdx                     ; restore original KERNEL_GS_BASE MSR
        wrmsr                           ;
        mov     ecx, 0C0000101h         ;
        pop     rax                     ;
        pop     rdx                     ; restore original GS_BASE MSR
        wrmsr                           ;

        mov     rax, rbx                ; return result in RAX
        pop     rbx                     ; restore original RBX
        ret                             ; return back to caller

 

PoC||GTFO

This wouldn’t be a complete article without some easy to use paste would it?

You can find the full proof of concept implementation on my github at https://github.com/ajkhoury/Errata1337