Jump to content
Nytro

Root Cause Analysis – Integer Overflows

Recommended Posts

[h=2]Root Cause Analysis – Integer Overflows[/h]Published July 2, 2013 | postauthoricon.pngBy Corelan Team (Jason Kratzer)

Table of Contents [hide]

[h=3]Foreword[/h] 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

[h=3]Introduction[/h] 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.

[h=3]Analyzing the Crash Data[/h] 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
<...truncated...>

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.

[h=3]Identifying the Cause of Exception[/h] [h=4]Page heap[/h] 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.

<Agent name="LocalAgent">
<Monitor class="debugger.WindowsDebugEngine">
<Param name="CommandLine" value="C:\Program Files\GRETECH\GomPlayer\GOM.EXE "C:\fuzzed.mov"" />
<Param name="StartOnCall" value="GOM.EXE" />
</Monitor>
<Monitor class="process.PageHeap">
<Param name="Executable" value="GOM.EXE"/>
</Monitor>
</Agent>

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.

RCAIntegerOverflow6_thumb.png

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

[h=4]Initial analysis[/h] 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.

RCAIntegerOverflow1_thumb.png

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:

RCAIntegerOverflow8_thumb.png

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 (0×80000027 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.

[h=3]Reversing the Faulty Function[/h] RCAIntegerOverflow3_thumb.png

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 0×00000000 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 0×41424344 (ABCD) will be written as 0×44434241 (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 0×4. 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\\n\", @$t0; r @$t0 = @$t0 + 1; .if (@ebx <= 0x4) {.printf \"1st condition is true. Exiting block iteration\\n\"; } .else {.printf \"1st condition is false (@ebx == 0x%p). Performing iteration\\n\", @ebx; gc}"
bp GSFU!DllUnregisterServer+0x236a9 ".printf \"The value, 0x%p, is taken from 0x%p and written to 0x%p\\n\", @edx, @esi, (@ecx+@eax*4); gc"
bp GSFU!DllUnregisterServer+0x236b9 ".if (@eax == @ecx) {.printf \"2nd is false. Exiting block iteration.\\n\\n\"; } .else {.printf \"2nd condition is true. ((@eax == 0x%p) <= (@ecx == 0x%p)). Performing iteration\\n\\n\", @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 0×29 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.

0:000> db 0x0700df60
0700df60 b5 94 00 00 d4 52 00 00-a8 52 00 00 2c 52 00 00 .....R...R..,R..
0700df70 7c 52 00 00 80 52 00 00-a4 52 00 00 28 53 00 00 |R...R...R..(S..
0700df80 18 53 00 00 94 52 00 00-20 53 00 00 ac 52 00 00 .S...R.. S...R..
0700df90 28 53 00 00 e0 51 00 00-d0 52 00 00 88 52 00 00 (S...Q...R...R..
0700dfa0 e0 52 00 00 94 52 00 00-18 53 00 00 14 52 00 00 .R...R...S...R..
0700dfb0 14 52 00 00 5c 52 00 00-34 52 00 00 08 52 00 00 .R..\R..4R...R..
0700dfc0 d4 51 00 00 84 51 00 00-d8 51 00 00 d8 50 00 00 .Q...Q...Q...P..
0700dfd0 3c 51 00 00 04 52 00 00-a4 51 00 00 bc 50 00 00 <q...r...q...p..

Now let’s break down the information returned by our breakpoints:

  • First, taking a look at our write instructions we can see that the data being written appears to be the contents of our “Sample Size Table”. Our vulnerable block is responsible for reading 32 bits during each iteration from a region of memory beginning at 0x07009F14 and writing it to a region beginning at 0x0700DF60 (these addresses may be different for you and will likely change after each execution). This is good a good sign as it means that we can control what data is being written.
  • Furthermore, we can see that during our second condition, eax is being compared against the same value being provided as the “Number of Entries” element within our “stsz” atom. This means that we can control at least 1 of the 2 conditions which will determine how many times our write instruction occurs. This is good. As with our previous example (KMPlayer), we demonstrated that if we can write beyond the intended boundary of our function, we may be able to overwrite sensitive data.
  • As for our first condition, it’s not yet apparent where the value stored in ebx is derived. More on this in a bit.

At this point, things are looking pretty good. So far we’ve determined that we can control the data we write and at least one of our conditions. However, we still haven’t figured out yet why we’re writing beyond our boundary and into the guard page. In order to determine this, we’ll need to enumerate some information regarding the region where our data is being written, such as the size and type (whether it be stack, heap, or virtually allocated memory). To do so, we can use corelan0cd3r’s mona extension for WinDbg. Before we do however, we’ll need to modify Gflags to only enable standard page heap verification. The reason for this is that when using full page heap verification, Gflags will modify our memory layout in such a way that will not accurately reflect our memory state when run without GFlags. To enable standard page heap verification, we’ll execute the following command:

gflags.exe /p /enable gom.exe

Next, let’s go ahead and start our process under WinDbg. This time, we’ll only apply 1 breakpoint in order to halt execution upon execution of our first write instruction.

0:000> bp GSFU!DllUnregisterServer+0x236a9 ".printf \"The value, 0x%p, is taken from 0x%p and written to 0x%p\\n\", @edx, @esi, (@ecx+@eax*4)"
Bp expression 'GSFU!DllUnregisterServer+0x236a9' could not be resolved, adding deferred bp
0:000> g

The value, 0x000094b5, is taken from 0x06209c4c and written to 0x06209dc0
eax=00000000 ebx=000000ec ecx=06209dc0 edx=000094b5 esi=06209c4c edi=06209bb8
eip=06034989 esp=0012bdb4 ebp=06209c38 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200202
GSFU!DllUnregisterServer+0x236a9:
06034989 891481 mov dword ptr [ecx+eax*4],edx ds:0023:06209dc0=00000000
0:000> !py mona info -a 0x06209dc0
Hold on...
[+] Generating module info table, hang on...
- Processing modules
- Done. Let's rock 'n roll.
[+] NtGlobalFlag: 0x02000000
0x02000000 : +hpa - Enable Page Heap

[+] Information about address 0x06209dc0
{PAGE_READWRITE}
Address is part of page 0x06200000 - 0x0620a000
This address resides in the heap

Address 0x06209dc0 found in
_HEAP @ 06200000, Segment @ 06200680
( bytes ) (bytes)
HEAP_ENTRY Size PrevSize Unused Flags UserPtr UserSize Remaining - state
06209d98 000000d8 00000050 00000014 [03] 06209dc0 000000a4 0000000c Extra present,Busy (hex)
00000216 00000080 00000020 00000164 00000012 Extra present,Busy (dec)

Chunk header size: 0x8 (8)
Extra header due to GFlags: 0x20 (32) bytes
DPH_BLOCK_INFORMATION Header size: 0x20 (32)
StartStamp : 0xabcdaaaa
Heap : 0x86101000
RequestedSize : 0x0000009c
ActualSize : 0x000000c4
TraceIndex : 0x0000193e
StackTrace : 0x04e32364
EndStamp : 0xdcbaaaaa
Size initial allocation request: 0xa4 (164)
Total space for data: 0xb0 (176)
Delta between initial size and total space for data: 0xc (12)
Data : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ...

[+] Disassembly:
Instruction at 06209dc0 : ADD BYTE PTR [EAX],AL

Output of !address 0x06209dc0:

Usage: <unclassified>
Allocation Base: 06200000
Base Address: 06200000
End Address: 0620a000
Region Size: 0000a000
Type: 00020000.MEM_PRIVATE
State: 00001000.MEM_COMMIT
Protect: 00000004.PAGE_READWRITE

Good. So here we can see that we’re writing to an allocated heap chunk. The requested size of our block is 0x9C. Based on our access violation, we can already determine that the current state of our mutated file will attempt to write more than 0x9C bytes of data. After 0x9C bytes, our boundary is reached and an access violation is triggered. Considering the structure in which we’re writing our data, it appears as if we’ve identified a very simple example of a heap overflow. If we are able to control the length of the data being written and another heap chunk sits in a location following our written data, we may be able to write beyond the bounds of our chunk and corrupt the metadata (chunk header) of the following chunk, or application data stored in that adjacent chunk (that is of course with GFlags disabled). More on this later.

However, before we attempt to do so, we still have not determined the actual cause of our exception. Why is it that we are allocating a region that is only 0x9C bytes, yet attempting to write significantly more? Our next step in the process will be to determine where our allocated size of 0x9C comes from. Is this some value specified in the file?

There are in in fact several methods we could use to determine this. We could set a breakpoint on all heap allocations of size 0x9C. Once we’ve identified the appropriate allocation, we can then look into the calling function in order to determine where the size is derived.

Fortunately for us, with GFlags enabled, that is unnecessary. As I mentioned earlier, when page heap verification is enabled, a field within the page heap header contains a pointer to the stack trace of our allocated block. A pointer to this stack trace is listed in !mona’s output under DPH_BLOCK_INFORMATION*** table (highlighted above). This allows us to see which functions were called just prior to our allocation.

This information can also be obtained without !mona by using the !heap command while supplying an address within the heap chunk:

!heap –p –a 0x06209dc0

You can also retrieve this information using the ‘dt’ command and the address of the chunk’s “StartStamp”.

dt _DPH_BLOCK_INFORMATION 0x06209da0.

With that said, let’s use the ‘dds’ command to display the stack trace of the allocated chunk.

0:000> dds 0x04e32364

04e32364 abcdaaaa
04e32368 00000001
04e3236c 00000004
04e32370 00000001
04e32374 0000009c
04e32378 06101000
04e3237c 04fbeef8
04e32380 04e32384
04e32384 7c94b244 ntdll!RtlAllocateHeapSlowly+0x44
04e32388 7c919c0c ntdll!RtlAllocateHeap+0xe64
04e3238c 0609c2af GSFU!DllGetClassObject+0x29f8f
04e32390 06034941 GSFU!DllUnregisterServer+0x23661

Here we can see two GOM functions (GSFU!DLLUnregisterServer and GSFU!DLLGetClassObject) are called prior to the allocation. First, let’s take a quick glance at the function just prior to our call to ntdll!RtlAllocateHeap using IDA Pro.

RCAIntegerOverflow4_thumb.png

So as we would expect, here we can see a call to HeapAlloc. The value being provided as dwBytes would be 0x9C (our requested size).

It’s important to note here that IDA Pro, unlike WinDbg, has the ability to enumerate functions such as this. When it identifies a call to a known function, it will automatically apply comments in order to identify known variables supplied to that function. In the case of HeapAlloc (ntdll!RtlAllocateHeap), it will accept 3 arguments; dwBytes (size of the allocation), dwFlags, and hHeap (a pointer to the owning heap). More information on this function can be found at the MSDN page here.

Now in order to identify where the value of dwBytes is introduced, let’s go ahead and take a quick look at the previous function (GSFU!DllUnregisterServer+0×23661) in our stack trace.RCAIntegerOverflow5_thumb.png

Interesting. Here we can see that a call to the Calloc function is made, which in turn calls HeapAlloc. Before we continue, we need to have a short discussion about Calloc.

Calloc is a function used to allocate a contiguous block of memory when parsing an array. It accepts two arguments:

size_t num ; Number of Objects size_t size ; Size of Objects It will allocate a region of memory using a size derived by multiplying both arguments (Number of Objects * Size of Objects). Then, by calling memset it will zero initialize the array (not really important for the purpose of this article). What is important to note however, is that rather than using the CRT version of Calloc (msvcrt!calloc), an internal implementation is used. We can see this by following the call (the code is included in the GSFU module rather than making an external call to msvcrt)***. The importance of this will become clear very soon.

You can easily follow any call in IDA Pro by simply clicking on the called function. In this case, clicking on “_calloc” will bring us to our inlined function. We can determine that the function has been inlined as GSFU.ax is our only loaded module. A jump to the msvcrt!calloc function would be displayed by an “extrn”, or external, data reference (DREF).

Now, with a quick look at our two calling functions, let’s go ahead and set a one time breakpoint on the first value being supplied to Calloc so that once it is hit, another breakpoint is applied to ntdll!RtlAllocateHeap. Then, we’ll trace until ntdll!RtlAllocateHeap is hit.

Let’s go ahead and apply the following breakpoint, and then tell the process to continue running (g)

0:000> bp GSFU!DllUnregisterServer+0x23653 /1 "bp ntdll!RtlAllocateHeap; ta"
Bp expression 'GSFU!DllUnregisterServer+0x23653 /1' could not be resolved, adding deferred bp
0:000> g

eax=050d9d70 ebx=000000f8 ecx=050d9d70 edx=80000027 esi=050d9c48 edi=050d9bb8
eip=06034934 esp=0012bdb0 ebp=050d9c38 iopl=0 nv up ei ng nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200286
GSFU!DllUnregisterServer+0x23653
06034933 52 push edx ; Number of Elements
eax=050d9d70 ebx=000000f8 ecx=050d9d70 edx=80000027 esi=050d9c48 edi=050d9bb8
eip=06034934 esp=0012bdb0 ebp=050d9c38 iopl=0 nv up ei ng nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200286
GSFU!DllUnregisterServer+0x23654:
06034934 6a04 push 4 ; Size of Elements
eax=050d9d70 ebx=000000f8 ecx=050d9d70 edx=80000027 esi=050d9c48 edi=050d9bb8
eip=06034936 esp=0012bdac ebp=050d9c38 iopl=0 nv up ei ng nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200286
GSFU!DllUnregisterServer+0x23656:
06034936 83c604 add esi,4
eax=050d9d70 ebx=000000f8 ecx=050d9d70 edx=80000027 esi=050d9c4c edi=050d9bb8
eip=06034939 esp=0012bdac ebp=050d9c38 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200202
GSFU!DllUnregisterServer+0x23659:
06034939 83eb0c sub ebx,0Ch
eax=050d9d70 ebx=000000ec ecx=050d9d70 edx=80000027 esi=050d9c4c edi=050d9bb8
eip=0603493c esp=0012bdac ebp=050d9c38 iopl=0 nv up ei pl nz ac po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200212
GSFU!DllUnregisterServer+0x2365c:
0603493c e8e6780600 call GSFU!DllGetClassObject+0x29f07 (0609c227) ; Calloc

...truncated...

eax=0012bd94 ebx=000000ec ecx=050d9d70 edx=80000027 esi=00000004 edi=050d9bb8
eip=05d5c236 esp=0012bd78 ebp=0012bda4 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200206
GSFU!DllGetClassObject+0x29f16:
05d5c236 0faf750c imul esi,dword ptr [ebp+0Ch] ss:0023:0012bdb0=80000027
eax=0012bd94 ebx=000000ec ecx=050d9d70 edx=80000027 esi=0000009c edi=050d9bb8
eip=05d5c23a esp=0012bd78 ebp=0012bda4 iopl=0 ov up ei pl nz na pe cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200a07
GSFU!DllGetClassObject+0x29f1a:
05d5c23a 8975e0 mov dword ptr [ebp-20h],esi ss:0023:0012bd84=0012d690

...truncated...

eax=0012bd94 ebx=000000ec ecx=050d9d70 edx=80000027 esi=0000009c edi=00000000
eip=05d5c2a0 esp=0012bd78 ebp=0012bda4 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246
GSFU!DllGetClassObject+0x29f80:
05d5c2a0 56 push esi ; Allocation Size
eax=0012bd94 ebx=000000ec ecx=050d9d70 edx=80000027 esi=0000009c edi=00000000
eip=05d5c2a1 esp=0012bd74 ebp=0012bda4 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246
GSFU!DllGetClassObject+0x29f81:
05d5c2a1 6a08 push 8 ; Flags
eax=0012bd94 ebx=000000ec ecx=050d9d70 edx=80000027 esi=0000009c edi=00000000
eip=05d5c2a3 esp=0012bd70 ebp=0012bda4 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246
GSFU!DllGetClassObject+0x29f83:
05d5c2a3 ff35a0cada05 push dword ptr [GSFU!DllGetClassObject+0x7a780 (05dacaa0)] ds:0023:05dacaa0=05dc0000 ; HeapHandle
eax=0012bd94 ebx=000000ec ecx=050d9d70 edx=80000027 esi=0000009c edi=00000000
eip=05d5c2a9 esp=0012bd6c ebp=0012bda4 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246
GSFU!DllGetClassObject+0x29f89:
05d5c2a9 ff15ece0d605 call dword ptr [GSFU!DllGetClassObject+0x3bdcc (05d6e0ec)] ds:0023:05d6e0ec={ntdll!RtlAllocateHeap (7c9100c4)}
Breakpoint 1 hit
eax=0012bd94 ebx=000000ec ecx=050d9d70 edx=80000027 esi=0000009c edi=00000000
eip=7c9100c4 esp=0012bd68 ebp=0012bda4 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246
ntdll!RtlAllocateHeap:
7c9100c4 6804020000 push 204h

When analyzing operations like this, I typically find it best to start from the bottom up. Since we already know that our requested allocation size is 0x9C, we can begin at the point where the value 0x9C is provided as the dwBytes argument for ntdll!RtlAllocateHeap (GSFU!DllGetClassObject+0x29f80).

The next thing we need to do is look for the instruction, prior to our push instruction, that either introduces the value 0x9C to esi or modifies it. Looking back a few lines, we see this instruction:

eax=0012bd94 ebx=000000ec ecx=050d9d70 edx=80000027 esi=00000004 edi=050d9bb8
eip=05d5c236 esp=0012bd78 ebp=0012bda4 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200206
GSFU!DllGetClassObject+0x29f16:
05d5c236 0faf750c imul esi,dword ptr [ebp+0Ch] ss:0023:0012bdb0=80000027

Interesting. It appears that we’re performing signed multiplication of the value contained in esi (0×4) and our “Number of Entries” element within the “stsz” atom (as pointed to by our stack entry located at 0x0012bdb0). This makes sense since Calloc, as we had previously discussed, will perform an allocation of data with a size of (Number of Elements * Size of Elements). However, there seems to be a problem with our math. When multiplying 0×80000027 * 0×4, our result should be 0x20000009C rather than 0x0000009C. The reason for this is that we’re attempting to store a value larger than what our 32 bit register can hold. When doing so, an integer overflow occurs and our result is “wrapped,” causing only the 32 least significant bits to be stored in our register.

With this, we can control the size of our allocations by manipulating the value contained within our “Number of Entries” element. By allocating a chunk smaller than the data we intend to write, we can trigger a heap overflow.

However, the root cause of our issue is not exactly as clear as it may seem. When we looked at our function in IDA Pro earlier, we determined that rather than using the CRT version of calloc (msvcrt!calloc), GOM used a wrapped version instead. Had the actual Calloc function been used, this vulnerability would not exist. To explain this, let’s take a look at the code snippet below:

#include <stdio.h>
#include <malloc.h>

int main( void )
{

int size = 0x4; // Size of Element
int num = 0x80000027; // Number of Elements
int *buffer;
printf( "Attempting to allocate a buffer with size: 0x20000009C" );
buffer = (int *)calloc( size, num ); // Size of Element * Number of Elements
if( buffer != NULL )
printf( "Allocated buffer with size (0x%X)\n", _msize(buffer) );
else
printf( "Failed to allocate buffer.\n" );
free( buffer );
}

The example above demonstrates a valid (albeit it, not the best) use of the Calloc. Here we’re trying to allocate an array with a size of 0x200000009C (0×4 * 0×80000027). Let’s see what would happen if we were to compile and run this code:

Attempting to allocate a buffer with size: 0x4 * 0x200000009C Failed to allocate buffer.

Interesting. Calloc will fail to allocate a buffer due to checks intended in detect wrapped values. Under Windows XP SP3, this functionality can be seen in the following 2 instructions.

eax=ffffffe0 ebx=00000000 ecx=00000004 edx=00000000 esi=016ef79c edi=016ef6ee
eip=77c2c0dd esp=0022ff1c ebp=0022ff48 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
msvcrt!calloc+0x1a:
77c2c0dd f7f1 div eax,ecx
eax=3ffffff8 ebx=00000000 ecx=00000004 edx=00000000 esi=016ef79c edi=016ef6ee
eip=77c2c0df esp=0022ff1c ebp=0022ff48 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
msvcrt!calloc+0x1c:
77c2c0df 3b450c cmp eax,dword ptr [ebp+0Ch] ss:0023:0022ff54=80000027

Here we can see that the (near) maximum value for a 32 bit register (0xFFFFFFE0) is divided by our first argument supplied to Calloc. The result is then compared against the second value supplied to calloc. If the second value is larger, Calloc is able to determine that an integer overflow will occur and exit.

However, the _Calloc function found in the GSFU module, unlike msvcrt!calloc, does not contain this check. Take a look at the following example:

#include <stdio.h>
#include <malloc.h>

int _calloc( size_t num, size_t size )
{
size_t total = num * size; // Integer overflow occurs here
return (total);
}

int main( void )
{
int size = 4; // Size of Element
int num = 0x80000017; // Number of Elements
int *buffer;
int chunk_size = _calloc( size, num );
printf ("Attempting to allocate a buffer with size: 0x%X\n", chunk_size);
buffer = (int *)malloc(chunk_size);
if( buffer != NULL )
printf( "Allocated buffer with size (0x%X)\n", _msize(buffer) );
else
printf( "Failed to allocate buffer.\n" );
free( buffer );
}

Here we can see that instead of using the actual calloc function, we’re multiplying our two values (“Element Size” and “Number of Elements”) and storing the result in a variable called “chunk_size”. That value is then supplied as the size argument to malloc.

Using the values from our mutated seed, let’s take a look at our sample program’s output:

Attempting to allocate a buffer with size: 0x9C Allocated buffer with size (0x9C) As we expected, the application readily accepts our wrapped value (0x9C) and provides this as the size argument being supplied to malloc. This in turn will cause our buffer to be undersized allowing our heap overflow to occur.

ARTICOL COMPLET:

https://www.corelan.be/index.php/2013/07/02/root-cause-analysis-integer-overflows/

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.



×
×
  • Create New...