mercredi 29 juin 2011

Windows Kernel Exploitation Basics - Part 1 : Introduction to DVWDDriver



1. WTFBBQ ?!
 
While software exploitation is becoming increasingly difficult in urserland on the modern Windows systems due to the various mitigation techniques (ASLR, DEP, SafeSEH, SEHOP, /GS...), the security of Drivers is becoming a growing concern among the IT security community.

In a serie of posts, I will try to share my exploration of the obscure world of kernel exploitation on Windows systems. Because there are not so many documentations available on the internet about this topic - and there are not so easy to understand - I've found that posting about that topic would maybe be useful to some beginners like me. Of course, I'm not mastering the topic and mistakes are likely to be present in my posts, therefore don't hesitate to correct me in the comments =)

Actually, after reading the Windows chapter of the excellent book "A guide to Kernel Exploitation" [1], I've decided to play a bit with the driver that is used by the authors to illustrate the most classic vulnerabilities in drivers and the way they can be exploited. This driver is called DVWDDriver - for Damn Vulnerable Windows Driver - and is available on the book's website: http://www.attackingthecore.com/codex.php?chp=chapter6. To be honest, I didn't get all the subtleties just by reading the book, without looking in details the source codes and some additional papers on the topic. This is why I'm writing those articles =)

In this post, I will just present the driver and its vulnerabilities, and in the next articles I will try to share my understanding of the ways we can exploit those vulnerabilities. Of course, I will not invent anything and everything is based on the available documentation. The goal of this serie of posts is to give a global overview of the different methods of exploitation and also the technical details with the commented source code.

Ok, no more bullshit, let's begin our journey into the DVWDDriver...

2. Damn Vulnerable Windows Driver - Presentation

The DVWDDriver can handle 3 different IOCTL (I/O Control Code):
  • DEVICEIO_DVWD_STORE: permits to copy a buffer from userland into a buffer stored in a global structure of the KMD (Kernel-Mode Driver).
  • DEVICEIO_DVWD_OVERWRITE: permits to retrieve the content of the buffer located in kernelland. This is done by copying the content of the buffer in kernelland into a buffer at a given address. Yeah... no check is made about that address and will see that here resides a vulnerability.
  • DEVICEIO_DVWD_STACKOVERFLOW: permits to copy the content of a buffer passed in parameter to a local buffer.
The first IOCTL is handled by the function DvwdHandleIoctlStore() that will make a call to TriggerStore(). Basically, this function checks if the pointer to the structure that contains the buffer and its size ({buffer, size}) points to a memory address in userland, using the ProbeForRead() routine (see [2]). Then it calls the SetSavedData() function that is aimed to copy the content of the buffer into the buffer of a global structure (in kernelland of course). Before doing the copy, the function uses the ProbeForRead() routine again, but this time on the pointer to the buffer. It is for checking if the buffer is also in userland.

DEVICEIO_DVWD_STORE  --> DvwdHandleIoctlStore() --> TriggerStore()
{ buffer, size }                                         |
                                                         |
                                                         V
                                                    SetSavedData()

The second IOCTL is handled by the function DvwdHandleIoctlOverwrite() that will make a call to TriggerOverwrite(). In the same way that the function TriggerStore(), this function checks if the pointer to the structure that is passed in parameter ({ buffer, size }) points to an address in userland (address < = 0x7FFFFFFF). Then, it calls the GetSavedData() function that is aimed to copy the content of the buffer contained into the global structure to the buffer in the structure passed in parameter. However, no additional check is made here in order to verify that the destination buffer is located in userland..

DEVICEIO_DVWD_OVERWRITE  --> DvwdHandleIoctlOverwrite() --> TriggerOverwrite()
{ buffer, size }                                                 |
                                                                 |
                                                                 V
                                                            GetSavedData()

The 2 previous IOCTL permit to exploit an Arbitrary Memory Overwrite vulnerability, and will see in the next paragraph why.

The third IOCTL is handled by the function DvwdHandleIoctlStackOverflow() that will simply make a call to the vulnerable function TriggerOverflow(). We'll see that this function is vulnerable to a Stack-based Buffer Overflow. 

DEVICEIO_DVWD_STACKOVERFLOW  --> DvwdHandleIoctlOverwrite()
buffer, length                                 |
                                               |
                                               V
                                         TriggerOverflow()


3. 1st Vulnerability: Arbitrary Memory Overwrite

We have briefly seen that the GetSavedData() function doesn't check if the pointer to the destination buffer (received in parameter) is in userland or not. The function copies the content of the data saved in the global structure to this buffer. The problem is that the unchecked pointer is controlled by the user. Therefore, if the userland process specifies an arbitrary value - like an address in kernelland - the function ends up overwriting an arbitrary kernel memory range. And since it's possible to write into the buffer of the global structure of the KMD thanks to DEVICEIO_DVWD_STORE IOCTL, we can write an arbitrary amount of data at the address we want. This is called an Arbitrary Memory Overwrite vulnerability or write-what-where vulnerability.

Here is the commented source code of the vulnerable function:
//=============================================================================
//          Part of the KMD vulnerable to Arbitrary overwrite
//=============================================================================

#define GLOBAL_SIZE_MAX 0x100

UCHAR GlobalBuffer[GLOBAL_SIZE_MAX];
ARBITRARY_OVERWRITE_STRUCT GlobalOverwriteStruct = {&GlobalBuffer, 0};

// Copy the content located at GlobalOverwriteStruct.StorePtr to 
// overwriteStruct->StorePtr (No check is performed to ensured that
// it points to userland !!!)
VOID GetSavedData(PARBITRARY_OVERWRITE_STRUCT overwriteStruct) {

 ULONG size = overwriteStruct->Size;
 PAGED_CODE();

 if(size > GlobalOverwriteStruct.Size)
  size = GlobalOverwriteStruct.Size; 
 
 // ---- VULNERABILITY ------------------------------------------------------
 RtlCopyMemory(overwriteStruct->StorePtr, GlobalOverwriteStruct.StorePtr, size);
 // -------------------------------------------------------------------------
}

// Copy a buffer located into kernel memory into a userland buffer
// 
// stream is a pointer that should address a userland structure 
// type ARBITRARY_OVERWRITE_STRUCT
NTSTATUS TriggerOverwrite(UCHAR *stream) {

 ARBITRARY_OVERWRITE_STRUCT overwriteStruct;
 NTSTATUS NtStatus = STATUS_SUCCESS; 
 PAGED_CODE();
  
 __try {
  // Initialize a ARBITRARY_OVERWRITE_STRUCT structure (in kernel land)
  RtlZeroMemory(&overwriteStruct, sizeof(ARBITRARY_OVERWRITE_STRUCT));

  // Check if the pointer given in parameter is located in userland 
  // (if it's not the case, an exception is triggered)
  ProbeForRead(stream, sizeof(ARBITRARY_OVERWRITE_STRUCT), TYPE_ALIGNMENT(char));
  
  // Copy the ARBITRARY_OVERWRITE_STRUCT from userland to the newly 
  // initialized structure located in kernel land
  RtlCopyMemory(&overwriteStruct, stream, sizeof(ARBITRARY_OVERWRITE_STRUCT));

  // Call the vulnerable function
  GetSavedData(&overwriteStruct);
 }
 __except(ExceptionFilter()) {
  NtStatus = GetExceptionCode();
  DbgPrint("[!!] Exception Triggered: Handler body: Exception Code: %d\r\n", NtStatus);   
 }

 return NtStatus;                                      
}
In the next articles, we'll see how to exploit this kind of vulnerability.

4. 2nd vulnerability: Stack-based Buffer Overflow

The TriggerOverflow() function just checks if the buffer it receives in parameter is in userland, and if it's the case it copies it into a local buffer. This local buffer is just 64-byte length. It's obvious that there is a classic stack overflow here because the size of the source buffer is NOT checked... Well, it's not so classic because it occurs in kernelland =)

//=============================================================================
//          Part of the KMD vulnerable to Stack Overflow
//=============================================================================

#define LOCAL_BUFF 64

NTSTATUS __declspec(dllexport) TriggerOverflow(UCHAR *stream, UINT32 len) {
 char buf[LOCAL_BUFF];
 NTSTATUS NtStatus = STATUS_SUCCESS; 
 PAGED_CODE();  
 
 __try {
  // Check if stream points to userland
  ProbeForRead(stream, len, TYPE_ALIGNMENT(char));
  
  // ---- VULNERABILITY --------------------------------------------------
  RtlCopyMemory(buf, stream, len);
  // ---------------------------------------------------------------------
 } 
 __except(ExceptionFilter()) {
  NtStatus = GetExceptionCode();
  DbgPrint("[!!] Exception Triggered: Handler body: Exception Code: %d\\r\n", NtStatus);   
 }
 
 return NtStatus;
 
}

5. Test platform

The exploitation techniques that will be presented in the next article were tested on a Windows Server 2003 SP2 (32-bit).

I use the tool "OSR Driver Loader" in order to quickly load the driver. It is available here: http://www.osronline.com/article.cfm?article=157 or in the archive.


6. Sources

The archive contains:

  • original/ : contains the original versions of DVWDDriver and of the exploit DVWDExploit, without my comments
  • OsrLoader/ : the tool used for loading the driver
  • bin/ : contains the exploit executable DVWDExploit.exe and the driver DVWDDriver.sys
  • src/ : contains all the sources with my comments and my modifications for the stack overflow exploitation. Driver's sources are into src/krn/ and the MS Visual Studio 2010 project for the exploit is into src/usr/.

Let's introduce the different files of the exploit:

  • DVWDExploit.cpp: main function of the exploit
  • Kernel.cpp: functions used for building the kernel payload
  • Mapping.cpp: functions used for creating memory maps (executable or not)
  • Trigger32.cpp: exploitation functions for 32-bit architectures
  • Shellcode32.cpp: token's SID patch shellcodes for 32-bit architectures
There is also an exploit for 64-bit architectures. I won't talk about that in this serie of articles.


References

[1] A Guide to Kernel Exploitation (Attacking the Core), by Enrico Perla & Massimiliano Oldani
http://www.attackingthecore.com

[2] ProbeForRead() Routine
http://msdn.microsoft.com/en-us/library/ff559876(VS.85).aspx

[3] OSR Driver Loader download
http://www.osronline.com/article.cfm?article=157