Skip to content

Latest commit

 

History

4 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

mirage

Transport agnostic traffic morphing for Rust. mirage reshapes the sizes, timing, and burst patterns of network traffic so that statistical flow classifiers and behavioral machine learning have less signal to work with. It implements three techniques from the DAITA lineage of research: constant size frames, Poisson cover traffic, and probabilistic pattern distortion.

mirage is not a transport. It takes bytes in and produces constant size wire frames. You carry those frames over whatever transport you already use. On the receive side, mirage drops the cover frames and reassembles the original bytes.

The three techniques

  1. Constant size morphing. Every frame is padded or fragmented to exactly 1440 bytes, chosen so that one frame plus TLS and TCP and IP overhead lands just under the common 1500 byte MTU. The packet size histogram collapses to a single spike.
  2. Cover traffic. Dummy frames are generated on Poisson intervals with exponentially distributed gaps. Memoryless timing flattens the autocorrelation that periodicity scoring tools rely on. Dummy frames are flagged internally and silently discarded by the receiver.
  3. Pattern distortion. A probabilistic state machine injects bursts of dummy frames around real sends. The same event produces different behavior on every run, so the cover pattern itself does not become a fingerprint.

Install

cargo add mirage-shaper

Quickstart

use bytes::Bytes;
use mirage_shaper::{CoverRate, MorphingPipeline};

#[tokio::main]
async fn main() {
    let mut pipe = MorphingPipeline::builder()
        .target_size(1440)
        .cover_traffic(CoverRate::Poisson { lambda: 1.0 / 6.0 })
        .quiet_noisy()
        .build();

    let frames = pipe.send(Bytes::from_static(b"tasking complete"));
    for frame in &frames {
        let wire_bytes = frame.encode();
        // Hand wire_bytes to your transport. Every frame is exactly 1440 bytes.
        let _ = wire_bytes;
    }

    pipe.shutdown();
}

Examples

  1. cargo run --example basic runs the full pipeline in one process with no network: fragmentation, padding, dummy injection, reassembly.
  2. cargo run --example http_channel moves real frames over real HTTP using reqwest, against a tiny embedded server that drops dummies and reassembles the payload.
  3. cargo run --example plain_channel sends the same payload as a raw POST with no morphing. Run it under sudo tcpdump -i lo0 -n port 8799 and then run http_channel under the same capture for a side by side comparison of wire shapes.

Talk and demos

This library accompanies the talk "Shapeshifting C2: Applying DAITA Traffic Shaping to Defeat DPI and DLP Detection". The slide deck is in docs/talk and the simulation demos that produce the figures are in docs/demos.

Honest limits

  1. A constant 1440 byte frame size is itself a fingerprint. A defender trained on this exact configuration can learn the spike. The stronger construction is distribution matching against a real application, which is a roadmap item rather than shipped code.
  2. Cover traffic is not invisibility. Time to detect grows roughly quadratically with the cover ratio, and with unlimited observation time a periodic component still leaks.
  3. The bundled quiet/noisy state machine is a simplified Maybenot style machine. It would not survive an adversary trained on its exact configuration. Treat it as a starting point and generate per deployment machines.
  4. These techniques shape the network layer only. Application layer mimicry, identity provider risk engines, and endpoint telemetry are out of scope.

License

GNU GPLv3. See LICENSE.

About

Transport agnostic traffic morphing for Rust.

Resources

Stars

5 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages