Heap-Based Buffer Overflows

Just as with stack-based buffer overflows, heap buffers can be overflowed with equally disastrous consequences. Before delving into the details of heap overflows, let us discuss what a heap is. In simple terms, a heap is an area of memory that a program can use for storage of dynamic data. Consider, for example, a Web server. Before the server is compiled into a binary, it has no idea what kind of requests its clients will make. Some requests will be 20 bytes long, whereas another request may be 20,000 bytes. The server needs to deal equally well with both situations. Rather than use a fixed- sized buffer on the stack to process requests, the server would use the heap. It requests that some space be allocated on the heap, which is used as a buffer to deal with the request. Using the heap helps memory management, making for a much more scalable piece of software.

The Process Heap

Every process running on Win32 has a default heap known as the process heap . Calling the C function GetProcessHeap() will return a handle to this process heap. A pointer to the process heap is also stored in the Process Environment Block (PEB). The following assembly code will return a pointer to the process heap in the EAX register:

 mov eax, dword ptr fs:[0x30]      mov eax, dword ptr[eax+0x18] 

Many of the underlying functions of the Windows API that require a heap to do their processing use this default process heap.

Dynamic Heaps

Further into the default process heap, under Win32, a process can create as many dynamic heaps as it sees fit. These dynamic heaps are available globally within a process and are created with the HeapCreate() function.

Working with the Heap

Before a process can store anything on the heap it needs to allocate some space. This essentially means that the process wants to borrow a chunk of the heap in which to store things. An application will use the HeapAllocate() function to do this, passing such information as how much space on the heap the application needs. If all goes well, the heap manager allocates a block of memory from the heap and passes back to the caller a pointer to the chunk of memory it's just made available. Needless to say, the heap manager needs to keep a track of what it's already assigned; to do so, it uses a few heap management structures. These structures basically contain information about the size of the allocated blocks and a pair of pointers that point to another pointer that points to the next available block.

Incidentally, we mentioned that an application will use the HeapAllocate() function to request a chunk of the heap. There are other heap functions available, and they pretty much exist for backward compatibility. Win16 had two heaps: It had a global heap that every process could access, and each process had its own local heap. Win32 still has such functions as LocalAlloc() and GlobalAlloc() . However, Win32 has no such differentiation as did Win16: On Win32 both of these functions allocate space from the process' default heap. Essentially these functions forward to HeapAllocate() in a fashion similar to:

 h = HeapAllocate(GetProcessHeap(),0,size); 

Once a process has finished with the storage, it can free itself and be available for use again. Freeing allocated memory is as easy as calling HeapFree ”or the LocalFree or GlobalFree functions, provided you're freeing a block from the default process heap.

For a more detailed look at working with the heap, read the MSDN documentation at http://msdn.microsoft.com/library/default.asp?url=/library/en-us/memory/base/memory_management _reference.asp .

How the Heap Works

An important point to note is that while the stack grows toward address 0x00000000 , the heap does the opposite . This means that two calls to HeapAllocate will create the first block at a lower virtual address than the second. Consequently, any overflow of the first block will overflow into the second block.

Every heap, whether the default process heap or a dynamic heap, starts with a structure that contains, among other data, an array of 128 LIST_ENTRY structures that keeps track of free blocks ”we'll call this array FreeLists . Each LIST_ENTRY holds two pointers (as described in Winnt.h ), and the beginning of this array can be found offset 0x178 bytes into the heap structure. When a heap is first created, two pointers, which point to the first block of memory available for allocation, are set at FreeLists[0] . At the address that these pointers point to ”the beginning of the first available block ”are two pointers that point to FreeLists[0] . So, assuming we create a heap with a base address of 0x00350000 , and the first available block has an address of 0x00350688 , then:

  • at address 0x00350178 ( FreeList[0].Flink ) is a pointer with a value of 0x00350688 ( First Free Block ).

  • at address 0x0035017C ( FreeList[0].Blink ) is a pointer with a value of 0x00350688 ( First Free Block ).

  • at address 0x00350688 ( First Free Block ) is a pointer with a value of 0x00350178 ( FreeList[0] ).

  • at address 0x0035068C ( First Free Block + 4 ) is a pointer with a value of 0x00350178 ( FreeList[0] ).

In the event of an allocation (by a call to RtlAllocateHeap asking for 260 bytes of memory, for example) the FreeList[0].Flink and FreeList[0].Blink pointers are updated to point to the next free block that will be allocated. Furthermore, the two pointers that point back to the FreeList array are moved to the end of the newly allocated block. With every allocation or free these pointers are updated, and in this fashion allocated blocks are tracked in a doubly linked list. When a heap-based buffer is overflowed into the heap control data, the updating of these pointers allows the arbitrary dword overwrite; an attacker has an opportunity to modify program-control data such as function pointers and thus gain control of the process's path of execution. The attacker will overwrite the program control data that is most likely to let him or her gain control of the application. For example, if the attacker overwrites a function pointer with a pointer to his or her buffer, but before the function pointer is accessed, an access violation occurs, and likely the attacker will fail to gain control. In such a case, the attacker would have been better off overwriting the pointer to the exception handler ”thus when the access violation occurs, the attacker's code is executed instead.

Before getting to the details of exploiting heap-based overflows to run arbitrary code, let's delve deeper into what the problem involves.

The following code is vulnerable to a heap overflow:

 #include <stdio.h>      #include <windows.h>          DWORD MyExceptionHandler(void);      int foo(char *buf);      int main(int argc, char *argv[])      {             HMODULE l;             l = LoadLibrary("msvcrt.dll");             l = LoadLibrary("netapi32.dll");             printf("\n\nHeapoverflow program.\n");             if(argc != 2)                     return printf("ARGS!");             foo(argv[1]);                                                          return 0;      }          DWORD MyExceptionHandler(void)      {             printf("In exception handler....");             ExitProcess(1);             return 0;      }          int foo(char *buf)      {             HLOCAL h1 = 0, h2 = 0;             HANDLE hp;                 __try{                     hp = HeapCreate(0,0x1000,0x10000);                     if(!hp)                            return printf("Failed to create heap.\n");                         h1 = HeapAlloc(hp,HEAP_ZERO_MEMORY,260);                         printf("HEAP: %.8X %.8X\n",h1,&h1);                         // Heap Overflow occurs here:                     strcpy(h1,buf);                                          // This second call to HeapAlloc() is when we gain control                     h2 = HeapAlloc(hp,HEAP_ZERO_MEMORY,260);                     printf("hello");             }             __except(MyExceptionHandler())             {                     printf("oops...");             }             return 0;      } 
Note  

For best results, compile with Microsoft's Visual C++ 6.0 from a command line: cl /TC heap.c.

The vulnerability in this code is the strcpy() call in the foo() function. If the buf string is longer than 260 bytes (the size of the destination buffer) then the heap control structure is overwritten. This control structure has two pointers that both point to the FreeLists array where we can find a pair of pointers to the next free block. When freeing or allocating, the heap manager switches these around, moving one pointer into the second, and then the second pointer into the first.

By passing an overly long argument (for example, 300 bytes) to this program (which is then passed to function foo where the overflow occurs), the code access violates at the following when the second call to HeapAlloc() is made:

 77F6256F 89 01                mov         dword ptr [ecx],eax 77F62571 89 48 04             mov         dword ptr [eax+4],ecx 

Although we're triggering this with a second call to HeapAlloc , a call to HeapFree or HeapRealloc would elicit the same effect. If we look at the ECX and EAX registers, we can see that they both contain data from the string we have passed as an argument to the program. We've overwritten pointers in the heap-management structure, so when this is updated to reflect the change in the heap when the second call to HeapAlloc() is made, we end up completely owning both registers. Now look at what the code does.

 mov dword ptr [ecx],eax 

This means that the value in EAX should be moved into the address pointed to by ECX . As such, we can overwrite a full 32 bits anywhere in the virtual address space of the process (that's marked as writable) with any 32-bit value we want. We can exploit this by overwriting program control data. There is a caveat, however. Look at the next line of code.

 mov dword ptr [eax+4],ecx 

We have now flipped the instructions. Whatever the value is in the EAX register (used to overwrite the value pointed to by ECX in the first line) must also point to writable memory, because whatever is in ECX is now being written to the address pointed to by EAX+4 . If EAX does not point to writable memory, then an access violation will occur. This is not actually a bad thing and lends itself to one of the more common ways of exploiting heap overflows. Attackers will often overwrite the pointer to a handler in an exception registration structure on the stack, or the Unhandled Exception Filter, with a pointer to a block of code that will get them back to their code if an exception is thrown. Lo and behold, if EAX points to non-writable memory, then we get an exception, and the arbitrary code executes. Even if EAX is writable, because EAX does not equal ECX , the low-level heap functions will more than likely go down some error path and throw an exception anyway. So overwriting a pointer to an exception handler is probably the easiest way to go when exploiting heap-based overflows.



The Shellcoder's Handbook. Discovering and Exploiting Security
Hacking Ubuntu: Serious Hacks Mods and Customizations (ExtremeTech)
ISBN: N/A
EAN: 2147483647
Year: 2003
Pages: 198
Authors: Neal Krawetz

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net