12 November 2013 Microsoft published bulletin MS13-092 (KB2893986), where they described vulnerability in Windows Server 2012 Hyper-V, which allowed generate BSOD in root OS from guest OS or execute arbitrary code in other guest OS on same physical host.

Load patch and see, what files was changed. It was hvix64.exe and hvax64.exe (version 6.2.9200.20840), debugging dll kdhvcom.dll and hvservice.sys (probably it contains code for hypervisor hibernation, but I could be wrong). We see hvix64.exe (I have intel CPU).

Next, we havr to find patch, which was issued before KB2893986 – it was KB2885465. According http://support.microsoft.com/kb/2885465 hvix64.exe version is 6.2.9200.20811 from 30.08.2013, but there is version 6.2.9200.20814 from 04.09.2013 in archive.

Next, we take bindiff plugin for IDA PRO and try compare these versions of hvix64.exe. The main problem of hvix64.exe debugging – we haven’t public symbols. We have much of unknown functions in IDA PRO, which was called with sub_ prefixes. More than – many functions IDA PRO doesn’t recognize and we need recover it manually (using scripts, if it is possible). According first part of article (http://hvinternals.blogspot.com/2015/10/hyper-v-debugging-for-beginners.htm) we load hvix64.exe in hypervisor, import known functions from hvloader.exe, kdhvcom.dll, winload.exe using bindiff (yes, parts of them are identical), create hypercall’s table using mVmcallHandlersTable.py (https://github.com/gerhart01/Hyper-V-scripts/blob/master/CreatemVmcallHandlersTable2016.py), configure debugger, attach to host server, recover interrupt handlers using script, find VM_EXIT handler. Also, we must find all instruction, which names begin from loc_ and manually make functions from them (P key in IDA PRO). In hvix64.exe many functions are ended with calls of exception generation (without return), and IDA PRO cannot recognize it automatically.

We can read in vulnerability description (http://technet.microsoft.com/en-us/security/bulletin/ms13-092): «…The vulnerability could allow elevation of privilege if an attacker passes a specially crafted function parameter in a hypercall from an existing running virtual machine to the hypervisor. The vulnerability could also allow denial of service for the Hyper-V host if the attacker passes a specially crafted function parameter in a hypercall from an existing running virtual machine to the hypervisor». Based on this information we must concentrate on hypercalls handlers.

Pointer to mVmcallHandlersTable, which contains parameters for all hypercalls, is loaded in r9. This table consists of 0x8C structures (which are called “hvcall entries” on some screenshots), every of which consists of 8 2-bytes elements. 3th to 6th elements are actively used in mHandle64bVMCallMemory function for checking size of hypercall’s input and hypercall’s output parameters.

Depending on rep id value mHandle64bVMCallMemory function breaks up 2 big blocks. We see part of function, which is called with condition rep id = 0, because we see in bindiff that this function part was changed bigger, then other parts of code.

If input_gpa size is less or equals 40 bits, then corresponding hypercall handler from mVmcallHandlersTable will be called. We can conclude, that hypervisor differently handles input_gpa and output_gpa with different size (1..40, or 41..52, or 53..64 main significant bit – we have 3 size ranges). Try to construct hypercall, which can pass all checks (without fast bit and with 3th hvcall entry element which is not 0) with input_gpa and output_gpa values in every of these 3 intervals (I set boundary and middle values). I test it for HvCreatePort hypercall.

range 1..40 (min value):

movrdx, 0

movr8,1

vmcall

range 1..40 (middle value):

movrdx, FFFFFEh

movr8, FFFFFFh

vmcall

range 1..40 (max value):

movrdx, FFFFFFFFFFh

movr8,FFFFFFFFFEh

vmcall

range 41..52 (min value):

movrdx, 10000000000h

movr8,10000000001h

vmcall

range 41..52 (middle value)

movrdx, 200000000000h

movr8, 200000000001h

vmcall

range 41..52 (max value)

movrdx, FFFFFFFFFFFEh

movr8, FFFFFFFFFFFFh

vmcall

on the middle value 41..52 range of we see exception in debugger attaching to hypervisor (therefore not need test 53..64 range):

FFFFF800060F133F: The instruction at 0xFFFFF800060F133F referenced memory at 0x0. The memory could not be written -> 0000000000000000 (exc.code c0000005, tid 1).

If we continue executing, hypervisor will hang. In VMware it will be hanging, on physical host we get BSOD:

We can see, that address 0xffffc80000000000 is not changed after reboot (as many addresses of hypervisor structures. Generally, Hyper-V has bad address space randomization).

See in details what happened when hypercall 0x57 was called for input_gpa = 200000000000h

Test r15, r15 returns false:

Go to this code (r11b was zeroed and there is input_gpa in rsi):

R10 contains pointer to some structure (we call it struct1)

WINDBG>dd @r10

00000080`b5859000 0000001b 00000003 00000001 00000000

00000080`b5859010 00000000 00000000 00000000 00002000

00000080`b5859020 00000000 00000000 00000000 00000000

00000080`b5859030 b585b000 00000080 00000000 00000000

00000080`b5859040 00000000 00000000 b5802000 00000080

00000080`b5859050 00000000 00000000 87000fe7 00000001

00000080`b5859060 00000000 00000000 00000000 00000000

00000080`b5859070 00000002 00000100 00000000 00000000

Hypercall is not called (ebx equals 1007h before jnz instruction)

After mHandle64bVMCallMemory finished and we returned to mParse_VMEXIT:

Then go to mBSOD_L3

Which analyses result of mHandle64bVMCallMemory

If edx = 1b go to mBOSD_L2

Which checks some values of struct1

I don’t see whether data with offset 110 was changed from mHandle64bVMCallMemory to mBSOD_L1.

See mBSOD_L1 if bindiff (patched hypervisor on left) – we see that check on 40 bit length (and above) was added.

Next mBSOD_Handle64VmcallMemory is called, where SPA element, which points to GPA, is loaded. Index is (input_gpa shr 12)*8. If memory was not loaded we get exception 0xC0000005, which cause BSOD in root-partition.

We see only one part of vulnerability. BSOD is generated by function, which we didn’t consider in beginning, but without mHandle64VmcallMemory investigation it will be hard to reproduce vulnerability.

I use HvCreatePort hypercall for demonstrating BSOD, despite on guest OS doesn’t have permissions for execute it. But that permissions are checked on far stage hypercall’s handling after mVmcallHandlersTable was called, and it allow to get BSOD even on that hypercall.

Remote code execution was not demonstrated in this article, because I don’t find a way to do that. I believe that it is a theme for other investigation with class is “not for beginners.”