mirror of
https://github.com/maride/afl-transmit.git
synced 2024-11-23 07:44:24 +00:00
Only sync main nodes across servers
This commit is contained in:
parent
a95b6ce3fe
commit
7807b45ed5
30
README.md
30
README.md
@ -4,12 +4,11 @@ Transfer AFL files over a mesh to fuzz across multiple servers
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
|
- Automatically syncs the fuzzers over all nodes
|
||||||
- No obscure dependencies, no painful setup process - just a single, self-contained binary
|
- 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))
|
- 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
|
- Encrypts traffic between nodes using AES-256, dropping plaintext packets
|
||||||
- Usable on UNIX-like systems (Linux, OSX) and Windows
|
- Usable on UNIX-like systems (Linux, OSX) and Windows
|
||||||
- Reduces the amount of transmitted test cases to a bare minimum
|
|
||||||
|
|
||||||
## Usage
|
## 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.
|
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
|
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.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`
|
- 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`
|
- 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.
|
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.
|
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.
|
|
||||||
|
|
||||||
|
@ -4,7 +4,6 @@ import (
|
|||||||
"archive/tar"
|
"archive/tar"
|
||||||
"bytes"
|
"bytes"
|
||||||
"compress/flate"
|
"compress/flate"
|
||||||
"flag"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
@ -12,39 +11,25 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var noDuplicates bool
|
// PackFuzzer packs the fuzzer into a TAR: queue/, fuzz_bitmap, fuzzer_stats
|
||||||
|
func PackFuzzer(fuzzer string, fuzzerDirectory string) ([]byte, error) {
|
||||||
// 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) {
|
|
||||||
// Create TAR archive
|
// Create TAR archive
|
||||||
var tarBuffer bytes.Buffer
|
var tarBuffer bytes.Buffer
|
||||||
tarWriter := tar.NewWriter(&tarBuffer)
|
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 fuzz_bitmap file
|
||||||
// - the fuzzer_stats file
|
// - the fuzzer_stats file
|
||||||
// - the queue/ directory - but avoiding duplicates
|
// - 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™
|
// We need full paths to read, but will write relative paths into the TAR archive
|
||||||
packSingleFile(tarWriter, absFuzzerPath, relFuzzerPath, "fuzz_bitmap")
|
absFuzzerPath := fuzzerDirectory
|
||||||
packSingleFile(tarWriter, absFuzzerPath, relFuzzerPath, "fuzzer_stats")
|
relFuzzerPath := strings.TrimPrefix(fuzzer, fuzzerDirectory)
|
||||||
packQueueFiles(tarWriter, absFuzzerPath, relFuzzerPath, &pkgCont)
|
|
||||||
}
|
// Read-n-Pack™
|
||||||
|
packSingleFile(tarWriter, absFuzzerPath, relFuzzerPath, "fuzz_bitmap")
|
||||||
|
packSingleFile(tarWriter, absFuzzerPath, relFuzzerPath, "fuzzer_stats")
|
||||||
|
packQueueFiles(tarWriter, absFuzzerPath, relFuzzerPath)
|
||||||
|
|
||||||
// Close TAR archive
|
// Close TAR archive
|
||||||
tarWriter.Close()
|
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
|
// 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
|
// Get list of queue files
|
||||||
queuePath := fmt.Sprintf("%s%c%s%cqueue", absPath, os.PathSeparator, relPath, os.PathSeparator)
|
queuePath := fmt.Sprintf("%s%c%s%cqueue", absPath, os.PathSeparator, relPath, os.PathSeparator)
|
||||||
filesInDir, readErr := ioutil.ReadDir(queuePath)
|
filesInDir, readErr := ioutil.ReadDir(queuePath)
|
||||||
@ -107,37 +92,7 @@ func packQueueFiles(tarWriter *tar.Writer, absPath string, relPath string, pkgCo
|
|||||||
continue
|
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
|
// Pack into the archive
|
||||||
packSingleFile(tarWriter, absPath, relPath, fmt.Sprintf("queue%c%s", os.PathSeparator, f.Name()))
|
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
|
|
||||||
}
|
|
||||||
|
2
main.go
2
main.go
@ -3,7 +3,6 @@ package main
|
|||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/maride/afl-transmit/logistic"
|
|
||||||
"github.com/maride/afl-transmit/net"
|
"github.com/maride/afl-transmit/net"
|
||||||
"github.com/maride/afl-transmit/stats"
|
"github.com/maride/afl-transmit/stats"
|
||||||
"github.com/maride/afl-transmit/watchdog"
|
"github.com/maride/afl-transmit/watchdog"
|
||||||
@ -20,7 +19,6 @@ func main() {
|
|||||||
net.RegisterSenderFlags()
|
net.RegisterSenderFlags()
|
||||||
net.RegisterListenFlags()
|
net.RegisterListenFlags()
|
||||||
net.RegisterCryptFlags()
|
net.RegisterCryptFlags()
|
||||||
logistic.RegisterPackerFlags()
|
|
||||||
stats.RegisterStatsFlags()
|
stats.RegisterStatsFlags()
|
||||||
RegisterGlobalFlags()
|
RegisterGlobalFlags()
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
@ -13,21 +13,26 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
rescan int
|
rescan int
|
||||||
isMainNode bool
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// RegisterWatchdogFlags registers required flags for the watchdog
|
// RegisterWatchdogFlags registers required flags for the watchdog
|
||||||
func RegisterWatchdogFlags() {
|
func RegisterWatchdogFlags() {
|
||||||
flag.IntVar(&rescan, "rescan", 30, "Minutes to wait before rescanning local fuzzer directories")
|
flag.IntVar(&rescan, "rescan", 30, "Minutes to wait before rescanning local fuzzer directory")
|
||||||
flag.BoolVar(&isMainNode, "main", false, "Set this option if this afl-transmit instance is running on the node running the main afl-fuzz instance")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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) {
|
func WatchFuzzers(outputDirectory string) {
|
||||||
// Loop forever
|
// Loop forever
|
||||||
for {
|
for {
|
||||||
// Pack important parts of the fuzzers into an archive
|
// Search for main fuzzer
|
||||||
packedFuzzers, packerErr := logistic.PackFuzzers(getTargetFuzzer(outputDirectory), outputDirectory)
|
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 {
|
if packerErr != nil {
|
||||||
log.Printf("Failed to pack fuzzer: %s", packerErr)
|
log.Printf("Failed to pack fuzzer: %s", packerErr)
|
||||||
continue
|
continue
|
||||||
@ -41,24 +46,24 @@ func WatchFuzzers(outputDirectory string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Searches in the specified output directory for fuzzers which we want to pack.
|
// Searches in the specified output directory for the main fuzzer.
|
||||||
// Identifying the main fuzzer is done by searching for the file "is_main_node"
|
// Identifying the main fuzzer is done by searching for the file "is_main_node". On secondary-only servers, this relies
|
||||||
// This relies on the "election process" done by secondary fuzzers if they don't find a local main node.
|
// on the "election process" done by secondary fuzzers if they don't find a local main node. In that election process, a
|
||||||
// If that is detected, a secondary fuzzer becomes the main node in terms of syncing. That means we need to focus on
|
// secondary fuzzer becomes the main node in terms of syncing. That means we need to focus on the main node even if
|
||||||
// the main node even if there is no real main node running locally.
|
// 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
|
// 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
|
// 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)
|
// (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
|
// find main fuzzer directory - its the only one required by the secondaries
|
||||||
|
|
||||||
// List files (read: fuzzers) in output directory
|
// List files (read: fuzzers) in output directory
|
||||||
filesInDir, readErr := ioutil.ReadDir(outputDirectory)
|
filesInDir, readErr := ioutil.ReadDir(outputDirectory)
|
||||||
if readErr != nil {
|
if readErr != nil {
|
||||||
log.Printf("Failed to list directory content of %s: %s", outputDirectory, readErr)
|
return "", fmt.Errorf("Failed to list directory content of %s: %s", outputDirectory, readErr)
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, f := range filesInDir {
|
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
|
// 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 fmt.Sprintf("%s%c%s", outputDirectory, os.PathSeparator, f.Name()), nil
|
||||||
return []string{
|
|
||||||
fullPath,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Failed to find the main node - probably we are in --main mode by accident
|
// 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 "", fmt.Errorf("Unable to find main node in %s", outputDirectory)
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user