Foreword

Over the past few years, Corelan Team has received many exploit related questions, including "I have found a bug and I don’t seem to control EIP, what can I do ?"; "Can you write a tutorial on heap overflows" or "what are Integer overflows".

In this article, Corelan Team member Jason Kratzer (pyoor) tries to answer some of these questions in a very practical, hands-on way. He went to great lengths to illustrate the process of finding a bug, taking the crash information and reverse engineering the crash context to identifying the root cause of the bug, to finally discussing multiple ways to exploit the bug. Of course, most – if not all – of the techniques in this document were discovered many years ago, but I’m sure this is one of the first (public) articles that shows you how to use them in a real life scenario, with a real application. Although the techniques mostly apply to Windows XP, we believe it is required knowledge, necessary before looking at newer versions of the Windows Operating system and defeating modern mitigation techniques.

enjoy !

– corelanc0d3r

Introduction

In my previous article, we discussed the process used to evaluate a memory corruption bug that I had identified in a recently patched version of KMPlayer. With the crash information generated by this bug we were able to step through the crash, identify the root cause of our exception, and determine exploitability. In doing so, we were able to identify 3 individual methods that could potentially be used for exploitation. This article will serve as a continuation of the series with the intention of building upon some of the skills we discussed during the previous “Root Cause Analysis” article. I highly recommend that if you have not done so already, please review the contents of that article (located here) before proceeding.

For the purpose of this article, we’ll be analyzing an integer overflow that I had identified in the GOM Media Player software developed by GOM Labs. This bug affects GOM Media Player 2.1.43 and was reported to the GOM Labs development team on November 19, 2012. A patch was released to mitigate this issue on December 12, 2012.

As with our previous bug, I had identified this vulnerability by fuzzing the MP4/QT file formats using the Peach Framework (v2.3.9). In order to reproduce this issue, I have provided a bare bones fuzzing template (Peach PIT) which specifically targets the vulnerable portion of the MP4/QT file format. You can find a copy of that Peach PIT here. The vulnerable version of GOM player can be found here.

Analyzing the Crash Data

Let’s begin by taking a look at the file, LocalAgent_StackTrace.txt, which was generated by Peach at crash time. I’ve included the relevant portions below:

(cdc.5f8): Access violation - code c0000005 (first chance) r eax=00000028 ebx=0000004c ecx=0655bf60 edx=00004f44 esi=06557fb4 edi=06555fb8 eip=063b4989 esp=0012bdb4 ebp=06557f00 iopl=0 nv up ei pl nz na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00210206 GSFU!DllUnregisterServer+0x236a9: 063b4989 891481 mov dword ptr [ecx+eax*4],edx ds:0023:0655c000=???????? kb ChildEBP RetAddr Args to Child WARNING: Stack unwind information not available. Following frames may be wrong. 0012bdc0 063b65eb 064dcda8 06555fb8 0652afb8 GSFU!DllUnregisterServer+0x236a9 0012bdd8 063b8605 064dcda8 06555fb8 0652afb8 GSFU!DllUnregisterServer+0x2530b 0012be00 063b8a85 064dcda8 0652afb8 0652afb8 GSFU!DllUnregisterServer+0x27325 0012be18 063b65eb 064dcda8 0652afb8 06510fb8 GSFU!DllUnregisterServer+0x277a5 0012be30 063b8605 064dcda8 0652afb8 06510fb8 GSFU!DllUnregisterServer+0x2530b 0012be58 063b8a85 064dcda8 06510fb8 06510fb8 GSFU!DllUnregisterServer+0x27325 0012be70 063b65eb 064dcda8 06510fb8 06500fb8 GSFU!DllUnregisterServer+0x277a5 INSTRUCTION_ADDRESS:0x00000000063b4989 INVOKING_STACK_FRAME:0 DESCRIPTION:User Mode Write AV SHORT_DESCRIPTION:WriteAV CLASSIFICATION:EXPLOITABLE BUG_TITLE:Exploitable - User Mode Write AV starting at GSFU!DllUnregisterServer+0x00000000000236a9 (Hash=0x1f1d1443.0x00000000) EXPLANATION:User mode write access violations that are not near NULL are exploitable.

(You can download the complete Peach crash data here)

As we can see here, we’ve triggered a write access violation by attempting to write the value of edx to the location pointed at by [ecx+eax*4]. This instruction fails of course because the location [ecx+eax*4] points to an inaccessible region of memory. (0655c000=????????)

Since we do not have symbols for this application, the stack trace does not provide us with any immediately evident clues as to the cause of our exception.

Furthermore, we can also see that !exploitable has made the assumption that this crash is exploitable due to the fact that the faulting instruction attempts to write data to an out of bounds location and that location is not near null. It makes this distinction because a location that is near null may be indicative of a null pointer dereference and these types of bugs are typically not exploitable (though not always). Let’s try and determine if !exploitable is in fact, correct in this assumption.

Identifying the Cause of Exception

Page heap

Before we begin, there’s something very important that we must discuss. Take a look at the bare bones Peach PIT I’ve provided; particularly the Agent configuration beginning at line 55.

Using this configuration, I’ve defined the primary monitor as the “WindowsDebugEngine” which uses PyDbgEng, a wrapper for the WinDbg engine dbgeng.dll, in order to monitor the process. This is typical of most Peach fuzzer configurations under windows. However, what’s important to note here is the second monitor, “process.PageHeap”. This monitor enables full page heap verification by using the Microsoft Debugging tool, GFlags (Global Flags Editor). In short, GFlags is a utility that is packaged with the Windows SDK, and enables users to more easily troubleshoot potential memory corruption issues. There are a number of configuration options available with GFlags. For the purpose of this article, we’ll only be discussing 2: standard and full page heap verification.

When using page heap verification, a special page header prefixes each heap chunk. The image below displays the structure of a standard (allocated) heap chunk and the structure of an (allocated) heap chunk with page heap enabled.

This information can also be extracted by using the following display types variables: # Displays the standard heap metadata. Replace 0xADDRESS with the heap chunk start address dt _HEAP_ENTRY 0xADDRESS # Displays the page heap metadata. Replace 0xADDRESS with the start stamp address. dt _DPH_BLOCK_INFORMATION 0xADDRESS

One of the most important additions to the page heap header is the "user stack traces" (+ust) field. This field contains a pointer to the stack trace of our allocated chunk. This means that we’re now able to enumerate which functions eventually lead to the allocation or free of a heap chunk in question. This is incredibly useful when trying to track down the root cause of our exception.

Both standard and full heap verification prefix each chunk with this header. The primary difference between standard and full page heap verification is that under standard heap verification, fill patterns are placed at the end of each heap chunk (0xa0a0a0a0). If for instance a buffer overflow were to occur and data was written beyond the boundary of the heap chunk, the fill pattern located at the end of the chunk would be overwritten and therefore, corrupted. When our now corrupt block is accessed by the heap manager, the heap manager will detect that the fill pattern has been modified/corrupted and cause an access violation to occur.

With full page heap verification enabled, rather than appending a fill pattern, each heap chunk is placed at the end of a (small) page. This page is followed by another (small) page that has the PAGE_NOACCESS access level set. Therefore, as soon as we attempt to write past the end of the heap chunk, an access violation will be triggered directly (in comparison with having to wait for a call to the heap manager). Of course, the use of full page heap will drastically change the heap layout, because a heap allocation will trigger the creation of a new page. In fact, the application may even run out of heap memory space if your application is performing a lot of allocations.

For a full explanation of GFlags, please take a look at the MSDN documentation here.

Now the reason I’ve brought this up, is that in order to replicate the exact crash generated by Peach, we’ll need to enable GFlags for the GOM.exe process. GFlags is part of the Windows Debugging Tools package which is now included in the Windows SDK. The Windows 7 SDK, which is recommended for both Windows XP and 7 can be found here.

In order to enable full page heap verification for the GOM.exe process, we’ll need to execute the following command.

C:\Program Files\Debugging Tools for Windows (x86)>gflags /p /enable GOM.exe /full

Initial analysis

With that said, let’s begin by comparing our original seed file and mutated file using the 010 Binary Editor.

Please note that in the screenshot below, “Address A” and “Address B” correlate with OriginalSeed.mov and MutatedSeed.mov respectively.

Here we can see that our fuzzer applied 8 different mutations and removed 1 block element entirely (as identified by our change located at offset 0x12BE).

As documented in the previous article, you should begin by reverting each change, 1 element at a time, from their mutated values to those found in the original seed file. After each change, save the updated sample and open it up in GOM Media Player while monitoring the application using WinDbg.

windbg.exe "C:\Program Files\GRETECH\GomPlayer\GOM.EXE" "C:\Path-To\MutatedSeed.mov"

The purpose here is to identify the minimum number of mutated bytes required to trigger our exception. Rather than documenting each step of the process which we had already outlined in the previous article, we’ll simply jump forward to the end result. Your minimized sample file should now look like the following:

Here we can see that a single, 4 byte change located at file offset 0x131F was responsible for triggering our crash. In order to identify the purpose of these bytes, we must identify what atom or container they belong to.

Just prior to our mutated bytes, we can see the ASCII string “stsz”. This is known as a FourCC. The QuickTime and MPEG-4 file formats rely on these FourCC strings in order to identify various atoms or containers used within the file format. Knowing that, we can lookup the structure of the “stsz” atom in the QuickTime File Format Specification found here.

Size: 0x00000100 Type: 0x7374737A (ASCII stsz) Version: 0x00 Flags: 0x000000 Sample Size: 0x00000000 Number of Entries: 0x8000000027 Sample Size Table(1): 0x000094B5 Sample Size Table(2): 0x000052D4

Looking at the layout of the “stsz” atom, we can see that the value for the “Number of Entries” element has been replaced with a significantly larger value (0x80000027 compared with the original value of 0x3B). Now that we’ve identified the minimum change required to trigger our exception, let’s take a look at the faulting block (GSFU!DllUnregisterServer+0x236a9) in IDA Pro.

Reversing the Faulty Function

Without any state information, such as register values or memory locations used during run time, we can only make minor assumptions based on the instructions contained within this block. However, armed with only this information, let’s see what we can come up with.

Let’s assume that eax and edx are set to 0x00000000 and that esi points to 0xAABBCCDD

A single byte is moved from the location pointed at by esi to edx resulting in edx == 0x000000AA

A single byte is moved from the location pointed at by [esi+1] to ecx

edx is shifted left by 8 bytes resulting in 0x0000AA00

ecx is added to @edx resulting in 0x0000AABB

A single byte is moved from the location pointed at by [esi+2] to ecx

edx is again shifted left by 8 bytes resulting in 0x00AABB00

ecx is again added to edx resulting in 0x00AABBCC

A single byte is moved from the location pointed at by [esi+3] to ecx

edx is again shifted left by 8 bytes resulting in 0xAABBCC00

And finally, ecx is added to edx resulting in 0xAABBCCDD

So what does this all mean? Well, our first 10 instructions appear to be an overly complex version of the following instruction:

movzx edx, dword ptr [esi]

However, upon closer inspection what we actually see is that due to the way bytes are stored in memory, this function is actually responsible for reversing the byte order of the input string. So our initial read value of 0x41424344 (ABCD) will be written as 0x44434241 (DCBA).

With that said, let’s reduce our block down to:

loc_3B04960: cmp ebx, 4 jl short loc_3B0499D ; Jump outside of our block movzx edx, dword ptr [esi] ; Writes reverse byte order ([::-1]) mov ecx, [edi+28h] mov ecx, [ecx+10h] mov [ecx+eax*4], edx ; Exception occurs here. ; Write @edx to [ecx+eax*4] mov edx, [edi+28h] mov ecx, [edx+0Ch] add esi, 4 sub ebx, 4 inc eax cmp eax, ecx jb short loc_3B04960

Now before we actually observe our block in the debugger, there are still a few more characteristics we can enumerate.

The value pointed to by esi is moved to edx

edx is then written to [ecx+eax*4]

The value of esi is increased by 4

The value of ebx is decreased by 4

eax is incremented by 1

The value of eax is compared against ecx. If eax is equal to ecx, exit the block. Otherwise, jump to our first instruction.

Once at the beginning of our block, ebx is then compared against 0x4. If ebx is less than 4, exit the block. Otherwise, perform our loop again.

To summarize, our first instruction attempts to determine if ebx is less than or equal to 4. If it is not, we begin our loop by moving a 32 bit value at memory location “A” and write it to memory location “B”. Then we check to make sure eax is not equal to ecx. If it isn’t, then we return to the beginning of our loop. This process will continue, performing a block move of our data until one of our 2 conditions are met.

With a rough understanding of the instruction set, let’s observe its behavior in our debugger. We’ll set the following breakpoints which will halt execution if either of our conditions cause our block iteration to exit and inform us of what data is being written and to where.

r @$t0 = 1 bp GSFU!DllUnregisterServer+0x23680 ".printf \"Block iteration #%p\

\", @$t0; r @$t0 = @$t0 + 1; .if (@ebx <= 0x4) {.printf \"1st condition is true. Exiting block iteration\

\"; } .else {.printf \"1st condition is false (@ebx == 0x%p). Performing iteration\

\", @ebx; gc}" bp GSFU!DllUnregisterServer+0x236a9 ".printf \"The value, 0x%p, is taken from 0x%p and written to 0x%p\

\", @edx, @esi, (@ecx+@eax*4); gc" bp GSFU!DllUnregisterServer+0x236b9 ".if (@eax == @ecx) {.printf \"2nd is false. Exiting block iteration.\

\

\"; } .else {.printf \"2nd condition is true. ((@eax == 0x%p) <= (@ecx == 0x%p)). Performing iteration\

\

\", @eax, @ecx; gc}"

With our breakpoints set, you should see something similar to the following:

Block iteration #00000001 1st condition is false (@ebx == 0x000000ec). Performing iteration The value, 0x000094b5, is taken from 0x07009f14 and written to 0x0700df60 2nd condition is true. ((@eax == 0x00000001) <= (@ecx == 0x80000027)). Performing iteration Block iteration #00000002 1st condition is false (@ebx == 0x000000e8). Performing iteration The value, 0x000052d4, is taken from 0x07009f18 and written to 0x0700df64 2nd condition is true. ((@eax == 0x00000002) <= (@ecx == 0x80000027)). Performing iteration ...truncated... Block iteration #00000028 1st condition is false (@ebx == 0x00000050). Performing iteration The value, 0x00004fac, is taken from 0x07009fb0 and written to 0x0700dffc 2nd condition is true. ((@eax == 0x00000028) <= (@ecx == 0x80000027)). Performing iteration Block iteration #00000029 1st condition is false (@ebx == 0x0000004c). Performing iteration The value, 0x00004f44, is taken from 0x07009fb4 and written to 0x0700e000 (1974.1908): Access violation - code c0000005 (first chance) First chance exceptions are reported before any exception handling. This exception may be expected and handled. eax=00000028 ebx=0000004c ecx=0700df60 edx=00004f44 esi=07009fb4 edi=07007fb8 eip=06e64989 esp=0012bdb4 ebp=07009f00 iopl=0 nv up ei pl nz na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00210206 GSFU!DllUnregisterServer+0x236a9: 06e64989 891481 mov dword ptr [ecx+eax*4],edx ds:0023:0700e000=????????

Here we can see that neither of our conditions caused our block iteration to exit. Our instruction block performed 0x29 writes until a memory boundary was reached (likely caused by our full page heap verification) which triggers an access violation. Using the ‘db’ command, we let’s take a look at the data we’ve written.