Unverified Commit 8da050f2 authored by Philipp Winter's avatar Philipp Winter
Browse files

Implement proof-of-concept flow obfuscator.

Obfs4 can obfuscate its flow signature, but only by adding padding to
the end of a chunk of data that's written to the wire.

This patch implements sharknado, which improves on obfs4's flow
obfuscation.  Sharknado can "break" a packet burst by probabilistically
sending padding while receiving data.  For example, sharknado can -- in
theory -- turn the following packet sequence:

client -- 1500 bytes -> server
client -- 1500 bytes -> server
client --  500 bytes -> server

into the following sequences:

client -- 1500 bytes -> server
client <- 1500 bytes -- server
client -- 1500 bytes -> server
client --  500 bytes -> server

The idea (or hope, rather) is that this feature can help us reduce the
accuracy of deep learning-based website fingerprinting classifiers
because these classifiers frequently rely on packet sequences.

As for implementation, sharknado implements a net.Conn interface.
Instead of writing directly to its socket, obfs4 writes to sharknado,
which wraps the original socket.
parent c357dd56
......@@ -49,6 +49,7 @@ import (
"gitlab.com/yawning/obfs4.git/common/replayfilter"
"gitlab.com/yawning/obfs4.git/transports/base"
"gitlab.com/yawning/obfs4.git/transports/obfs4/framing"
"gitlab.com/yawning/obfs4.git/transports/sharknado"
)
const (
......@@ -265,7 +266,8 @@ func (sf *obfs4ServerFactory) WrapConn(conn net.Conn) (net.Conn, error) {
iatDist = probdist.New(sf.iatSeed, 0, maxIATDelay, biasedDist)
}
c := &obfs4Conn{conn, true, lenDist, iatDist, sf.iatMode, bytes.NewBuffer(nil), bytes.NewBuffer(nil), make([]byte, consumeReadSize), nil, nil}
c := &obfs4Conn{conn, true, lenDist, iatDist, sf.iatMode, bytes.NewBuffer(nil), bytes.NewBuffer(nil), make([]byte, consumeReadSize), nil, nil, false}
c.Conn = sharknado.NewSharknadoConn(conn, c.getPadding)
startTime := time.Now()
......@@ -292,6 +294,8 @@ type obfs4Conn struct {
encoder *framing.Encoder
decoder *framing.Decoder
connEstablished bool
}
func newObfs4ClientConn(conn net.Conn, args *obfs4ClientArgs) (c *obfs4Conn, err error) {
......@@ -312,7 +316,8 @@ func newObfs4ClientConn(conn net.Conn, args *obfs4ClientArgs) (c *obfs4Conn, err
}
// Allocate the client structure.
c = &obfs4Conn{conn, false, lenDist, iatDist, args.iatMode, bytes.NewBuffer(nil), bytes.NewBuffer(nil), make([]byte, consumeReadSize), nil, nil}
c = &obfs4Conn{conn, false, lenDist, iatDist, args.iatMode, bytes.NewBuffer(nil), bytes.NewBuffer(nil), make([]byte, consumeReadSize), nil, nil, false}
c.Conn = sharknado.NewSharknadoConn(conn, c.getPadding)
// Start the handshake timeout.
deadline := time.Now().Add(clientHandshakeTimeout)
......@@ -370,6 +375,7 @@ func (conn *obfs4Conn) clientHandshake(nodeID *ntor.NodeID, peerIdentityKey *nto
okm := ntor.Kdf(seed, framing.KeyLength*2)
conn.encoder = framing.NewEncoder(okm[:framing.KeyLength])
conn.decoder = framing.NewDecoder(okm[framing.KeyLength:])
conn.connEstablished = true
return nil
}
......@@ -443,6 +449,7 @@ func (conn *obfs4Conn) serverHandshake(sf *obfs4ServerFactory, sessionKey *ntor.
if _, err = conn.Conn.Write(frameBuf.Bytes()); err != nil {
return err
}
conn.connEstablished = true
return nil
}
......@@ -629,6 +636,25 @@ func (conn *obfs4Conn) padBurst(burst *bytes.Buffer, toPadTo int) (err error) {
return
}
// getPadding must be of type sharknado.PaddingFunc. This is just a silly
// proof-of-concept. In a more mature implementation, we would want a more
// flexible wrapper around conn.makePacket.
func (conn *obfs4Conn) getPadding(n int) ([]byte, error) {
// We're still busy with the handshake and haven't determined our shared
// secret yet. We therefore cannot send padding data just yet.
if !conn.connEstablished {
return nil, fmt.Errorf("Connection not yet established. No padding available.")
}
var frameBuf bytes.Buffer
err := conn.makePacket(&frameBuf, packetTypePayload, nil, uint16(n))
if err != nil {
return nil, err
}
return frameBuf.Bytes(), nil
}
func init() {
flag.BoolVar(&biasedDist, biasCmdArg, false, "Enable obfs4 using ScrambleSuit style table generation")
}
......
package sharknado
import (
"math/rand"
"net"
"time"
"gitlab.com/yawning/obfs4.git/common/log"
)
// PaddingFunc must be implemented by transports that want to use sharknado.
// The function takes as input the number of padding bytes that we need and
// returns a []byte (or error) that is ready to be written to the wire.
// Needless to say, a transport must support padding to use sharknado.
type PaddingFunc func(int) ([]byte, error)
// SharknadoConn implements the net.Conn interface.
type SharknadoConn struct {
// Embed a net.Conn and inherit its members.
net.Conn
GetPadding PaddingFunc
random *rand.Rand
}
func NewSharknadoConn(conn net.Conn, getPadding PaddingFunc) *SharknadoConn {
randSource := rand.NewSource(time.Now().UnixNano())
return &SharknadoConn{conn, getPadding, rand.New(randSource)}
}
func (sn SharknadoConn) shouldSendPadding() bool {
// Break the receive burst with probability 0.1. This is just a silly
// proof-of-concept. We probably want a state machine with smartness that
// decides when, exactly, we want to break a burst.
return sn.random.Intn(10) == 0
}
func (sn SharknadoConn) sendPadding() (int, error) {
// Let's just ask for 1024 bytes worth of padding data. Did I mention that
// this is just a silly prototype?
data, err := sn.GetPadding(1024)
if err != nil {
return 0, err
}
log.Infof("Injecting %d bytes of dummy traffic.", len(data))
return sn.Conn.Write(data)
}
func (sn SharknadoConn) Read(b []byte) (int, error) {
n, err := sn.Conn.Read(b)
if sn.shouldSendPadding() {
if _, err = sn.sendPadding(); err != nil {
log.Infof("Failed to send padding: %s", err)
}
}
return n, err
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment