📜 ⬆️ ⬇️

Exploring Hyper-V internal mechanisms: Part 2



Since the publication of the first part of the article, nothing has changed globally in the world: the Earth has not hit the celestial axis, the popularity of cloud services is still growing, still no new holes were found in the Microsoft hypervisor, and researchers don’t want to spend their time searching bugs in poorly documented and little studied technology. Therefore, I suggest you refresh your memory with the first part of the previous number, replenish your bar’s stock and start reading, because today we’ll make a driver interacting with the hypervisor interface and tracking messages sent by the hypervisor, as well as examine the operation of the components of Data Exchange Integration Services.


Hypervisor Message Processing


')
On dvd.xakep.ru we have laid out a driver written using Visual Studio 2013. It should be loaded into the root of the OS, for example using OSRLoader. To send IOCTL codes, use a simple program SendIOCTL.exe. After sending the INTERRUPT_CODE IOCTL code, the driver starts processing the data transmitted by the hypervisor through the null SIM slot. Unfortunately, the HvlpInterruptCallback variable, which contains the address of the array with message handler pointers, is not exported by the kernel, so to find it, you need to analyze the code of the HvlRegisterInterruptCallback function that contains the address of the array that we export. Also, unfortunately, it will not be possible to simply call HvlRegisterInterruptCallback to register your message handler, since at the very beginning of the function there is a check of the value of the HvlpFlags variable. If the variable is equal to 1 (and this value is assigned to it at the initial stages of kernel loading), the function stops execution, returns error code 0xC00000BB (STATUS_NOT_SUPPORTED) and, accordingly, the handler registration does not occur, therefore you will have to write your own version of the HvlpInterruptCallback function to replace the handlers. In the hyperv4 driver, the necessary actions are performed by the RegisterInterrupt function:


int RegisterInterrupt() { UNICODE_STRING uniName; PVOID pvHvlRegisterAddress = NULL; PHYSICAL_ADDRESS pAdr = {0}; ULONG i,ProcessorCount; //      ProcessorCount = KeQueryActiveProcessorCount(NULL); //      HvlRegisterInterruptCallback DbgLog("Active processor count",ProcessorCount); RtlInitUnicodeString(&uniName,L"HvlRegisterInterruptCallback"); pvHvlRegisterAddress = MmGetSystemRoutineAddress(&uniName); if (pvHvlRegisterAddress == NULL){ DbgPrintString("Cannot find HvlRegisterInterruptCallback!"); return 0; } DbgLog16("HvlRegisterInterruptCallback address ",pvHvlRegisterAddress); //     HvlpInterruptCallback, FindHvlpInterruptCallback((unsigned char *)pvHvlRegisterAddress); //       ArchmHvlRegisterInterruptCallback((uintptr_t)&ArchmWinHvOnInterrupt,(uintptr_t)pvHvlpInterruptCallbackOrig,WIN_HV_ON_INTERRUP_INDEX); ArchmHvlRegisterInterruptCallback((uintptr_t)&ArchXPartEnlightenedIsr,(uintptr_t)pvHvlpInterruptCallbackOrig,XPART_ENLIGHTENED_ISR0_INDEX); ArchmHvlRegisterInterruptCallback((uintptr_t)&ArchXPartEnlightenedIsr,(uintptr_t)pvHvlpInterruptCallbackOrig,XPART_ENLIGHTENED_ISR1_INDEX); ArchmHvlRegisterInterruptCallback((uintptr_t)&ArchXPartEnlightenedIsr,(uintptr_t)pvHvlpInterruptCallbackOrig,XPART_ENLIGHTENED_ISR2_INDEX); ArchmHvlRegisterInterruptCallback((uintptr_t)&ArchXPartEnlightenedIsr,(uintptr_t)pvHvlpInterruptCallbackOrig,XPART_ENLIGHTENED_ISR3_INDEX); //    SIMP    ,       SIM 


WARNING


During experiments related to the intensive work of virtual machines, it is better to replace one handler in the HvlpInterruptCallback array, since replacing all immediately leads to unstable system operation (at least, with a large flow of debug messages through KdPrint and WPP).



 //      ,     Mm MapIoSpace,             for (i = 0; i < ProcessorCount; i++) {KeSetSystemAffinityThreadEx(1i64 << i); DbgLog("Current processor number",KeGetCurrentProcessorNumberEx(NULL)); pAdr.QuadPart = ArchReadMsr (HV_X64_MSR_SIMP) & 0xFFFFFFFFFFFFF000; pvSIMP[i] = MmMapIoSpace (pAdr, PAGE_SIZE, MmCached); if (pvSIMP[i] == NULL){ DbgPrintString("Error during pvSIMP MmMapIoSpace"); return 1; } DbgLog16("pvSIMP[i] address", pvSIMP[i]); pAdr.QuadPart = ArchReadMsr (HV_X64_MSR_SIEFP) & 0xFFFFFFFFFFFFF000; pvSIEFP[i] = MmMapIoSpace(pAdr, PAGE_SIZE, MmCached); if (pvSIEFP[i] == NULL){DbgPrintString("Error during pvSIEFP MmMapIoSpace"); return 1; } DbgLog16("pvSIEFP address", pvSIEFP[i]); } return 0; } 



Fig. 1. HvlpInterruptCallback array with modified handlers

The array HvlpInterruptCallback after the execution of the RegisterInterrupt function (if all the handlers are replaced at the same time) looks like this (see Figure 1). The replacement is done in the same way as the original code: one handler for hypervisor messages and four for processing messages from VMBus. The ArchmWinHvOnInterrupt and ArchXPartEnlightenedIsr procedures save all registers on the stack and transfer control to the parsing functions of ParseHvMessage and ParseVmbusMessage messages, respectively (mPUSHAD and mPOPAD are macros that save registers on the stack) (see Figure 2).



Fig. 2. ArchmWinHvOnInterrupt and ArchXPartEnlightenedIsr

After parsing, control is transferred to the original WinHvOnInterrupt and XPartEnlightenedIsr procedures. The function of parsing messages hypervisor looks like this:


 void ParseHvMessage() { PHV_MESSAGE phvMessage, phvMessage1; //      ULONG uCurProcNum = KeGetCurrentProcessorNumberEx(NULL); Unlock+0x162 vmbkmcl!VmbChannelEnable+0x231 vmbus!PipeStartChannel+0x9e vmbus!PipeAccept+0x81 vmbus!InstanceCreate+0x90 .................................. nt!IopParseDevice+0x7b3 nt!ObpLookupObjectName+0x6d8 nt!ObOpenObjectByName+0x1e3 nt!IopCreateFile+0x372 nt!NtCreateFile+0x78 nt!KiSystemServiceCopyEnd+0x13 ntdll!NtCreateFile+0xa KERNELBASE!CreateFileInternal+0x30a KERNELBASE!CreateFileW+0x66 vmbuspipe!VmbusPipeClientOpenChannel+0x44 icsvc!ICTransportVMBus::ClientNotification+0x60 vmbuspipe!VmbusPipeClientEnumeratePipes+0x1ac icsvc!ICTransportVMBusClient::Open+0xe5 icsvc!ICEndpoint::Connect+0x66 icsvc!ICChild::Run+0x65 icsvc!ICKvpExchangeChild::Run+0x189 icsvc!ICChild::ICServiceWork+0x137 icsvc!ICChild::ICServiceMain+0x8f .................................. 


The Data Exchange component is activated on the virtual machine. After activating the Data Exchange component in the properties of the virtual machine and clicking the Apply root button of the OS, the HvPostMessage sends the guest OS a message with the code CHANNELMSG_OFFERCHANNEL (see Figure 9). The transmitted data contains the GUID of the device that is a child of the VMBUS (see Figure 10). The guest OS then processes the data and calls the vmbus! InstanceDeviceControl function.



Fig. 9. Device GUID in the message


Fig. 10. Conclusion! Devnode for a VMBus device

Part of the stack:


 WINDBG>k Call Sitent!IoAllocateMdl vmbus!InstanceCloseChannel+0x22d (   ,     ) vmbus!InstanceDeviceControl+0x118 .................................. vmbkmcl!KmclpSynchronousIoControl+0xa7 vmbkmcl!KmclpClientOpenChannel+0x2a6 vmbkmcl!KmclpClientFindVmbusAnd if (pvSIMP[uCurProcNum] != NULL){ phvMessage = (PHV_MESSAGE)pvSIMP[uCurProcNum]; } else{ DbgPrintString("pvSIMP is NULL"); return; } //      1-  SIM phvMessage1 = (PHV_MESSAGE)((PUINT8)pvSIMP[uCurProcNum]+ HV_MESSAGE_SIZE); // for SINT1 if (phvMessage1->Header.MessageType != 0){ DbgPrintString("SINT1 interrupt"); } //         //        TLFS switch (phvMessage->Header.MessageType) { case HvMessageTypeX64IoPortIntercept: PrintIoPortInterceptMessage(phvMessage); break; case HvMessageTypeNone:DbgPrintString("HvMessageTypeNone"); break; case HvMessageTypeX64MsrIntercept:PrintMsrInterceptMessage(phvMessage); break; case HvMessageTypeX64CpuidIntercept:PrintCpuidInterceptMessage(phvMessage); break; case HvMessageTypeX64ExceptionIntercept:PrintExceptionInterceptMessage(phvMessage); break; default: DbgLog("Unknown MessageType", phvMessage-> Header.MessageType); break; } } 


The function obtains the number of the active logical processor, the address of the SIM page and reads the value of the zero SIM slot. First, an analysis of the type of message phvMessage-> Header.MessageType is performed, since the message body is different for each type. You can see the following picture in DbgView (see fig. 3).


Fig. 3. DbgView output when the hypervisor processes MSR registers


ParseVmbusMessage function (Fig. 4).

The function obtains the number of the active logical processor, the address of the SIM page and reads the value of the fourth SIM slot. For example, the CHANNELMSG_OPENCHANNEL and CHANNELMSG_GPADL_HEADER messages are parsed, but in the LIS source codes you can see the format of all message types and easily add the necessary handlers. Messages for the VMBus bus are usually generated when you turn on / off a virtual machine or one of the components of Integration Services. For example, if you turn on the Data Exchange component, the debugger or DbgView will show the information shown in Figure 2. five.


Fig. 5. Debug output when the Data Exchange component is enabled.

Integration Services - Data Exchange


Next, we consider how data is exchanged between the guest and the root OS for the example of one of the components of the integration services - Data Exchange. This component allows root OS to read data from a specific branch of the guest OS registry. To check in the guest OS, create in the HKEY_LOCAL_MACHINE \\ SOFTWARE \\ Microsoft \ Virtual Machine \ Guest branch a key with the KvPDataValue value (see Figure 6).



Fig. 6. KvPDataValue Key

The following PowerShell script was used to obtain the key value in the OS root (see Figure 7).


Fig. 7. PowerShell script to query registry values ​​from guest OS

The script will return the value of the KvPDataKey key (see Figure 8). The script gets the entire available set of values ​​using $ vm.GetRelated ("Msvm_KvpExchangeComponent"). GuestExchangeItems and only after that it parses each received object to find the KvPDataKey key. Accordingly, the script will work only if nt! IoAllocateMdl with the size of the allocated buffer 0xC000 is called in the properties of this function. The result of the function is a formed MDL structure (see Fig. 11). Next, nt! MmProbeAndLockPages is called, after the completion of which the MDL structure is supplemented with PFN elements (see Fig. 12). In this example, a contiguous area of ​​physical memory was allocated, although this condition is optional. Next, vmbus! ChCreateGpadlFromNtmdl is called (the second parameter is the address of the MDL), which calls vmbus! ChpCreateGpaRanges, passing all the same MDL as the first parameter. Next, the PFN elements are copied from the MDL structure to a separate buffer (see Fig. 13), which will become the body of the CHANNELMSG_GPADL_HEADER message sent by the guest OS to the root OS by calling vmbus! ChSendMessage. hv! HvPostMessage or in winhv! WinHvPostMessage you can see the message (Figure 14).


Fig. 8. Script Execution Result


Fig. 11. Value of MDL structure


Fig. 12. PFN in MDL structure


Fig. 13. Plot of code responsible for copying PFN to separate buffer


Fig. 14. PFN Array Processed by the Hypervisor

The first 16 bytes is the general header of the message, where, for example, 0xF0 is the size of the message body, a VMBus-package is placed inside, the header of which contains the packet type - 8 (CHANNELMSG_GPADL_HEADER), rangecount is 1, which means that one packet contained all the data that was needed to transfer. In the case of a large amount of data, the guest OS driver would divide them into parts and send them in separate messages. Then the root OS sends the message CHANNELMSG_OPENCHANNEL_RESULT, then the guest OS sends CHANNELMSG_OPENCHANNEL. After that, the root OS executes the WorkItem procedure. (see fig. 15).



Fig. 15. Calling ChMapGpadlView

During its execution, vmbusr! ChMapGpadlView-> vmbusr! PkParseGpaRanges is called, which, in turn, receives a pointer to the message part containing the buffer size 0xC000 and PFN, sent in the CHANNELMSG_GPADL_HEADER message. Next, vmbusr! XPartLockChildPagesSynchronous-> vmbusr! XPartLockChildPages is called, after which the function is executed from the vid.sys driver (function name is unknown, since there are no characters for the driver), which is passed as a second parameter to the PFN block sent earlier in the message from the guest OS ( see figure 16).



Fig. 16. Processing guest PFN with vid.sys driver

Immediately after returning from the function to [rsp + 30h] there is a pointer to the newly created MDL structure (see Fig. 17).



Fig. 17. MDL structure returned by the vid.sys driver

The size of the allocated buffer is also 0xC000. After that, the root OS sends the message CHANNELMSG_OPENCHANNEL_RESULT. This completes the activation process of the Data Exchange component. This creates a kind of shared-buffer, visible as a guest, and root OS. This can be verified by writing arbitrary data to the buffer in the guest OS, for example, using the command:

 WINDBG>!ed 2d5bb000 aaaaaaaa WINDBG>!db 2d5bb000 \#2d5bb000 aa aa aa aa 10 19 00 


And in root OS, view the contents of the page corresponding to the PFN, as returned by the vid.sys driver function:

 WINDBG>!db 1367bb000 \#1367bb000 aa aa aa aa 10 19 


As you can see, the values ​​match, so this is really the same physical memory area. Recall that in the previous steps we determined that when activating the Data Exchange component, a port of type HvPortTypeEvent is created with TargetSint = 5. Accordingly, all operations with this port in the OS root will be processed by KiVmbusInterrupt1, from which vmbusr! XPartEnlightenedIsr is called, and it will turn, calls KeInsertQueueDpc with the DPC parameter (its value is shown in Figure 19).



Fig. 19. DPC value queued by XPartEnlightenedIsr

From vmbusr! ParentRingInterruptDpc, vmbusr! PkGetReceiveBuffer will be executed after several calls.

 WINDBG>k Child-SP RetAddr Call Site fffff800\`fcc1ea38 fffff800\`6cdc440c vmbusr!PkGetReceiveBuffer+0x2c fffff800\`fcc1ea40 fffff800\`6cdc41a7 vmbusr!PipeTryReadSingle+0x3c fffff800\`fcc1eaa0 fffff800\`6cdc4037 vmbusr!PipeProcessDeferredReadWrite+0xe7 fffff800\`fcc1eaf0 fffff800\`6c96535e vmbusr!PipeEvtChannelSignalArrived+0x63 fffff800\`fcc1eb30 fffff800\`6cdc4e3d vmbkmclr!KmclpVmbusManualIsr+0x16 fffff800\`fcc1eb60 fffff800\`fb2d31e0 vmbusr!ParentRingInterruptDpc+0x5d 


If you look at this memory area, you will see the parameters of the guest OS.

 WINDBG> dc ffffd0016fe33000 L1000 ………………………………………………………………………………………………………………… ffffd001\`6fe35b30 0065004e 00770074 0072006f 0041006b NetworkA ffffd001\`6fe35b40 00640064 00650072 00730073 00500049 ddressIP ffffd001\`6fe35b50 00340076 00000000 00000000 00000000 v.4............. ………………………………………………………………………………………………………………… ffffd001\`6fe35d20 00000000 00000000 00000000 00000000 ................ ffffd001\`6fe35d30 00300031 0030002e 0030002e 0033002e 1.0...0...0...3. ffffd001\`6fe35d40 00000000 00000000 00000000 00000000 ................ WINDBG>!pte ffffd001\`6fe35b30 VA ffffd0016fe35b30 PXE at FFFFF6FB7DBEDD00 PPE at FFFFF6FB7DBA0028 PDE at FFFFF6FB74005BF8 PTE at FFFFF6E800B7F1A8 contains 0000000000225863 contains 00000000003B7863 contains 000000010FB12863 contains 80000001367BD963 pfn 225 ---DA--KWEV pfn 3b7 ---DA--KWEV pfn 10fb12 ---DA-KWEV pfn 1367bd -G-DA--KW-V pfn 1367bd —  PFN 3-    MDL 


Also, the same function in rdx is passed a pointer containing an offset relative to the address of the beginning of the common guest OS pages (in the example, it is 4448h), which needs to be read:

 vmbusr!PkGetReceiveBuffer+0x4e: mov r8,r10 ( r10d      rdx) add r8,qword ptr [rcx+20h] —  rcx+20           WINDBG>!pte @r8 VA ffffd0016ff22448 PXE at FFFFF6FB7DBEDD00 PPE at FFFFF6FB7DBA0028 PDE at FFFFF6FB74005BF8 PTE at FFFFF6E800B7F910 contains 0000000000225863 contains 00000000003B7863 contains 000000010FB12863 contains 80000001367C0963 pfn 225 ---DA--KWEV pfn 3b7 ---DA--KWEV pfn 10fb12 ---DA-KWEV pfn 1367c0 -G-DA--KW-V 


Put a breakpoint at the beginning of the vmbusr! PkGetReceiveBuffer function and execute a PowerShell script. The breakpoint will work, it will be seen that the structure is passed to the function (pointer to rcx) and there is a pointer to the memory block in rcx + 18:

 WINDBG>? poi(@rcx+18) Evaluate expression: -52770386006016 = ffffd001\`6fe33000 WINDBG>!pte ffffd001\`6fe33000 VA ffffd0016fe33000 PXE at FFFFF6FB7DBEDD00 PPE at FFFFF6FB 7DBA0028 PDE at FFFFF6FB74005BF8 PTE at FFFFF6E800B7F198 contains 0000000000225863 contains 00000000003B7863 contains 000000010FB12863 contains 80000001367BB963 pfn 225 ---DA--KWEV pfn 3b7 ---DA--KWEV pfn 10fb12 ---DA--KWEV pfn 1367bb -G-DA--KW-V WINDBG>r cr3 cr3=00000000001ab000 WINDBG>!vtop 1ab000 ffffd 0016fe33000 Amd64VtoP: Virt ffffd001\`6fe33000, pagedir 1ab000 Amd64VtoP: PML4E 1abd00 Amd64VtoP: PDPE 225028 Amd64VtoP: PDE 3b7bf8 Amd64VtoP: PTE 00000001\`0fb12198 Amd64VtoP: Mapped phys 00000001\`367bb000 Virtual address ffffd0016fe33000 translates to physical address 1367bb000. 


INFO


Information on KvP technology can be found on MSDN blogs:
goo.gl/R0U52l
goo.gl/UeZRK2


If you put a breakpoint on the instruction add r8, qword ptr [rcx + 20h], then after several iterations in r8 you can see the name and value of the key KvpDataKey:


 WINDBG>dc @r8 ffffd001\`6ff21d10 ....H........... ffffd001\`6ff21d20 ....(........... ffffd001\`6ff21d30 00020006 00000148 00000000 00000000 00000001 00000 a28 00000003 00050002    0a140000 00000000 00000515 00000103 ................ ffffd001\`6ff21d40 00000004 00000001 00000016 0000001a ................ ffffd001\`6ff21d50 0076004b 00440050 00740061 004b0061 KvPDataK ffffd001\`6ff21d60 00790065 00000000 00000000 00000000 ey............ ............................................................. .......... ffffd001\`6ff21f50 0076004b 00440050 00740061 00560061 KvPDataV ffffd001\`6ff21f60 006c0061 00650075 00000000 00000000 alue........ WINDBG>!pte ffffd001\`6ff21f50 VA ffffd0016ff21f50 PXE at FFFFF6FB7DBEDD00 PPE at FFFFF6FB7DBA0028 PDE at FFFFF6FB74005BF8 PTE at FFFFF6E800B7F908 contains 0000000000225863 contains 00000000003B7863 contains 000000010FB12863 contains 80000001367BF963 pfn 225 ---DA--KWEV pfn 3b7 ---DA--KWEV pfn 10fb12 ---DA-KWEV pfn 1367bf -G-DA--KW-V 


Then, after the PkGetReceiveBuffer is completed, the PipeTryReadSingle function copies the data from the shared buffer using the memmove function.

The block size (in this case, A28) is indicated directly in the block itself, but if a number greater than 4000h is specified, no copying will be performed. Thus, it can be seen that the data exchange between the root OS and the guest OS uses a common buffer, and the hypervisor interface is used only to notify the root OS that it is necessary to read data from this buffer. In principle, the same operation could be performed by sending several messages using winhv! HvPostMessage, but this would lead to a significant decrease in performance.

Using the Hypervisor Interception Interface



Let us configure the hypervisor so that it sends a root OS notification in case the cpuid instruction with the 0x11114444 parameter is executed in one of the guest OS. For this, Hyper-V provides an interface in the form of the HvInstallIntercept hypercall. The hyperv4 driver implements the SetupIntercept function, which receives a list of identifiers for all active guest OSes and calls WinHvInstallIntercept for each.


 int SetupIntercept() { HV_INTERCEPT_DESCRIPTOR Descriptor; HV_INTERCEPT_PARAMETERS Parameters = {0}; HV_STATUS hvStatus = 0; HV_PARTITION_ID PartID = 0x0, NextPartID = 0; //       RAX- CPUID    0x11114444,             DbgPrintString("SetupInterception was called"); Parameters.CpuidIndex = 0x11114444; Descriptor.Type = HvInterceptTypeX64Cpuid; Descriptor.Parameters = Parameters; hvStatus = WinHvGetPartitionId(&PartID); do{ hvStatus = WinHvGetNextChildPartition(PartID,NextPartID,&NextPartID); if (NextPartID != 0){ DbgLog("Child partition id", NextPartID); hvStatus = WinHvInstallIntercept(NextPartID, HV_INTERCEPT_ACCESS_MASK_EXECUTE, &Descriptor); DbgLog("hvstatus of WinHvInstallIntercept = ",hvStatus); } } while ((NextPartID != HV_PARTITION_ID_INVALID) && (hvStatus == 0)); return 0;} 


We will also change the PrintCpuidInterceptMessage function so that it is in the guest OS in the EAX register (or RAX, if the code that executes the CPUID instruction is executed in longmode) is 0x11114444, wrote down the DefaultResultRdx structure of the HV_X64_CPUID_INTERCEPT_MESSAGE rapple, registered, in a field of the HV_X64_CPUID_INTERCEPT_MESSAGE, ram. , value 0x12345678:


 void PrintCpuidInterceptMessage(PHV_MESSAGE hvMessage) {PHV_X64_CPUID_INTERCEPT_MESSAGE_phvCPUID = (PHV_X64_CPUID_NTERCEPT_MESSAGE)_hvMessage->Payload; DbgLog(" phvCPUID->DefaultResultRax",phvCPUID->DefaultResultRax); DbgLog(" phvCPUID->DefaultResultRbx",phvCPUID->DefaultResultRbx); DbgLog(" phvCPUID->DefaultResultRcx",phvCPUID->DefaultResultRcx); DbgLog(" phvCPUID->DefaultResultRdx",phvCPUID->DefaultResultRdx); if (phvCPUID->Rax == 0x11114444){ phvCPUID->DefaultResultRdx =0x12345678; DbgLog16(" phvCPUID->Header.Rip",phvCPUID->Header.Rip); DbgPrintString(" Interception was handled"); } } 


To check in the guest OS, run the test utility that calls the CPUID with Eax equal to 0x11114444. Before installing the interception, the utility will display the result shown in Fig. 20.


Fig. 20. Result of the CPUID instruction on a regular guest OS

After activating interception, the result will be as follows (see Fig. 21).


Fig. 21. Result of the CPUID instruction after setting the capture

At the same time, a message will be displayed in the root of the OS (see Figure 22).


Fig. 22. Debug output of processing a hypervisor message when interception is set

Immediately you should pay attention to the fact that this trick will pass only if the root OS has not previously installed hooks for the specified conditions. In this case, after the hyperv driver replaces the value, control passes to the original WinHvOnInterrupt, which calls the processing function from the vid.sys driver (this function is the fourth parameter of the winhvr! WinHvCreatePartition function, called in the OS root when creating a child partition when the virtual machine starts ), which may cause a change in the result In our case, such a handler, of course, was not installed, the hypervisor analyzed the data in the null SIM slot and corrected the result of the CPUID instruction.


Finally


Despite the fact that after reading my work, your brain probably stood in the pose of a river scorpion (and if you read it to it at all - respect you from all of our editorial staff) ... so, I digress. This article was more of an overview, demonstrating the work of some of the functions and components of the Microsoft virtualization system. However, I hope the examples will help to better understand the principles of operation of these components and will allow for a more in-depth analysis of their safety.


Posted by: gerhart



First published in the Hacker magazine dated 12/2014.

Subscribe to "Hacker"




Source: https://habr.com/ru/post/247273/


All Articles