📜 ⬆️ ⬇️

Battlefield - Hypervisor

image In one of the tasks of the in-person tour of the hacker competition NeoQUEST-2013, the quest participants were confronted by a formidable opponent - the hypervisor! To escape from prison, they needed only to read a line from the memory of the process. But there were a few BUT ... The hypervisor in every possible way complicated the lives of our contestants, namely:
• did not allow just to read the process memory, controlling the address space
• served as an anti-debugger
• checked the integrity of the process image by computing the hash of its section
• linked the program to a computer, etc.

Participants had to smash their heads to find a non-trivial solution to bypass the hypervisor! Imagine yourself in their place and fight the hypervisor, step by step, stuffing "bumps", continually going back a step, and in the end, we will achieve victory and get the key! Below the cut is a detailed look at how the hypervisor controlled access to the key, and what methods the participants attempted to bypass.


')

How it all began


Participants in the NeoQUEST 2013 full-time tour - the winners of the February online tour of NeoQUEST - had a powerful incentive to win. They had to get out of the cold and damp walls of the prison in order to avoid being executed on a plausible electric chair! The prison, of course, was virtual, but the chair was real!



The contestants were given 8 hours to “escape”, and, hoping that it would take about 2 hours to complete each task, we prepared 5 tasks (one spare!), But it turned out, as we thought, no one task passed that four tasks turned out to be enough).
The NeoQUEST-2013 tasks concerned the security of both common areas of information security - cryptography, cloud and web technologies, botnets, and much more, and the security of non-standard devices and technologies (hypervisors, smart cards, etc.), which required even programming "Bare" iron, in particular, the controller Arduino SDK. The winner of the quest , AVictor, just recently returned from Amsterdam from the RSA conference , wrote a little about each of the tasks, and the trip was the main prize of the NeoQUEST-2013 full-time tour! The second place went to v0s (Vlad Roskov), who received the Cat C15 protected smartphone Caterpillar as an award.

image

And now about the hypervisor!


Only v0s managed to bypass the hypervisor. The task was formulated as follows: two identical Windows computers are available, on which the executable KeyReader.exe is present. At the entrance, he gets a member ID, and then displays “Key successfully readed. Press any key for exit. ” And hangs, completing its work by pressing the key. By default, members work under an account with administrator rights. Required to get the key.



When running on any other machines besides these two, KeyReader.exe reports an error:



Imagine yourself in the place of participants and try to get to the bottom of the truth! Judging by the message, the key is in the memory of the process, and we need to read it from there. It sounds easy, try it. Run the Windows Task Manager, find the KeyReader.exe process, dump it while waiting for a keystroke, and feed the dump to the Russinovich strings utility. The dump weighs 3.3 MB, and the utility finds 55,000 lines. It is not very obvious how to search for a key among them, but if you search for the word key, the following line will be found:



It seems that this is not what is needed. It is not yet clear what exactly happened, and why the dump contains this line, but this is clearly not a password. Let's try to figure out how KeyReader.exe works with IDA, and already with a great understanding of what is happening, perform the task. The program is easily decompiled, and in general terms its meaning becomes clear. The main logic is presented below:



The memory page aligned to the page is highlighted , reset, then the participant's Id is read and the AcquireKey function is called . The AcquireKey function is small, and it contains the following code, which used to be an assembler insert:



In the code there is a vmcall instruction that performs a call to the hypervisor with the parameters passed through the registers. The EAX register contains the 'NeoQ' value, the EDX register is the 'strt' value, the EBX register is a pointer to the previously allocated memory page, the ECX register is the page size, the EDI register is the participant id. The result of the call is returned through the ESI register. For simplicity, we can assume that the vmcall instruction transfers control to the hypervisor if the hypervisor is present or calls #UD if it is not present.

A full description of the operation algorithm can be found in the document 64-ia-32-architectures-software-developer-vol-2b-manual from Intel. Now it is clear that the key is stored in the hypervisor and is copied into the program memory when accessing it via the vmcall instruction with certain parameters.

Let's try to debug the program in steps and see what will be in memory after accessing the hypervisor. We will use OllyDbg 2.01 as a debugger. We start KeyReader.exe under debugging, we set breakpoint on the following instruction after vmcall. Enter the participant's id, press Enter, and get into the set breakpoint. The EBX register contains the address 0x1F5000, but zeros are located on it. In ESI, too, 0.



Press F9 and see that an error has occurred.



There are three debugging mechanisms:
• code tooling instructions int3
• step-by-step debugging with interrupt int1 after each instruction
• debug registers D0-D7, which allow you to catch calls to memory
In our case, when setting breakpoint on the instruction, we rewrote the first byte of the instruction to 0xcc (int 0x3). When the program reaches this place, int3 is executed, control passes to the debugger, it restores the rubbed byte, executes the restored instruction, and writes int3 again over the instruction. Thus, breakpoints change the executable image, and the hypervisor, apparently, checks its integrity before copying the key into the program's memory. Also hinted at this error message.

This problem is easily solved in the following way. Set a breakpoint for a few instructions before vmcall, after hitting the breakpoint, remove it and execute the code step by step. Having executed Vmcall, we will receive:



In the EBX register, the address is 0x165000, where the familiar string “key: NICE TRY, THIS IS NOT A KEY” is located . The hypervisor still does not read the key.

You can write a small driver that would use the KeStackAttachProcess and KeUnstackDetachProcess functions to read the process memory, but let's say right away that this would not work either. You can go the thorny path and force Windows to unload the memory of the process of interest to us on the disk, in order to then parse pagefile.sys and find the page corresponding to the buffer with the key. But on this page we would see all the same line.

Let's try the following method - inject our dll into the KeyReader process, and from it read the memory in which the key is located. To begin with we will start process, we will attach to it by means of Olly Dbg and we will look at the address on which the buffer with a key is located. The pointer to the buffer is located at 0x40eb3c.



In our case, the memory for the buffer was allocated at 0x600000, deattachy debugger, the application continues to work. We write a dll that will read the memory at this address and save it to disk in the file a.txt. The code will look like this:



As an injector, we used an example from the book Windows via C / C ++. Inject dll and get the output file a.txt, containing the following line:



This is the key.

Any variation on the subject of code injection would not be in the KeyReader process image would be the solution. For example, one of the participants in the debugger corrected the code of the ReadConsoleInputA function in kernel32.dll, which is called by the getch function, and read a line from it at a known address. As you can see, the task is not the most difficult, but only one person ended up with it. Most likely, this was due to a lack of time, and the task itself is such that it almost does not imply a logical chain that would lead to an answer. Participants had to go through all the ways they knew to read the memory of someone else's process in order to stumble upon a working option at some point.

In general, it was even more interesting to prepare this task than to go through it :). To control access to the page, it was necessary to control changes in all related translation tables from the virtual to the physical address. Given the variety of maping options and the fact that the address of the physical page to which we control access may change, this is not so trivial.

By what principle did the hypervisor in one case give access to the key, but not in the other? When accessing a page with a key, he checked 3 conditions:
• Reading must be from user mode
• The hash of that section of the .text image of the process from which the key is being accessed must be strictly defined
• Reading a key is possible only from the address space in which the vmcall call occurred to receive the key.

The first condition discarded all attempts to read the key from the kernel. These include the ReadProcessMemory function and writing your own driver. The second condition did not allow changing the image code and setting break points. By the way, only the text section was checked, so that you could add your own section with a code that would have been called differently and change the entry point to it. Then call the key retrieval function from your code and save it to disk. The third condition did not allow other processes, in which the memory of the KeyReader process was mapped, to read the key. This condition is somewhat contrived, since in order to obtain such a situation, you will have to write your own driver and your application that would communicate with it, since we did not find ready-made utilities with such functionality on Windows7. Under Windows XP, the WinHex RAM Editor worked in a similar way when reading physical memory. The main purpose of the conditions was the inability to perform the task using only ready-made utilities, and without writing a single line of code.

In conclusion...


While preparing the task with the hypervisor, we wanted to make the participants wonder at the unusual nature of the task. After all, it would seem, what problems can there be with reading a line from the process memory? And judging by the fact that only one participant completed the task, we succeeded! We hope that Habr's readers were interested! Soon, expect articles with a detailed analysis of the most "iron" job NeoQUEST-2013!

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


All Articles