Title: Exploit Development: GroupWise Messenger Server
Original: http://metasploit.blogspot.com/2006/04/exploit-development-groupwise_14.html
Copyright (C) 2006 Metasploit.com
Exploit modules developed for the Metasploit Framework are designed to contain the smallest amount
of "boilerplate" code as possible. This allows us to extend features and APIs without having to
rewrite each and every exploit module. The exploit development process can be time consuming and
frustrating - most of the time spent on an exploit is only represented by a few lines of finished
code. In this post, I would like to walk you through the development process of a typical exploit
module.
On April 13th, 3Com's Zero Day Initiative released an advisory about a buffer overflow in the
instant messaging server for Novell GroupWise. The advisory explains that a long Accept-Language
header will overwrite the stack, leading to arbitrary code execution.
The first step is to obtain the vulnerable software and install it onto a patched virtual machine.
In this case, I chose a Windows 2000 SP4 VMWare image, made sure it was up to date, then started
to look for the software. According to Novell's advisory, the vulnerable component can be found in
the Novell GroupWise 7 Beta (and fixed in Support Pack 1, Beta 2), but it can also be downloaded
as a separate component, without having to install all of GroupWise. GroupWise Messenger requires
a NetWare tree and context, which requires eDirectory, which requires the Novell NetWare Client
software. The process for installing GroupWise Messenger, from scratch is:
1) Locate a copy of the NetWare client and install it.
2) Download the eDirectory 8.8 Evaluation from Novell and install it.
3) Download the GroupWise Messenger 2 Evaluation from Novell and install it.
Once you get past the configuration phase, the Messenger installer will ask you if you would like
to start the agents (via a checkbox on the last window), check this and finish the install.
Two windows should pop up, one of them is the Archiving Agent and the other is the Messaging
Agent. The Messaging Agent is responsible for hosting the vulnerable web service on port 8300.
Open up your browser and verify that the web server is online and ready to serve requests. If the
agent spits out an error when it starts, you probably specified an invalid redirect address during
the install process, just reinstall the Messenger software using a valid, non-loopback IP address.
If you don't see either window, open up the Services control panel item, find the Novell Messager
Messaging Agent service, and restart it.
Now that the Messaging Agent is running, we can start playing with the bug. This involves some
basic debugging skills and a couple tools. I prefer to use WinDbg from Microsoft, but many folks
like the OllyDbg interface and features better. Regardless of which one you use, start it up, and
attach to the Messenging Agent process. The process name will be listed as nmma.exe, and yes there
are two of them, but in most cases the one with the higher process ID is the correct one. If you
are using WinDbg, use F6 to open the Attach to Process dialog, find nmma.exe, and expand the
process information by clicking on the little X to view the command line. The process you want
will show nnmMessagingAgent in the command line. Complete attaching to the process, and use the go
command (in WinDbg), to get the process running again.
Now that we have a debugger attached to process, its time to reproduce the bug. We need to send a
HTTP GET request, with an Accept-Language header consisting of a string over 16 bytes in length.
We want to start off with the longest string first and keep decrementing this string with each
request until we get the crash. This ensures that the largest number of bytes under our control
will be in memory and gives us the best chance of smashing a SEH pointer in the first attempt. The
data we use as the string is a non-repeating (a lie, it does repeat, but not for quite a few
bytes) alpha-numeric text string generated by Metasploit Framework's Pex library, specifically
Pex::Text::PatternCreate(). A sample string looks like:
$ perl -I framework-2.5/lib -e 'use Pex; print Pex::Text::PatternCreate(64)'
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0A
Start off using 8192 bytes, then 4096, then 3000, then 2000. When we try 2000 bytes, the debugger
throws an exception:
(e10.314): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000000 ebx=02c9e1b0 ecx=ffffffff
edx=61614273 esi=02c9e690 edi=61614273
eip=00430a7a esp=02c9e164 ebp=02c9e170
nmma+0x30a7a:
00430a7a f2ae repne scasb es:61614273=??
The scasb instruction compares the byte specified by the address in the edi register with the byte
stored in the low 8 bits of the eax register. This opcode increments the edi by one each time it
is called. The repne prefix causes this operation to be repeated, for as many times as the ecx
register specifies, until the comparison returns true. In this case, we see that eax is 0 and ecx
is set to the largest 32-bit value (0xffffffff). This means that this instruction will start
reading at the memory address stored in edi and keep scanning until it finds a NULL byte (or 4Gb
of data has been processed, not a likely occurrence). The edi register is set to 0x61614273, which
is definitely part of the data that we control. To figure out what offset into our string is being
used here, we use the patternOffset.pl script located in the sdk directory of the Metasploit
Framework (v2.5).
$ perl sdk/patternOffset.pl 0x61614273 8192
No output. This means that before our data was used, the application modified it. We know the data
is being used to overwrite some kind of string pointer and we know that the application is trying
to figure out how long this string is by scanning it, looking for a NULL byte, and then seeing
what ecx has been decremented to, obtaining the length of the string. The function we are in must
be strlen() or an equivelent.
If we use Memory view in WinDbg and specifying esp as the address, we notice that the entire stack
is covered in our data. The k command, which displays the call stack, shows that the return
address of the current function has been smashed with the long text string. Upon closer
inspection, we can see that all uppercase characters in our string have been converted to their
lowercase equivalents.
We detach from the process and use the Services control panel to restart the service, wait for it
to initialize, and then reattach WinDbg. The Novell advisory states that any value greater than 16
bytes will trigger the overflow, so instead of using our long string value, we send only 20 bytes,
with the last 4 bytes specified as the string 0000 (0x30303030). The exception is thrown again,
this time with edi set to 0x30303d42, 3346 bytes above the address we supplied. To pass this
exception, we need to set this offset to a memory address, that when 3346 is added to it, points
to a NULL terminated string.
Finding a NULL terminated string in memory isn't difficult, but we need to ensure that the address
of the string doesn't contain any uppercase characters, NULLs, new lines, carriage returns,
commas, or semicolons. Since we are just reading memory, we can use any loaded DLL that has an
address in an acceptable range. On my system the dclient.dll module (part of eDirectory) is loaded
at base address 0x61000000 and extends to 0x6104f000. Most of the addresses between 0x61010101 and
0x6104efff should work for us. The address 0x6102010c points to "0x01 0x00", a one-byte string
that should allow us to pass the strlen() code. We then decrement this address by 3346 bytes,
giving us 0x6101f3f9.
We detach from the process, restart the service, and reattach again. This time, we are going to
send 16 bytes of padding, the 0x6101f3f9 address, packed in little-endian format
("\xf9\xf3\x01\x61") and followed by 1024 bytes of the non-repeating string pattern. A new
exception appears:
(dc4.dac): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000000 ebx=61346961 ecx=00000002
edx=6102010b esi=6102010b edi=61346961
eip=00430a92 esp=02c9e164 ebp=02c9e170
nmma+0x30a92:
00430a92 f3a4 rep movsb ds:6102010b=01 es:61346961=??
The movsb instruction takes one byte from the address specified by the esi register and writes it
to the address specified by the edi register, incrementing both esi and edi by one. The rep prefix
indicates that this will repeat for as many times as the value in the ecx register specifies. We
can guess that the value in ecx is the return value of our strlen() function, with one byte added
to it to account for the NULL trailer. The edi register points to an address that is most
definitely under our control. We know that the application will convert all uppercase characters
to their lowercase equivalents, so we can start taking a guess at what offset into our non-
repeating string is being used as the destination pointer.
$ perl sdk/patternOffset.pl 0x61346961 1024
[no output]
$ perl sdk/patternOffset.pl 0x41346941 1024
252
Great! We know that 252 bytes into our pattern, we can overwrite a character pointer that is used
as the destination address in the above memory copy routine. At this point, we can set our source
pointer to a string we control, and the destination pointer to anything we want to overwrite, and
have a field day modifying global variables, overwriting pointers, and generally having our way
with the process.
Now lets see what happens if we cause the memory copy to complete without an error. We need
another address, this time pointing to writable memory, in a known location, that isn't made up of
our restricted characters. Since we are using dclient.dll already for the source pointer, we might
as well use it for the destination pointer as well. Using the objdump command, we see that the
.data section of the dclient.dll module is at address 0x61041000. To make our memory copy safe, we
increment our source pointer by one byte, so that points to NULL byte directly, then we find a
destination pointer the .data section that also points to a single NULL byte. It just so happens
the first dozen bytes of the .data section are already NULLs, so we can use 0x61041001 as the
destination pointer and 0x6101f3fa as the source pointer. Technically, we can just use make both
the source and the destination pointers the same and have them point to the same writable address,
but you get the idea.
We bounce the service and reattach with the debugger. The next request will be 1024 bytes of non-
repeating data, with the 4 bytes at offset 16 replaced with the source address, and the 4 bytes at
offset 272 replaced with the destination address.
(db8.c58): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000000 ebx=00000002 ecx=6104100a
edx=61041001 esi=02c9e690 edi=02c9e559
eip=61386961 esp=02c9e2a4 ebp=00000000
61386961 ?? ???
We now have control of the execution path. We use patternOffset.pl to figure out what offset into
our buffer matches up with the address in EIP. After converting the downcased 0x61's to 0x41's, we
see that it is offset 264. The question is, what address do we put here? If we open up the WinDbg
Memory view again and enter esp as the address, we see the following bytes:
02c9e2a4 69 39 61 6a
Again, we guess at what the un-downcased address was, convert this to little-endian, get
0x6a413969, then call patternOffset.pl to get offset 268. If we can find a sequence of opcodes in
memory that perform a jmp esp, or a push esp; ret, we can use this to return directly back to our
buffer. The Framework includes a tool specifically for this purpose, msfpescan. Since our exploit
code is already dependent on dclient.dll, lets use msfpescan to look for a jmp esp instruction
there:
$ msfpescan -f dclient.dll -j esp
0x6103c3d3 jmp esp
0x6103c92b jmp esp
0x6103ca53 jmp esp
0x6103cbfb jmp esp
With the exception of the third address (since 53 = uppercase S), all of these addresses can be
used to jump back into our code. If we felt like being crafty, we could use the memcpy() routine
to write a jmp esp opcode into a location of our choice, and then return into it. If we replace
the 4 bytes at offset 264 with one of these addresses, the application will jump to the location
in the esp register and then return into the code we place at offset 268. Since offset 268 is 4
bytes before our destination register, we need to jump past it, and then place our shellcode into
the area immediately past the destination register. The easiest way to represent a jmp opcode in
x86 is with "\xeb\xXX", where XX is replaced by the number of bytes to jump. This method is
limited to jumping 129 bytes forward (127 + 2 bytes for the opcode itself) or 127 bytes backward.
At offset 268, we place 2 bytes, "\xeb\x06", which will jump right over the destination pointer.
We then place a single byte at offset 276, "\xcc", which represents the "int3" instruction that
will cause the application to trap the debugger.
The entire string now looks like:
my $pattern = Pex::Text::PatternCreate(1024);
substr($pattern, 16, 4, pack('V', 0x6101f3fa)); #SRC
substr($pattern, 272, 4, pack('V', 0x61041001)); # DST
substr($pattern, 264, 4, pack('V', 0x6103c3d3)); # JMP ESP
substr($pattern, 268, 2, "\xeb\x06"); # JMP +6
substr($pattern, 276, 1, "\xcc"); # TRAP
We detach from the process, restart the service, reattach, and send our new string:
(d78.33c): Break instruction exception - code 80000003 (first chance)
eax=00000000 ebx=00000002 ecx=6104100a
edx=61041001 esi=02c9e690 edi=02c9e559
eip=02c9e2ac esp=02c9e2a4 ebp=00000000
02c9e2ac cc int 3
Hurray! We now have arbitrary code execution. The final trick is replacing the 0xCC byte with real
shellcode that doesn't contain any of the restricted or uppercase characters. The current version
of the Metasploit Framework has a tough time avoiding A-Z, so only a few payloads can be
successfully encoded (such as win32_reverse_ord). The finished exploit module for version 2.5 can
be automatically installed using the msfupdate -u command.
-HD
The Novell Messenger Messaging Agent service terminated unexpectedly. It has done this 78 time(s).
The following corrective action will be taken in 0 milliseconds: No action
Metasploit Framework (http://metasploit.com/projects/Framework/)
Zero Day Initiative (http://www.zerodayinitiative.com/)
Zero Day Initiative advisory (http://www.zerodayinitiative.com/advisories/ZDI-06-008.html)
VMWare Image (http://www.vmware.com/)
Novell (http://download.novell.com/)
Novell's advisory (http://support.novell.com/cgi-bin/search/searchtid.cgi?10100861.htm)
WinDbg (http://www.microsoft.com/whdc/devtools/debugging/default.mspx)
OllyDBg (http://www.ollydbg.de/)
win32_reverse_ord (http://metasploit.com:55555/PAYLOADS?MODE=SELECT&MODULE=win32_reverse_ord)
finished exploit module (http://metasploit.com/projects/Framework/link.php?type=exploit&vers=2&name=novell_messenger_acceptlang)
# milw0rm.com [2006-04-19]