BeOCD

  •  

    April 2008
    M T W T F S S
    « Mar   May »
     123456
    78910111213
    14151617181920
    21222324252627
    282930  
  • Archives

  • Subscribe

A look at MS08-021 (CVE-2008-1083) (again…)

Posted by Bow Sineath on April 14, 2008

Those of you that are observant will notice that I removed the previous two posts regarding this vulnerability in favor of completely rewriting the analysis. The reason being that I made a few mistakes in the last one that impacted the analysis. I haven’t exactly been at full capacity for the past week or two, which is why I’m trying to exercise my brain on this a little bit :) Anyway, no more excuses! On with it…

Ok, so MS08-021 addresses a number of vulnerabilities in the GDI library. Vulnerabilities in GDI always get a good deal of attention and this one is no exception, we’re already seeing instances of it exploited in the wild. The one I chose to detail is CVE-2008-1083 which is an integer overflow when handling WMF files.

For starters, the overflow occurs in the pbmiConvertInfo function, which has numerous cross references. That said, the function everyone seems to credit this vulnerability to is CreateDIBPatternBrushPt, which calls pbmiConvertInfo. So first, a little background on pbmiConvertInfo.

The function is not exported and has a number of cross references, 9 to be exact. Since the advisory mentions CreateDIBPatternBrushPt, that is the path that I’m going to follow as well (when we get there). The function takes four arguments, best I can tell the first argument is a void pointer, the second an unsigned integer, the third a void pointer-to-pointer, and fourth an unsigned integer. I’d say roughly that the prototype is something like this

void *pbmiConvertInfo(void *, UINT, void **, UINT)

The first argument is of particular importance and plays a key role in the values we use for exploitation and in the code path this function takes. According to how I see other functions handling calls to pbmiConvertInfo, the first argument is going to be one of two types, either a BITMAPCOREHEADER or BITMAPINFOHEADER. My original belief was that it was just a BITMAPINFOHEADER, but reading the code, some portions (as you’ll see later on) made checks to see if the first DWORD of the structure was 0xC and, depending on whether or not it was, the code paths were significantly different. I then read the documentation for the WMF file format, specifically the section on DeviceIndependentBitmaps, which said if the first DWORD of the DIB object is 0xC then it is a BitmapCoreHeader object, otherwise it is a BitmapInfoHeader object. You’ll see where this makes a big difference later on. I’m going to go ahead and state that, in order for us to follow the correct code path for exploitation, the size value must be 0xC, which indicates we will be looking at a BITMAPCOREHEADER structure.

Here is the code block where the overflow actually occurs:

mov [ebp+var_10], 28h
mov eax, large fs:18h
mov edi, [ebp+arg_0]
imul edi, [ebp+var_4]
mov ecx, [ebp+var_8]
mov eax, [eax+30h]
lea ecx, [edi+ecx+28h]
push ecx
push 0
push dword ptr [eax+18h]
mov [ebp+var_14], edi
call ds:__imp__RtlAllocateHeap@12 ; RtlAllocateHeap(x,x,x)
test eax, eax
mov [ebp+var_C], eax

What we see here is that an attempt to perform a size calculation can be overflowed. The calculation itself is relatively simple:

(arg_0 * var_4) + var_8 + 0×28

What becomes important from here is what values we control and how, in addition to how we can reach that code block above. I’m going to start with detailing where those values come from (briefly), then explain how we arrive at this code block.

first, arg_0. I had a bit of trouble with this one because I didn’t see the following instructions right off:

loc_77F18EDF:
lea esi, [ebp+arg_0]
push esi
lea esi, [ebp+arg_4]
push esi
push [ebp+var_10]
mov [ebp+var_4], 4
push edx
push ecx
push eax
call _cCalculateColorTableSize@24 ; cCalculateColorTableSize(x,x,x,x,x,x)
test eax, eax
jz loc_77F273B0

What we see here is that a pointer to arg_0 is being passed into cCalculateColorTableSize. I won’t go into too much detail on cCalculateColorTableSize for the sake of brevity, but I will say that the pointer passed as its last argument (aka *arg_0) is where the result of various calculations and conditional tests is placed. That value is obviously used in the multiplication and will play a role in what value we need to trigger the overflow, however the value from cCalculateColorTableSize isn’t the one that actually causes the overflow.

Now, on to var_4. This variable can have 3 possible values: 0, 2, and 4. Depending on the value of arg_4, depends on what the value of var_4 will be. I would write this out in C but blogger screws up the formatting, so I’ll just explain it. If arg_4 is equal to 1, then var_4 will be 2; if arg_4 is equal to 2, then var_4 is set to 0; if none of those two conditions are met, then it is left at 4.

Now we get to var_8, this one is going to be important, so I’m gonna spend a little more time on it. I completely missed where the value of var_8 came from because, for whatever reason, IDA didn’t properly disassembly 4 or 5 lines of pbmiConvertInfo and I didn’t see these instructions right off. Anyway, there is a conditional jump that takes place after var_4 is given its value, it jumps (note: does not call) to this code block:

push ebx
call _cjBitmapBitsSize@4 ; cjBitmapBitsSize(x)
mov [ebp-8], eax
jmp loc_77F18F1F

So the return value of cjBitmapBitSize is put into var_8, so now we need to understand what that function is doing. It takes one argument, which is a pointer to the same structure that pbmiConvertInfo received as a first argument (so a pointer to our BITMAPCOREHEADER/BITMAPINFOHEADER structures).

The first thing cjBitmapBitSize checks for is a size value of 0xC, which for our purposes is going to evaluate to true. Then it jumps directly into a block of code that performs a series of calculations, which looks like this:

movzx edx, word ptr [ecx+8]
movzx eax, word ptr [ecx+0Ah]
imul eax, edx
movzx edx, word ptr [ecx+4]
imul eax, edx
add eax, 1Fh
and eax, 0FFFFFFE0h
cdq
push 8
pop esi
idiv esi
movzx ecx, word ptr [ecx+6]
imul eax, ecx

I guess I should go ahead and give the structure definition for BITMAPCOREHEADER as well:

typedef struct tagBITMAPCOREHEADER {
DWORD bcSize;
WORD bcWidth;
WORD bcHeight;
WORD bcPlanes;
WORD bcBitCount;
} BITMAPCOREHEADER, *PBITMAPCOREHEADER;

So it starts off by multiplying the value of bcPlanes and bcBitCount, then multiplies the result by bcWidth, then adds 0×1F, and clears the lower 5 bits. It then divides all of that by 8 and multiplies it by bcHeight. So it would look something like this:

(((((bcPlanes * bcBitCount) * bcWidth) + 0×1F) & 0xFFFFFFE0) / 8) * bcHeight)

That value is then returned and put into pbmiConvertInfo’s var_8.

So at this point we know where the overflow is, where the values come from, and what structure we must use during exploitation (sortof). Now I’ll explain how we get to our vulnerable code path and how I know we are using a BITMAPCOREHEADER structure instead of BITMAPINFOHEADER. So when we go back to pbmiConvertInfo, we see that one of the first things it does is check that first value for 0xC:

mov eax, [ebx]
cmp eax, 0Ch
mov [ebp+var_10], eax
jz loc_77F1FAA8

Where ebx is the pointer passed in through arg_0. I thought when I first looked over the code that the constant checks for 0xC were kindof arbitrary and odd, it makes sense now though :) Like I said earlier, the documentation states that if the first DWORD of the DIB object is 0xC then we are dealing with a BITMAPCOREHEADER. If that check is false then a series of other size checks are performed, which aren’t really relevant to us at the moment. So after that last block, assuming that the conditional for 0xC evaluates to true, we get to this series of instructions:

movzx eax, word ptr [ebx+0Ah]
xor ecx, ecx
inc edi
jmp loc_77F18EDF

One thing to notice here is that the other code path does NOT set edi to one and its value of 0 remains, which means later on we’ll skip past the RtlAllocateHeap call, which is not what we want (obviously). So it dereferences the value of bcBitCount, sets ecx to 0, sets edi to one and then jumps back to setting up for the call to cCalculateColorTableSize. Again, I’m not going to go in too much depth on that, but just know that the value of arg_0 is changed in that function to a variety of different values which will become important when we start talking about exploitation.

Now we get to the last code block I’m going to discuss in this post and that is this:

test edi, edi
jnz loc_77F1FB29

These two instructions occur before setting up the call to RtlAllocateHeap. If edi evaluates to a non-zero value then the RtlAllocateHeap call occurs, if it evaluates to 0 then pbmiConvertInfo follows a completely different path (and actually returns the pointer in arg_0 to the caller). Remember how I said it was important that edi was incremented earlier? Well this is why.

What we see later is that there are two inlined memory copy operations that take place depending on the value of arg_4. I will discuss those in another post.

I hope this one makes more sense, as it is correct. In my previous post I made a glaring mistake :)

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>