diff --git a/README.md b/README.md index 6fe6380..512542b 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,11 @@ Transfer AFL files over a mesh to fuzz across multiple servers ## Features +- Automatically syncs the fuzzers over all nodes - No obscure dependencies, no painful setup process - just a single, self-contained binary - Using DEFLATE compression format (see [RFC 1951](https://www.ietf.org/rfc/rfc1951.html)) -- Automatically syncs the main fuzzer to secondary nodes, and all secondary fuzzers back to the main node - Encrypts traffic between nodes using AES-256, dropping plaintext packets - Usable on UNIX-like systems (Linux, OSX) and Windows -- Reduces the amount of transmitted test cases to a bare minimum ## Usage @@ -23,9 +22,9 @@ As a countermeasure, use the `--restrict-to-peers` flags to only allow connectio Let's assume you have three servers running with some instances of AFL, all in secondary (`-S`) mode, except the main fuzzer running on the box 10.0.0.1. To sync test cases across those servers, you'd need to run -- on 10.0.0.1: `./afl-transmit --fuzzer-directory /ram/output --main --peers 10.0.0.2,10.0.0.3` -- on 10.0.0.2: `./afl-transmit --fuzzer-directory /ram/output --peers 10.0.0.1` -- on 10.0.0.3: `./afl-transmit --fuzzer-directory /ram/output --peers 10.0.0.1` +- on 10.0.0.1: `./afl-transmit --fuzzer-directory /ram/output --peers 10.0.0.2,10.0.0.3` +- on 10.0.0.2: `./afl-transmit --fuzzer-directory /ram/output --peers 10.0.0.1,10.0.0.3` +- on 10.0.0.3: `./afl-transmit --fuzzer-directory /ram/output --peers 10.0.0.1,10.0.0.2` Because *afl-transmit* stays in the foreground, you should probably run it in a `tmux` window or something comparable. @@ -43,24 +42,3 @@ dd if=/dev/urandom bs=32 count=1 2>/dev/null | base64 | tee transmit.key ``` As already said, the same key must be used on all nodes. - -### Traffic reduction - -On default, *afl-transmit* avoids sending files with the same name present in different fuzzer directories. -This will greatly reduce the traffic between your nodes (I measured 621 kB to 1.3 kB, for example). -Please note that there might be some edge cases when you don't want that behaviour, e.g. -- you want to preserve the queue of each fuzzer -- you expect your fuzzers to give the same (file) name to different test cases, in which case *afl-transmit* would mistakenly assume that the file has the same *contents* and not only the same *name* -- you don't care for traffic - -To avoid reducing the transmitted files by comparing filenames, add `--no-duplicates=false` as argument. - -Also on default, *afl-transmit* tries to check if the queue of the observed fuzzers contain test cases which originated from another fuzzer instance. -In that case, the file name contains the keyword "sync" in it, and looks e.g. like this: `id:001815,time:0,orig:id:001805,sync:main,src:001794` -If it was copied from another fuzzer, it means that the file is already present in the fuzzer cluster, and can safely be skipped on those fuzzer instances which copied it. -Please note that this will produce false positives if the filename of your testcases contain `,sync:` for whatever reason. - -To avoid reducing the transmitted files by filtering synced files out, add `--avoid-synced=false` as argument. - -If you still have trouble paying the invoice for your ISP due to heavy traffic usage, try increasing the `--rescan` value, so files are transmitted less often. - diff --git a/logistic/packer.go b/logistic/packer.go index 0a4820f..26b598a 100644 --- a/logistic/packer.go +++ b/logistic/packer.go @@ -4,7 +4,6 @@ import ( "archive/tar" "bytes" "compress/flate" - "flag" "fmt" "io/ioutil" "log" @@ -12,39 +11,25 @@ import ( "strings" ) -var noDuplicates bool - -// avoidSynced is the flag to set if files containing "sync" should be packed or not. -// Those files are from another fuzzer - no need to pack them twice. -var avoidSynced bool - -// RegisterPackerFlags registers flags which are required by the packer -func RegisterPackerFlags() { - flag.BoolVar(&noDuplicates, "no-duplicates", true, "Avoid transmitting the same file multiple times, e.g. because it is present in multiple fuzzer's queues") - flag.BoolVar(&avoidSynced, "avoid-synced", true, "Avoid transmitting files containing the keyword 'sync', as they are from other fuzzers anyways, and should be included by their afl-transmit instance") -} - -// PackFuzzers packs all targeted fuzzers into a TAR - at least queue/, fuzz_bitmap, fuzzer_stats -func PackFuzzers(fuzzers []string, fuzzerDirectory string) ([]byte, error) { +// PackFuzzer packs the fuzzer into a TAR: queue/, fuzz_bitmap, fuzzer_stats +func PackFuzzer(fuzzer string, fuzzerDirectory string) ([]byte, error) { // Create TAR archive var tarBuffer bytes.Buffer tarWriter := tar.NewWriter(&tarBuffer) - // Essentially we want to pack three things from each targeted fuzzer: + // Essentially we want to pack three things from the targeted fuzzer: // - the fuzz_bitmap file // - the fuzzer_stats file // - the queue/ directory - but avoiding duplicates - var pkgCont []string // list of queue files already present in the archive - for _, fuzzer := range fuzzers { - // We need full paths to read, but will write relative paths into the TAR archive - absFuzzerPath := fuzzerDirectory - relFuzzerPath := strings.TrimPrefix(fuzzer, fuzzerDirectory) - // Read-n-Pack™ - packSingleFile(tarWriter, absFuzzerPath, relFuzzerPath, "fuzz_bitmap") - packSingleFile(tarWriter, absFuzzerPath, relFuzzerPath, "fuzzer_stats") - packQueueFiles(tarWriter, absFuzzerPath, relFuzzerPath, &pkgCont) - } + // We need full paths to read, but will write relative paths into the TAR archive + absFuzzerPath := fuzzerDirectory + relFuzzerPath := strings.TrimPrefix(fuzzer, fuzzerDirectory) + + // Read-n-Pack™ + packSingleFile(tarWriter, absFuzzerPath, relFuzzerPath, "fuzz_bitmap") + packSingleFile(tarWriter, absFuzzerPath, relFuzzerPath, "fuzzer_stats") + packQueueFiles(tarWriter, absFuzzerPath, relFuzzerPath) // Close TAR archive tarWriter.Close() @@ -90,7 +75,7 @@ func packSingleFile(tarWriter *tar.Writer, absPath string, relPath string, fileN } // Packs the files in the given directory into a tar archive -func packQueueFiles(tarWriter *tar.Writer, absPath string, relPath string, pkgCont *[]string) { +func packQueueFiles(tarWriter *tar.Writer, absPath string, relPath string) { // Get list of queue files queuePath := fmt.Sprintf("%s%c%s%cqueue", absPath, os.PathSeparator, relPath, os.PathSeparator) filesInDir, readErr := ioutil.ReadDir(queuePath) @@ -107,37 +92,7 @@ func packQueueFiles(tarWriter *tar.Writer, absPath string, relPath string, pkgCo continue } - // Check if we should care fore duplicates - if noDuplicates && checkDuplicate(f.Name(), pkgCont) { - // that file is already present in the package - avoid packing it again - continue - } - - // Check if we should care for the keyword 'sync' in file name - if avoidSynced && strings.Contains(f.Name(), ",sync:") { - // seems like this file was put into the queue of this fuzzer by syncing it from another fuzzer. We don't - // need to transmit it then, because the fuzzer which found that case will have the same file but without - // the keyword "sync" in it. Simply put, we avoid sending the same file multiple times with different names. - continue - } - // Pack into the archive packSingleFile(tarWriter, absPath, relPath, fmt.Sprintf("queue%c%s", os.PathSeparator, f.Name())) - - if noDuplicates { - // Append added file name to the list of things included in the package - *pkgCont = append(*pkgCont, f.Name()) - } } } - -// checkDuplicate checks if name is already present in pkgCont -func checkDuplicate(name string, pkgCont *[]string) bool { - for _, p := range *pkgCont { - if p == name { - return true - } - } - - return true -} diff --git a/main.go b/main.go index 903e89d..fcfc615 100644 --- a/main.go +++ b/main.go @@ -3,7 +3,6 @@ package main import ( "flag" "fmt" - "github.com/maride/afl-transmit/logistic" "github.com/maride/afl-transmit/net" "github.com/maride/afl-transmit/stats" "github.com/maride/afl-transmit/watchdog" @@ -20,7 +19,6 @@ func main() { net.RegisterSenderFlags() net.RegisterListenFlags() net.RegisterCryptFlags() - logistic.RegisterPackerFlags() stats.RegisterStatsFlags() RegisterGlobalFlags() flag.Parse() diff --git a/watchdog/watchdog.go b/watchdog/watchdog.go index e18ce2e..3de82a7 100644 --- a/watchdog/watchdog.go +++ b/watchdog/watchdog.go @@ -13,21 +13,26 @@ import ( var ( rescan int - isMainNode bool ) // RegisterWatchdogFlags registers required flags for the watchdog func RegisterWatchdogFlags() { - flag.IntVar(&rescan, "rescan", 30, "Minutes to wait before rescanning local fuzzer directories") - flag.BoolVar(&isMainNode, "main", false, "Set this option if this afl-transmit instance is running on the node running the main afl-fuzz instance") + flag.IntVar(&rescan, "rescan", 30, "Minutes to wait before rescanning local fuzzer directory") } -// Watch over the specified directory, send updates to peers and re-scan after the specified amount of seconds +// WatchFuzzers watches over the specified directory, sends updates to peers and re-scans after the specified amount of seconds func WatchFuzzers(outputDirectory string) { // Loop forever for { - // Pack important parts of the fuzzers into an archive - packedFuzzers, packerErr := logistic.PackFuzzers(getTargetFuzzer(outputDirectory), outputDirectory) + // Search for main fuzzer + targetFuzzer, targetErr := getTargetFuzzer(outputDirectory) + if targetErr != nil { + log.Printf("Failed to detect main fuzzer: %s", targetErr) + continue + } + + // Pack important parts of the fuzzer into an archive + packedFuzzers, packerErr := logistic.PackFuzzer(targetFuzzer, outputDirectory) if packerErr != nil { log.Printf("Failed to pack fuzzer: %s", packerErr) continue @@ -41,24 +46,24 @@ func WatchFuzzers(outputDirectory string) { } } -// Searches in the specified output directory for fuzzers which we want to pack. -// Identifying the main fuzzer is done by searching for the file "is_main_node" -// This relies on the "election process" done by secondary fuzzers if they don't find a local main node. -// If that is detected, a secondary fuzzer becomes the main node in terms of syncing. That means we need to focus on -// the main node even if there is no real main node running locally. +// Searches in the specified output directory for the main fuzzer. +// Identifying the main fuzzer is done by searching for the file "is_main_node". On secondary-only servers, this relies +// on the "election process" done by secondary fuzzers if they don't find a local main node. In that election process, a +// secondary fuzzer becomes the main node in terms of syncing. That means we need to focus on the main node even if +// there is no real main node running locally. Good thing is that the elected node will create a "is_main_node" file, so +// we really just need to search for that. // However it is important to avoid transmitting the is_main_node file - else, the secondaries will think that there is // a main node running locally. Skipping the is_main_node file means the other fuzzers (and especially the elected main -// fuzzer) detect the transmitted fuzzer as a normal (and dead) secondary. ... blabla +// fuzzer) detect the transmitted fuzzer as a normal (and dead) secondary. // // (see AFLplusplus/src/afl-fuzz.run.c:542) -func getTargetFuzzer(outputDirectory string) []string { +func getTargetFuzzer(outputDirectory string) (string, error) { // find main fuzzer directory - its the only one required by the secondaries // List files (read: fuzzers) in output directory filesInDir, readErr := ioutil.ReadDir(outputDirectory) if readErr != nil { - log.Printf("Failed to list directory content of %s: %s", outputDirectory, readErr) - return nil + return "", fmt.Errorf("Failed to list directory content of %s: %s", outputDirectory, readErr) } for _, f := range filesInDir { @@ -75,13 +80,9 @@ func getTargetFuzzer(outputDirectory string) []string { } // is_main_node file exists, so we found the correct fuzzer directory - return it - fullPath := fmt.Sprintf("%s%c%s", outputDirectory, os.PathSeparator, f.Name()) - return []string{ - fullPath, - } + return fmt.Sprintf("%s%c%s", outputDirectory, os.PathSeparator, f.Name()), nil } // Failed to find the main node - probably we are in --main mode by accident - log.Printf("Unable to find main node in %s - sure afl-transmit should run in --main mode?", outputDirectory) - return nil + return "", fmt.Errorf("Unable to find main node in %s", outputDirectory) }