├── .gitignore ├── LICENSE ├── README.md └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | afl-launch -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Ben Nagy. All Rights Reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without modification, 4 | are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this 7 | list of conditions and the following disclaimer. 8 | 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation and/or 11 | other materials provided with the distribution. 12 | 13 | 3. The name of the author(s) may not be used to endorse or promote products 14 | derived from this software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) "AS IS" AND ANY EXPRESS OR IMPLIED 17 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 18 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 19 | SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 20 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT 21 | OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 24 | IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 25 | OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | afl-launch 2 | ======= 3 | 4 | ## About 5 | 6 | `afl-launch` is a simple program to spawn afl fuzzing instances from the 7 | command line. It provides no compelling features; it is simply my version of 8 | this tool. 9 | 10 | ``` 11 | Usage of afl-launch: 12 | -XXX 13 | [HACK] substitute XXX in the target args with an 8 char random string [HACK] 14 | -f string 15 | Filename template (substituted and passed via -f) 16 | -i string 17 | afl-fuzz -i option (input location) 18 | -m string 19 | afl-fuzz -m option (memory limit), 'none' for no limit (defaults to afl) 20 | -n int 21 | Number of instances to launch (default 1) 22 | -name string 23 | Base name for instances. Fuzzers will work in /-[M|S] 24 | -no-master 25 | Launch all instances with -S 26 | -o string 27 | afl-fuzz -o option (output location) 28 | -t string 29 | afl-fuzz -t option (timeout) 30 | -x string 31 | afl-fuzz -x option (extras location) 32 | ``` 33 | 34 | The launcher DOES NOT CHECK if the `afl-fuzz` instance errored out. Before 35 | starting a multiple launch, you should start `afl-fuzz` once manually with your 36 | desired `-i` `-o` `-x` (etc) options to make sure everything works. 37 | 38 | If you don't supply a base name, the launcher will pick a random one. 39 | 40 | Example: 41 | ``` 42 | ./afl-launch -i ~/testcases/pdf -o ~/fuzzing/pdf -n 4 -- pdftoppm @@ 43 | ``` 44 | 45 | A note on the `-f` flag - the idea is that you pass a template like 46 | `/dev/shm/whatever.xml` and the launcher will substitute it as `-f 47 | /dev/shm/-S12.xml` when it invokes `afl-fuzz`. This is so that you can 48 | have AFL create testcase files on a ramdisk, and avoid stressing your disks. 49 | Queue entries and crashes are still saved as usual in the location specified 50 | by `-o`. Don't be an idiot like me and run everything on a ramdisk. 51 | 52 | Another note about ttys - this tool just spawns all the processes and then 53 | exits. If you want them to stay running unattended then the easiest and (IMHO) 54 | best way is just to run it inside a `screen` session (`man screen`). 55 | 56 | ### -XXX 57 | 58 | There is a hacky option that can be used for a few things. If you pass -XXX 59 | then the literal string `XXX` anywhere in the target command (after the `--` 60 | in the command line) will be replaced with a random 8 character string. I use 61 | this for targets that require a `-o` flag for output filename, like 62 | `stupidprogram -i @@ -out /dev/shm/XXX.jpg`. 63 | 64 | ### They launched.. now what? 65 | 66 | Use `afl-whatsup ` with the same location you used for -o to get the 67 | afl-fuzz summary output. For bonus points, be a unix nerd and do like `watch 68 | -n 60 afl-whatsup -s ~/fuzzing/targetname` 69 | 70 | This is what that looks like: 71 | ``` 72 | Every 60.0s: afl-whatsup -s ~/fuzzing/targetname Sun Jun 7 10:40:36 2015 73 | 74 | status check tool for afl-fuzz by 75 | 76 | Summary stats 77 | ============= 78 | 79 | Fuzzers alive : 40 80 | Total run time : 161 days, 22 hours 81 | Total execs : 4513 million 82 | Cumulative speed : 12904 execs/sec 83 | Pending paths : 75 faves, 29250 total 84 | Pending per fuzzer : 1 faves, 731 total (on average) 85 | Crashes found : 9806 locally unique 86 | ``` 87 | 88 | ## Installation 89 | 90 | You should follow the [instructions](https://golang.org/doc/install) to 91 | install Go, if you haven't already done so. 92 | 93 | Download, build and install `afl-launch`: 94 | ```bash 95 | $ go get -u github.com/bnagy/afl-launch 96 | ``` 97 | 98 | ## TODO 99 | 100 | Nothing on the list. Open an issue if you want something. 101 | 102 | ## Contributing 103 | 104 | * Fork and send a pull request 105 | * Report issues 106 | 107 | ## License & Acknowledgements 108 | 109 | BSD style, see LICENSE file for details. 110 | 111 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/rand" 5 | "flag" 6 | "io" 7 | "log" 8 | "os" 9 | "os/exec" 10 | "path" 11 | "regexp" 12 | "strconv" 13 | "strings" 14 | ) 15 | 16 | const MAXFUZZERS = 256 17 | const AFLNAME = "afl-fuzz" 18 | 19 | var ( 20 | flagNoMaster = flag.Bool("no-master", false, "Launch all instances with -S") 21 | flagNum = flag.Int("n", 1, "Number of instances to launch") 22 | flagName = flag.String("name", "", "Base name for instances. Fuzzers will work in /-[M|S]") 23 | flagTimeout = flag.String("t", "", "afl-fuzz -t option (timeout)") // afl -t option supports a + suffix 24 | flagMem = flag.String("m", "", "afl-fuzz -m option (memory limit)") 25 | flagInput = flag.String("i", "", "afl-fuzz -i option (input location)") 26 | flagExtras = flag.String("x", "", "afl-fuzz -x option (extras location)") 27 | flagOutput = flag.String("o", "", "afl-fuzz -o option (output location)") 28 | flagFile = flag.String("f", "", "Filename template (substituted and passed via -f)") 29 | flagXXX = flag.Bool("XXX", false, "[HACK] substitute XXX in the target args with an 8 char random string [HACK]") 30 | 31 | subRegex = regexp.MustCompile("XXX") 32 | ) 33 | 34 | func randomName(n int) (result string) { 35 | buf := make([]byte, n) 36 | _, err := io.ReadFull(rand.Reader, buf) 37 | if err != nil { 38 | panic(err) 39 | } 40 | for _, b := range buf { 41 | result += string(b%26 + 0x61) 42 | } 43 | return 44 | } 45 | 46 | func spawn(fuzzerName string, args []string) { 47 | 48 | // if the user wants to use a special location for the testfiles ( like a 49 | // ramdisk ) then they can provide any filename /path/to/whatever.xxx and 50 | // we'll sub out 'whatever' for the name of this fuzzer and keep the base 51 | // and the extension. 52 | if len(*flagFile) > 0 { 53 | base, _ := path.Split(*flagFile) 54 | ext := path.Ext(*flagFile) 55 | args = append(args, "-f", path.Join(base, fuzzerName+ext)) 56 | } 57 | 58 | // Create a logfile for afl's stdout. Truncates any existing logfile. 59 | fuzzerDir := path.Join(*flagOutput, fuzzerName) 60 | err := os.MkdirAll(fuzzerDir, 0777) 61 | if err != nil { 62 | log.Fatalf(err.Error()) 63 | } 64 | fd, err := os.Create(path.Join(fuzzerDir, "afl-launch.log")) 65 | if err != nil { 66 | log.Fatalf(err.Error()) 67 | } 68 | defer fd.Close() 69 | 70 | args = append(args, "--") 71 | if *flagXXX { 72 | progArgs := make([]string, len(flag.Args())) 73 | copy(progArgs, flag.Args()) 74 | for i, elem := range progArgs { 75 | if subRegex.MatchString(elem) { 76 | progArgs[i] = subRegex.ReplaceAllString(elem, randomName(8)) 77 | } 78 | } 79 | args = append(args, progArgs...) 80 | } else { 81 | args = append(args, flag.Args()...) 82 | } 83 | 84 | cmd := exec.Command(AFLNAME, args...) 85 | cmd.Stdout = fd 86 | err = cmd.Start() 87 | if err != nil { 88 | // If this fails to start it will be OS issues like no swap or rlimit 89 | // or something, so it's not something we can handle gracefully. It's 90 | // NOT the same as the afl-fuzz process exiting because the args are 91 | // incorrect. 92 | log.Fatalf(err.Error()) 93 | } 94 | cmd.Process.Release() 95 | log.Printf("%s %s\n", AFLNAME, strings.Join(args, " ")) 96 | } 97 | 98 | func main() { 99 | 100 | flag.Parse() 101 | if len(flag.Args()) < 2 { 102 | log.Fatalf("no command to fuzz, eg: targetname @@") 103 | } 104 | 105 | // can we find afl? 106 | _, err := exec.LookPath(AFLNAME) 107 | if err != nil { 108 | log.Fatalf("couldn't find %s in $PATH", AFLNAME) 109 | } 110 | // sanity for n 111 | if *flagNum > MAXFUZZERS { 112 | log.Fatalf("too many fuzzers: %d", *flagNum) 113 | } 114 | // sanity for name 115 | if len(*flagName) > 32 { 116 | log.Fatalf("base name too long (%d), must be <= 32", len(*flagName)) 117 | } 118 | 119 | // collect the proxy args for afl-fuzz 120 | baseArgs := []string{} 121 | for _, v := range []string{"t", "m", "i", "x", "o"} { 122 | f := flag.Lookup(v) 123 | if f != nil && f.Value.String() != f.DefValue { 124 | baseArgs = append(baseArgs, "-"+v, f.Value.String()) 125 | } 126 | } 127 | 128 | baseName := *flagName 129 | if len(baseName) == 0 { 130 | baseName = randomName(5) 131 | } 132 | 133 | // first instance is a master unless indicated otherwise 134 | if *flagNoMaster { 135 | name := baseName + "-" + "S" + "0" 136 | spawn(name, append(baseArgs, "-S", name)) 137 | } else { 138 | name := baseName + "-" + "M" + "0" 139 | spawn(name, append(baseArgs, "-M", name)) 140 | } 141 | 142 | // launch the rest 143 | for i := 1; i < *flagNum; i++ { 144 | name := baseName + "-" + "S" + strconv.Itoa(i) 145 | spawn(name, append(baseArgs, "-S", name)) 146 | } 147 | } 148 | --------------------------------------------------------------------------------