KL0209LIT_fffap.txt 02.11.2009 Krakow Labs Literature [www.krakowlabs.com] Fuzzing for Fun and Profit http://www.krakowlabs.com/res/lit/KL0209LIT_fffap.txt rush@KL (Jeremy Brown) [rush[at]krakowlabs[dot]com] KL0209LIT_fffap.txt Krakow Labs Literature "Fuzzing for Fun and Profit" rush@KL (Jeremy Brown) [rush[at]krakowlabs[dot]com] ============================================================================================================================ 1) Introduction 1.1) Fuzzer Specifications 2) Local Fuzzing 2.1) Information Gathering 2.2) Writing the Fuzzer (mbsefuzz.c) 2.3) Fuzzing the Target 3) Remote Fuzzing 3.1) Information Gathering 3.2) Writing the Fuzzer (sftpfuzz.pl) 3.3) Fuzzing the Target 4) Conclusion 4.1) Disclaimer ============================================================================================================================ 1) Introduction Many different resources define fuzzing many different ways. I believe this definition is more suiting than most: "Fuzzing is targeting input and delivering data that is handled by a target with the intent of identifying bugs." Fuzzing can occur theoretically where ever input is possible. There are two kinds of fuzzing: "dumb" and "smart". Dumb fuzzing is fuzzing without regard for any guidelines that may be required for input. Smart fuzzing is just the opposite. While dumb fuzzers are easy to write and easy to use, smart fuzzers are almost always preferred. Smart fuzzers actually know what how the handle the target's specifications for data. what input it can fuzz, and how to fuzz it. When we refer to fuzzers in this literature, the reader should assume we are elaborating on smart fuzzers. Fuzzing can be done locally or remote. Some examples of local fuzzing is through command line, manipulating file formats, user interface input, and more. Remote fuzzing is usually fuzzing protocols, servers, etc. The data you use to fuzz with, called the fuzzing oracle, is essential to being successful at fuzzing. The fuzzing oracle can be random data, or data that is not random at all but still provides reliable angles to fuzz which have proved successful in triggering bugs in the past. This article focuses on the latter technique. Linux is also the host operating system that our fuzzing examples will work on best and/or without modification. The following table is information on what a fuzzing oracle should at least have to possibly trigger vulnerabilities. ------------------------------------------------------------------------------------------------------------------------------- Buffer Overflows The ability to overflow buffers in the stack or heap; often exploitable to execute code unless data is uncontrollably corrupted. EXAMPLE: sprintf(buf, "%s", input); [we control 'input'] ------------------------------------------------------------------------------------------------------------------------------- Format Strings The ability to control a function's format string; often exploitable to execute code unless writing data isn't possible, then information disclosure may be achieved. EXAMPLE: syslog(priority, input); [we control 'input'] ------------------------------------------------------------------------------------------------------------------------------- Integer Overflows The ability to overflow an integer; often non-exploitable unless integer can be overflowed to affect size calculation of a buffer where we control input. EXAMPLE: if(len > 512) { return -1; } memcpy(buf, input, len); [we control 'input' and 'len'] ------------------------------------------------------------------------------------------------------------------------------- Out-of-Bounds Breakage The ability to exploit unsafe functions; often exploitable to read, write, or execute files unless integrity checks are placed in passing functions. EXAMPLE: system(input); [we control 'input' and it is not sanitized] ------------------------------------------------------------------------------------------------------------------------------- Too much data, your fuzzer shows characteristics of a stress test. Not enough data and you may miss something. Finding that balance shouldn't be a main goal, nor should it be completely defined: there is no line to cross in fuzzing. You either fuzz, or you don't, and there is no standard, just structure; find your niche and develop beauty in code and command line. Fuzzing includes a lot of testing. You could spend hours and hours modifying and compiling and running the same but slightly different code over and over just to get the better results. Planning, preparation, and testing are a part of fuzzing, and laboring hours on end for the humble task of perfection, stability, and reproduceability can thankfully be very rewarding. Fuzzing is useful because... 1) Fuzzing can find bugs in firmware/software that aren't open source, therefore restricting classical auditing by the public. 2) Fuzzing can be a fast and reliable bug finding solution, making source code auditing look so hard and fuzzing seem easy. 3) Fuzzing can also be used as a stress tester and memory management problem detector. It is, as concepted, a bug finding art. ============================================================================================================================ 1.1 Fuzzer Specifications Fuzzing is not usually done by hand, so people write fuzzers. There are three key elements each fuzzer should include: 1) A robust fuzzing oracle 2) A specific data format to prepare for fuzzing 3) A way of communicating with the target Fuzzers should be semi-automatic or automatic as they fuzz and may provide features to create a quality atmosphere when fuzzing. Some fuzzer include debuggers, event notification such as alerts and logging, and sometimes even automatic exploit generation. Fuzzing features, in most situations, are only limited to the imagination. A typical fuzzer could be outlined something like this (this example is in no particular programming or scripting language): (BEGIN FUZZER) FUZZORACLE = "A" x 550, "A" x 1100, "A" x 2100, "%n%n%n%n%n", "-1", "32767", "test|id > /tmp/fuzzed|test"; ..... OPTIONS = "FILE", "DIRECTORY", "SEND", "STORE", "RENAME"; ..... loop(run-through-fuzz-data) { send(option[count], fuzzoracle[count], target); } (END FUZZER) ============================================================================================================================ 2. Local Fuzzing Local fuzzing deals with fuzzing applications locally or hosted on the target system. This can include, but isn't limited to: Command Line Fuzzing - Fuzzing applications via the command line and/or environmental variables File Format Fuzzing - Fuzzing applications that read files in a specific format or format(s) Kernel Fuzzing - Fuzzing core kernel features, kernel modules, and system calls As said previously, if a target takes input, it can probably be fuzzed. ============================================================================================================================ 2.1 Information Gathering As the first step in many technical projects, information gathering is vital. Knowing exactly what input your fuzzing and how your target works is very important when writing a fuzzer. Information sources include RFCs, API specifications, other technical documentation, sniffing, and reverse engineering. For this example of local fuzzing, we will be exploring MBSE BBS (http://www.mbse.eu/mbse/mbsebbs/index.html) which had a local buffer overflow in its suid "mbuseradd" program. Writing a fuzzer shouldn't be very hard for this application. linux:/home/fuzz/mbsebbs-0.70.0/unix# make install install -c -s -o root -g root -m 6711 mbuseradd /opt/mbse/bin install -c -s -o root -g root -m 6711 mbpasswd /opt/mbse/bin install -c -s -o root -g root -m 0755 mblogin /opt/mbse/bin linux:/home/fuzz/mbsebbs-0.70.0/unix# exit exit fuzz@linux:~$ First, lets see exactly what we can fuzz. fuzz@linux:~$ /opt/mbse/bin/mbuseradd mbuseradd commandline: mbuseradd [gid] [name] [comment] [usersdir] fuzz@linux:~$ Seems we have 4 different arguments we can fuzz. Now lets check out the source and look for any environmental variables that it might take as input. fuzz@linux:~$ grep getenv audit/mbse*/*/mbuseradd.c sprintf(shell, "%s/bin/mbsebbs", getenv("MBSE_ROOT")); fuzz@linux:~$ Alright, we can fuzz MBSE_ROOT too. ============================================================================================================================ 2.2 Writing the Fuzzer Fuzzers can be written in probably any programming or scripting language but this example will be written in C. When writing a fuzzer, keep in mind the principles we discussed earlier in section 1.1. [mbsefuzz.c] #include #include #include #define MBUSERADD "/opt/mbse/bin/mbuseradd" #define LOGFILE "mbsefuzz.log" #define FZORCTOTAL 20-1 #define ENVTOTAL 1-1 #define GID "1" #define NAME "mbsefuzz" #define COMMENT "fuzzing" #define USERSDIR "/tmp" void fuzz(char *bin, char *desc, char *src, char *a, char *b, char *c, char *d); struct { char *data; char *desc; } fzorc[] = // fuzzing oracle { {"", "A x 550"}, {"", "A x 1100"}, {"", "A x 2100"}, {"", "A x 4200"}, {"", "A x 8400"}, {"%n%n%n%n%n", "%n x 5"}, {"%%20n", "%%20n"}, {"%n%p%s%d%x", "%n%p%s%d%x"}, {"%.1024d", "%.1024d"}, {"%.2049d", "%.2049d"}, {"-1", "-1"}, {"32767", "32767"}, {"65535", "65535"}, {"-2147483647", "-2147483647"}, {"0xffffffff", "0xffffffff"}, {"a|id > /tmp/FZ|b", "a|id > /tmp/FZ|b"}, {"a`id > /tmp/FZ`b", "a`id > /tmp/FZ`b"}, {"a'id > /tmp/FZ'b", "a'id > /tmp/FZ'b"}, {"a;id > /tmp/FZ;b", "a;id > /tmp/FZ;b"}, {"a&&id > /tmp/FZ&&b", "a&&id > /tmp/FZ&&b"}, }; struct { char *data; } envvar[] = // options example, usually more than one in there { {"MBSE_ROOT"}, }; void fuzz(char *bin, char *desc, char *src, char *a, char *b, char *c, char *d) { FILE *fd; if(fork() == 0) { execl(bin, bin, a, b, c, d, 0); } else { int pid, signal, status; pid = wait(&status); if(WIFSIGNALED(status)) { signal = WTERMSIG(status); printf("***** SIG%d CAUGHT [%s + %s] *****\n", signal, src, desc); fd = fopen(LOGFILE, "a+"); fprintf(fd, "[%s]->SIG%d [%s + %s]\n", bin, signal, src, desc); fclose(fd); } } } int main() { char of1[550], of2[1100], of3[2100], of4[4200], of5[8400], *src, source[32]; int i; memset(of5, 'A', sizeof(of5)); of5[8400] = 0; fzorc[4].data = of5; memset(of4, 'A', sizeof(of4)); of4[4200] = 0; fzorc[3].data = of4; memset(of3, 'A', sizeof(of3)); of3[2100] = 0; fzorc[2].data = of3; memset(of2, 'A', sizeof(of2)); of2[1100] = 0; fzorc[1].data = of2; memset(of1, 'A', sizeof(of1)); of1[550] = 0; fzorc[0].data = of1; src = "CL: GID"; for(i = 0; i <= FZORCTOTAL; i++) // loops { fuzz(MBUSERADD, fzorc[i].desc, src, fzorc[i].data, NAME, COMMENT, USERSDIR); } src = "CL: NAME"; for(i = 0; i <= FZORCTOTAL; i++) { fuzz(MBUSERADD, fzorc[i].desc, src, GID, fzorc[i].data, COMMENT, USERSDIR); } src = "CL: COMMENT"; for(i = 0; i <= FZORCTOTAL; i++) { fuzz(MBUSERADD, fzorc[i].desc, src, GID, NAME, fzorc[i].data, USERSDIR); } src = "CL: USERSDIR"; for(i = 0; i <= FZORCTOTAL; i++) { fuzz(MBUSERADD, fzorc[i].desc, src, GID, NAME, COMMENT, fzorc[i].data); } src = "ENV: "; for(i = 0; i <= ENVTOTAL; i++) { char *env = envvar[i].data; snprintf(source, sizeof(source), "%s%s", src, env); for(i = 0; i <= FZORCTOTAL; i++) { setenv(env, fzorc[i].data, 1); fuzz(MBUSERADD, fzorc[i].desc, source, GID, NAME, COMMENT, USERSDIR); } } return 0; } [mbsefuzz.c] We now have a simple, local fuzzer with command line and environmental fuzzing capabilities, as well as fault detection. ============================================================================================================================ 2.3 Fuzzing the Target Since we have written the fuzzer, we can compile and run it against our target. Note: Some of the fuzz data is valid for mbse and may add some accounts to your system, clean out /etc/passwd after use. fuzz@linux:~$ gcc -o mbsefuzz mbsefuzz.c fuzz@linux:~$ ./mbsefuzz mbuseradd: Argument 1 is too long mbuseradd: Argument 1 is too long mbuseradd: Argument 1 is too long mbuseradd: Argument 1 is too long mbuseradd: Argument 1 is too long useradd: unknown group %n%n%n%n%n useradd: unknown group %%20n useradd: unknown group %n%p%s%d%x useradd: unknown group %.1024d useradd: unknown group %.2049d useradd: unknown group -1 ..... useradd: invalid shell `AAAAA...../bin/mbsebbs' useradd: invalid shell `AAAAA...../bin/mbsebbs' useradd: invalid shell `AAAAA...../bin/mbsebbs' ***** SIG11 CAUGHT [ENV: MBSE_ROOT + A x 4200] ***** ***** SIG11 CAUGHT [ENV: MBSE_ROOT + A x 8400] ***** useradd: invalid shell `%n%n%n%n%n/bin/mbsebbs' useradd: invalid shell `%%20n/bin/mbsebbs' useradd: invalid shell `%n%p%s%d%x/bin/mbsebbs' useradd: invalid shell `%.1024d/bin/mbsebbs' useradd: invalid shell `%.2049d/bin/mbsebbs' ..... useradd: invalid shell `a'id > /tmp/FZ'b/bin/mbsebbs' useradd: invalid shell `a;id > /tmp/FZ;b/bin/mbsebbs' useradd: invalid shell `a&&id > /tmp/FZ&&b/bin/mbsebbs' fuzz@linux:~$ fuzz@linux:~$ cat mbsefuzz.log [/opt/mbse/bin/mbuseradd]->SIG11 [ENV: MBSE_ROOT + A x 4200] [/opt/mbse/bin/mbuseradd]->SIG11 [ENV: MBSE_ROOT + A x 8400] fuzz@linux:~$ Looks like we did catch a bug or two. Lets also quickly check /tmp for OBB. fuzz@linux:~$ ls /tmp/FZ* ls: /tmp/FZ*: No such file or directory fuzz@linux:~$ Nope, no out-of-bounds breakage here. Let us now further explore what we did find. fuzz@linux:~$ cat mbsefuzz.log [/opt/mbse/bin/mbuseradd]->SIG11 [ENV: MBSE_ROOT + A x 4200] [/opt/mbse/bin/mbuseradd]->SIG11 [ENV: MBSE_ROOT + A x 8400] (same bug as previous) fuzz@linux:~$ fuzz@linux:~$ export MBSE_ROOT=`perl -e 'print "A" x 4200'` fuzz@linux:~$ /opt/mbse/bin/mbuseradd mbuseradd commandline: mbuseradd [gid] [name] [comment] [usersdir] fuzz@linux:~$ /opt/mbse/bin/mbuseradd a b c d Segmentation fault fuzz@linux:~$ fuzz@linux:~$ su Password: linux:/home/fuzz# gdb /opt/mbse/bin/mbuseradd GNU gdb 6.3 Copyright 2004 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i386-linux"...(no debugging symbols found) Using host libthread_db library "/lib/libthread_db.so.1". (gdb) r a b c d Starting program: /opt/mbse/bin/mbuseradd a b c d (no debugging symbols found) (no debugging symbols found) (no debugging symbols found) (no debugging symbols found) [Thread debugging using libthread_db enabled] [New Thread 16384 (LWP 11571)] (no debugging symbols found) (no debugging symbols found) Program received signal SIGSEGV, Segmentation fault. [Switching to Thread 16384 (LWP 11571)] 0x41414141 in ?? () (gdb) i r eax 0x0 0 ecx 0xbfffd994 -1073751660 edx 0x0 0 ebx 0x41414141 1094795585 esp 0xbfffe9d0 0xbfffe9d0 ebp 0x41414141 0x41414141 esi 0x41414141 1094795585 edi 0x41414141 1094795585 eip 0x41414141 0x41414141 eflags 0x10246 66118 cs 0x23 35 ss 0x2b 43 ds 0x2b 43 es 0x2b 43 fs 0x0 0 gs 0x0 0 (gdb) bt #0 0x41414141 in ?? () #1 0x41414141 in ?? () #2 0x41414141 in ?? () #3 0x41414141 in ?? () #4 0x64414141 in ?? () #5 0x6220622f in ?? () #6 0xbfffeb00 in ?? () #7 0x40016ed8 in _r_debug () #8 0xbfffeac4 in ?? () #9 0xbfffea24 in ?? () #10 0x400c630c in ?? () from /lib/libc.so.6 #11 0x40069270 in ?? () #12 0x40073f7e in __pthread_alt_unlock () from /lib/libpthread.so.0 Previous frame inner to this frame (corrupt stack?) (gdb) q The program is running. Exit anyway? (y or n) y linux:/home/fuzz# exit exit fuzz@linux:~$ The instruction pointer (EIP) is overwritten along with many other registers with our fuzzing data. VULNERABLE CODE: [mbsebbs-0.70.0/unix/mbuseradd.c] shell = calloc(PATH_MAX, sizeof(char)); ..... sprintf(shell, "%s/bin/mbsebbs", getenv("MBSE_ROOT")); [mbsebbs-0.70.0/unix/mbuseradd.c] Conclusion: Our target is suid root and contains an exploitable stack-based buffer overflow. Our fuzzer was able to detect it for us :) GNU/Linux MBSE-BBS 0.70.0 & Below Stack Overflow Exploit http://www.milw0rm.com/exploits/3154 ============================================================================================================================ 3. Remote Fuzzing Remote fuzzing deals with fuzzing a target remotely or a the network. This can include, but isn't limited to: Network Protocol Fuzzing - Fuzzing applications or even a kernel that implements a specific protocol Database Fuzzing - Fuzzing database modules and/or database input sanitation policies Web Application Fuzzing - Fuzzing input vectors of web applications hosted on a web server ============================================================================================================================ 3.1 Information Gathering For this example of remote fuzzing, we will be exploring GoodTech SSH Server (http://www.goodtechsys.com/sshdnt2000.asp) which was vulnerable to a remote buffer overflow vulnerability in its SFTP server part. We need to figure out the protocol standards and get information on how to communicate with the SSH/SFTP server in order to fuzz it. PERL extensions that can be installed through CPAN can be extremely helpful in writing an efficient fuzzer. There is actually a PERL extension that will allow us to communicate through SSH2 with SFTP servers making fuzzing a breeze, it is called Net::SSH2::SFTP (http://search.cpan.org/~dbrobins/Net-SSH2-0.18/lib/Net/SSH2/SFTP.pm). You will need to install libssh2 (http://www.libssh2.org/wiki/index.php/Main_Page) first in order for Net::SSH2 to install correctly. After that, you can install Net::SSH2 by running "cpan" from your shell (as root, usually) and doing a "install Net::SSH2". After you have installed the library and extension, we can use it for our perl-based SFTP fuzzer and it allows us to work with the SSH2 protocol fairly easily. By looking at the documentation for Net::SSH2, it will allow us to fuzz the following parameters (for SFTP): open -> Open or create a file opendir -> Open a directory unlink -> Delete a file rename -> Rename a file or directory mkdir -> Create a directory rmdir -> Delete a directory stat -> Get file attributes setstat -> Set file attributes symlink -> Create a symbolic link readlink -> Return the target of a link realpath -> Resolve a file's path These functions are called by each method provided, and we will fuzz their parameters (being our input), on the SFTP server. ============================================================================================================================ 3.2 Writing the Fuzzer This example for SFTP fuzzing will be written in PERL and will be using libssh2/Net::SSH2 (this is not the only way to use and fuzz SFTP, other libaries and extensions that may be more extensive and/or low-level are available). [sftpfuzz.pl] #!/usr/bin/perl use Net::SSH2; @fzorc = ("A" x 550, "A" x 1100, "A" x 2100, "A" x 4200, "A" x 8400, # overflow "\%n\%n\%n\%n\%n", "\%\%20n", "\%n\%p\%s\%d\%x", "%.1024d", "%.2049d", # format string "-1", "32767", "65535", "-2147483647", "0xffffffff", # numbers "a|id > /tmp/FZ|b", "a`id > /tmp/FZ`b", "a'id > /tmp/FZ'b", # out-of-bounds breakage "a;id > /tmp/FZ;b", "a&&id > /tmp/FZ&&b"); @fzdesc = ("A x 550", "A x 1100", "A x 2100", "A x 4200", "A x 8400", "\%n\%n\%n\%n\%n", "\%\%20n", "\%n\%p\%s\%d\%x", "%.1024d", "%.2049d", "-1", "32767", "65535", "-2147483647", "0xffffffff", "a|id > /tmp/FZ|b", "a`id > /tmp/FZ`b", "a'id > /tmp/FZ'b", "a;id > /tmp/FZ;b", "a&&id > /tmp/FZ&&b"); @funcs1 = ("open", "opendir", "unlink", "mkdir", "rmdir", "stat", "setstat", "readlink", "realpath"); # 1 arg @funcs2 = ("rename", "symlink"); # 2 args $server = "1.2.3.4"; $user = "sftp"; $pass = "fuzz"; $logfile = "sftpfuzz.log"; $| = 1; $ssh2 = Net::SSH2->new(); $ssh2->connect($server); $ssh2->disconnect(); $i = 0; for($z = 0; $z < 9; $z++) { foreach(@fzorc) { $func = $funcs1[$z]; $arg = 1; $fuzz = $_; sftpfuzz($func, $fuzz, $arg, $i); if($i == 19) { $i = -1; } $i++; } } $i = 0; for($z = 0; $z < 2; $z++) { foreach(@fzorc) { $func = $funcs2[$z]; $arg = 2; $fuzz = $_; sftpfuzz($func, $fuzz, $arg, $i); if($i == 19) { $i = -1; } $i++; } } sub sftpfuzz { $func = $_[0]; $fuzz = $_[1]; $arg = $_[2]; $i = $_[3]; $desc = $fzdesc[$i]; $ssh2 = Net::SSH2->new(); $ssh2->connect($server) or logit($func, $i); print "sftpfuzz fuzzing [sftp + $func + $desc]\n"; if($ssh2->auth_password($user, $pass)) { $sftp = $ssh2->sftp(); if($arg == 1) { $fuzr = $sftp->$func($fuzz); } if($arg == 2) { $fuzr = $sftp->$func($fuzz, $fuzz); } } else { die "ERROR: auth_password($user/$pass)\n"; } $ssh2->disconnect(); } sub logit { $fuzz = $_[0]; $i = $_[1]; $desc = $fzdesc[$i-1]; open(FD, ">>$logfile"); print FD $server . " -> [sftp + $func + $desc]\n"; close(FD); die "$server down -> check $logfile\n"; } [sftpfuzz.pl] Now we have a simple, remote SFTP fuzzer that can somewhat reliably tell us at least if we find any faults in the server. ============================================================================================================================ 3.3 Fuzzing the Target Work time is over-- play time is upon us. Lets run the fuzzer against our target. fuzz@linux:~$ perl sftpfuzz.pl sftpfuzz fuzzing [sftp + open + A x 550] 1.2.3.4 down -> check sftpfuzz.log fuzz@linux:~$ Looks like we've got something... fuzz@linux:~$ cat sftpfuzz.log 1.2.3.4 -> [sftp + open + A x 550] fuzz@linux:~$ The server went down right after we hit "open" with a 550 byte request. Lets now check out the target process on our machine. EAX 00000001 ECX 41414141 EDX 00890608 EBX 00000000 ESP 01448968 ASCII "AAAAA....." EBP 41414141 ESI 0000014F EDI 0144E7E0 EIP 41414141 The instruction pointer (EIP) and other registers are overwritten with our fuzzing data. Conclusion: Our target contains a remotely exploitable stack-based buffer overflow. Source code auditing wasn't available here because our target seems to be closed source. Once again, our fuzzer was able to detect the vulnerability for us :) GoodTech SSH Remote Buffer Overflow Exploit http://www.milw0rm.com/exploits/6804 ============================================================================================================================ 4. Conclusion Fuzzing is a developing art. As we progress in computer security, fuzzing will grow stronger as well. Nonpublished code will sit on the most remote boxes, possibly thanking fuzzing for leading the way in its R&D. One might guess millions will be made from the marketing of fuzzing technologies. Many things will come from the vulnerabilities they discover. Fuzzers are code-- code programmed by human beings. They can be as perfect and flawless as we are. The age of fuzzing has made its debut some years ago, was silently studied, and reawoken to fuel vulnerability discovery like never before. Enjoy the game, fuzz some code. ============================================================================================================================ 4.1 Disclaimer Krakow Labs assumes no liability for the use or misuse of any or all information contained in this document or information available at or referring to this document. Any or all information contained in this document or available at or referring to this document is not misleading and all information provided by Krakow Labs in this document is accurate to the best knowledge of Krakow Labs. This document can be published and/or reproduced as long as the document's data is left unchanged. Krakow Labs may be accessed via krakowlabs.com for more information, personal reference, or other agendas supporting Krakow Labs. KL0209LIT_fffap.txt 02.11.2009 Krakow Labs Literature [www.krakowlabs.com] Fuzzing for Fun and Profit http://www.krakowlabs.com/res/lit/KL0209LIT_fffap.txt rush@KL (Jeremy Brown) [rush[at]krakowlabs[dot]com] KL0209LIT_fffap.txt