Add AES encryption between nodes

This commit is contained in:
maride 2021-04-23 16:17:44 +02:00
parent 0a4217b5b9
commit ddaab52527
5 changed files with 132 additions and 1 deletions

View File

@ -6,6 +6,7 @@ Transfer AFL files over a mesh to fuzz across multiple servers
- 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 - 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 UNIXoid (Linux, OSX) systems and Windows - Usable on UNIXoid (Linux, OSX) systems and Windows
## Usage ## Usage
@ -22,3 +23,17 @@ As a countermeasure, use the `--restrict-to-peers` flags to only allow connectio
- On your host 10.0.0.2: `./afl-transmit --fuzzer-directory /ram/output --peers 10.0.0.1` - On your host 10.0.0.2: `./afl-transmit --fuzzer-directory /ram/output --peers 10.0.0.1`
- On your host 10.0.0.3: `./afl-transmit --fuzzer-directory /ram/output --peers 10.0.0.1` - On your host 10.0.0.3: `./afl-transmit --fuzzer-directory /ram/output --peers 10.0.0.1`
### Crypto
If you want to encrypt your traffic between the nodes - which is advised, as it increases security and there is nearly no argument against it - you can do so by specifying a random key with `--key`.
To keep *afl-transmit* simple, the symmetric encryption algorithm AES256-GCM was chosen over an asymmetric variant. This means you need to specify the same key on all nodes.
Key generation is fairly simple, you just need to get 32 random bytes from somewhere (buy them, or use `/dev/urandom`), and wrap them with base64.
For example like this:
```
dd if=/dev/urandom bs=32 count=1 2>/dev/null | base64 | tee transmit.key
./afl-transmit --key $(cat transmit.key) --fuzzer-directory ...
```
As already said, the same key must be used on all nodes.

View File

@ -19,6 +19,7 @@ func main() {
watchdog.RegisterWatchdogFlags() watchdog.RegisterWatchdogFlags()
net.RegisterSenderFlags() net.RegisterSenderFlags()
net.RegisterListenFlags() net.RegisterListenFlags()
net.RegisterCryptFlags()
RegisterGlobalFlags() RegisterGlobalFlags()
flag.Parse() flag.Parse()
@ -31,6 +32,13 @@ func main() {
// Read peers file // Read peers file
net.ReadPeers() net.ReadPeers()
// Initialize crypto if desired
cryptErr := net.InitCrypt()
if cryptErr != nil {
fmt.Printf("Failed to initialize crypt function: %s", cryptErr)
return
}
// Start watchdog for local afl instances // Start watchdog for local afl instances
go watchdog.WatchFuzzers(outputDirectory) go watchdog.WatchFuzzers(outputDirectory)

86
net/crypt.go Normal file
View File

@ -0,0 +1,86 @@
package net
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"flag"
"fmt"
"io"
)
var (
key string
sharedGCM cipher.AEAD
nonceSize int
)
// RegisterCryptFlags Registers the flags required for cryptography
func RegisterCryptFlags() {
flag.StringVar(&key, "key", "", "32 random bytes, base64-wrapped, to AES-encrypt traffic between nodes")
}
// InitCrypt creates a cipher object out of the key handed over via --key
func InitCrypt() error {
// Check if a key was handed over
if key == "" {
// no key, no service
return nil
}
// Unwrap base64'ed crypto bytes
rawKey, base64Err := base64.StdEncoding.DecodeString(key)
if base64Err != nil {
return fmt.Errorf("failed to unpack base64'ed key: %s", base64Err)
}
// Create cipher object using that key
block, cipherErr := aes.NewCipher(rawKey)
if cipherErr != nil {
return fmt.Errorf("failed to use your key as AES256 key: %s", cipherErr)
}
// Create GCM with cipher object
gcm, gcmErr := cipher.NewGCM(block)
if gcmErr != nil {
return fmt.Errorf("failed to create GCM for your key: %s", gcmErr)
}
// Set shared GCM instance
sharedGCM = gcm
nonceSize = gcm.NonceSize()
// No error to report
return nil
}
// CryptApplicable checks if we are able to encrypt or decrypt things, means if this instance was given a proper key
func CryptApplicable() bool {
// if the shared GCM object is set, we were able to get the key off user's hands, else we don't crypt
return sharedGCM != nil
}
// Encrypt encrypts the given bytes using the symmetric key
func Encrypt(plain []byte) ([]byte, error) {
// create nonce and fill it with random bytes
nonce := make([]byte, nonceSize)
_, readErr := io.ReadFull(rand.Reader, nonce)
if readErr != nil {
return nil, fmt.Errorf("failed to get random bytes: %s", readErr)
}
// Encrypt plaintext
return sharedGCM.Seal(nonce, nonce, plain, nil), nil
}
// Decrypt decrypts the given bytes using the symmetric key
func Decrypt(enc []byte) ([]byte, error) {
// Sanity check on input
if len(enc) < sharedGCM.NonceSize() {
return nil, fmt.Errorf("failed to decrypt packet: too short")
}
// Decrypt encrypted bytes
return sharedGCM.Open(nil, enc[:nonceSize], enc[nonceSize:], nil)
}

View File

@ -76,9 +76,20 @@ func handle(conn net.Conn, outputDirectory string) {
// Read raw content // Read raw content
cont, contErr := ioutil.ReadAll(conn) // bufio.NewReader(conn).ReadString('\x00') cont, contErr := ioutil.ReadAll(conn) // bufio.NewReader(conn).ReadString('\x00')
// Check if we are able to decrypt
if CryptApplicable() {
// Decrypt packet
var decryptErr error
cont, decryptErr = Decrypt(cont)
if decryptErr != nil {
log.Printf("Failed to decrypt packet from %s: %s", conn.RemoteAddr().String(), decryptErr)
return
}
}
if contErr == nil || contErr == io.EOF { if contErr == nil || contErr == io.EOF {
// We received the whole content, time to process it // We received the whole content, time to process it
unpackErr := logistic.UnpackInto([]byte(cont), outputDirectory) unpackErr := logistic.UnpackInto(cont, outputDirectory)
if unpackErr != nil { if unpackErr != nil {
log.Printf("Encountered error processing packet from %s: %s", conn.RemoteAddr().String(), unpackErr) log.Printf("Encountered error processing packet from %s: %s", conn.RemoteAddr().String(), unpackErr)
} }

View File

@ -37,6 +37,17 @@ func CreatePeer(address string) Peer {
// Sends the given content to the peer // Sends the given content to the peer
func (p *Peer) SendToPeer(content []byte) { func (p *Peer) SendToPeer(content []byte) {
// Encrypt content if desired
if CryptApplicable() {
// Encrypt packet
var encryptErr error
content, encryptErr = Encrypt(content)
if encryptErr != nil {
log.Printf("Failed to decrypt packet from %s: %s", p.Address, encryptErr)
return
}
}
// Build up a connection // Build up a connection
tcpConn, dialErr := net.Dial("tcp", p.Address) tcpConn, dialErr := net.Dial("tcp", p.Address)
if dialErr != nil { if dialErr != nil {