Developing Exploit Modules

Using the Metasploit Framework for exploit development has many advantages. Utilizing all of the protocols, mixins, and other utility code can vastly improve time-to-shell. In addition, once an exploit has been implemented as a Metasploit module, you get all of the payloads, encoders, evasion options, and even post-exploitation tasks for free. Due to the abstraction design of the framework, future implementation changes can have a powerful impact on existing exploits. When you add all of this up, it's literally a no-lose situation.

NOTE: For a deep dive into the technical inner workings of the Metasploit Framework, I recommend reading through the Developer's Guide. If nothing else, at least read through Section 6.3.

We also highly recommend reading the source:HACKING file to get a feel for our coding style. We aim to keep the code as consistent as possible throughout the code base.

Most Metasploit modules exploit memory corruption vulnerabilities. As such, you might notice a memory-corruption focus to the following text.

Use the following table of contents to jump to specific sections as desired.

What You Will Need

An Editor

I highly recommend that you use one that understands Ruby :) Syntax highlighting and auto-indentation is a huge win, almost a must.
Anyway, you likely already have an editor you are in love with, so I won't waste time here :)

A Target Vulnerability

You can't have an exploit without a vulnerability. You likely have one in mind already, or else you probably wouldn't be reading this.

Depending on the type of vulnerability you're targeting, you may need additional tools. Memory corruption vulnerabilities will certainly require the use of a debugger. Your favorite debugger will inevitably be what you decide to use :)

Setting Up for Development

We recommend using a fully updated framework instance straight from our subversion repository. By doing so, you will ensure minimal difficulties when merging your exploit, should you choose to contribute it to the community. This is especially true when you have modified other framework functionality to suit the needs of your exploit. To make your task easier, I have some recommendations.

First, if you're developing a browser exploit I recommend you set the URIPATH variable to something static to facilitate faster development time. Using a value like "/test" or "/exploit" will be fine.

Second, you may want to set the LogLevel and begin monitoring the ~/.msf3/logs/framework.log file in case you trigger an exceptions. The framework will often show you some descriptive text about the exception. If you set LogLevel higher, it will usually also log a stack trace.

Third, depending on the underlying protocols, mixins, etc there may be additional debugging settings to set. Good examples are the "DEBUG" and "VERBOSE" Boolean variables. These will typically show additional output that may help you in figuring out where things got hung up. When setting the "DEBUG" variable, certain underlying functionality may change. For example, the default x86 NOP generator will start generating only 0x90 bytes instead of fully randomized super NOP sleds. This is useful for debugging, but you won't want to use it when doing reliability testing.

Choosing a Starting Point

Developing an exploit module typically starts with copying the existing module that is most recent and similar to the application/vector that you are targeting. For example, if you are writing an IE, exploit copying the latest exploit from modules/exploits/windows/browser that targets IE would be a huge win for development time. At this point in the game, the framework has on of almost every type of exploit that you can think of. If you can't decide which to copy, you could ask the community and someone will likely point you in the right direction.

Sometimes, you choose not to copy an existing module, or you can't find one that's close to what you're trying. If that's the case, then you more than likely want to pick the highest level base that you can. For example, exploiting a bug in an HTTP server, you probably want to include the HttpClient mixin into your module and use the methods defined within (vs implementing HTTP yourself). Of course, in some cases the device or software you're targeting isn't well behaved :-( In that situation, you'll have to drop down one level. In our example, that would mean moving down to a Tcp exploit instead.

Once you copy your module, you will need to take action before the framework will see the new module. Personally, I generally start a fresh framework instance after the new module is in place. Alternatively, you could use the loadpath command to scan an directory containing your new module. This may be ideal for those that keep their modules in custom locations on disk.

Development Process Overview

The general development process cycles between editing your module and executing it.

When exploiting memory corruption vulnerabilities, or other types of issues that may leave things in an inconsistent state, you will need to reset the state of your target in-between editing and executing. If you're targeting a buffer overflow in a service that automatically restarts, you'll likely have nothing to do here. Otherwise, you'll probably at least have to restart the target process or service. In some cases you may even need to revert a VM to a snapshot, or even (GASP) reboot. When you simply have to re-execute a process, such as when exploiting a browser or file-format handling program, you can use Shift+F5 for WinDbg, or Ctrl+F2 for Olly/Immunity Debugger.

For targets where a service doesn't restart, you may want to temporarily set it to automatically restart. Remember not to waste time trying to make a brute-force exploit tho! You can easily do that in windows with:

C:\> sc <servicename> failure reset= 0 actions= restart/0

For some services you may want to poll the system process list to see when the process exists and attach. In some cases, you may even have to edit the target application (IIS CGIs) to insert an endless loop and then restore the original bytes once attached.

Once your target is back up and running, you will likely get a great deal of use out of the "rexploit" and/or "rcheck" commands. These commands will reload your module from disk and execute the exploit or check methods respectively. If your exploit is Passive, its job and associated resources will be cleaned up prior to reloading.

Triggering the Vulnerability

Exploit development with Metasploit is no different than any other exploit development process at this level. Until you can trigger the vulnerability, you should keep things simple. This means not using randomization functions and/or evasion options. If you do, you'll just make your job much harder and confuse your fragile human mind. In order to get out of this step, it's generally required to be able to successfully control the target software. For memory corruption vulnerabilities, this means being able to control the flow of execution in an advantageous way. For other types, you may already be finished =)

Determining the Variables

Exploit reliability is often directly proportional to the number of unknown inputs that are influencing the state of a program. By efficiently determining and accurately recording the various relevant variables and their relationship to one another, you can achieve extremely reliable exploits (in many cases near 100%). Also, attention to detail will allow your exploit to take advantage of underlying improvements to payloads and so on.

Buffer Size and Offset

One of the first things that needs to be determined for reliable exploitation is what is commonly referred to as the "offset". This usually means the byte distance from the beginning of a buffer to a potentially important data item such as a return address or Structured Exception Handler. In some cases, there may be static or dynamic text preceding the data being copied to an output buffer. In those cases, it often provides more clarity to also record the exact size (or what determines the size) of the buffer.

In an attempt to facilitate gathering this information without reverse engineering, Metasploit provides a pattern generation tool. This tool is implemented as two methods in the Rex::Text class. The methods are pattern_create and pattern_offset. In addition, this functionality is available in the form of two scripts in the source:tools directory and implemented as part of the Byakugan WinDbg plugin.

To get the most out of this tool, use the "pattern_create" method in your exploit. It takes one argument, which specifies how many bytes to generate. Once a crash occurs (hopefully due to corrupted program counter), you can use the resulting register value in conjunction with the "pattern_offset" tool to find out what offset in the buffer that value comes from. Byakugan provides "!pattern_offset" to get this information directly from within WinDbg.

Also note that although command injection style exploits will not typically have an offset value, they may indeed have a buffer size after which the command is either not executed, or is truncated.

Illegal Characters

As an exploit developer, you have surely ran into numerous situations where the vulnerability no longer triggers, or the payload crashes before completion. These are both symptoms of using illegal characters. By following a simple process, you can easily determine the bad characters. It is important to specify the bad characters instead of just forcing the use of a specific encoder. By specifying the bad characters you ensure that the best encoder for the job will be selected, even if it is one that hasn't been developed yet.

With that said, you may encounter situations where all encoders fail. Faced with this problem, it may be necessary to develop a new or custom encoder to handle this specific situation. You can force an encoder with the EncoderType hash entry in the Payload information block. More information on developing encoders can be found in the DeveloperGuide.

Attention! In many cases it is possible to use an alternative encapsulation to avoid bad characters. For example, an HTTP exploit may be able to utilize URI-style percent-encoding. Also, browser exploits can often use Rex::Text.to_unescape to bypass restrictions.

While developing various exploits I have found two specific methods which work well. The first consists of using an ASCII sled. It is best used when you (still) get a crash, whether in the payload or otherwise. The second, range-based method works best when the characters you're sending cause the issue to no longer trigger.

The characters (or character sets) that you determine to be bad should be set, as a string, to the BadChars hash entry of the payload info hash. This is currently the only location for bad characters to be set at a module information level, but exploits may have additional sets of bad characters for different target program inputs. As such, many functions (including the Rex::Text::rand_* family of functions) take a "badchars" parameter. In most, if not all, cases it defaults to payload_badchars.

NOTE: Rex defines the following character sets within Rex::Text that can make your job easier here: UpperAlpha, LowerAlpha, Numerals, Alpha, AlphaNumeric, HighAscii, AllChars. For example, Rex::Text::AlphaNumeric. Of course, while these are a good starting point, we'll probably need to get more specific.

The ASCII sled Method

Using the sled method begins by sending all assumed-good characters. You generally want to start with your BadChars set to "", but if you already know certain characters are bad (such as "\0" or "\n"), start with those. In order to do that, you can use the following code:

1sled = Rex::Text.charset_exclude(payload_badchars)

Now, run the exploit to trigger the crash. Once your debugger presents you with the crash, inspect the memory where the ASCII sled is located. Copy the output to the clipboard. The format that it ends up in will depend on which debugger you're using.

Now, run the source:tools/find_badchars.rb script specifying your current set of bad characters with -b and your debugger type with -t. It will generate the same sled that you sent and wait for you to paste the output from the debugger. You should expect one of two things to happen. On one hand, it may tell you exactly one character that is bad. This generally happens when characters get modified, like when the program uses tolower(). On the other hand, you could get a long list (all the way to the end) of characters that are bad. This generally happens when a character gets removed by the target software. In the simple corruption case, simply add the bad character to the set. For deletions, add the first deleted character to the set. In either case, you'll want to go back and repeat the process until you no longer get any bad characters reported.

If you suspect specific bytes are being corrupted, but they are not related to the characters in use, you can use a buffer as shown below to see what's happening.

1  #sploit << payload.encoded
2  sploit << "A" * payload.encoded.length

This type of corruption usually occurs due to data items being used between when you're payload is copied into memory and when it is actually executed. Use of local stack variables are particularly notorious for causing these types of issues. In most cases they can be easily worked around by using a simple payload-fixer stub or just adding some NOP bytes or a jump over the bad stuff.

The Range Method

When using this method, you start by sending a minimal set of characters that you know are good. To do this, I usually manually build my own character set and pass it to charset_exclude instead of using payload_badchars. You might want to start with something like:

 1badchars =
 2   #(0x00..0x0f).to_a +
 3   #(0x10..0x1f).to_a +
 4   #(0x20..0x2f).to_a +
 5   (0x30..0x39).to_a +
 6   #(0x3a..0x3f).to_a +
 7   (0x41..0x5a).to_a +
 8   #(0x5b..0x60).to_a +
 9   (0x61..0x7a).to_a +
10   #(0x7b..0x7f).to_a +
11   #(0x80..0x8f).to_a +
12   #(0x90..0x9f).to_a +
13   #(0xa0..0xaf).to_a +
14   #(0xb0..0xbf).to_a +
15   #(0xc0..0xcf).to_a +
16   #(0xd0..0xdf).to_a +
17   #(0xe0..0xef).to_a +
18   #(0xf0..0xff).to_a +
19   []
20badchars = badchars.pack('C*')

I realize this is long and seems silly, but this will allow you to uncomment one line at a time to eliminate small blocks of the character set. The basic premise here is to divide and conquer. After uncommenting a line, try to run and see if you get your desired result. If not, comment it back and try a different one. After a bit you messing around, you will be able to easily figure out which nasty character is throwing a wrench in your fun.

Sending the Payload

Once you have collected all of the parameters, it's time to put the payload somewhere. The payload delivery method is dependent on many factors, including; available space, application specific constraints, available encoding methods, and character set restrictions. The payload is automatically made available to you via the payload.encoded method. In HttpServer-style browser exploits, payloads are typically regenerated for each client using the regenerate_payload method. When using regenerate_payload a new payload instance is returned and it's encoded method should be used instead.

If you're having trouble locating your payload in memory, try using a unique pattern and searching for it. For example, prefixing payload with 0xdabadb0b and then search using WinDbg with "s -b 0 L?-1 0b db ba da".

Randomizing Everything

Once you have your exploit working well. The last step is to randomize as much of the exploit as possible. Any byte that doesn't have any restrictions should be randomly generated. Any byte with restrictions should be randomly generated within the restrictions. If something doesn't have to be of a specific length, make sure it gets generated using a random length. The more entropy the better. This does two things. First, it decreases anti-virus detection, which will hamper your penetration test. Second, it forces IDS/IPS/etc vendors to signature the exact conditions of the vulnerability instead of just your exploit.

To randomize things you can use the Rex::Text.rand_* family of functions. The Msf::Exploit base class contains wrappers for these functions so that you do not need to include the Rex::Text portion of the method name. The main function you'll want to use is rand_text, but there are several others; rand_text_alphanumeric, rand_text_alpha, rand_text_alpha_lower, rand_text_alpha_upper, rand_text_english, rand_text_numeric, and rand_text_highascii. If you have specific character set requirements, use the one that best suits the situation. An example would be using rand_text_alpha for a name field rather than rand_text. Using too much entropy might raise suspicion.

Debugging

When developing, things inevitably go wrong. Although a vast majority of issues were due to coding mistakes, I have seen a few cases where the underlying framework is at fault. In either case, it's important to have a course of action to figure out what is going on.

Irb - Interactive Ruby

Unfortunately, the Ruby language doesn't currently have a wonderful interactive debugger like many other languages. There is reportedly some work in progress in that area, but AFAIK nothing usable yet. As such, the closest thing currently is Irb (Interactive Ruby). Metasploit supports invoking an Irb shell at any time using the "irb" console command, or programmatically as follows:

1   Rex::Ui::Text::IrbShell.new(binding).run

Once inside Irb, you can access the active module using the active_module method. The framework object may also be of particular instance as it contains everything :)

Debug Prints

Whether you are in Irb or you are just adding some debug-prints to figure out what's going on, there are few tricks to looking around. One nice one is to use the inspect or to_s methods on various objects to get their pretty-printed output. This can be extremely helpful for objects, hashes, and arrays. inspect is particularly useful since it shows you structure that simple print may hide (ie, "puts 1"). Although we don't permit the use of $stdin/$stderr in committed modules, don't be afraid to use them in debugging.

In addition to the Ruby provided printing functions, Metasploit provides some of it's own implementations. This includes custom implementations of some class's to_s methods, as well as some special printing functions. One extremely useful method is the Rex::Text.to_hex_dump method. I cannot count how many times I spotted some stupid error (like double assigning instead of appending a string) by using this method.

Logging

You can also use the logging functions in lieu of debug prints. These can be especially useful if it's something you want to keep in after committing. For example, a case where you want to eventually be notified something happened but it isn't important enough to raise an exception.

Common Pitfalls

One common confusing situation when exploiting memory corruption vulnerabilities is the stack pointer being too close to the program counter. When this happens, self modifying code, like some of our encoders will corrupt themselves and crash horribly confusing you to no end. To avoid this, try to ensure the stack pointer is at least 2k or so above (think stack of plates, lower address on x86).

Additional References