Recently I started learning Windows Kernel Exploitation, so I decided to share some of my notes in form of a blog.
The previous part was about setting up the lab. Now, we will play a bit with HackSysExtremeVulnerableDriver by Ashfaq Ansari in order to get comfortable with it. In the next parts I am planning to walk through the demonstrated vulnerabilities and exploitation techniques.
What I use for this part:
- The lab described in the previous part
- HackSys Extreme Vulnerable Driver (HEVD) – prebuild version + the source code
- OSR Driver Loader
- DebugView (from SysInternals Suite)
- Visual Studio 2012 (you can use any version you like)
Installing and testing HEVD
First, I will show how to install HEVD. We will and configure Debugee and the Debugger in order to see the Debug Strings and HEVD’s symbols. We will also play a bit with dedicated exploits. You can see the video and read the explanations below:
Watching the DebugStrings
HEVD and the dedicated exploits prints a lot of information as DebugStrings. We can watch them from the Debugger machine (using WinDbg) as well as from Debugee machine (using DebugView).
Before installing HEVD, we will set up everything in order to see the strings that are being printed during driver’s initialization.
On the Debugger:
We need to break the execution of the Debugee in order to get the kd prompt (in WinDbg: Debug -> Break). Then, we enable printing Debug Strings via command:
ed nt!Kd_Default_Mask 8
After that, we can let the Debugee run further by executing the command:
Warning: Enabling this slows down the Debugee. So, whenever possible, try to watch DebugStrings locally (on the Debugee only).
On the Debugee:
We need to run DebugView as Administrator. Then we choose from the menu:
Capture -> Capture Kernel
Installing the driver
First, we will download the pre-build package (driver+exploit) on the Debugee (the victim machine), install them and test. We can find it on the github of HackSysTeam, in section releases (https://github.com/hacksysteam/HackSysExtremeVulnerableDriver/releases). The package contains two version of driver – vulnerable and not. We will pick the vulnerable one, built for 32 bit (i386).
We choose Service Start as Automatic. Then we click: [Register Service] and when it succeeded: [Start Service].
For driver installation I used OSR Driver Loader, that is a very convenient manager. But alternatively, you can do the installation from commandline, using:
sc create [service name] type=kernel binpath=[driver path] sc start [service name]
If the installation succeeded, we should see the HEVD banner printed on WinDbg (on the Debugger machine) as well as on DbgView on Debugee Machine.
The precompiled package of HEVD comes with symbols (sdb file) that we can also add to our Debugger. First, let’s stop the Debugee by sending it a break signal, and have a look at all the loaded modules.
To find the HEVD module, we can set a filter:
lm m H*
We will see, that it does not have any symbols attached. Well, it can be easily fixed. First, turn on:
– in order to print all the information about the paths to which WinDbg referred in search for the symbol. Then, try to reload the symbols:
…and try to refer to them again. You will see the path, where we can copy the pdb file. After moving the pdb file to the appropriate location on the Debugger machine, reload the symbols again. You can test them by trying to print all the functions from HEVD:
(See the details on the Video#1)
Testing the exploits
The same package contains also a set of the dedicated exploits. We can run each of them by executing an appropriate command. Let’s try to deploy some of them and set cmd.exe as a program to be executed.
Pool Overflow Exploit deployed:
If the exploitation went successful, the requested application (cmd.exe) will be deployed with elevated privileges.
By the command
we can confirm, that it is really run elevated:
At the same time, we can see on our Debugger machine the Debug Strings printed by the exploit:
All of the exploits, except the double fetch should run well on one core. If we want this exploit to work, we need to enable two cores on the Debugee machine.
WARNING: Some of the exploits are not 100% reliable and we can encounter a system crash after deploying them. Don’t worry, this is normal.
Hi driver, let’s talk!
Just like in case of the user land, in the kernel land exploitation begins from finding the points, where we can supply an input to the program. Then, we need to find the input that can corrupt the execution (in contrary to the user land – in kernel land a crash will directly result in having a blue screen!). Finally, we will be trying to craft the input in a way that let us control the execution of the vulnerable program.
In order to communicate with a driver from user mode we will be sending it IOCTLs – Input-Output controls. The IOCTL allows us to send from the user land some input buffer to the driver. This is the point from which we can attempt the exploitation.
HEVD contains demos of various classes of vulnerabilities. Each of them can be triggered using a different IOCTL and exploited by the supplied buffer. Some (but not all) will cause our system to crash when triggered.
Finding Device name & IOCTLs
Before we try to communicate with a driver, we need to know two things:
- the device that the driver creates (if it doesn’t create any, we will not be able to communicate)
- list of IOCTLs (Input-Output Controls) that the driver accepts
HEVD is open-source, so we can read all the necessary data directly from the source code. In real life, most of the time we will have to reverse the driver in order to get it.
Let’s have a look at the fragment of code where HEVD creates a device. https://github.com/hacksysteam/HackSysExtremeVulnerableDriver/blob/master/Driver/HackSysExtremeVulnerableDriver.c#L79
The name of the device is mentioned above.
Now, let’s see find the list of IOCTLs. We will start from looking at the array of IRPs:
The function linked to IRP_MJ_DEVICE_CONTOL will be dispatching IOCTLs sent to the driver. So, we need to take a look inside this function.
It contains a switch, that calls a handler function appropriate to handle a particular IOCTL. We can grab our list of IOCTLs by coping the switch cases. The values of the constants are defied in a header:
Writing a client application
Ok, we got all the necessary data that we can use to communicate with the driver by our own program. We can put it all together in a header file, i.e.: hevd_constants.h
Number of each IOCTL is created by a macro defined in a standard windows header winioctl.h:
If you include windows.h header, the above macro will be added automatically. For now, we not need to bother about meaning of the particular constants – we will just use the defined elements as they are.
Below you can see a tiny example. This application sends the STACK_OVERFLOW IOCTL to the driver: send_ioctl.cpp
Try to compile this program and deploy it on the Debugee machine. Start the DebugView and observe DebugStrings printed by the driver.
If you enabled printing DebugStrings on the Debugger machine, you should see similar output:
As we can see, the driver got our input and reported about it.
Exercise: let’s have a crash!
As an exercise, I created a small client for HEVD, that allows to send it various IOCTLs with the input buffer of the requested length. You can find the source code here:
..and the compiled 32 bit binary here.
Try to play with various IOCTLs, till you get the crash. Because the Debugee runs under the control of the Debugger, you should not get a blue screen – instead, WinDbg will get triggered. Try to make a brief crash analysis for every case. Start from printing the information by:
Some other helpful commands:
k - stack trace kb - stack trace with parameters r - registers dd [address]- display data as DWORD starting from the address
For more, check the WinDbg help file:
In our sample application, the user buffer is filled with “A” -> ASCII 0x41 (https://github.com/hasherezade/wke_exercises/blob/master/task1/src/main.cpp#L34):
RtlFillMemory(inBuffer, bufSize, 'A');
So, wherever we see it in the crash analysis, it means the particular data can be filled by the user.
Mind the fact, that triggering the same vulnerability can give you a different output, depending on the immediate source of the crash, that is related to i.e. size of the overflow, current layout of the memory, etc.
- http://expdev-kiuhnm.rhcloud.com/2015/05/17/windbg/ – introduction to WinDbg (by Massimiliano Tomassoli)
- https://github.com/mwrlabs/win_driver_plugin – An IDA Pro plugin to help when working with IOCTL codes or reversing Windows drivers (by Sam Brown)